diff options
Diffstat (limited to 'docs/dev')
| -rw-r--r-- | docs/dev/php-rpc.md | 7 | ||||
| -rw-r--r-- | docs/dev/plugin-stub-generation.md | 86 |
2 files changed, 90 insertions, 3 deletions
diff --git a/docs/dev/php-rpc.md b/docs/dev/php-rpc.md index f8f42e90..a1093ec5 100644 --- a/docs/dev/php-rpc.md +++ b/docs/dev/php-rpc.md @@ -131,10 +131,11 @@ function; an unknown name is an explicit error. Notable internal helpers: ## Proxy stubs -`php/stubs/` holds hand-written proxy stub classes (`Composer\EventDispatcher\Event`, +`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), written in the shape the future -stub generator will output. They are autoloaded with highest priority so a proxied FQCN can +`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 diff --git a/docs/dev/plugin-stub-generation.md b/docs/dev/plugin-stub-generation.md new file mode 100644 index 00000000..1fb77ff4 --- /dev/null +++ b/docs/dev/plugin-stub-generation.md @@ -0,0 +1,86 @@ +# Plugin proxy stub generation + +## Purpose + +The plugin PHP worker resolves proxied Composer FQCNs to thin stub classes whose +public methods forward to the Rust-side entity over RPC (see +`docs/dev/php-rpc.md`, "Proxy stubs"). These stub files, committed under +`crates/shirabe-php-rpc/php/stubs/` and embedded into the binary by the +`STUB_FILES` list in `crates/shirabe-php-rpc/src/lib.rs`, are *generated* by +`scripts/plugin-stub-generator` — never edited by hand. Generating them from the +Composer sources keeps every stub signature mechanically faithful to the real +class and turns "the stub is missing something the real class has" into a +generation failure instead of silent breakage. + +## Running + +``` +cd scripts/plugin-stub-generator +composer install # once; vendor/ is git-ignored +./generate-stubs # rewrites crates/shirabe-php-rpc/php/stubs/ +./generate-stubs --check # verifies committed stubs are fresh; exit 1 otherwise +``` + +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 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. +* the classifier report (`scripts/plugin-class-classifier/report.json`, override + with `--report=`). Run `scripts/plugin-class-classifier/classify` first; the + report is git-ignored. Every target must be classified `rust-proxy` or + `contract`, and a report with classification violations is rejected. + +The freshness check runs in `cargo test` as +`crates/shirabe-php-rpc/tests/generated_stubs.rs`; it returns early when PHP, +the generator's vendor directory or the classifier report is unavailable. + +## What the generator emits + +* **Root stubs** (targets whose parent class is not itself a target) carry the + proxy boilerplate: `__rhandle`/`__epoch` properties, a constructor that + accepts `(rhandle, epoch)` from proxy instantiation and throws a diagnosable + `RuntimeException` when plugin code tries to `new` the class directly, a + destructor releasing the Rust handle, and the wire descriptor helper. The + real `extends`/`implements` hierarchy is preserved and `\ShirabeRustStub` is + appended to the interface list. +* **Instance methods** forward via `\ShirabeRpcRuntime::callRust`. For a root + stub the emitted surface is the interface closure (each interface before the + ones it extends, methods in declaration order; a concrete redeclaration in + the class wins over the interface signature) followed by the class's own + remaining public methods. A subclass stub declares only the methods whose + *name* is new relative to the inherited stub surface; an omitted override + must match the inherited parameter list (names, arity, defaults, passing + modes — type declarations may differ), otherwise generation fails. +* Methods returning `self`/`static` perform the RPC and then `return $this;` + to preserve identity instead of round-tripping the handle. +* **Class constants and static methods** are materialized verbatim from the + real source (they read no instance state and run locally in the worker), + together with any non-public static helpers they call. +* **Imports**: the original file's `use` statements are kept in their original + order, restricted to names the emitted stub references; signatures declared + elsewhere (interface files) are re-spelled through that import table. + +## Coverage assertions + +Generation fails — instead of emitting something quietly wrong — on: + +* a target missing from the classifier report, classified other than + `rust-proxy`/`contract`, or a report carrying violations, +* public properties (a stub cannot forward property access), +* by-ref or variadic parameters, magic methods, static interface methods, +* 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, +* 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. + +When a future Composer release adds a public member the emitter cannot handle, +these assertions surface it at generation time; extending the emitter (or +deciding the porting policy) is then an explicit step, mirroring the +completeness stance of `docs/dev/plugin-class-classification.md`. |
