aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src')
-rw-r--r--crates/shirabe/src/command/require_command.rs15
-rw-r--r--crates/shirabe/src/event_dispatcher/event_dispatcher.rs37
2 files changed, 39 insertions, 13 deletions
diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs
index 05f86480..c1730942 100644
--- a/crates/shirabe/src/command/require_command.rs
+++ b/crates/shirabe/src/command/require_command.rs
@@ -53,7 +53,7 @@ pub struct RequireCommand {
lock: std::cell::RefCell<String>,
/// contents before modification if the lock file exists
lock_backup: std::cell::RefCell<Option<String>>,
- dependency_resolution_completed: std::cell::Cell<bool>,
+ dependency_resolution_completed: std::rc::Rc<std::cell::Cell<bool>>,
repos: std::cell::RefCell<Option<crate::repository::RepositoryInterfaceHandle>>,
repository_sets:
std::cell::RefCell<IndexMap<String, std::rc::Rc<std::cell::RefCell<RepositorySet>>>>,
@@ -76,7 +76,7 @@ impl RequireCommand {
composer_backup: std::cell::RefCell::new(String::new()),
lock: std::cell::RefCell::new(String::new()),
lock_backup: std::cell::RefCell::new(None),
- dependency_resolution_completed: std::cell::Cell::new(false),
+ dependency_resolution_completed: std::rc::Rc::new(std::cell::Cell::new(false)),
repos: std::cell::RefCell::new(None),
repository_sets: std::cell::RefCell::new(IndexMap::new()),
};
@@ -759,14 +759,13 @@ impl RequireCommand {
self.dependency_resolution_completed.set(false);
// PHP: $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC,
// function () use (&$dependencyResolutionCompleted) { $dependencyResolutionCompleted = true; }, 10000);
- // TODO(phase-c): the event dispatcher's Callable::Closure is a placeholder variant that
- // stores no actual closure, so the listener that flips dependency_resolution_completed
- // cannot be registered. Resolving needs the closure model (Callable holding an Rc<dyn Fn>)
- // plus dependency_resolution_completed shared (Rc<RefCell<bool>>) into both the listener
- // and this command.
+ let dependency_resolution_completed = self.dependency_resolution_completed.clone();
composer.get_event_dispatcher().borrow_mut().add_listener(
InstallerEvents::PRE_OPERATIONS_EXEC,
- crate::event_dispatcher::Callable::Closure,
+ crate::event_dispatcher::Callable::Closure(std::rc::Rc::new(move |_event| {
+ dependency_resolution_completed.set(true);
+ PhpMixed::Null
+ })),
10000,
);
diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
index 6deb130e..1f1d9619 100644
--- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
+++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs
@@ -34,16 +34,34 @@ use shirabe_php_shim::{
/// Represents a callable listener. PHP's `callable` may be a string (command, script, or
/// "Class::method"), a `[object|string, method]` pair, or a `\Closure`.
///
-/// TODO(plugin): Subscriber- and Closure-based listeners come from plugins and are not
+/// TODO(plugin): Subscriber-based (`ArrayCallable`) listeners come from plugins and are not
/// implemented yet — only the string forms used by composer.json `scripts` work here.
-#[derive(Debug, Clone)]
+#[derive(Clone)]
pub enum Callable {
String(String),
/// `[$className_or_object, $methodName]` array callable. The first element is represented
/// here as `PhpMixed` to keep parity with PHP's loose typing.
ArrayCallable(Box<PhpMixed>, String),
- /// PHP `\Closure` placeholder.
- Closure,
+ /// PHP `\Closure`, invoked with the event exactly like `$callable($event)` in
+ /// `EventDispatcher::doDispatch`. Today this is only produced by Composer's own commands
+ /// registering an inline listener on themselves (e.g. `RequireCommand`'s
+ /// `dependencyResolutionCompleted` tracker) — Plugin-supplied closures remain out of scope
+ /// pending Plugin API.
+ Closure(std::rc::Rc<dyn Fn(&dyn EventInterface) -> PhpMixed>),
+}
+
+impl std::fmt::Debug for Callable {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Callable::String(s) => f.debug_tuple("String").field(s).finish(),
+ Callable::ArrayCallable(first, method) => f
+ .debug_tuple("ArrayCallable")
+ .field(first)
+ .field(method)
+ .finish(),
+ Callable::Closure(_) => f.write_str("Closure(..)"),
+ }
+ }
}
/// The Event Dispatcher.
@@ -326,7 +344,16 @@ impl EventDispatcher {
}
);
let is_string_callable = matches!(callable, Callable::String(_));
- if !is_string_callable {
+ if let Callable::Closure(ref closure) = callable {
+ let _ = self.make_autoloader(event, &callable);
+ // Closures are always callable in PHP (is_callable() returns true for any \Closure),
+ // so the is_callable()/RuntimeException branch below never applies here.
+ r#return = if matches!(closure(event), PhpMixed::Bool(false)) {
+ 1
+ } else {
+ 0
+ };
+ } else if !is_string_callable {
// TODO(plugin): non-string callable handling — verify is_callable, invoke,
// and replicate the get_class / write_error / is_callable error path from PHP.
let _ = self.make_autoloader(event, &callable);