Skip to content

Commit f3329c7

Browse files
committed
Playground Boot: Align the boot process between remote.html and CLI (#1389)
Aligns the boot process between the in-browser Playground Remote and Node-oriented Playground CLI. With this PR, both apps use a similar `createPHP()` function that: * Sets up the SAPI name * Sets up PHP ini entries * Sets up the `/phpinfo.php` route * Sets up platform-level mu0plugins * Proxies filesystem directories from secondary PHP instances to primary * Sets up PHP runtime rotation to avoid OOM errors in long-running primary processes There are still the following discrepancies: * The in-browser PHP sets up a SAPI name conditionally, Node.js one always uses `cli` (it probably shouldn't) * The in-browser PHP uses a custom spawn handler * The in-browser PHP uses a different set of php.ini directives * The in-browser PHP loads more mu-plugins * The Node.js PHP sets up CA certificates for HTTPS connections (the in-browser PHP [will fake the CA chain eventually](#1093)) This is the first step towards a consistent Boot Protocol, see #1379 for more details. ## Testing Instructions * Confirm the CI checks work * Run `bun packages/playground/cli/src/cli.ts server --login`, confirm the server starts without issues, test wp-admin and HTTPS-reliant features like the plugin directories. We'll need a set of unit tests for these new boot-related features, let's create them sooner than later.
1 parent 4755327 commit f3329c7

File tree

5 files changed

+256
-246
lines changed

5 files changed

+256
-246
lines changed

packages/playground/cli/src/setup-php.ts

+8-15
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import {
77
} from '@php-wasm/universal';
88
import { rootCertificates } from 'tls';
99
import { dirname } from '@php-wasm/util';
10-
import { envPHP_to_loadMuPlugins } from '@wp-playground/wordpress';
10+
import {
11+
preloadPhpInfoRoute,
12+
enablePlatformMuPlugins,
13+
preloadRequiredMuPlugin,
14+
} from '@wp-playground/wordpress';
1115

1216
export async function createPhp(
1317
requestHandler: PHPRequestHandler<NodePHP>,
@@ -44,20 +48,9 @@ export async function createPhp(
4448
'/internal/shared/ca-bundle.crt',
4549
rootCertificates.join('\n')
4650
);
47-
php.writeFile(
48-
'/internal/shared/preload/env.php',
49-
envPHP_to_loadMuPlugins
50-
);
51-
php.writeFile(
52-
'/internal/shared/preload/phpinfo.php',
53-
`<?php
54-
// Render PHPInfo if the requested page is /phpinfo.php
55-
if ( '/phpinfo.php' === $_SERVER['REQUEST_URI'] ) {
56-
phpinfo();
57-
exit;
58-
}`
59-
);
60-
php.mkdir('/internal/shared/mu-plugins');
51+
await preloadPhpInfoRoute(php);
52+
await enablePlatformMuPlugins(php);
53+
await preloadRequiredMuPlugin(php);
6154
} else {
6255
/**
6356
* @TODO: Consider an API similar to

packages/playground/cli/src/setup-wp.ts

+6-14
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ import {
1616
readAsFile,
1717
} from './download';
1818
import { withPHPIniValues } from './setup-php';
19-
import {
20-
playgroundMuPlugin,
21-
preloadSqliteIntegration,
22-
} from '@wp-playground/wordpress';
19+
import { preloadSqliteIntegration } from '@wp-playground/wordpress';
2320

2421
/**
2522
* Ensures a functional WordPress installation in php document root.
@@ -52,9 +49,11 @@ export async function setupWordPress(
5249
monitor
5350
),
5451
]);
55-
5652
await prepareWordPress(php, wpZip);
57-
await preloadSqliteIntegration(php, sqliteZip);
53+
// Setup the SQLite integration if no custom database drop-in is present
54+
if (!php.fileExists('/wordpress/wp-content/db.php')) {
55+
await preloadSqliteIntegration(php, sqliteZip);
56+
}
5857

5958
const preinstalledWpContentPath = path.join(
6059
CACHE_FOLDER,
@@ -101,7 +100,7 @@ export async function setupWordPress(
101100
* the sqlite-database-integration zip file.
102101
*
103102
* This is a TypeScript function for now, just to get something off the
104-
* ground, but it will be superseded by the PHP Blueprints library developed
103+
* ground, but it may be superseded by the PHP Blueprints library developed
105104
* at https://github.com/WordPress/blueprints-library/
106105
*
107106
* That PHP library will come with a set of functions and a CLI tool to
@@ -111,13 +110,6 @@ export async function setupWordPress(
111110
* as that's viable.
112111
*/
113112
async function prepareWordPress(php: NodePHP, wpZip: File) {
114-
php.mkdir('/internal/shared/mu-plugins');
115-
php.writeFile(
116-
'/internal/shared/mu-plugins/0-playground.php',
117-
playgroundMuPlugin
118-
);
119-
120-
// Extract WordPress {{{
121113
php.mkdir('/tmp/unzipped-wordpress');
122114
await unzip(php, {
123115
zipFile: wpZip,

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

+6-57
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,8 @@ import {
77
LatestSupportedWordPressVersion,
88
SupportedWordPressVersions,
99
} from '@wp-playground/wordpress-builds';
10-
import {
11-
envPHP_to_loadMuPlugins,
12-
playgroundMuPlugin,
13-
wordPressRewriteRules,
14-
} from '@wp-playground/wordpress';
15-
import {
16-
PHPRequestHandler,
17-
proxyFileSystem,
18-
writeFiles,
19-
} from '@php-wasm/universal';
10+
import { wordPressRewriteRules } from '@wp-playground/wordpress';
11+
import { PHPRequestHandler } from '@php-wasm/universal';
2012
import {
2113
SyncProgressCallback,
2214
bindOpfs,
@@ -28,13 +20,7 @@ import {
2820
unzip,
2921
} from '@wp-playground/blueprints';
3022

31-
/** @ts-ignore */
32-
import transportFetch from './playground-mu-plugin/playground-includes/wp_http_fetch.php?raw';
33-
/** @ts-ignore */
34-
import transportDummy from './playground-mu-plugin/playground-includes/wp_http_dummy.php?raw';
35-
/** @ts-ignore */
36-
import playgroundWebMuPlugin from './playground-mu-plugin/0-playground.php?raw';
37-
import { joinPaths, randomString } from '@php-wasm/util';
23+
import { randomString } from '@php-wasm/util';
3824
import {
3925
requestedWPVersion,
4026
createPhp,
@@ -169,37 +155,12 @@ export class PlaygroundWorkerEndpoint extends WebPHPEndpoint {
169155

170156
const scopedSiteUrl = setURLScope(wordPressSiteUrl, scope).toString();
171157
const requestHandler = new PHPRequestHandler({
172-
phpFactory: async ({ isPrimary }) => {
173-
const php = await createPhp(requestHandler);
174-
php.defineConstant('SCOPED_SITE_PATH', new URL(scopedSiteUrl).pathname);
175-
if (isPrimary) {
176-
php.writeFile(
177-
'/internal/shared/preload/env.php',
178-
envPHP_to_loadMuPlugins
179-
);
180-
php.writeFile(
181-
'/internal/shared/preload/phpinfo.php',
182-
`<?php
183-
// Render PHPInfo if the requested page is /phpinfo.php
184-
if ( SCOPED_SITE_PATH . '/phpinfo.php' === $_SERVER['REQUEST_URI'] ) {
185-
phpinfo();
186-
exit;
187-
}
188-
`
189-
);
190-
} else {
191-
proxyFileSystem(await requestHandler.getPrimaryPhp(), php, [
192-
'/tmp',
193-
requestHandler.documentRoot,
194-
'/internal/shared',
195-
]);
196-
}
197-
return php;
198-
},
158+
phpFactory: async ({ isPrimary }) =>
159+
await createPhp(requestHandler, scopedSiteUrl, isPrimary),
199160
documentRoot: DOCROOT,
200161
absoluteUrl: scopedSiteUrl,
201162
rewriteRules: wordPressRewriteRules,
202-
});
163+
}) as PHPRequestHandler<WebPHP>;
203164
const apiEndpoint = new PlaygroundWorkerEndpoint(
204165
requestHandler,
205166
downloadMonitor,
@@ -241,18 +202,6 @@ try {
241202
});
242203
}
243204

244-
// Always install the playground mu-plugin, even if WordPress is loaded
245-
// from the OPFS. This ensures:
246-
// * The mu-plugin is always there, even when a custom WordPress directory
247-
// is mounted.
248-
// * The mu-plugin is always up to date.
249-
await writeFiles(primaryPhp, joinPaths('/internal/shared/mu-plugins'), {
250-
'0-playground.php': playgroundMuPlugin,
251-
'1-playground-web.php': playgroundWebMuPlugin,
252-
'playground-includes/wp_http_dummy.php': transportDummy,
253-
'playground-includes/wp_http_fetch.php': transportFetch,
254-
});
255-
256205
if (virtualOpfsDir) {
257206
await bindOpfs({
258207
php: primaryPhp,

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

+43-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,24 @@ import {
1010
SupportedPHPVersionsList,
1111
rotatePHPRuntime,
1212
PHPRequestHandler,
13+
proxyFileSystem,
14+
writeFiles,
1315
} from '@php-wasm/universal';
1416
import { EmscriptenDownloadMonitor } from '@php-wasm/progress';
15-
import { createSpawnHandler, phpVar } from '@php-wasm/util';
17+
import { createSpawnHandler, joinPaths, phpVar } from '@php-wasm/util';
1618
import { createMemoizedFetch } from './create-memoized-fetch';
1719
import { logger } from '@php-wasm/logger';
20+
/** @ts-ignore */
21+
import transportFetch from './playground-mu-plugin/playground-includes/wp_http_fetch.php?raw';
22+
/** @ts-ignore */
23+
import transportDummy from './playground-mu-plugin/playground-includes/wp_http_dummy.php?raw';
24+
/** @ts-ignore */
25+
import playgroundWebMuPlugin from './playground-mu-plugin/0-playground.php?raw';
26+
import {
27+
enablePlatformMuPlugins,
28+
preloadPhpInfoRoute,
29+
preloadRequiredMuPlugin,
30+
} from '@wp-playground/wordpress';
1831

1932
export type ReceivedStartupOptions = {
2033
wpVersion?: string;
@@ -59,15 +72,42 @@ export const startupOptions = {
5972
phpExtensions: receivedParams.phpExtensions || [],
6073
} as ParsedStartupOptions;
6174

62-
export async function createPhp(requestHandler: PHPRequestHandler<WebPHP>) {
75+
export async function createPhp(
76+
requestHandler: PHPRequestHandler<WebPHP>,
77+
siteUrl: string,
78+
isPrimary: boolean
79+
) {
6380
const php = new WebPHP();
6481
php.requestHandler = requestHandler as any;
82+
6583
php.initializeRuntime(await createPhpRuntime());
66-
php.setPhpIniEntry('memory_limit', '256M');
6784
if (startupOptions.sapiName) {
6885
await php.setSapiName(startupOptions.sapiName);
6986
}
87+
php.setPhpIniEntry('memory_limit', '256M');
7088
php.setSpawnHandler(spawnHandlerFactory(requestHandler.processManager));
89+
90+
if (isPrimary) {
91+
const scopedSitePath = new URL(siteUrl).pathname;
92+
await preloadPhpInfoRoute(
93+
php,
94+
joinPaths(scopedSitePath, 'phpinfo.php')
95+
);
96+
await enablePlatformMuPlugins(php);
97+
await preloadRequiredMuPlugin(php);
98+
await writeFiles(php, joinPaths('/internal/shared/mu-plugins'), {
99+
'1-playground-web.php': playgroundWebMuPlugin,
100+
'playground-includes/wp_http_dummy.php': transportDummy,
101+
'playground-includes/wp_http_fetch.php': transportFetch,
102+
});
103+
} else {
104+
proxyFileSystem(await requestHandler.getPrimaryPhp(), php, [
105+
'/tmp',
106+
requestHandler.documentRoot,
107+
'/internal/shared',
108+
]);
109+
}
110+
71111
// Rotate the PHP runtime periodically to avoid memory leak-related crashes.
72112
// @see https://github.com/WordPress/wordpress-playground/pull/990 for more context
73113
rotatePHPRuntime({

0 commit comments

Comments
 (0)