Skip to content

[PHP-WASM] Add per-command spawn handlers - #3995

Merged
adamziel merged 6 commits into
trunkfrom
codex/php-command-spawn-handlers
Jul 14, 2026
Merged

[PHP-WASM] Add per-command spawn handlers#3995
adamziel merged 6 commits into
trunkfrom
codex/php-command-spawn-handlers

Conversation

@bgrgicak

@bgrgicak bgrgicak commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

PHP only had a single spawn hook. It got the command and had to decide what process to start. That is too coarse for integrations that need to own one binary, such as sendmail, while leaving the rest of process spawning to the normal handler.

This adds PHP.setCommandSpawnHandler(command, handler). The command is matched against the basename of argv[0], so /usr/sbin/sendmail hits a sendmail handler. A command-specific handler wins over the generic setSpawnHandler() callback. Everything else still goes to the generic spawn handler.

The matcher is deliberately not a shell. ENV=value sendmail does not hit the sendmail handler, because ENV=value is the first token. That is not a regression: there was no command-specific dispatch before, and callers that need shell syntax can still use the generic spawn handler.

The dispatch point now lives on the PHP instance instead of storing user handlers directly on the Emscripten runtime. A runtime hot swap gets a fresh dispatcher, and the registered handlers stay attached to the PHP object. If no handler matches, the dispatcher throws the same SPAWN_UNSUPPORTED error as before, so PHP still sees ENOSYS for popen() and proc_open().

The unit test covers the unsupported-spawn path, command-specific routing, and fallback to the generic handler.

Route every process spawn through a PHP-level dispatcher so specific
binaries (by argv[0] basename) can be intercepted via
setCommandSpawnHandler() regardless of the generic setSpawnHandler().
Spawn handlers now live on the PHP instance rather than the Emscripten
runtime, so they survive hotSwapPHPRuntime() without being copied over.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a per-command spawn-handler dispatch layer to PHP so specific binaries (by basename(argv[0]), e.g. sendmail) can be intercepted without being overridden by a later generic setSpawnHandler() call, and so handlers survive hotSwapPHPRuntime().

Changes:

  • Introduces PHP.setCommandSpawnHandler(command, handler) and a private #dispatchSpawn() router.
  • Moves spawn handler state onto the PHP instance and installs a runtime-level dispatcher in initializeRuntime().
  • Updates hot-swap logic to stop copying runtime.spawnProcess between runtimes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
packages/php-wasm/universal/src/lib/php.ts Adds per-command spawn handlers and a centralized dispatcher; updates runtime initialization and hot-swap behavior accordingly.
packages/php-wasm/universal/src/lib/php.spec.ts Adjusts a runtime mock shape in tests (removes spawnProcess field).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/php-wasm/universal/src/lib/php.ts Outdated
Comment thread packages/php-wasm/universal/src/lib/php.ts Outdated
Comment thread packages/php-wasm/universal/src/lib/php.ts
Comment thread packages/php-wasm/universal/src/lib/php.ts Outdated
Comment thread packages/php-wasm/universal/src/lib/php.ts
Comment thread packages/php-wasm/universal/src/lib/php.ts
@bgrgicak

Copy link
Copy Markdown
Collaborator Author

@adamziel I would love your feedback on this PR as the rest of SMTP depends on it.

? [command as string, ...args]
: splitShellCommand(command as string);
const commandSpawnHandler =
commandArray[0] &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker: This won't handle ENV=value php script.php. The current spawn handles doesn't either so we do not regress, so it's fine.

// The runtime never sees user-provided spawn handlers directly –
// every spawn flows through the dispatcher so that per-command
// handlers cannot be displaced by a setSpawnHandler() call.
runtime.spawnProcess = (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to break passing spawnProcess to the PHP initialization handler, so this wouldn't work anymore:

import { spawn } from 'node:child_process';
import { loadNodeRuntime } from '@php-wasm/node';
import { PHP } from '@php-wasm/universal';

const php = new PHP(
	await loadNodeRuntime('8.4', {
		emscriptenOptions: {
			spawnProcess(command: string, args: string[], options: any) {
				return spawn(command, args, {
					...options,
					shell: true,
				});
			},
		},
	})
);

const result = await php.run({
	code: `<?php echo exec('echo Playground');`,
});

console.log(result.text); // Playground

Is anyone relying on this semantics? If yes, what would we break and how could we preserve this semantics? If noone is using it, I think we're fine.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done some research on SourceGraph and GitHub:

I think we're good.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for figuring this out, I completely missed that we also allow to set the spawnHandler using emscriptenOptions.spawnProcess

adamziel added 2 commits July 14, 2026 17:09
…hp-command-spawn-handlers

# Conflicts:
#	packages/php-wasm/universal/src/lib/php.spec.ts
@adamziel
adamziel merged commit bfe38df into trunk Jul 14, 2026
53 checks passed
@adamziel
adamziel deleted the codex/php-command-spawn-handlers branch July 14, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment