123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- <?php
- namespace Symfony\Component\EventDispatcher;
- use Psr\EventDispatcher\StoppableEventInterface;
- use Symfony\Component\EventDispatcher\Debug\WrappedListener;
- use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
- class EventDispatcher implements EventDispatcherInterface
- {
- private $listeners = [];
- private $sorted = [];
- private $optimized;
- public function __construct()
- {
- if (__CLASS__ === static::class) {
- $this->optimized = [];
- }
- }
-
- public function dispatch($event)
- {
- $eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
- if (\is_object($event)) {
- $eventName = $eventName ?? \get_class($event);
- } elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) {
- @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', EventDispatcherInterface::class), \E_USER_DEPRECATED);
- $swap = $event;
- $event = $eventName ?? new Event();
- $eventName = $swap;
- } else {
- throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, "%s" given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event)));
- }
- if (null !== $this->optimized && null !== $eventName) {
- $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
- } else {
- $listeners = $this->getListeners($eventName);
- }
- if ($listeners) {
- $this->callListeners($listeners, $eventName, $event);
- }
- return $event;
- }
-
- public function getListeners($eventName = null)
- {
- if (null !== $eventName) {
- if (empty($this->listeners[$eventName])) {
- return [];
- }
- if (!isset($this->sorted[$eventName])) {
- $this->sortListeners($eventName);
- }
- return $this->sorted[$eventName];
- }
- foreach ($this->listeners as $eventName => $eventListeners) {
- if (!isset($this->sorted[$eventName])) {
- $this->sortListeners($eventName);
- }
- }
- return array_filter($this->sorted);
- }
-
- public function getListenerPriority($eventName, $listener)
- {
- if (empty($this->listeners[$eventName])) {
- return null;
- }
- if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
- $listener[0] = $listener[0]();
- $listener[1] = $listener[1] ?? '__invoke';
- }
- foreach ($this->listeners[$eventName] as $priority => &$listeners) {
- foreach ($listeners as &$v) {
- if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
- $v[0] = $v[0]();
- $v[1] = $v[1] ?? '__invoke';
- }
- if ($v === $listener) {
- return $priority;
- }
- }
- }
- return null;
- }
-
- public function hasListeners($eventName = null)
- {
- if (null !== $eventName) {
- return !empty($this->listeners[$eventName]);
- }
- foreach ($this->listeners as $eventListeners) {
- if ($eventListeners) {
- return true;
- }
- }
- return false;
- }
-
- public function addListener($eventName, $listener, $priority = 0)
- {
- $this->listeners[$eventName][$priority][] = $listener;
- unset($this->sorted[$eventName], $this->optimized[$eventName]);
- }
-
- public function removeListener($eventName, $listener)
- {
- if (empty($this->listeners[$eventName])) {
- return;
- }
- if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
- $listener[0] = $listener[0]();
- $listener[1] = $listener[1] ?? '__invoke';
- }
- foreach ($this->listeners[$eventName] as $priority => &$listeners) {
- foreach ($listeners as $k => &$v) {
- if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
- $v[0] = $v[0]();
- $v[1] = $v[1] ?? '__invoke';
- }
- if ($v === $listener) {
- unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
- }
- }
- if (!$listeners) {
- unset($this->listeners[$eventName][$priority]);
- }
- }
- }
-
- public function addSubscriber(EventSubscriberInterface $subscriber)
- {
- foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
- if (\is_string($params)) {
- $this->addListener($eventName, [$subscriber, $params]);
- } elseif (\is_string($params[0])) {
- $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
- } else {
- foreach ($params as $listener) {
- $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
- }
- }
- }
- }
-
- public function removeSubscriber(EventSubscriberInterface $subscriber)
- {
- foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
- if (\is_array($params) && \is_array($params[0])) {
- foreach ($params as $listener) {
- $this->removeListener($eventName, [$subscriber, $listener[0]]);
- }
- } else {
- $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
- }
- }
- }
-
- protected function callListeners(iterable $listeners, string $eventName, $event)
- {
- if ($event instanceof Event) {
- $this->doDispatch($listeners, $eventName, $event);
- return;
- }
- $stoppable = $event instanceof ContractsEvent || $event instanceof StoppableEventInterface;
- foreach ($listeners as $listener) {
- if ($stoppable && $event->isPropagationStopped()) {
- break;
- }
-
- $listener($listener instanceof WrappedListener ? new LegacyEventProxy($event) : $event, $eventName, $this);
- }
- }
-
- protected function doDispatch($listeners, $eventName, Event $event)
- {
- foreach ($listeners as $listener) {
- if ($event->isPropagationStopped()) {
- break;
- }
- $listener($event, $eventName, $this);
- }
- }
-
- private function sortListeners(string $eventName)
- {
- krsort($this->listeners[$eventName]);
- $this->sorted[$eventName] = [];
- foreach ($this->listeners[$eventName] as &$listeners) {
- foreach ($listeners as $k => &$listener) {
- if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
- $listener[0] = $listener[0]();
- $listener[1] = $listener[1] ?? '__invoke';
- }
- $this->sorted[$eventName][] = $listener;
- }
- }
- }
-
- private function optimizeListeners(string $eventName): array
- {
- krsort($this->listeners[$eventName]);
- $this->optimized[$eventName] = [];
- foreach ($this->listeners[$eventName] as &$listeners) {
- foreach ($listeners as &$listener) {
- $closure = &$this->optimized[$eventName][];
- if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
- $closure = static function (...$args) use (&$listener, &$closure) {
- if ($listener[0] instanceof \Closure) {
- $listener[0] = $listener[0]();
- $listener[1] = $listener[1] ?? '__invoke';
- }
- ($closure = \Closure::fromCallable($listener))(...$args);
- };
- } else {
- $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
- }
- }
- }
- return $this->optimized[$eventName];
- }
- }
|