diff options
Diffstat (limited to 'crates/shirabe')
| -rw-r--r-- | crates/shirabe/src/event_dispatcher/event_dispatcher.rs | 39 | ||||
| -rw-r--r-- | crates/shirabe/src/plugin/plugin_manager.rs | 16 | ||||
| -rw-r--r-- | crates/shirabe/tests/command/archive_command_test.rs | 1 | ||||
| -rw-r--r-- | crates/shirabe/tests/installer_test.rs | 1 | ||||
| -rw-r--r-- | crates/shirabe/tests/plugin/subscriber_test.rs | 32 |
5 files changed, 67 insertions, 22 deletions
diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs index 2a25eac8..80131e25 100644 --- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs +++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs @@ -1205,25 +1205,21 @@ try {{ .push(listener); } - pub fn remove_listener(&mut self, listener: &Callable) { - for (_event_name, priorities) in self.listeners.iter_mut() { - for (_priority, listeners) in priorities.iter_mut() { - let mut to_remove: Vec<usize> = Vec::new(); - for (index, candidate) in listeners.iter().enumerate() { - let same = match (listener, candidate) { - (Callable::String(a), Callable::String(b)) => a == b, - // TODO(plugin): array callable identity (compare object refs) - _ => false, - }; - let array_obj_match = matches!(candidate, Callable::ArrayCallable(_, _)) - && matches!(listener, Callable::ArrayCallable(_, _)); - if same || array_obj_match { - to_remove.push(index); - } - } - for idx in to_remove.into_iter().rev() { - listeners.remove(idx); - } + /// PHP's parameter is `callable|object`; every caller in Composer and its test suite + /// passes an object, and no `Callable` shape holds a bare object, so the parameter is + /// narrowed to the object's cross-RPC identity (its P-table handle). Of PHP's two match + /// conditions only `$candidate[0] === $listener` can fire for an object listener; it maps + /// to phandle equality on `Callable::PhpMethod`. + pub fn remove_listener(&mut self, listener: &shirabe_php_rpc::PhpObjHandle) { + for priorities in self.listeners.values_mut() { + for listeners in priorities.values_mut() { + // TODO(plugin): an `ArrayCallable`'s object half is a `PhpMixed` without + // cross-RPC identity, so `$candidate[0] === $listener` is undecidable for it; + // only `PhpMethod` candidates are compared. + listeners.retain(|candidate| match candidate { + Callable::PhpMethod(handle, _) => handle.phandle != listener.phandle, + _ => true, + }); } } } @@ -1791,6 +1787,7 @@ pub trait EventDispatcherInterface: std::fmt::Debug { ) -> anyhow::Result<i64>; fn add_listener(&mut self, event_name: &str, listener: Callable, priority: i64); fn add_subscriber(&mut self, subscriber: &dyn EventSubscriberInterface) -> anyhow::Result<()>; + fn remove_listener(&mut self, listener: &shirabe_php_rpc::PhpObjHandle); fn has_event_listeners(&mut self, event: &dyn EventInterface) -> bool; } @@ -1831,6 +1828,10 @@ impl EventDispatcherInterface for EventDispatcher { self.add_subscriber(subscriber) } + fn remove_listener(&mut self, listener: &shirabe_php_rpc::PhpObjHandle) { + self.remove_listener(listener); + } + fn has_event_listeners(&mut self, event: &dyn EventInterface) -> bool { self.has_event_listeners(event) } diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs index a3810d0c..aaee7d5a 100644 --- a/crates/shirabe/src/plugin/plugin_manager.rs +++ b/crates/shirabe/src/plugin/plugin_manager.rs @@ -772,9 +772,19 @@ impl PluginManager { .borrow_mut() .deactivate(self.composer_full(), self.io.clone())?; - // TODO(plugin): remove_listener accepts any callable/object in PHP; here we have - // a plugin instance and need to translate to a Callable, which is not portable - // without runtime reflection. + // PHP passes the plugin object itself; its cross-RPC identity (the P-table handle) is + // carried by the subscriber accessor, and `addSubscriber` is the only source of + // listeners capturing a plugin object today. + // TODO(plugin): a plugin registering `[$this, 'method']` listeners directly through an + // EventDispatcher proxy would have no removal path here; no such RPC surface exists yet. + let subscriber_handle = removed + .borrow() + .as_event_subscriber() + .map(|s| s.subscriber_handle()); + if let Some(handle) = subscriber_handle { + let event_dispatcher = self.composer_full().borrow().get_event_dispatcher(); + event_dispatcher.borrow_mut().remove_listener(&handle); + } Ok(()) } diff --git a/crates/shirabe/tests/command/archive_command_test.rs b/crates/shirabe/tests/command/archive_command_test.rs index 655020e0..6d20fda0 100644 --- a/crates/shirabe/tests/command/archive_command_test.rs +++ b/crates/shirabe/tests/command/archive_command_test.rs @@ -89,6 +89,7 @@ mockall::mock! { &mut self, subscriber: &'a dyn shirabe::event_dispatcher::EventSubscriberInterface, ) -> anyhow::Result<()>; + fn remove_listener<'a>(&mut self, listener: &'a shirabe_php_rpc::PhpObjHandle); fn has_event_listeners(&mut self, event: &dyn EventInterface) -> bool; } } diff --git a/crates/shirabe/tests/installer_test.rs b/crates/shirabe/tests/installer_test.rs index 1a8b7ccc..f5e84558 100644 --- a/crates/shirabe/tests/installer_test.rs +++ b/crates/shirabe/tests/installer_test.rs @@ -194,6 +194,7 @@ impl EventDispatcherInterface for StubEventDispatcher { ) -> anyhow::Result<()> { Ok(()) } + fn remove_listener(&mut self, _listener: &shirabe_php_rpc::PhpObjHandle) {} fn has_event_listeners(&mut self, _event: &dyn EventInterface) -> bool { false } diff --git a/crates/shirabe/tests/plugin/subscriber_test.rs b/crates/shirabe/tests/plugin/subscriber_test.rs index 8de3d8c2..1d6a7ece 100644 --- a/crates/shirabe/tests/plugin/subscriber_test.rs +++ b/crates/shirabe/tests/plugin/subscriber_test.rs @@ -94,6 +94,38 @@ fn test_subscriber_listener_returning_false_sets_return_code() { } #[test] +fn test_remove_plugin_removes_its_subscribed_listeners() { + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + install_subscriber_plugin(&set_up); + + assert_eq!(0, dispatch(&set_up, "post-install-cmd")); + + let plugin = set_up + .pm + .borrow() + .get_plugins() + .iter() + .find(|p| p.borrow().get_class_name() == "Subscriber\\Plugin") + .expect("the subscriber plugin is registered") + .clone(); + set_up.pm.borrow_mut().remove_plugin(&plugin).unwrap(); + + // removePlugin removed the plugin's `[$subscriber, 'onPostInstall']` listener, so the + // second dispatch produces no further output. + let return_code = dispatch(&set_up, "post-install-cmd"); + + assert_eq!(0, return_code); + assert_eq!( + "activate subscriber-v1\nsubscriber saw post-install-cmd\n", + set_up.io.borrow().get_output() + ); +} + +#[test] fn test_unrelated_event_does_not_reach_the_subscriber() { if !php_runtime_available() { return; |
