Skip to content

Commit

Permalink
PHP Blueprints demo page (#1070)
Browse files Browse the repository at this point in the history
## What is this PR doing?

Supersedes #1051

Adds a PHP Blueprints demo page where the use of
[blueprints.phar](WordPress/blueprints#28) PHP
library in Playground may be further explored. The showcase is
intentionally not added to
http://localhost:5400/website-server/demos/index.html as PHP Blueprints
may become a part of Playground core soon enough.

For more context see:

* #1025
* https://github.com/WordPress/blueprints

## How does it work?

* The built Blueprints library is included with this PR via the
`blueprints.phar` file
* A number of PHP.wasm improvements have been merged to support it:
   * #1064
   * #1065
   * #1069
* This PR ships a `fetch` subprocess handler to enable streaming network
data in the Blueprints library – it uses [a special network transport
called
`fetch`](https://github.com/WordPress/blueprints/blob/efa8deef56095bd5bcb94868787e29f7b54350f3/src/WordPress/DataSource/PlaygroundFetchSource.php)
that requests network data via `proc_open()` when running in Playground.
Why subprocesses? They provide everything a custom network handler
needs: pipes, asynchronous execution, exit codes, internal PHP plumbing.

## Follow-up work

* Support a real-time progress bar

## Testing instructions

Go to http://localhost:5400/website-server/demos/php-blueprints.html and
confirm it looks like on the screenshot below:

![CleanShot 2024-02-28 at 15 46
14@2x](https://github.com/WordPress/wordpress-playground/assets/205419/47a91d99-07f3-40a5-a046-b58f8cda952e)
  • Loading branch information
adamziel authored Feb 28, 2024
1 parent 7734f6a commit cfada35
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 3 deletions.
29 changes: 26 additions & 3 deletions packages/playground/remote/src/lib/worker-thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,10 @@ try {
// let's always fail.
php.setSpawnHandler(
createSpawnHandler(async function (args, processApi, options) {
if (args[0] === 'exec') {
args.shift();
}

// Mock programs required by wp-cli:
if (
args[0] === '/usr/bin/env' &&
Expand All @@ -323,6 +327,24 @@ try {
});
processApi.flushStdin();
processApi.exit(0);
} else if (args[0] === 'fetch') {
processApi.flushStdin();
fetch(args[1]).then(async (res) => {
const reader = res.body?.getReader();
if (!reader) {
processApi.exit(1);
return;
}
while (true) {
const { done, value } = await reader.read();
if (done) {
processApi.exit(0);
break;
}
processApi.stdout(value);
}
});
return;
} else if (args[0] === 'php') {
if (!childPHP) {
childPHP = new WebPHP(await recreateRuntime(), {
Expand Down Expand Up @@ -360,7 +382,7 @@ try {
$GLOBALS['argv'] = array_merge([
"/wordpress/wp-cli.phar",
"--path=/wordpress"
], ${phpVar(args.slice(1))});
], ${phpVar(args.slice(2))});
// Provide stdin, stdout, stderr streams outside of
// the CLI SAPI.
Expand All @@ -379,10 +401,10 @@ try {
}`,
env: options.env,
});
} else if (args[1].includes('wp-cli.phar')) {
} else if (args[1] === 'wp-cli.phar') {
result = await childPHP.run({
throwOnError: true,
code: `${cliBootstrapScript} require( "/wordpress/wp-cli.phar" )`,
code: `${cliBootstrapScript} require( "/wordpress/wp-cli.phar" );`,
env: {
...options.env,
// Set SHELL_PIPE to 0 to ensure WP-CLI formats
Expand All @@ -395,6 +417,7 @@ try {
result = await childPHP.run({
throwOnError: true,
scriptPath: args[1],
env: options.env,
});
}
processApi.stdout(result.bytes);
Expand Down
Binary file added packages/playground/website/demos/blueprints.phar
Binary file not shown.
30 changes: 30 additions & 0 deletions packages/playground/website/demos/php-blueprints.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<head>
<title>Blueprints PHP library</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
margin: 0;
padding: 20px;
}
</style>
</head>
<body>
<h2>Blueprints PHP library</h2>
<p>
Uses the
<a href="https://github.com/WordPress/blueprints/"
>PHP implementation of Blueprints</a
>
to set up a WordPress site. Open the devtools for full output.
</p>

<iframe id="wp" style="width: 100vw; height: 50vh; border: 0"></iframe>

<h2>PHP Blueprint execution result output – it may take a while to load</h2>
<pre id="output" style="width: 100vw; height: 50vh; border: 0"></pre>
<script type="module">
await import('./php-blueprints.ts');
</script>
</body>
125 changes: 125 additions & 0 deletions packages/playground/website/demos/php-blueprints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { startPlaygroundWeb } from '@wp-playground/client';
import { getRemoteUrl } from '../src/lib/config';
import { joinPaths } from '@php-wasm/util';
export {};

const iframe = document.querySelector('iframe')!;
const playground = await startPlaygroundWeb({
iframe,
remoteUrl: getRemoteUrl().toString(),
// Blueprint v1, implemented in TypeScript:
blueprint: {
preferredVersions: {
wp: 'latest',
// Required for the PHP library to run:
php: '8.2',
},
features: {
networking: true,
},
// landingPage: '/wp-content/index.php',
landingPage: '/',
// Required for the PHP library to run:
phpExtensionBundles: ['kitchen-sink'],
},
});

const response = await fetch('./blueprints.phar');
const phar = new Uint8Array(await response.arrayBuffer());
await playground.writeFile(
joinPaths(await playground.documentRoot, 'blueprints.phar'),
phar
);
const outputDiv = document.getElementById('output')!;

try {
const wpCliRequest = fetch(
'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'
);
const wpCliResponse = await wpCliRequest;
const wpCli = await wpCliResponse.arrayBuffer();
await playground.writeFile('/wordpress/wp-cli.phar', new Uint8Array(wpCli));

// Blueprint v2, implemented in PHP. The PHP builder is not required. It only
// produces a JSON document that is then used to run the Blueprint.
const result = await playground.run({
code: `<?php
use WordPress\\Blueprints\\Model\\DataClass\\Blueprint;
use WordPress\\Blueprints\\Model\\BlueprintBuilder;
use WordPress\\Blueprints\\Model\\DataClass\\UrlResource;
use function WordPress\\Blueprints\\run_blueprint;
// Provide stdin, stdout, stderr streams outside of
// the CLI SAPI.
define('STDIN', fopen('php://stdin', 'rb'));
define('STDOUT', fopen('php://stdout', 'wb'));
define('STDERR', fopen('/tmp/stderr', 'wb'));
/*
* When the .phar file is build with this box option:
* > "check-requirements": false,
* Then requiring it breaks http and https requests:
*
* > echo file_get_contents('http://localhost:5400/website-server/');
* > <b>Warning</b>: PHP Request Startup: Failed to open stream: Operation timed out in <b>php-wasm run script</b> on line <b>13</b><br />
*
* The check is therefore disabled for now.
*/
require '/wordpress/blueprints.phar';
$blueprint = BlueprintBuilder::create()
// This isn't a WordPress zip file since wordpress.org
// doesn't expose the right CORS headers. It is a HTTPS-hosted
// zip file nonetheless, and we can use it for testing.
// Uncomment this as needed
// ->setWordPressVersion( 'https://downloads.wordpress.org/plugin/hello-dolly.1.7.3.zip' )
->withFile( 'wordpress.txt', (new UrlResource())->setUrl('https://downloads.wordpress.org/plugin/hello-dolly.zip') )
->withSiteOptions( [
'blogname' => 'My Playground Blog',
] )
->withWpConfigConstants( [
'WP_DEBUG' => true,
'WP_DEBUG_LOG' => true,
'WP_DEBUG_DISPLAY' => true,
'WP_CACHE' => true,
] )
->withPlugins( [
'https://downloads.wordpress.org/plugin/hello-dolly.zip',
// When the regular UrlDataSource is used, the second
// downloaded zip file always errors with:
// > Failed to open stream: Operation timed out
'https://downloads.wordpress.org/plugin/classic-editor.zip',
'https://downloads.wordpress.org/plugin/gutenberg.17.7.0.zip',
] )
->withTheme( 'https://downloads.wordpress.org/theme/pendant.zip' )
->withContent( 'https://raw.githubusercontent.com/WordPress/theme-test-data/master/themeunittestdata.wordpress.xml' )
->andRunSQL( <<<'SQL'
CREATE TABLE tmp_table ( id INT );
INSERT INTO tmp_table VALUES (1);
INSERT INTO tmp_table VALUES (2);
SQL
)
->withFile( 'wordpress.txt', 'Data' )
->toBlueprint()
;
echo "Running the following Blueprint:\n";
echo json_encode($blueprint, JSON_PRETTY_PRINT)."\n\n";
$results = run_blueprint( $blueprint, '/wordpress' );
echo "Blueprint execution finished!\n";
echo "Contents of /wordpress/wp-content/plugins:";
print_r(glob('/wordpress/wp-content/plugins/*'));
`,
throwOnError: true,
});

outputDiv.textContent = result.text;
console.log(result.text);
} catch (e) {
console.error(e);
outputDiv.textContent = e + '';
throw e;
}

console.log(await playground.listFiles('/wordpress/wp-content/plugins'));
Binary file added packages/playground/website/demos/wordpress.zip
Binary file not shown.
1 change: 1 addition & 0 deletions packages/playground/website/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"demos/peer.ts",
"demos/terminal.ts",
"demos/terminal-component.tsx",
"demos/php-blueprints.ts",
"./cypress.config.ts"
]
}

0 comments on commit cfada35

Please sign in to comment.