Skip to content

Commit cfada35

Browse files
authored
PHP Blueprints demo page (#1070)
## 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)
1 parent 7734f6a commit cfada35

File tree

6 files changed

+182
-3
lines changed

6 files changed

+182
-3
lines changed

packages/playground/remote/src/lib/worker-thread.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ try {
303303
// let's always fail.
304304
php.setSpawnHandler(
305305
createSpawnHandler(async function (args, processApi, options) {
306+
if (args[0] === 'exec') {
307+
args.shift();
308+
}
309+
306310
// Mock programs required by wp-cli:
307311
if (
308312
args[0] === '/usr/bin/env' &&
@@ -323,6 +327,24 @@ try {
323327
});
324328
processApi.flushStdin();
325329
processApi.exit(0);
330+
} else if (args[0] === 'fetch') {
331+
processApi.flushStdin();
332+
fetch(args[1]).then(async (res) => {
333+
const reader = res.body?.getReader();
334+
if (!reader) {
335+
processApi.exit(1);
336+
return;
337+
}
338+
while (true) {
339+
const { done, value } = await reader.read();
340+
if (done) {
341+
processApi.exit(0);
342+
break;
343+
}
344+
processApi.stdout(value);
345+
}
346+
});
347+
return;
326348
} else if (args[0] === 'php') {
327349
if (!childPHP) {
328350
childPHP = new WebPHP(await recreateRuntime(), {
@@ -360,7 +382,7 @@ try {
360382
$GLOBALS['argv'] = array_merge([
361383
"/wordpress/wp-cli.phar",
362384
"--path=/wordpress"
363-
], ${phpVar(args.slice(1))});
385+
], ${phpVar(args.slice(2))});
364386
365387
// Provide stdin, stdout, stderr streams outside of
366388
// the CLI SAPI.
@@ -379,10 +401,10 @@ try {
379401
}`,
380402
env: options.env,
381403
});
382-
} else if (args[1].includes('wp-cli.phar')) {
404+
} else if (args[1] === 'wp-cli.phar') {
383405
result = await childPHP.run({
384406
throwOnError: true,
385-
code: `${cliBootstrapScript} require( "/wordpress/wp-cli.phar" )`,
407+
code: `${cliBootstrapScript} require( "/wordpress/wp-cli.phar" );`,
386408
env: {
387409
...options.env,
388410
// Set SHELL_PIPE to 0 to ensure WP-CLI formats
@@ -395,6 +417,7 @@ try {
395417
result = await childPHP.run({
396418
throwOnError: true,
397419
scriptPath: args[1],
420+
env: options.env,
398421
});
399422
}
400423
processApi.stdout(result.bytes);
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE html>
2+
<head>
3+
<title>Blueprints PHP library</title>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<style>
7+
body {
8+
margin: 0;
9+
padding: 20px;
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
<h2>Blueprints PHP library</h2>
15+
<p>
16+
Uses the
17+
<a href="https://github.com/WordPress/blueprints/"
18+
>PHP implementation of Blueprints</a
19+
>
20+
to set up a WordPress site. Open the devtools for full output.
21+
</p>
22+
23+
<iframe id="wp" style="width: 100vw; height: 50vh; border: 0"></iframe>
24+
25+
<h2>PHP Blueprint execution result output – it may take a while to load</h2>
26+
<pre id="output" style="width: 100vw; height: 50vh; border: 0"></pre>
27+
<script type="module">
28+
await import('./php-blueprints.ts');
29+
</script>
30+
</body>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { startPlaygroundWeb } from '@wp-playground/client';
2+
import { getRemoteUrl } from '../src/lib/config';
3+
import { joinPaths } from '@php-wasm/util';
4+
export {};
5+
6+
const iframe = document.querySelector('iframe')!;
7+
const playground = await startPlaygroundWeb({
8+
iframe,
9+
remoteUrl: getRemoteUrl().toString(),
10+
// Blueprint v1, implemented in TypeScript:
11+
blueprint: {
12+
preferredVersions: {
13+
wp: 'latest',
14+
// Required for the PHP library to run:
15+
php: '8.2',
16+
},
17+
features: {
18+
networking: true,
19+
},
20+
// landingPage: '/wp-content/index.php',
21+
landingPage: '/',
22+
// Required for the PHP library to run:
23+
phpExtensionBundles: ['kitchen-sink'],
24+
},
25+
});
26+
27+
const response = await fetch('./blueprints.phar');
28+
const phar = new Uint8Array(await response.arrayBuffer());
29+
await playground.writeFile(
30+
joinPaths(await playground.documentRoot, 'blueprints.phar'),
31+
phar
32+
);
33+
const outputDiv = document.getElementById('output')!;
34+
35+
try {
36+
const wpCliRequest = fetch(
37+
'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'
38+
);
39+
const wpCliResponse = await wpCliRequest;
40+
const wpCli = await wpCliResponse.arrayBuffer();
41+
await playground.writeFile('/wordpress/wp-cli.phar', new Uint8Array(wpCli));
42+
43+
// Blueprint v2, implemented in PHP. The PHP builder is not required. It only
44+
// produces a JSON document that is then used to run the Blueprint.
45+
const result = await playground.run({
46+
code: `<?php
47+
use WordPress\\Blueprints\\Model\\DataClass\\Blueprint;
48+
use WordPress\\Blueprints\\Model\\BlueprintBuilder;
49+
use WordPress\\Blueprints\\Model\\DataClass\\UrlResource;
50+
use function WordPress\\Blueprints\\run_blueprint;
51+
52+
// Provide stdin, stdout, stderr streams outside of
53+
// the CLI SAPI.
54+
define('STDIN', fopen('php://stdin', 'rb'));
55+
define('STDOUT', fopen('php://stdout', 'wb'));
56+
define('STDERR', fopen('/tmp/stderr', 'wb'));
57+
58+
/*
59+
* When the .phar file is build with this box option:
60+
* > "check-requirements": false,
61+
* Then requiring it breaks http and https requests:
62+
*
63+
* > echo file_get_contents('http://localhost:5400/website-server/');
64+
* > <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 />
65+
*
66+
* The check is therefore disabled for now.
67+
*/
68+
require '/wordpress/blueprints.phar';
69+
70+
$blueprint = BlueprintBuilder::create()
71+
// This isn't a WordPress zip file since wordpress.org
72+
// doesn't expose the right CORS headers. It is a HTTPS-hosted
73+
// zip file nonetheless, and we can use it for testing.
74+
// Uncomment this as needed
75+
// ->setWordPressVersion( 'https://downloads.wordpress.org/plugin/hello-dolly.1.7.3.zip' )
76+
77+
->withFile( 'wordpress.txt', (new UrlResource())->setUrl('https://downloads.wordpress.org/plugin/hello-dolly.zip') )
78+
->withSiteOptions( [
79+
'blogname' => 'My Playground Blog',
80+
] )
81+
->withWpConfigConstants( [
82+
'WP_DEBUG' => true,
83+
'WP_DEBUG_LOG' => true,
84+
'WP_DEBUG_DISPLAY' => true,
85+
'WP_CACHE' => true,
86+
] )
87+
->withPlugins( [
88+
'https://downloads.wordpress.org/plugin/hello-dolly.zip',
89+
// When the regular UrlDataSource is used, the second
90+
// downloaded zip file always errors with:
91+
// > Failed to open stream: Operation timed out
92+
'https://downloads.wordpress.org/plugin/classic-editor.zip',
93+
'https://downloads.wordpress.org/plugin/gutenberg.17.7.0.zip',
94+
] )
95+
->withTheme( 'https://downloads.wordpress.org/theme/pendant.zip' )
96+
->withContent( 'https://raw.githubusercontent.com/WordPress/theme-test-data/master/themeunittestdata.wordpress.xml' )
97+
->andRunSQL( <<<'SQL'
98+
CREATE TABLE tmp_table ( id INT );
99+
INSERT INTO tmp_table VALUES (1);
100+
INSERT INTO tmp_table VALUES (2);
101+
SQL
102+
)
103+
->withFile( 'wordpress.txt', 'Data' )
104+
->toBlueprint()
105+
;
106+
107+
echo "Running the following Blueprint:\n";
108+
echo json_encode($blueprint, JSON_PRETTY_PRINT)."\n\n";
109+
$results = run_blueprint( $blueprint, '/wordpress' );
110+
echo "Blueprint execution finished!\n";
111+
echo "Contents of /wordpress/wp-content/plugins:";
112+
print_r(glob('/wordpress/wp-content/plugins/*'));
113+
`,
114+
throwOnError: true,
115+
});
116+
117+
outputDiv.textContent = result.text;
118+
console.log(result.text);
119+
} catch (e) {
120+
console.error(e);
121+
outputDiv.textContent = e + '';
122+
throw e;
123+
}
124+
125+
console.log(await playground.listFiles('/wordpress/wp-content/plugins'));
4.78 MB
Binary file not shown.

packages/playground/website/tsconfig.app.json

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"demos/peer.ts",
3030
"demos/terminal.ts",
3131
"demos/terminal-component.tsx",
32+
"demos/php-blueprints.ts",
3233
"./cypress.config.ts"
3334
]
3435
}

0 commit comments

Comments
 (0)