Add WordPress 1.0 support via PHP 5.6 and MySQL proxy#3394
Draft
wp-playground-bot wants to merge 11 commits intoWordPress:trunkfrom
Draft
Add WordPress 1.0 support via PHP 5.6 and MySQL proxy#3394wp-playground-bot wants to merge 11 commits intoWordPress:trunkfrom
wp-playground-bot wants to merge 11 commits intoWordPress:trunkfrom
Conversation
Enable compiling PHP 5.6 to WebAssembly so WordPress Playground can run
WordPress 1.x through 4.x, which require PHP 5.2+. PHP 5.6 is backward
compatible with PHP 5.2 code, so a single legacy version covers all older
WordPress releases.
Changes:
- Dockerfile: Add PHP 5.x version guards (fiber-asm, imagick, chunk-alloc
patches, ASM arithmetic, cli_server)
- C sources: Add PHP 5.x compatibility for proc_open, dns_polyfill,
post_message_to_js, and wasm_memory_storage extensions
- Package scaffolding: node-builds/5-6 and web-builds/5-6 with full NX
project configuration
- Loader modules: Add case '5.6' to node and web getPHPLoaderModule()
- Version registry: Add PHP 5.6 to supported-php-versions.mjs and
LegacyPHPVersions in supported-php-versions.ts (separate from
SupportedPHPVersions to avoid test regressions)
- Test suite: Dedicated legacy-php-versions.spec.ts with 12 tests
covering execution, file I/O, networking, proc_open, SQLite, JSON,
sessions, mbstring, error handling, memory, and filesystem ops
- tsconfig.base.json: Path aliases for new packages
Note: WASM binaries must be compiled in a Docker-enabled environment:
node packages/php-wasm/compile/build.js --PLATFORM=node --PHP_VERSION=5.6 \
--WITH_IMAGICK=no --WITH_OPCACHE=no
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PHP 5.6 is now functional in Playground with both web JSPI and node asyncify variants. This required fixing several compatibility issues between PHP 5.6's C codebase and modern Emscripten/clang: Dockerfile: SQLite duplicate symbol fix for PHP 5.x bundled sqlite3, readdir_r compatibility, x86_64 inline assembly guards, PCRE JIT disable, curl-config shim for PHP 5.x detection, and compiler warning suppression flags (-Wno-implicit-int, -Wno-implicit-function-declaration, -Wno-incompatible-function-pointer-types, etc). php_wasm.c: PHP 5.x API compat for size_t vs int return types in SAPI callbacks, and dup parameter in add_next_index_stringl / RETVAL_STRINGL macros. The test suite's isVersionBuilt() now checks for actual .wasm binary files instead of just directory existence, preventing crashes when placeholder stubs are present but no real binaries exist. Built with limited extensions (no OpenSSL, cURL, GD, ImageMagick, MySQL, mbregex) for this initial release. Verified: PHP boots, initializes SAPI, serves HTTP 200 with X-Powered-By: PHP/5.6.40, and executes PHP code producing correct output.
Resolved three merge conflicts: - packages/php-wasm/supported-php-versions.mjs: Kept PR's newer version numbers and PHP 5.6 entry - packages/php-wasm/node/project.json: Kept both test-legacy-php (PR) and test-file-locking targets (trunk) - packages/php-wasm/node/src/lib/load-runtime.ts: Merged LegacyPHPVersion import with trunk's new imports, kept modernVersion cast with trunk's truthy check
WordPress Playground uses SQLite under the hood, but PHP 5.6 can't run the sqlite-database-integration plugin (it requires PHP 7.2+). This adds a MySQL wire protocol proxy that lets PHP 5.6 connect to what it thinks is a real MySQL server. Behind the scenes, a second PHP 8.x instance translates the MySQL queries to SQLite using the sqlite-database-integration plugin. The proxy implements the MySQL binary protocol: handshake, authentication, COM_QUERY, COM_PING, COM_QUIT, result sets, OK/ERR/EOF packets. The boot process accepts a new `mysqlProxyPort` option that configures WordPress with standard MySQL database constants instead of the db.php drop-in approach.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds the ability to boot very old WordPress (1.0.x) in Playground by introducing PHP 5.6 runtimes and a MySQL wire-protocol proxy that translates queries to SQLite via a separate PHP 8.x instance.
Changes:
- Add PHP 5.6 “legacy” loader support for Node and Web builds (new packages + version typing).
- Implement a MySQL binary-protocol proxy (and an SQLite-backed variant) to support WP 1.0’s
mysql_*calls. - Add WordPress 1.0.2 module metadata and integration tests for MySQL-proxy-based boot.
Reviewed changes
Copilot reviewed 44 out of 49 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.base.json | Adds TS path aliases for new 5.6 build packages. |
| packages/playground/wordpress/src/test/wordpress-1.0-boot.spec.ts | New integration test for WP 1.0 boot via proxy + PHP 5.6. |
| packages/playground/wordpress/src/test/mysql-proxy-boot.spec.ts | New integration test for MySQL proxy boot path using PHP 5.6. |
| packages/playground/wordpress/src/boot.ts | Adds legacy WP boot path + MySQL proxy configuration option. |
| packages/playground/wordpress-builds/src/wordpress/wp-versions.json | Adds WP 1.0 mapping to 1.0.2. |
| packages/playground/wordpress-builds/src/wordpress/get-wordpress-module-details.ts | Provides remote module details for WP 1.0.2 zip. |
| packages/php-wasm/web/src/lib/get-php-loader-module.ts | Enables selecting PHP 5.6 loader on web. |
| packages/php-wasm/web-builds/5-6/tsconfig.lib.json | Build TS config for web PHP 5.6 package. |
| packages/php-wasm/web-builds/5-6/tsconfig.json | Project references for web PHP 5.6 package. |
| packages/php-wasm/web-builds/5-6/src/index.ts | Loader entry that chooses JSPI vs asyncify. |
| packages/php-wasm/web-builds/5-6/project.json | Nx project config for building/publishing web 5.6 package. |
| packages/php-wasm/web-builds/5-6/package.json | Package manifest for @php-wasm/web-5-6. |
| packages/php-wasm/web-builds/5-6/build.js | Bundle script for web 5.6 loader package. |
| packages/php-wasm/web-builds/5-6/asyncify/php_5_6.js | Placeholder asyncify loader artifact for 5.6. |
| packages/php-wasm/web-builds/5-6/README.md | Basic docs for @php-wasm/web-5-6. |
| packages/php-wasm/universal/src/lib/supported-php-versions.ts | Introduces LegacyPHPVersions / LegacyPHPVersion. |
| packages/php-wasm/universal/src/lib/index.ts | Re-exports legacy version types/lists. |
| packages/php-wasm/supported-php-versions.mjs | Adds 5.6 metadata to supported versions list. |
| packages/php-wasm/node/src/test/mysql-proxy.spec.ts | Adds unit/integration tests for MySQL proxy protocol. |
| packages/php-wasm/node/src/test/legacy-php-versions.spec.ts | Adds a separate test suite for legacy PHP 5.6 runtime basics. |
| packages/php-wasm/node/src/lib/networking/sqlite-over-mysql-proxy.ts | Implements SQLite-backed MySQL protocol proxy adapter. |
| packages/php-wasm/node/src/lib/networking/mysql-proxy.ts | Implements MySQL wire protocol server + query delegation. |
| packages/php-wasm/node/src/lib/networking/mysql-protocol.ts | Adds MySQL protocol packet encode/decode utilities. |
| packages/php-wasm/node/src/lib/load-runtime.ts | Allows loading legacy PHP versions in Node runtime loader. |
| packages/php-wasm/node/src/lib/index.ts | Exports new networking proxy APIs. |
| packages/php-wasm/node/src/lib/get-php-loader-module.ts | Enables selecting PHP 5.6 loader on Node. |
| packages/php-wasm/node/project.json | Registers new MySQL proxy test + adds legacy test target. |
| packages/php-wasm/node-builds/5-6/tsconfig.lib.json | Build TS config for node PHP 5.6 package. |
| packages/php-wasm/node-builds/5-6/tsconfig.json | Project references for node PHP 5.6 package. |
| packages/php-wasm/node-builds/5-6/src/index.ts | Node loader entry that chooses JSPI vs asyncify. |
| packages/php-wasm/node-builds/5-6/project.json | Nx project config for building/publishing node 5.6 package. |
| packages/php-wasm/node-builds/5-6/package.json | Package manifest for @php-wasm/node-5-6. |
| packages/php-wasm/node-builds/5-6/jspi/php_5_6.js | Placeholder JSPI loader artifact for 5.6. |
| packages/php-wasm/node-builds/5-6/build.js | Bundle script for node 5.6 loader package. |
| packages/php-wasm/node-builds/5-6/README.md | Basic docs for @php-wasm/node-5-6. |
| packages/php-wasm/compile/php/proc_open.h | Adds PHP 5.x compatible struct/headers for proc_open stubs. |
| packages/php-wasm/compile/php/proc_open.c | Adds PHP 5.x stub proc_open implementation + conditional includes. |
| packages/php-wasm/compile/php/php_wasm.c | Fixes PHP 5.x API differences in string/write function signatures. |
| packages/php-wasm/compile/php/php5.6.patch | Adds PHP 5.6 patch (copy() empty file crash workaround). |
| packages/php-wasm/compile/php/apply-mysqlnd-patch.sh | Makes mysqlnd patch application non-fatal on missing/mismatched code. |
| packages/php-wasm/compile/php/Dockerfile | Updates build steps for PHP 5.6 compatibility and conditional features. |
| packages/php-wasm/compile/php-wasm-memory-storage/wasm_memory_storage.c | Disables custom memory storage for PHP 5.x via no-op hooks. |
| packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.c | Adds PHP 5.x compatible parameter parsing and TSRMLS usage. |
| packages/php-wasm/compile/php-post-message-to-js/post_message_to_js.c | Fixes PHP 5.x parameter parsing + return handling for postMessage bridge. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+275
to
+276
| const usesMyqlProxy = !!options.mysqlProxyPort; | ||
| if (usesMyqlProxy) { |
Comment on lines
+279
to
+281
| * via standard MySQL functions (mysqli). No db.php drop-in | ||
| * is needed. This is used for PHP 5.6 where the modern | ||
| * sqlite-database-integration plugin can't run directly. |
| // If SQLite integration is preloaded via core, we're good | ||
| // If a MySQL proxy is configured (usesSqlite is true in that case), | ||
| // the database is handled through the proxy. Skip further checks. | ||
| if (php.isFile('/internal/shared/preload/0-sqlite.php')) { |
Comment on lines
+291
to
303
| // Extensions are only available for modern PHP versions. | ||
| const modernVersion = phpVersion as SupportedPHPVersion; | ||
| if (options?.withXdebug) { | ||
| emscriptenOptions = await withXdebug( | ||
| phpVersion, | ||
| modernVersion, | ||
| emscriptenOptions, | ||
| typeof options.withXdebug === 'object' ? options.withXdebug : {} | ||
| ); | ||
| } | ||
|
|
||
| if (options?.withIntl === true) { | ||
| emscriptenOptions = await withIntl(phpVersion, emscriptenOptions); | ||
| emscriptenOptions = await withIntl(modernVersion, emscriptenOptions); | ||
| } |
| */ | ||
| export async function getPHPLoaderModule( | ||
| version: SupportedPHPVersion | string = LatestSupportedPHPVersion | ||
| version: SupportedPHPVersion | LegacyPHPVersion = LatestSupportedPHPVersion |
Comment on lines
+84
to
+95
| return async (query: string): Promise<QueryResult | null> => { | ||
| if (!initialized) { | ||
| await initializeDriver(php, sqlitePluginPath, sqliteDatabasePath); | ||
| initialized = true; | ||
| } | ||
|
|
||
| // Write the query to a temp file to avoid escaping issues | ||
| // with embedded quotes, backslashes, etc. | ||
| php.writeFile('/tmp/mysql-proxy-query.sql', query); | ||
|
|
||
| const result = await php.run({ | ||
| code: `<?php |
Comment on lines
+214
to
+222
| port: addr.port, | ||
| host: addr.address, | ||
| close: () => | ||
| new Promise<void>((res) => { | ||
| server.close(() => res()); | ||
| // Force close all existing connections | ||
| server.unref(); | ||
| }), | ||
| }); |
Comment on lines
+6
to
+9
| throw new Error( | ||
| 'PHP 5.6 WASM binaries have not been compiled yet. ' + | ||
| 'Run: node packages/php-wasm/compile/build.js --PLATFORM=web --PHP_VERSION=5.6' | ||
| ); |
Comment on lines
+5
to
+6
| "declaration": true, | ||
| "types": ["node"] |
Address review feedback for the MySQL binary protocol proxy: - Serialize packet processing with a promise chain to prevent overlapping async handlers when TCP delivers data events in quick succession - Track active sockets and destroy them on server.close() so the close promise resolves immediately instead of hanging - Remove findFreePorts() TOCTOU race by passing port 0 directly to server.listen() and reading the assigned port from server.address() - Use unique temp file names per query to prevent concurrent overwrites - Escape PHP string literals to prevent path injection - Fix typo: usesMyqlProxy → usesMysqlProxy - Rewrite DB constants in wp-config.php via defineWpConfigConstants instead of php.defineConstant to avoid duplicate define() notices - Strengthen test assertions to check HTTP 200 and specific page content
9519e15 to
3b8dc3b
Compare
WordPress doesn't use the COM_FIELD_LIST command, and the constant was exported but never imported anywhere. The proxy's default case already handles unknown commands with a proper error response.
WordPress 1.0 (2004) can now boot in Playground through the MySQL binary protocol proxy introduced in the previous PR. The boot process detects legacy WordPress versions by the absence of wp-load.php (introduced in WP 2.6) and takes a separate code path: - Sets up PHP compatibility shims for old superglobals ($HTTP_GET_VARS, $HTTP_POST_VARS) that were removed in PHP 5.4 but WP 1.x needs - Runs the multi-step 1.x installer (steps 1, 2, 3) instead of the modern single-POST installer - Configures MySQL proxy connection so WP 1.0's mysql_connect() talks to our proxy, which translates queries to SQLite via PHP 8.x WordPress 1.0.2 is registered as a remote-fetched version from wordpress.org (272KB, no minification needed for such a small release).
…tallation The legacy WordPress boot path had three issues from review feedback: configureWordPressForMySQLProxy was called before ensureWpConfig, but it needs wp-config.php to exist first. Reordered so ensureWpConfig runs first, then the MySQL proxy constants are written into the existing config file. WordPress 1.x was written for register_globals=On, which was removed in PHP 5.4. Without emulation, query string and form variables aren't available as globals, breaking core WordPress code paths. Added EGPCS extraction loop to the compatibility preload shim. The installer ran three HTTP requests with no verification — silent failures would leave WordPress in a broken state. Each step now checks for 5xx responses and throws with diagnostic output. A final homepage request confirms the install actually worked. Strengthened the test assertions from toBeLessThan(500) to toBe(200) with an HTML structure check, and documented the network dependency for the WordPress 1.0.2 download.
The comment incorrectly described the SQLite preload file check as a MySQL proxy check. The preload file is created by preloadSqliteIntegration or mounted via hooks — the MySQL proxy case is handled separately via the usesSqlite flag.
3b8dc3b to
03f1b4f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WordPress 1.0 (released January 2004) can now run in Playground. It boots
through the MySQL binary protocol proxy from #3393 — WordPress 1.0 calls
mysql_connect() thinking it's talking to a real MySQL server, while a
PHP 8.x instance translates the queries to SQLite behind the scenes.
The boot process detects legacy WordPress by checking for the absence
of wp-load.php (introduced in WP 2.6) and takes a separate code path
that handles three key differences from modern WordPress:
that WordPress 1.x relies on but were removed in PHP 5.4
single-POST install endpoint
WordPress 1.0.2 is fetched directly from wordpress.org as a remote
module (272KB — no minification needed for such a small release).
Test plan