aboutsummaryrefslogtreecommitdiffhomepage
path: root/docs/dev
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-06 02:40:00 +0900
committernsfisis <nsfisis@gmail.com>2026-08-06 02:40:00 +0900
commit90764c477f24e4d67d3e0cae943c33fd2b8ae220 (patch)
tree8274fb1e266d6a14653c2a440cc6831ffda1c6bf /docs/dev
parent0e89281dd7f057d3829508b8e6c5d85c3f111c45 (diff)
downloadphp-shirabe-90764c477f24e4d67d3e0cae943c33fd2b8ae220.tar.gz
php-shirabe-90764c477f24e4d67d3e0cae943c33fd2b8ae220.tar.zst
php-shirabe-90764c477f24e4d67d3e0cae943c33fd2b8ae220.zip
feat(plugin): widen the RPC surface to LibraryInstaller-based plugins
An installer that extends LibraryInstaller reaches for the Composer object graph in ways the proxy did not answer: the download manager and the config, the writable repository methods, a React promise as its own return value, and `new Package(...)` from its supports() path. * `Composer::getConfig`/`getDownloadManager` are dispatched, and both classes become generated proxy stubs. Their reads and mutators answer from the Rust entity; the surfaces needing stubs of their own (ConfigSourceInterface, DownloaderInterface) stay explicit errors. * The download manager's futures are driven to completion and handed back as already-settled React promises, since PHP declares a non-nullable PromiseInterface there. A promise a plugin returns is drained the same way: settled yields its value, rejected re-raises, pending is an explicit error. * Proxy stubs now carry the real class's constructor and ask the Rust side to allocate the entity; reviving a stub for an existing entity binds the handle without running it. Classes Rust cannot build name themselves in the error. * The package proxy covers `Package`'s own setters, `CompletePackage`'s metadata, `RootPackage`'s root-only state, and `BasePackage::$id`. Link values and release dates still have no wire image, so the methods carrying them remain explicit errors.
Diffstat (limited to 'docs/dev')
-rw-r--r--docs/dev/php-rpc.md28
-rw-r--r--docs/dev/plugin-stub-generation.md16
2 files changed, 31 insertions, 13 deletions
diff --git a/docs/dev/php-rpc.md b/docs/dev/php-rpc.md
index e6797533..d0eed382 100644
--- a/docs/dev/php-rpc.md
+++ b/docs/dev/php-rpc.md
@@ -126,6 +126,13 @@ function; an unknown name is an explicit error. Notable internal helpers:
(the unconditional `InstalledVersions::reload($versions)` plus the reflection-based
`selfDir`/`installedIsLocalDir` restore) into the worker; skipped only when the class is not
even autoloadable there, i.e. no Composer PHP runtime and therefore no observer code.
+- `__shirabe_resolved_promise` — wraps a value in `\React\Promise\resolve()`, so a Rust method
+ whose PHP signature declares `PromiseInterface` (the `DownloadManager` surface) can answer
+ with the object type the caller expects. The Rust future has already run to completion by
+ then; deferred resolution across the boundary does not exist yet.
+- `__shirabe_settle_promise` — the inverse: drains a promise a plugin returned to Rust. React
+ settles synchronously, so an already-settled promise yields its value here (a rejection is
+ re-thrown as the Throw reply); one that is still pending is an explicit error.
- `__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`
@@ -138,10 +145,13 @@ function; an unknown name is an explicit error. Notable internal helpers:
- `__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.
-The runtime service endpoint (handle 0) answers `__shirabe_find_file` (autoload lookups) and
+The runtime service endpoint (handle 0) answers `__shirabe_find_file` (autoload lookups),
`__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.
+the Rust process, against the Rust-side application state — and `__shirabeConstruct`, which
+allocates the Rust entity behind a `new SomeProxiedClass(...)` written by plugin code and
+answers with `[rhandle, epoch]`. Classes whose entity Rust cannot build are an explicit error
+naming the class.
## Proxy stubs and runtime classes
@@ -158,17 +168,19 @@ by `scripts/plugin-stub-generator/generate-stubs` and must not be edited by hand
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).
+- `Composer\EventDispatcher\Event` — dual-mode: revived from a Rust handle (through
+ `__shirabeBind`) 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`. `clone` on a stub calls `__shirabeClone` on the
+their destructors send `ReleaseRustHandle`. Reviving a stub for an existing entity bypasses its
+constructor (`newInstanceWithoutConstructor` plus `__shirabeBind`), because the constructor
+carries the real class's own signature and belongs to plugin code building a *new* entity. `clone` on a stub calls `__shirabeClone` on the
entity and rebinds the copy to the handle that answers, so the two stubs never share (and never
double-release) one entity; entities with no clone semantics answer with an explicit error.
diff --git a/docs/dev/plugin-stub-generation.md b/docs/dev/plugin-stub-generation.md
index 33025df7..27835921 100644
--- a/docs/dev/plugin-stub-generation.md
+++ b/docs/dev/plugin-stub-generation.md
@@ -46,12 +46,18 @@ 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
+ proxy boilerplate: `__rhandle`/`__epoch` properties, the `__shirabeBind`
+ binder the registry calls when reviving a stub for an existing entity, 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.
+* **Constructors** reproduce the real class's parameter list and forward to the
+ Rust side (`__shirabeConstruct` on handle 0), which allocates the entity and
+ answers with its handle; a class Rust cannot build answers with an explicit
+ error naming it. One is emitted for every root stub and for every subclass
+ that declares a public constructor of its own, so `new SomeProxiedClass(...)`
+ in plugin code never yields an unbound stub. Proxy revival does not run them
+ (see `__shirabeBind` above).
* **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
@@ -89,8 +95,8 @@ 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,
-* by-ref or variadic parameters, static interface methods, magic methods
- other than `__toString`/`__clone`,
+* by-ref or variadic parameters (in constructors too), static interface
+ methods, magic methods 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
neither a target nor provided by `php/runtime/`,