aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/php/worker.php
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 /crates/shirabe-php-rpc/php/worker.php
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 'crates/shirabe-php-rpc/php/worker.php')
-rw-r--r--crates/shirabe-php-rpc/php/worker.php55
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) {