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 /docs/dev | |
| 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 'docs/dev')
| -rw-r--r-- | docs/dev/php-rpc.md | 49 | ||||
| -rw-r--r-- | docs/dev/plugin-stub-generation.md | 19 |
2 files changed, 55 insertions, 13 deletions
diff --git a/docs/dev/php-rpc.md b/docs/dev/php-rpc.md index a1093ec5..298b4059 100644 --- a/docs/dev/php-rpc.md +++ b/docs/dev/php-rpc.md @@ -128,18 +128,47 @@ function; an unknown name is an explicit error. Notable internal helpers: even autoloadable there, i.e. no Composer PHP runtime and therefore no observer code. - `__shirabe_get_property` — for testing only: reads a public property of a P-table entity. - `__shirabe_oracle_roundtrip` — codec oracle support for tests. +- `__shirabe_console_application_boot` — builds the worker-side `Composer\Console\Application` + (the `php/runtime/` definition) from the Rust handoff: the shared `$composer`/`$io` proxies, + the initial working directory and disable-by-default flags, `\Shirabe\RustCommandStub` rows + mirroring the built-in commands, and the live plugin-provided command entities. +- `__shirabe_run_console_application` — runs one stringified command line through a booted + worker-side application; output goes to the inherited stdio, the exit code returns over the + wire, and a failure propagates as a Throw (the booted application does not catch exceptions). +- `__shirabe_read_command_definition` — reads a command's input definition (plus help text and + extra usages) as plain data, so the Rust side mirrors it for `help`/`list` rendering. -## Proxy stubs +The runtime service endpoint (handle 0) answers `__shirabe_find_file` (autoload lookups) and +`__shirabe_run_rust_command` — the reverse half of the two-world command split: a +`\Shirabe\RustCommandStub` forwards its stringified input here and the built-in command runs in +the Rust process, against the Rust-side application state. -`php/stubs/` holds the proxy stub classes (`Composer\EventDispatcher\Event`, -`Composer\Script\Event`, `Composer\PartialComposer`, `Composer\Composer`, and the -`Composer\IO\{BaseIO,ConsoleIO,BufferIO,NullIO}` hierarchy). They are generated by -`scripts/plugin-stub-generator/generate-stubs` and must not be edited by hand; see -`docs/dev/plugin-stub-generation.md`. They are autoloaded with highest priority so a proxied FQCN can -never be shadowed by the real implementation; `__shirabe_require` restores that priority after -loading code that prepends its own autoloader. Stubs are interned per rhandle -(`WeakReference`-based registry) so identity (`===`) holds, and their destructors send -`ReleaseRustHandle`. +## Proxy stubs and runtime classes + +`php/stubs/` holds the proxy stub classes (`Composer\Script\Event`, `Composer\PartialComposer`, +`Composer\Composer`, the `Composer\IO\{BaseIO,ConsoleIO,BufferIO,NullIO}` hierarchy, the +package/repository graph, and `Composer\EventDispatcher\EventDispatcher`). They are generated +by `scripts/plugin-stub-generator/generate-stubs` and must not be edited by hand; see +`docs/dev/plugin-stub-generation.md`. + +`php/runtime/` holds hand-written worker-side classes that are not mechanical proxies: + +- `Composer\Console\Application` — a same-FQCN two-world implementation (never the real class + file): plugin-provided commands run under it inside the worker, and its Composer-specific + surface (`getIO()`/`getComposer()`/...) answers from the Rust handoff. +- `Shirabe\RustCommandStub` — the reverse stub for built-in commands registered into that + application. +- `Composer\EventDispatcher\Event` — dual-mode: revived from a Rust handle it proxies like a + generated stub, while a natively-constructed instance (real Composer code in the worker does + `new PreCommandRunEvent(...)`, whose parent constructor lands here) is a faithful in-process + port of the real base class and crosses the wire as a P-table entity + (`__shirabeRustHandleDescriptor()` returns null in native mode). + +Both sets are written into the same autoload directory at worker spawn and resolved with +highest priority, so these FQCNs can never be shadowed by the real implementation; +`__shirabe_require` restores that priority after loading code that prepends its own autoloader. +Stubs are interned per rhandle (`WeakReference`-based registry) so identity (`===`) holds, and +their destructors send `ReleaseRustHandle`. ## The P table diff --git a/docs/dev/plugin-stub-generation.md b/docs/dev/plugin-stub-generation.md index 09bb5625..e10c0e9c 100644 --- a/docs/dev/plugin-stub-generation.md +++ b/docs/dev/plugin-stub-generation.md @@ -25,6 +25,12 @@ Inputs: * `targets.list` — the FQCNs to emit, stub base classes before their subclasses. Growing the stub set means adding a line here and regenerating. +* the hand-written classes under `crates/shirabe-php-rpc/php/runtime/` (their + FQCNs derive from the file paths). These are two-world implementations with + behavior of their own — not mechanical proxies — so the generator never emits + them, but it accepts them as base classes of generated stubs (computing the + inherited surface from the real Composer class the runtime file mirrors) and + fails if a `targets.list` entry would shadow one. * the Composer checkout (`composer/`, override with `--composer-root=`); the generator locates sources through the checkout's own PSR-4 autoload map, so interfaces from vendor packages (e.g. `Psr\Log\LoggerInterface`) resolve too. @@ -83,12 +89,19 @@ Generation fails — instead of emitting something quietly wrong — on: other than `__toString`/`__clone`, * an omitted override diverging from the inherited stub signature, * a subclass target listed before its base class, or extending a class that is - not a target, + neither a target nor provided by `php/runtime/`, +* a target whose FQCN is also provided by `php/runtime/`, * non-public class constants (materializing them is unsupported so far). `generate-stubs` (in both modes) additionally fails when a `.php` file exists -under the stubs directory that no target produces, or when `STUB_FILES` in -`crates/shirabe-php-rpc/src/lib.rs` does not embed every generated file. +under the stubs directory that no target produces, or when `STUB_FILES` / +`RUNTIME_FILES` in `crates/shirabe-php-rpc/src/lib.rs` does not embed every +generated stub / runtime file. It also cross-checks the handoff property table +for `Composer\Console\Application` (declared in `generate-stubs` itself) against +the real class: a property upstream adds without a handoff classification — or a +table row the class no longer declares — fails generation, so the worker-side +runtime application can never silently drop plugin-visible state after a +Composer version bump. When a future Composer release adds a public member the emitter cannot handle, these assertions surface it at generation time; extending the emitter (or |
