diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-05 03:58:03 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-05 03:58:03 +0900 |
| commit | 9b444a9a879b75a6af3d3c7ba8b9a4294574c3ec (patch) | |
| tree | b1041b55bc3ed36a391370cc2f5ececc8cb386b2 /crates/shirabe-php-rpc/php/worker.php | |
| parent | be3458128ef09b3d1074b134f2822162e077e165 (diff) | |
| download | php-shirabe-9b444a9a879b75a6af3d3c7ba8b9a4294574c3ec.tar.gz php-shirabe-9b444a9a879b75a6af3d3c7ba8b9a4294574c3ec.tar.zst php-shirabe-9b444a9a879b75a6af3d3c7ba8b9a4294574c3ec.zip | |
feat(plugin): run plugin-provided commands in a worker-side application
A same-FQCN Composer\Console\Application, hand-written under the new
php/runtime/ tree, hosts CommandProvider commands inside the PHP worker:
PhpCommandProxy overrides run() and forwards the stringified input, so the
real Symfony machinery binds, validates and executes against the live
command object, while help/list render Rust-side from a definition read
back at construction. Reverse \Shirabe\RustCommandStub rows let a plugin
command invoke built-in commands back in the Rust process, keeping every
command on the side whose helper set it was written for.
Composer\EventDispatcher\Event moves from a generated stub to a dual-mode
runtime class: the real BaseCommand::initialize constructs a
PreCommandRunEvent natively in the worker, which a proxy-only constructor
guard rejected. Its PRE_COMMAND_RUN dispatch reaches a new EventDispatcher
stub whose dispatch supports the observably-no-op no-listener case and
fails explicitly otherwise. The stub generator now accepts runtime-provided
classes as stub bases (never as targets) and cross-checks the Application
handoff property table against the real class.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-rpc/php/worker.php')
| -rw-r--r-- | crates/shirabe-php-rpc/php/worker.php | 70 |
1 files changed, 67 insertions, 3 deletions
diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php index 50e9ef76..241dad57 100644 --- a/crates/shirabe-php-rpc/php/worker.php +++ b/crates/shirabe-php-rpc/php/worker.php @@ -18,8 +18,13 @@ const SHIRABE_MAX_FRAME_LEN = 268435456; // 256 MiB, mirrored on the Rust side. /** Marker interface every Rust-proxy stub class implements. */ interface ShirabeRustStub { - /** @return array{__rhandle: int, __class: string, __epoch: int} */ - public function __shirabeRustHandleDescriptor(): array; + /** + * Null when this instance holds no Rust handle: a dual-mode class (see php/runtime/) that + * was constructed natively in this process crosses the wire as a P-table entity instead. + * + * @return ?array{__rhandle: int, __class: string, __epoch: int} + */ + public function __shirabeRustHandleDescriptor(): ?array; } /** Interns proxy stubs so the same Rust handle always yields the same stub instance. */ @@ -199,7 +204,11 @@ final class ShirabeRpcRuntime public static function toWire($value) { if ($value instanceof ShirabeRustStub) { - return $value->__shirabeRustHandleDescriptor(); + $descriptor = $value->__shirabeRustHandleDescriptor(); + if ($descriptor !== null) { + return $descriptor; + } + // A natively-constructed dual-mode instance falls through to the P table below. } if (is_object($value)) { return ShirabePhpObjectRegistry::descriptor($value); @@ -616,6 +625,61 @@ ShirabeRpcRuntime::$dispatch = [ } return $obj->{$args[1]}; }, + // Builds the worker-side Composer\Console\Application (the runtime/ definition, not the + // real class) from the Rust handoff; the caller keeps the returned handle and runs + // plugin-provided commands through __shirabe_run_console_application. + '__shirabe_console_application_boot' => static function ($args) { + return \Composer\Console\Application::__shirabeBoot($args[0]); + }, + // Runs one command line (the stringified input of the Rust-side run) through a booted + // worker-side application; output goes to the inherited stdio, the exit code returns + // over the wire, and a command failure propagates as an RPC throw (catchExceptions is + // off on the booted application). + '__shirabe_run_console_application' => static function ($args) { + [$app, $inputString] = $args; + if (!$app instanceof \Composer\Console\Application) { + throw new RuntimeException('__shirabe_run_console_application expects an application handle'); + } + return $app->run(new \Symfony\Component\Console\Input\StringInput($inputString)); + }, + // Reads a command's input definition (plus help text and extra usages) as plain data, so + // the Rust side can mirror it for `help`/`list` rendering without executing anything. + '__shirabe_read_command_definition' => static function ($args) { + $command = $args[0]; + if (!$command instanceof \Symfony\Component\Console\Command\Command) { + throw new RuntimeException('__shirabe_read_command_definition expects a command handle'); + } + $definition = $command->getDefinition(); + $arguments = []; + foreach ($definition->getArguments() as $argument) { + $arguments[] = [ + 'name' => $argument->getName(), + 'required' => $argument->isRequired(), + 'isArray' => $argument->isArray(), + 'description' => $argument->getDescription(), + 'default' => $argument->getDefault(), + ]; + } + $options = []; + foreach ($definition->getOptions() as $option) { + $options[] = [ + 'name' => $option->getName(), + 'shortcut' => $option->getShortcut(), + 'acceptValue' => $option->acceptValue(), + 'isValueRequired' => $option->isValueRequired(), + 'isArray' => $option->isArray(), + 'isNegatable' => $option->isNegatable(), + 'description' => $option->getDescription(), + 'default' => $option->getDefault(), + ]; + } + return [ + 'arguments' => $arguments, + 'options' => $options, + 'help' => $command->getHelp(), + 'usages' => $command->getUsages(), + ]; + }, ]; ShirabeRpcRuntime::serveForever(); |
