diff --git a/libraries/src/Event/LazyServiceSubscriber.php b/libraries/src/Event/LazyServiceSubscriber.php new file mode 100644 index 0000000000000..040f7cba6fb28 --- /dev/null +++ b/libraries/src/Event/LazyServiceSubscriber.php @@ -0,0 +1,154 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event; + +use Joomla\CMS\Extension\PluginInterface; +use Joomla\Event\DispatcherInterface; +use Joomla\Event\SubscriberInterface; +use Psr\Container\ContainerInterface; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Decorator for lazy subscribers be pulled from the service container when subscribed event dispatched. + * + * @since __DEPLOY_VERSION__ + */ +final class LazyServiceSubscriber implements LazySubscriberInterface, PluginInterface +{ + /** + * The service container + * + * @var ContainerInterface + * @since __DEPLOY_VERSION__ + */ + private $container; + + /** + * Subscriber class name, and id in the container + * + * @var string + * @since __DEPLOY_VERSION__ + */ + private $class; + + /** + * List of events, and listeners. + * + * @var array + * @since __DEPLOY_VERSION__ + */ + private $eventsAndListeners = []; + + /** + * Subscriber instance. + * + * @var object + * @since __DEPLOY_VERSION__ + */ + private $instance; + + /** + * Constructor. + * + * @param \Psr\Container\ContainerInterface $container Container + * @param string $class Subscriber class name, and id in the container + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(ContainerInterface $container, string $class) + { + $this->container = $container; + $this->class = $class; + + if (!is_subclass_of($class, SubscriberInterface::class)) { + throw new \InvalidArgumentException(sprintf('Class %s does not implement %s', $class, SubscriberInterface::class)); + } + + $this->eventsAndListeners = $class::getSubscribedEvents(); + } + + /** + * Retrieve the instance of Subscriber. + * + * @return object + * + * @throws \Throwable + * + * @since __DEPLOY_VERSION__ + */ + public function getSubscriberInstance(): object + { + if (!$this->instance) { + $this->instance = $this->container->get($this->class); + } + + return $this->instance; + } + + /** + * Returns an array of events and the listeners, the subscriber will listen to. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getEventsAndListeners(): array + { + return $this->eventsAndListeners; + } + + /** + * Method to call the event listeners. + * + * @param string $eventName + * @param array $arguments + * + * @return mixed + * + * @throws \InvalidArgumentException + * + * @since __DEPLOY_VERSION__ + */ + public function __call(string $eventName, array $arguments): mixed + { + $instance = $this->instance ?: $this->getSubscriberInstance(); + + if (!\is_callable([$instance, $eventName])) { + throw new \InvalidArgumentException(sprintf('Event "%s" not supported by %s.', $eventName, $this->class)); + } + + return \call_user_func_array([$instance, $eventName], $arguments); + } + + /** + * A dummy to complete the interface. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function registerListeners() + { + } + + /** + * A dummy to complete the interface. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function setDispatcher(DispatcherInterface $dispatcher) + { + } +} diff --git a/libraries/src/Event/LazySubscriberInterface.php b/libraries/src/Event/LazySubscriberInterface.php new file mode 100644 index 0000000000000..a44e7b7624fb4 --- /dev/null +++ b/libraries/src/Event/LazySubscriberInterface.php @@ -0,0 +1,56 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Event; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Decorator for lazy subscribers be pulled from the service container when subscribed event dispatched. + * + * @since __DEPLOY_VERSION__ + */ +interface LazySubscriberInterface +{ + /** + * Retrieve the instance of Subscriber. + * + * @return object + * + * @throws \Throwable + * + * @since __DEPLOY_VERSION__ + */ + public function getSubscriberInstance(): object; + + /** + * Returns an array of events and the listeners, the subscriber will listen to. + * + * The array keys are event names and the value can be: + * + * - The method name (of the subscriber instance) to call (priority defaults to 0) + * - An array composed of the method name (of the subscriber instance) to call and the priority + * - A callable instance to call + * - An array composed of the callable instance to call and the priority + * + * For instance: + * + * ['eventName' => 'methodName'] + * ['eventName' => ['methodName', $priority]] + * ['eventName' => $callable] + * ['eventName' => [$callable, $priority]] + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getEventsAndListeners(): array; +} diff --git a/libraries/src/Plugin/PluginHelper.php b/libraries/src/Plugin/PluginHelper.php index 9c3c591e311ae..3206ef2610766 100644 --- a/libraries/src/Plugin/PluginHelper.php +++ b/libraries/src/Plugin/PluginHelper.php @@ -10,9 +10,11 @@ namespace Joomla\CMS\Plugin; use Joomla\CMS\Cache\Exception\CacheExceptionInterface; +use Joomla\CMS\Event\LazySubscriberInterface; use Joomla\CMS\Factory; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherInterface; +use Joomla\Event\Priority; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -239,7 +241,19 @@ protected static function import($plugin, $autocreate = true, DispatcherInterfac return; } - $plugin->registerListeners(); + if ($plugin instanceof LazySubscriberInterface) { + foreach ($plugin->getEventsAndListeners() as $eventName => $params) { + if (\is_array($params) && !\is_callable($params)) { + $callback = !\is_string($params[0]) && \is_callable($params[0]) ? $params[0] : [$plugin, $params[0]]; + $dispatcher->addListener($eventName, $callback, $params[1] ?? Priority::NORMAL); + } else { + $callback = !\is_string($params) && \is_callable($params) ? $params : [$plugin, $params]; + $dispatcher->addListener($eventName, $callback); + } + } + } else { + $plugin->registerListeners(); + } } /** diff --git a/plugins/system/schedulerunner/services/provider.php b/plugins/system/schedulerunner/services/provider.php index a8ff358f768e1..a95799730991c 100644 --- a/plugins/system/schedulerunner/services/provider.php +++ b/plugins/system/schedulerunner/services/provider.php @@ -10,6 +10,7 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Event\LazyServiceSubscriber; use Joomla\CMS\Extension\PluginInterface; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; @@ -31,7 +32,7 @@ public function register(Container $container): void { $container->set( - PluginInterface::class, + ScheduleRunner::class, function (Container $container) { $plugin = new ScheduleRunner( $container->get(DispatcherInterface::class), @@ -41,6 +42,11 @@ function (Container $container) { return $plugin; } + )->set( + PluginInterface::class, + function (Container $container) { + return new LazyServiceSubscriber($container, ScheduleRunner::class); + } ); } }; diff --git a/plugins/system/tasknotification/services/provider.php b/plugins/system/tasknotification/services/provider.php index c0cfbcd564d85..91fd41e94773f 100644 --- a/plugins/system/tasknotification/services/provider.php +++ b/plugins/system/tasknotification/services/provider.php @@ -10,6 +10,7 @@ \defined('_JEXEC') or die; +use Joomla\CMS\Event\LazyServiceSubscriber; use Joomla\CMS\Extension\PluginInterface; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; @@ -33,7 +34,7 @@ public function register(Container $container): void { $container->set( - PluginInterface::class, + TaskNotification::class, function (Container $container) { $plugin = new TaskNotification( $container->get(DispatcherInterface::class), @@ -45,6 +46,11 @@ function (Container $container) { return $plugin; } + )->set( + PluginInterface::class, + function (Container $container) { + return new LazyServiceSubscriber($container, TaskNotification::class); + } ); } }; diff --git a/plugins/system/webauthn/services/provider.php b/plugins/system/webauthn/services/provider.php index c9517c55385b2..e35d3a135a1ba 100644 --- a/plugins/system/webauthn/services/provider.php +++ b/plugins/system/webauthn/services/provider.php @@ -12,6 +12,7 @@ use Joomla\Application\ApplicationInterface; use Joomla\Application\SessionAwareWebApplicationInterface; +use Joomla\CMS\Event\LazyServiceSubscriber; use Joomla\CMS\Extension\PluginInterface; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; @@ -40,7 +41,7 @@ public function register(Container $container) { $container->set( - PluginInterface::class, + Webauthn::class, function (Container $container) { $app = Factory::getApplication(); $session = $container->has('session') ? $container->get('session') : $this->getSession($app); @@ -72,6 +73,11 @@ function (Container $container) { return $plugin; } + )->set( + PluginInterface::class, + function (Container $container) { + return new LazyServiceSubscriber($container, Webauthn::class); + } ); }