aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/php/worker.php
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-05 03:58:03 +0900
committernsfisis <nsfisis@gmail.com>2026-08-05 03:58:03 +0900
commit9b444a9a879b75a6af3d3c7ba8b9a4294574c3ec (patch)
treeb1041b55bc3ed36a391370cc2f5ececc8cb386b2 /crates/shirabe-php-rpc/php/worker.php
parentbe3458128ef09b3d1074b134f2822162e077e165 (diff)
downloadphp-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.php70
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();