-
Notifications
You must be signed in to change notification settings - Fork 295
PHP SAPI module #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
PHP SAPI module #107
Conversation
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
… issuing a warning
This was referenced Jan 12, 2023
PHP builds regenerated in 3a175a2 |
bgrgicak
added a commit
that referenced
this pull request
May 7, 2025
## Motivation for the change, related issues Today, running the CLI apps with Bun is fast, but running with Node.js requires completing the build process which is much slower. I would like a faster update/run/debug loop when working on `@php-wasm/node`, especially for the XDebug work. In addition, if we can run unbuilt code directly in Node, we can debug that code in VSCode/Cursor and hopefully use the [WebAssembly DWARF Debugging](https://marketplace.visualstudio.com/items?itemName=ms-vscode.wasm-dwarf-debugging) VSCode extension to step-debug WebAssembly in the editor. ## Implementation details This PR adds: - New "debug" and "dev-node" (watch) nx targets for `@php-wasm/cli` that run in Node.js. Try `npx nx debug php-wasm-cli -- -i`. It starts quickly with no build process. - A "Debug PHP-WASM CLI" launch target for VSCode/Cursor. Just set a breakpoint and run the target. I tried running with packages like `ts-node` and `tsx` but did not manage to get any of them to work. So I tried with Node.js type-stripping and was able to get it working. Unfortunately, there are a lot of adjustments needed to run directly in Node.js this way. Most of the changes were small and unoffensive IMO, but some, like the need to avoid decorator syntax for now, were more annoying. Here's a list of the kinds of changes: - Add an ESM module loader for Node to resolve our package aliases to paths within the repo. - It might be possible for us to use a prebuilt package for this like [tsconfig-paths](https://www.npmjs.com/package/tsconfig-paths), but I wasn't able to get this to work properly. - The custom loader turned out to be fairly direct and a lot less trouble. - ~Upgrade TypeScript to get the `--erasableSyntaxOnly` option to enforce Node.js type-stripping compatibility.~ - NOTE: This update was backed out due to type conflicts between TS 5.8, `@typings/node`, and our current Vite version. This means we don't have linting checks to require only "erasable" types (types that can be stripped by Node.js), but this PR still allows our CLI packages to run in Node.js without a build step. - Adjust our TypeScript to avoid language features that break type-stripping. Docs [here](https://nodejs.org/docs/latest-v23.x/api/typescript.html#typescript-features). - Avoid enum declarations - Avoid type namespaces - Avoid parameter properties - Avoid decorator syntax for now. - Decorators are not supported by Node's type-stripping. Docs [here](https://nodejs.org/docs/latest-v23.x/api/typescript.html#typescript-features). - > Since Decorators are currently a [TC39 Stage 3 proposal](https://github.com/tc39/proposal-decorators) and will soon be supported by the JavaScript engine, they are not transformed and will result in a parser error. This is a temporary limitation and will be resolved in the future. - The workaround is to manually apply decorators. It is awkward, but thankfully, fs-helpers.ts is the only place I see using @decorator syntax. - Make type-only imports explicit. This is enforced by the [consistent-type-imports](https://typescript-eslint.io/rules/consistent-type-imports/) rule, and making the changes was automated using that rules fixer. (Just `nx run-many --all --target=lint --fix`) - We don't have to enforce this everywhere, but I think selective enforcement would be more confusing. - Add some Node.js boilerplate to the `@php-wasm/node` php_x_y.js files: - Define our own `__dirname` constant - Explicitly create a `require()` function since Emscripten uses require() in the generated code. Using the default, global require() triggers an error when used within an ES module, but the created require() appears to work fine and is the recommended fix. - Other small tweaks like adjusting how we resolve the default php.ini path for `@php-wasm/cli`. There are a few things left to do: - [x] Support importing raw file content in the custom loader. This is required by Playground CLI (for example, [here](https://github.com/Automattic/wordpress-playground-private/blob/b0ebbd963aedabf5adf4bd70d2e128e21c4fe682/packages/playground/blueprints/src/lib/steps/define-wp-config-consts.ts#L4)). - [x] Add nx related targets for Playground CLI - [x] Prompt for PHP version before launching CLI's in Debug - [x] Prompt for args before launch CLI's in Debug mode - [x] Fix remaining test failures ## Testing Instructions (or ideally a Blueprint) - CI - Run `npx nx debug php-wasm-cli` and see that it works to run the CLI (it also enables debugging, but that will be tested with the VSCode launch config) - Run `npx nx dev-node php-wasm-cli`, see that it works to run the CLI, and confirm that it reruns when relevant TS modules are changed. - Run `npx nx debug playground-cli` and see that it works to run the CLI (it also enables debugging, but that will be tested with the VSCode launch config) - Test the "Debug PHP-WASM CLI" launch config in VSCode or Cursor - Set a breakpoint in php-wasm/cli - Run the target - Confirm you hit the breakpoint - Test the "Debug Playground CLI" launch config in VSCode or Cursorr - Set a breakpoint in php-wasm/cli - Run the target - Confirm you hit the breakpoint --------- Co-authored-by: Bero <[email protected]>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
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.
What is this PR all about?
Introduces a PHP SAPI module that enables setting
$_POST
,$_SERVER
,php://input
and all the other PHP values from JavaScript.What problem does it solve?
Before this PR, most superglobal values were set by prepending code snippets like
$_SERVER['DOCUMENT_ROOT'] = ${JSON.stringify(documentRoot)};
every time some code was evaluated. Unfortunately, that technique couldn't populate everything, e.g.php://input
remained empty.How does it work?
PHP SAPI is used to integrate PHP with webservers and runtimes. A few SAPIs you might be familiar with are
php-cgi
,php-fpm
, andphp-cli
. A SAPI consumes the request information from the runtime, passes it to PHP, triggers the code execution, and passes the response back to the runtime.This PR introduces a WASM SAPI that accepts input information from JavaScript, sets up a PHP request, and passes a response back to JS. The most important changes are in the
php_wasm.c
file. The rest of the PR is adjusting the existing codebase to the new way of working with PHP.Briefly speaking, the SAPI module exposes a few setters like
wasm_set_query_string
orwasm_add_SERVER_entry
and awasm_sapi_handle_request()
function that triggers the request execution. The output information are written to/tmp/stdout
,/tmp/stderr
, and/tmp/headers.json
by the C module and read by the PHP JavaScript class.Because the request body and the query string are parsed by the same PHP internal functions as they would on a webserver, array syntax like
settings[newsletter]=1
is handled correctly.One surprising thing is the ability to set arbitrary
$_FILES
entries withwasm_add_uploaded_file
. This is because JavaScript typically has access to any uploadedFile
objects and it would be wasteful to re-serialize them only so that PHP can parse them all over again. Withwasm_add_uploaded_file
you can first write the uploaded files to the filesystem and then simply let PHP know about their existence.Solves #103