Skip to content

Commit

Permalink
WordPress Playground compatibility (#31)
Browse files Browse the repository at this point in the history
This PR makes the Blueprints PHP library work in Playground:

https://playground.wordpress.net/demos/php-blueprints.html

The main change is the addition of `PlaygroundFetchSource` to download
data using browser's `fetch()` (Playground support added in
WordPress/wordpress-playground#1070). The rest
is cosmetics.

Related Playground PR
WordPress/wordpress-playground#1051
  • Loading branch information
adamziel authored Mar 1, 2024
1 parent e819f95 commit 1d7224d
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 111 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ The Blueprints library is distributed as a .phar library. To build the .phar fil
vendor/bin/box compile
```

Note that in box.json, the `"check-requirements"` option is set to `false`. Somehow, keeping it as `true` results in a
.phar file
that breaks HTTP requests in Playground. @TODO: Investigate why this is the case.

To try the built .phar file, run:

```shell
rm -rf new-wp/* && USE_PHAR=1 php blueprint_compiling.php
```

5 changes: 4 additions & 1 deletion blueprint_compiling.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use WordPress\Blueprints\ContainerBuilder;
use WordPress\Blueprints\Model\BlueprintBuilder;
use function WordPress\Blueprints\run_blueprint;

Expand All @@ -21,6 +22,8 @@
'WP_CACHE' => true,
] )
->withPlugins( [
// Required for withContent():
'https://downloads.wordpress.org/plugin/wordpress-importer.zip',
'https://downloads.wordpress.org/plugin/hello-dolly.zip',
'https://downloads.wordpress.org/plugin/gutenberg.17.7.0.zip',
] )
Expand All @@ -37,6 +40,6 @@
->toBlueprint();


$results = run_blueprint( $blueprint, __DIR__ . '/new-wp' );
$results = run_blueprint( $blueprint, ContainerBuilder::ENVIRONMENT_NATIVE, __DIR__ . '/new-wp' );

var_dump( $results );
39 changes: 26 additions & 13 deletions src/WordPress/Blueprints/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,22 @@
use WordPress\Blueprints\Runner\Step\UnzipStepRunner;
use WordPress\Blueprints\Runner\Step\WPCLIStepRunner;
use WordPress\Blueprints\Runner\Step\WriteFileStepRunner;
use WordPress\Blueprints\Runtime\NativePHPRuntime;
use WordPress\Blueprints\Runtime\Runtime;
use WordPress\Blueprints\Runtime\RuntimeInterface;
use WordPress\DataSource\FileSource;
use WordPress\DataSource\PlaygroundFetchSource;
use WordPress\DataSource\ProgressEvent;
use WordPress\DataSource\UrlSource;

class ContainerBuilder {

const RUNTIME_NATIVE = 'native';
const RUNTIME_PLAYGROUND = 'playground';
const RUNTIME_WP_NOW = 'wp-now';
const RUNTIMES = [
self::RUNTIME_NATIVE,
self::RUNTIME_PLAYGROUND,
self::RUNTIME_WP_NOW,
const ENVIRONMENT_NATIVE = 'native';
const ENVIRONMENT_PLAYGROUND = 'playground';
const ENVIRONMENT_WP_NOW = 'wp-now';
const ENVIRONMENTS = [
self::ENVIRONMENT_NATIVE,
self::ENVIRONMENT_PLAYGROUND,
self::ENVIRONMENT_WP_NOW,
];

protected $container;
Expand All @@ -83,13 +84,13 @@ public function __construct() {
}


public function build( RuntimeInterface $runtime ) {
public function build( string $environment, RuntimeInterface $runtime ) {
$container = $this->container;
$container['runtime'] = function () use ( $runtime ) {
return $runtime;
};

if ( $runtime instanceof NativePHPRuntime ) {
if ( $environment === static::ENVIRONMENT_NATIVE ) {
$container['downloads_cache'] = function ( $c ) {
return new FileCache();
};
Expand All @@ -101,6 +102,18 @@ public function build( RuntimeInterface $runtime ) {
echo $event->url . ' ' . $event->downloadedBytes . '/' . $event->totalBytes . " \r";
};
};
$container[ "resource.resolver." . UrlResource::DISCRIMINATOR ] = function ( $c ) {
return new UrlResourceResolver( $c['data_source.url'] );
};
} elseif ( $environment === static::ENVIRONMENT_PLAYGROUND ) {
$container[ "resource.resolver." . UrlResource::DISCRIMINATOR ] = function ( $c ) {
return new UrlResourceResolver( $c['data_source.playground_fetch'] );
};
$container['progress_reporter'] = function ( $c ) {
return function ( ProgressEvent $event ) {
echo $event->url . ' ' . $event->downloadedBytes . '/' . $event->totalBytes . " \r";
};
};
} else {
throw new InvalidArgumentException( "Not implemented yet" );
}
Expand Down Expand Up @@ -218,9 +231,6 @@ function () use ( $c ) {
return new RunSQLStepRunner();
};

$container[ "resource.resolver." . UrlResource::DISCRIMINATOR ] = function ( $c ) {
return new UrlResourceResolver( $c['data_source.url'] );
};
$container[ "resource.resolver." . FilesystemResource::DISCRIMINATOR ] = function () {
return new FilesystemResourceResolver();
};
Expand Down Expand Up @@ -262,6 +272,9 @@ function () use ( $c ) {
$container['data_source.url'] = function ( $c ) {
return new UrlSource( $c['http_client'], $c['downloads_cache'] );
};
$container['data_source.playground_fetch'] = function ( $c ) {
return new PlaygroundFetchSource();
};

// Add a progress listener to all data sources
foreach ( $container->keys() as $key ) {
Expand Down
2 changes: 1 addition & 1 deletion src/WordPress/Blueprints/Model/BlueprintBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function withContent( $wxrs ) {
if ( ! is_array( $wxrs ) ) {
$wxrs = [ $wxrs ];
}
$this->withPlugin( 'https://downloads.wordpress.org/plugin/wordpress-importer.zip' );
// @TODO: Should this automatically add the importer plugin if it's not already installed?
foreach ( $wxrs as $wxr ) {
$this->addStep(
( new ImportFileStep() )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function run( SetSiteOptionsStep $input, Tracker $tracker ) {
// with a separate wp-cli command.
return $this->getRuntime()->evalPhpInSubProcess( <<<'CODE'
<?php
require 'wp-load.php';
require getenv('DOCROOT'). '/wp-load.php';
$site_options = getenv("OPTIONS") ? json_decode(getenv("OPTIONS"), true) : [];
foreach($site_options as $name => $value) {
update_option($name, $value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Symfony\Component\Process\Process;
use function WordPress\Blueprints\join_paths;

class NativePHPRuntime implements RuntimeInterface {
class Runtime implements RuntimeInterface {

public Filesystem $fs;

Expand All @@ -33,7 +33,7 @@ public function getDocumentRoot(): string {
}

public function resolvePath( string $path ): string {
return Path::makeAbsolute($path, $this->getDocumentRoot());
return Path::makeAbsolute( $path, $this->getDocumentRoot() );
}

public function withTemporaryDirectory( $callback ) {
Expand Down
9 changes: 4 additions & 5 deletions src/WordPress/Blueprints/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
namespace WordPress\Blueprints;

use Symfony\Component\Filesystem\Exception\IOException;
use WordPress\Blueprints\Runtime\NativePHPRuntime;
use WordPress\Blueprints\Runtime\Runtime;

function run_blueprint( $json, $documentRoot = '/wordpress' ) {
function run_blueprint( $json, $environment, $documentRoot = '/wordpress' ) {
$c = ( new ContainerBuilder() )->build(
new NativePHPRuntime(
$documentRoot
)
$environment,
new Runtime( $documentRoot )
);

return $c['blueprint.engine']->runBlueprint( $json );
Expand Down
40 changes: 40 additions & 0 deletions src/WordPress/DataSource/PlaygroundFetchSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace WordPress\DataSource;

use Psr\SimpleCache\CacheInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpClient\Response\StreamWrapper;
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use WordPress\Streams\StreamPeeker;
use WordPress\Streams\StreamPeekerContext;

class PlaygroundFetchSource extends BaseDataSource {

public $proc_handles = [];

public function stream( $resourceIdentifier ) {
$url = $resourceIdentifier;
$proc_handle = proc_open(
[ 'fetch', $url, ],
[ 1 => [ 'pipe', 'w' ], 2 => [ 'pipe', 'w' ] ],
$pipes
);
// This prevents the process handle from getting garbage collected and
// breaking the stdout pipe. However, how the program never terminates.
// Presumably we need to peek() on the resource handle and close the
// process handle when it's done.
// Without this line, we get the following error:
// PHP Fatal error: Uncaught TypeError: stream_copy_to_stream(): supplied resource is not a valid stream resource i
// var_dump()–ing first says
// resource(457) of type (stream)
// but then it says
// resource(457) of type (Unknown)
$this->proc_handles[] = $proc_handle;

return $pipes[1];
}

}

6 changes: 3 additions & 3 deletions src/WordPress/DataSource/UrlSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function stream( $resourceIdentifier ) {
if ( $this->cache->has( $url ) ) {
// Return a stream resource.
// @TODO: Stream directly from the cache
$cached = $this->cache->get( $url );
$cached = $this->cache->get( $url );
$data_size = strlen( $cached );
$this->events->dispatch( new ProgressEvent(
$url,
Expand All @@ -60,17 +60,17 @@ public function stream( $resourceIdentifier ) {
) );
},
] );
$stream = StreamWrapper::createResource( $response, $this->client );
$stream = StreamWrapper::createResource( $response, $this->client );
if ( ! $stream ) {
throw new \Exception( 'Failed to download file' );
}
$onChunk = function ( $chunk ) use ( $url, $response, $stream ) {
// Handle response caching
// @TODO: don't buffer, just keep appending to the cache.
static $bufferedChunks = [];
$bufferedChunks[] = $chunk;
if ( feof( $stream ) ) {
$this->cache->set( $url, implode( '', $bufferedChunks ) );
$bufferedChunks = [];
}
};
$onClose = function () use ( $response ) {
Expand Down
16 changes: 8 additions & 8 deletions src/WordPress/Zip/ZipStreamReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ static public function readEntry( $fp ) {
* @param resource $stream
*/
static protected function readFileEntry( $stream ): ZipFileEntry {
$data = self::read_bytes( $stream, 26 );
$data = unpack( 'vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength',
$data = self::read_bytes( $stream, 26 );
$data = unpack( 'vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength',
$data );
$path = self::read_bytes( $stream, $data['pathLength'] );
$path = self::read_bytes( $stream, $data['pathLength'] );
$extra = self::read_bytes( $stream, $data['extraLength'] );
$bytes = self::read_bytes( $stream, $data['compressedSize'] );

Expand Down Expand Up @@ -119,11 +119,11 @@ static protected function readFileEntry( $stream ): ZipFileEntry {
* @param resource stream
*/
static protected function readCentralDirectoryEntry( $stream ): ZipCentralDirectoryEntry {
$data = static::read_bytes( $stream, 42 );
$data = unpack( 'vversionCreated/vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength/vfileCommentLength/vdiskNumber/vinternalAttributes/VexternalAttributes/VfirstByteAt',
$data = static::read_bytes( $stream, 42 );
$data = unpack( 'vversionCreated/vversionNeeded/vgeneralPurpose/vcompressionMethod/vlastModifiedTime/vlastModifiedDate/Vcrc/VcompressedSize/VuncompressedSize/vpathLength/vextraLength/vfileCommentLength/vdiskNumber/vinternalAttributes/VexternalAttributes/VfirstByteAt',
$data );
$path = static::read_bytes( $stream, $data['pathLength'] );
$extra = static::read_bytes( $stream, $data['extraLength'] );
$path = static::read_bytes( $stream, $data['pathLength'] );
$extra = static::read_bytes( $stream, $data['extraLength'] );
$fileComment = static::read_bytes( $stream, $data['fileCommentLength'] );

return new ZipCentralDirectoryEntry(
Expand Down Expand Up @@ -204,7 +204,7 @@ static protected function read_bytes( $stream, $length ): string|bool {
return false;
}
$length -= strlen( $chunk );
$data .= $chunk;
$data .= $chunk;

if ( $length === 0 ) {
break;
Expand Down
2 changes: 1 addition & 1 deletion src/WordPress/Zip/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function zip_extract_to( $fp, $toPath ) {
if ( ! $entry->isFileEntry() ) {
continue;
}
$path = $toPath . '/' . sanitize_path( $entry->path );
$path = $toPath . '/' . sanitize_path( $entry->path );
$parent = dirname( $path );
if ( ! is_dir( $parent ) ) {
mkdir( $parent, 0777, true );
Expand Down
Loading

0 comments on commit 1d7224d

Please sign in to comment.