aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe')
-rw-r--r--crates/shirabe/src/event_dispatcher/event_dispatcher.rs39
-rw-r--r--crates/shirabe/src/plugin/plugin_manager.rs16
-rw-r--r--crates/shirabe/tests/command/archive_command_test.rs1
-rw-r--r--crates/shirabe/tests/installer_test.rs1
-rw-r--r--crates/shirabe/tests/plugin/subscriber_test.rs32
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;