aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-04 04:31:51 +0900
committernsfisis <nsfisis@gmail.com>2026-08-04 05:43:26 +0900
commit3f3ad76eafcc5513000ac7526c74c2009bb390d8 (patch)
treee601fd78b4bed6add607305098bdf6257eeba7d8 /crates/shirabe/src
parent695365a0ad68e4534425c64b7a6a4b6598b68ef7 (diff)
downloadphp-shirabe-3f3ad76eafcc5513000ac7526c74c2009bb390d8.tar.gz
php-shirabe-3f3ad76eafcc5513000ac7526c74c2009bb390d8.tar.zst
php-shirabe-3f3ad76eafcc5513000ac7526c74c2009bb390d8.zip
feat(plugin): remove subscribed listeners when a plugin is removed
Wire removePlugin to EventDispatcher::removeListener now that subscriber listeners carry the plugin's P-table handle: PHP's $candidate[0] === $listener identity check maps to phandle equality on Callable::PhpMethod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
-rw-r--r--crates/shirabe/src/event_dispatcher/event_dispatcher.rs39
-rw-r--r--crates/shirabe/src/plugin/plugin_manager.rs16
2 files changed, 33 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(())
}