diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-06 02:40:00 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-06 02:40:00 +0900 |
| commit | 90764c477f24e4d67d3e0cae943c33fd2b8ae220 (patch) | |
| tree | 8274fb1e266d6a14653c2a440cc6831ffda1c6bf /crates/shirabe-php-rpc/php/worker.php | |
| parent | 0e89281dd7f057d3829508b8e6c5d85c3f111c45 (diff) | |
| download | php-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 'crates/shirabe-php-rpc/php/worker.php')
| -rw-r--r-- | crates/shirabe-php-rpc/php/worker.php | 55 |
1 files changed, 54 insertions, 1 deletions
diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php index 12297c83..9a36abdd 100644 --- a/crates/shirabe-php-rpc/php/worker.php +++ b/crates/shirabe-php-rpc/php/worker.php @@ -25,6 +25,9 @@ interface ShirabeRustStub * @return ?array{__rhandle: int, __class: string, __epoch: int} */ public function __shirabeRustHandleDescriptor(): ?array; + + /** Binds this stub to an existing Rust entity, in place of running its constructor. */ + public function __shirabeBind(int $rhandle, int $epoch): void; } /** Interns proxy stubs so the same Rust handle always yields the same stub instance. */ @@ -46,7 +49,10 @@ final class ShirabeRustObjectRegistry "no proxy stub class is available for {$class}" ); } - $stub = new $class($rhandle, $epoch); + // The stub's constructor belongs to plugin code building a *new* entity; an entity that + // already exists is bound directly, so proxying never runs it. + $stub = (new ReflectionClass($class))->newInstanceWithoutConstructor(); + $stub->__shirabeBind($rhandle, $epoch); self::$internTable[$rhandle] = WeakReference::create($stub); return $stub; } @@ -626,6 +632,53 @@ ShirabeRpcRuntime::$dispatch = [ } return true; }, + // An already-fulfilled promise for a Rust-side call whose PHP signature declares + // PromiseInterface. The Rust future ran to completion before this is called, so there is + // nothing left to defer; see .ken/plugin-arch/design.md ยง10.1.6. + '__shirabe_resolved_promise' => static function ($args) { + if (!function_exists('React\\Promise\\resolve')) { + throw new RuntimeException( + 'react/promise is not loaded in the plugin process, so a PromiseInterface cannot be built' + ); + } + return \React\Promise\resolve($args[0]); + }, + // Drains a promise a plugin returned to the Rust side. React settles promises + // synchronously, so an already-settled one runs these handlers during then(); one that is + // still pending is an explicit error rather than a silently dropped continuation. + '__shirabe_settle_promise' => static function ($args) { + $promise = $args[0]; + if (!$promise instanceof \React\Promise\PromiseInterface) { + throw new RuntimeException('__shirabe_settle_promise expects a promise handle'); + } + $settled = false; + $value = null; + $rejected = false; + $reason = null; + $promise->then( + static function ($result) use (&$settled, &$value) { + $settled = true; + $value = $result; + }, + static function ($error) use (&$settled, &$rejected, &$reason) { + $settled = true; + $rejected = true; + $reason = $error; + } + ); + if (!$settled) { + throw new RuntimeException( + 'the promise returned to Shirabe is still pending; deferred resolution across the' + . ' RPC boundary is not implemented yet' + ); + } + if ($rejected) { + throw $reason instanceof Throwable + ? $reason + : new RuntimeException('the promise returned to Shirabe was rejected with ' . gettype($reason)); + } + return $value; + }, // For testing only: reads a public property of a P-table entity (PHPUnit asserts like // `$plugins[0]->version` have no method to call). '__shirabe_get_property' => static function ($args) { |
