From a02fc7d728a9973a3275a0f47604081c4439b424 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Tue, 4 Aug 2026 02:25:28 +0900 Subject: feat(plugin): activate plugins through the PHP RPC worker Implement the remainder of PluginManager::registerPackage: the plugin autoload map is built by the ported createLoader/parseAutoloads and served to the worker over the existing reverse-RPC autoloader, files entries go through a composerRequire-equivalent glue call, and already-defined classes take the upstream _composer_tmp rename/eval path. Instantiation uses the new NewObject/CallPhpMethod lanes backed by a P table in the worker; PhpPluginProxy adapts the resulting handle to PluginInterface, with $composer/$io exposed to plugin callbacks via an R table (unsupported methods stay explicit errors). Hand-written proxy stubs cover Composer, PartialComposer and the IO hierarchy, and the stub autoloader is re-prepended after loading the Composer PHP runtime so its vendor autoloader cannot shadow proxied FQCNs. FilesystemRepository::write now mirrors InstalledVersions::reload into a running worker (class_exists-guarded, so an unloaded class keeps its upstream lazy-load behavior), removing the previously undefined observation window. The installer pipeline passes the installed repository as a shared handle instead of a long-lived `&mut dyn`: plugin registration runs inside InstallationManager::execute and re-enters the same local repository through the RepositoryManager, which would panic on the RefCell re-borrow under the old shape. PluginInterface lifecycle methods now take an owned ComposerHandle (plugins retain $composer past the call) and return anyhow::Result (PHP plugin code may throw); the plugin list uses shared ownership so the identity comparison of removePlugin survives the dual storage in registeredPlugins, matching PHP reference semantics. Ports the activate/upgrade/uninstall tests of PluginInstallerTest, serialized across the shared worker process whose persistent class table is exactly what exercises the rename path. Co-Authored-By: Claude Fable 5 --- crates/shirabe/src/event_dispatcher/event_dispatcher.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'crates/shirabe/src/event_dispatcher') diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs index 2097c83d..f5b85ca1 100644 --- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs +++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs @@ -596,7 +596,7 @@ impl EventDispatcher { // The user's command class extends Symfony's Command, so the child // process needs the real symfony/console classes before it can even // autoload the user class. - self.ensure_composer_php_runtime()?; + Self::ensure_composer_php_runtime()?; if !self.php_runtime_bool( "class_exists", vec![PluginValue::string(class_name.clone())], @@ -699,7 +699,7 @@ try {{ "false" }, ); - self.ensure_script_autoloader()?; + Self::ensure_script_autoloader()?; let mut dispatcher = ScriptRpcDispatcher { loader: self.loader.clone(), event: None, @@ -1054,7 +1054,7 @@ try {{ })); }; - self.ensure_script_autoloader()?; + Self::ensure_script_autoloader()?; let rhandle = shirabe_php_rpc::alloc_rhandle(); let mut dispatcher = ScriptRpcDispatcher { loader: self.loader.clone(), @@ -1435,7 +1435,7 @@ try {{ /// Makes the worker's script-class autoloader active, so class queries and script execution /// in the child can resolve classes through the Rust-side [`ClassLoader`] built by /// [`Self::make_autoloader`]. - fn ensure_script_autoloader(&self) -> anyhow::Result<()> { + pub(crate) fn ensure_script_autoloader() -> anyhow::Result<()> { unwrap_php_result(call_function( "__shirabe_enable_script_autoloader", Vec::new(), @@ -1445,7 +1445,7 @@ try {{ /// Loads the Composer PHP runtime (symfony/console and friends) into the worker, needed /// before a `scripts` Command class can be autoloaded and hosted. - fn ensure_composer_php_runtime(&self) -> anyhow::Result<()> { + pub(crate) fn ensure_composer_php_runtime() -> anyhow::Result<()> { // TODO(plugin): the real PHP classes are taken from a Composer checkout for now; how // they ship with a released Shirabe binary is part of the plugin distribution work. let autoload = Self::composer_php_runtime_autoload().ok_or_else(|| { @@ -1484,7 +1484,7 @@ try {{ /// Runs a boolean runtime query (`class_exists`, `is_a`, ...) inside the PHP worker, with /// the script autoloader active so the query can trigger class loading. fn php_runtime_bool(&self, function: &str, args: Vec) -> anyhow::Result { - self.ensure_script_autoloader()?; + Self::ensure_script_autoloader()?; let mut dispatcher = ScriptRpcDispatcher { loader: self.loader.clone(), event: None, @@ -1616,7 +1616,7 @@ fn runtime_throw(message: String) -> PhpThrow { /// Collapses the two failure lanes of an RPC call into `anyhow`: the callers here treat a PHP /// exception raised during a runtime query as fatal for the current dispatch. -fn unwrap_php_result( +pub(crate) fn unwrap_php_result( outcome: anyhow::Result>, ) -> anyhow::Result { match outcome? { -- cgit v1.3.1