From b4e1d12ec64332fd128c876e62510d87d36d5e84 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 15 Aug 2022 21:53:34 -0700 Subject: [PATCH] CiviEventDispatcher - Fix pass-by-reference of hook-style arguments for service-based listeners Suppose you are firing `hook_civicrm_foo` to a serivce-based listener taht uses hook-style arguments. Conceptually, this is a call like: Civi::service('foo')->hook_civicrm_foo($arg1, &$arg2, $arg3); Before this patch, all values are pass-by-value. Changes to `&$arg2` are not propagated back out. The patch ensures that `&$arg2` propagates back out. --- Civi/Core/CiviEventDispatcher.php | 10 +++++- Civi/Core/Event/HookStyleServiceListener.php | 37 ++++++++++++++++++++ Civi/Core/Event/ServiceListener.php | 13 ++++--- 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 Civi/Core/Event/HookStyleServiceListener.php diff --git a/Civi/Core/CiviEventDispatcher.php b/Civi/Core/CiviEventDispatcher.php index d8db0119bc..3aec51f3d1 100644 --- a/Civi/Core/CiviEventDispatcher.php +++ b/Civi/Core/CiviEventDispatcher.php @@ -155,7 +155,15 @@ class CiviEventDispatcher extends EventDispatcher { throw new \InvalidArgumentException('Expected an array("service", "method") argument'); } - $this->addListener($eventName, new \Civi\Core\Event\ServiceListener($callback), $priority); + if ($eventName[0] === '&') { + $eventName = substr($eventName, 1); + $listener = new \Civi\Core\Event\HookStyleServiceListener($callback); + } + else { + $listener = new \Civi\Core\Event\ServiceListener($callback); + } + + $this->addListener($eventName, $listener, $priority); } /** diff --git a/Civi/Core/Event/HookStyleServiceListener.php b/Civi/Core/Event/HookStyleServiceListener.php new file mode 100644 index 0000000000..53a03c40b7 --- /dev/null +++ b/Civi/Core/Event/HookStyleServiceListener.php @@ -0,0 +1,37 @@ +hook_civicrm_foo($arg1, $arg2, ...); + */ +class HookStyleServiceListener extends ServiceListener { + + public function __invoke(...$args) { + if ($this->liveCb === NULL) { + $c = $this->container ?: \Civi::container(); + $this->liveCb = [$c->get($this->inertCb[0]), $this->inertCb[1]]; + } + + $result = call_user_func_array($this->liveCb, $args[0]->getHookValues()); + $args[0]->addReturnValues($result); + } + + public function __toString() { + $class = $this->getServiceClass(); + if ($class) { + return sprintf('$(%s)->%s(...$args) [%s]', $this->inertCb[0], $this->inertCb[1], $class); + } + else { + return sprintf('\$(%s)->%s(...$args)', $this->inertCb[0], $this->inertCb[1]); + } + } + +} diff --git a/Civi/Core/Event/ServiceListener.php b/Civi/Core/Event/ServiceListener.php index 63df85bc71..faf58cc7fd 100644 --- a/Civi/Core/Event/ServiceListener.php +++ b/Civi/Core/Event/ServiceListener.php @@ -24,18 +24,18 @@ class ServiceListener { * @var array * Ex: ['service_name', 'someMethod'] */ - private $inertCb = NULL; + protected $inertCb = NULL; /** * @var array|null * Ex: [$svcObj, 'someMethod'] */ - private $liveCb = NULL; + protected $liveCb = NULL; /** * @var \Symfony\Component\DependencyInjection\ContainerInterface */ - private $container = NULL; + protected $container = NULL; /** * @param array $callback @@ -53,7 +53,7 @@ class ServiceListener { return call_user_func_array($this->liveCb, $args); } - public function __toString() { + protected function getServiceClass(): ?string { $class = NULL; if (\Civi\Core\Container::isContainerBooted()) { try { @@ -63,6 +63,11 @@ class ServiceListener { catch (Throwable $t) { } } + return $class; + } + + public function __toString() { + $class = $this->getServiceClass(); if ($class) { return sprintf('$(%s)->%s($e) [%s]', $this->inertCb[0], $this->inertCb[1], $class); } -- 2.25.1