Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Attribute;
/**
* Service tag to autoconfigure event listeners.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AsEventListener
{
public function __construct(
public ?string $event = null,
public ?string $method = null,
public int $priority = 0,
public ?string $dispatcher = null,
) {
}
}

View File

@@ -1,19 +1,78 @@
CHANGELOG
=========
6.0
---
* Remove `LegacyEventDispatcherProxy`
5.4
---
* Allow `#[AsEventListener]` attribute on methods
5.3
---
* Add `#[AsEventListener]` attribute for declaring listeners on PHP 8
5.1.0
-----
* The `LegacyEventDispatcherProxy` class has been deprecated.
* Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`.
5.0.0
-----
* The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`.
* The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`.
* The `TraceableEventDispatcherInterface` has been removed.
* The `WrappedListener` class is now final.
4.4.0
-----
* `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`.
* Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events.
4.3.0
-----
* The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated
* deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead
4.1.0
-----
* added support for invokable event listeners tagged with `kernel.event_listener` by default
* The `TraceableEventDispatcher::getOrphanedEvents()` method has been added.
* The `TraceableEventDispatcherInterface` has been deprecated.
4.0.0
-----
* removed the `ContainerAwareEventDispatcher` class
* added the `reset()` method to the `TraceableEventDispatcherInterface`
3.4.0
-----
* Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated.
3.3.0
-----
* The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead.
* The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead.
3.0.0
-----
* The method `getListenerPriority($eventName, $listener)` has been added to the
`EventDispatcherInterface`.
* The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
and `Event::getName()` have been removed.
The event dispatcher and the event name are passed to the listener call.
* The method `getListenerPriority($eventName, $listener)` has been added to the
`EventDispatcherInterface`.
* The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
and `Event::getName()` have been removed.
The event dispatcher and the event name are passed to the listener call.
2.5.0
-----

View File

@@ -1,211 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Lazily loads listeners and subscribers from the dependency injection
* container.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Jordan Alliot <jordan.alliot@gmail.com>
*
* @deprecated since 3.3, to be removed in 4.0. Use EventDispatcher with closure factories instead.
*/
class ContainerAwareEventDispatcher extends EventDispatcher
{
/**
* The container from where services are loaded.
*
* @var ContainerInterface
*/
private $container;
/**
* The service IDs of the event listeners and subscribers.
*
* @var array
*/
private $listenerIds = array();
/**
* The services registered as listeners.
*
* @var array
*/
private $listeners = array();
/**
* Constructor.
*
* @param ContainerInterface $container A ContainerInterface instance
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$class = get_class($this);
if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) {
$class = get_parent_class($class);
}
if (__CLASS__ !== $class) {
@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
}
}
/**
* Adds a service as event listener.
*
* @param string $eventName Event for which the listener is added
* @param array $callback The service ID of the listener service & the method
* name that has to be called
* @param int $priority The higher this value, the earlier an event listener
* will be triggered in the chain.
* Defaults to 0.
*
* @throws \InvalidArgumentException
*/
public function addListenerService($eventName, $callback, $priority = 0)
{
@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
if (!is_array($callback) || 2 !== count($callback)) {
throw new \InvalidArgumentException('Expected an array("service", "method") argument');
}
$this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
}
public function removeListener($eventName, $listener)
{
$this->lazyLoad($eventName);
if (isset($this->listenerIds[$eventName])) {
foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) {
$key = $serviceId.'.'.$method;
if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) {
unset($this->listeners[$eventName][$key]);
if (empty($this->listeners[$eventName])) {
unset($this->listeners[$eventName]);
}
unset($this->listenerIds[$eventName][$i]);
if (empty($this->listenerIds[$eventName])) {
unset($this->listenerIds[$eventName]);
}
}
}
}
parent::removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
if (null === $eventName) {
return $this->listenerIds || $this->listeners || parent::hasListeners();
}
if (isset($this->listenerIds[$eventName])) {
return true;
}
return parent::hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
if (null === $eventName) {
foreach ($this->listenerIds as $serviceEventName => $args) {
$this->lazyLoad($serviceEventName);
}
} else {
$this->lazyLoad($eventName);
}
return parent::getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
$this->lazyLoad($eventName);
return parent::getListenerPriority($eventName, $listener);
}
/**
* Adds a service as event subscriber.
*
* @param string $serviceId The service ID of the subscriber service
* @param string $class The service's class name (which must implement EventSubscriberInterface)
*/
public function addSubscriberService($serviceId, $class)
{
@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED);
foreach ($class::getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->listenerIds[$eventName][] = array($serviceId, $params, 0);
} elseif (is_string($params[0])) {
$this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
public function getContainer()
{
@trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED);
return $this->container;
}
/**
* Lazily loads listeners for this event from the dependency injection
* container.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
*/
protected function lazyLoad($eventName)
{
if (isset($this->listenerIds[$eventName])) {
foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) {
$listener = $this->container->get($serviceId);
$key = $serviceId.'.'.$method;
if (!isset($this->listeners[$eventName][$key])) {
$this->addListener($eventName, array($listener, $method), $priority);
} elseif ($listener !== $this->listeners[$eventName][$key]) {
parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
$this->addListener($eventName, array($listener, $method), $priority);
}
$this->listeners[$eventName][$key] = $listener;
}
}
}
}

View File

@@ -11,11 +11,14 @@
namespace Symfony\Component\EventDispatcher\Debug;
use Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* Collects some data about event listeners.
@@ -24,35 +27,33 @@ use Psr\Log\LoggerInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableEventDispatcher implements TraceableEventDispatcherInterface
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
{
protected $logger;
protected $stopwatch;
private $called;
private $dispatcher;
private $wrappedListeners;
/**
* Constructor.
*
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param Stopwatch $stopwatch A Stopwatch instance
* @param LoggerInterface $logger A LoggerInterface instance
* @var \SplObjectStorage<WrappedListener, array{string, string}>|null
*/
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
private ?\SplObjectStorage $callStack = null;
private $dispatcher;
private array $wrappedListeners = [];
private array $orphanedEvents = [];
private $requestStack;
private string $currentRequestHash = '';
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->called = array();
$this->wrappedListeners = array();
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
{
$this->dispatcher->addListener($eventName, $listener, $priority);
}
@@ -68,11 +69,11 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
public function removeListener(string $eventName, callable|array $listener)
{
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
@@ -94,7 +95,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
public function getListeners(string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
@@ -102,13 +103,13 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
// we might have wrapped listeners for the event (if called while dispatching)
// in that case get the priority by wrapper
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
foreach ($this->wrappedListeners[$eventName] as $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
}
}
@@ -120,7 +121,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
public function hasListeners(string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}
@@ -128,137 +129,168 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
public function dispatch(object $event, string $eventName = null): object
{
if (null === $event) {
$event = new Event();
$eventName = $eventName ?? \get_class($event);
if (null === $this->callStack) {
$this->callStack = new \SplObjectStorage();
}
if (null !== $this->logger && $event->isPropagationStopped()) {
$currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
}
$this->preProcess($eventName);
$this->preDispatch($eventName, $event);
$e = $this->stopwatch->start($eventName, 'section');
$this->dispatcher->dispatch($eventName, $event);
if ($e->isStarted()) {
$e->stop();
try {
$this->beforeDispatch($eventName, $event);
try {
$e = $this->stopwatch->start($eventName, 'section');
try {
$this->dispatcher->dispatch($event, $eventName);
} finally {
if ($e->isStarted()) {
$e->stop();
}
}
} finally {
$this->afterDispatch($eventName, $event);
}
} finally {
$this->currentRequestHash = $currentRequestHash;
$this->postProcess($eventName);
}
$this->postDispatch($eventName, $event);
$this->postProcess($eventName);
return $event;
}
/**
* {@inheritdoc}
*/
public function getCalledListeners()
public function getCalledListeners(Request $request = null): array
{
$called = array();
foreach ($this->called as $eventName => $listeners) {
foreach ($listeners as $listener) {
$called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
if (null === $this->callStack) {
return [];
}
$hash = $request ? spl_object_hash($request) : null;
$called = [];
foreach ($this->callStack as $listener) {
[$eventName, $requestHash] = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$called[] = $listener->getInfo($eventName);
}
}
return $called;
}
/**
* {@inheritdoc}
*/
public function getNotCalledListeners()
public function getNotCalledListeners(Request $request = null): array
{
try {
$allListeners = $this->getListeners();
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
$this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
}
// unable to retrieve the uncalled listeners
return array();
return [];
}
$notCalled = array();
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
$called = false;
if (isset($this->called[$eventName])) {
foreach ($this->called[$eventName] as $l) {
if ($l->getWrappedListener() === $listener) {
$called = true;
$hash = $request ? spl_object_hash($request) : null;
$calledListeners = [];
break;
}
}
}
if (null !== $this->callStack) {
foreach ($this->callStack as $calledListener) {
[, $requestHash] = $this->callStack->getInfo();
if (!$called) {
if (!$listener instanceof WrappedListener) {
$listener = new WrappedListener($listener, null, $this->stopwatch, $this);
}
$notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName);
if (null === $hash || $hash === $requestHash) {
$calledListeners[] = $calledListener->getWrappedListener();
}
}
}
uasort($notCalled, array($this, 'sortListenersByPriority'));
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
if (!\in_array($listener, $calledListeners, true)) {
if (!$listener instanceof WrappedListener) {
$listener = new WrappedListener($listener, null, $this->stopwatch, $this);
}
$notCalled[] = $listener->getInfo($eventName);
}
}
}
uasort($notCalled, [$this, 'sortNotCalledListeners']);
return $notCalled;
}
public function getOrphanedEvents(Request $request = null): array
{
if ($request) {
return $this->orphanedEvents[spl_object_hash($request)] ?? [];
}
if (!$this->orphanedEvents) {
return [];
}
return array_merge(...array_values($this->orphanedEvents));
}
public function reset()
{
$this->callStack = null;
$this->orphanedEvents = [];
$this->currentRequestHash = '';
}
/**
* Proxies all method calls to the original event dispatcher.
*
* @param string $method The method name
* @param array $arguments The method arguments
*
* @return mixed
*/
public function __call($method, $arguments)
public function __call(string $method, array $arguments): mixed
{
return call_user_func_array(array($this->dispatcher, $method), $arguments);
return $this->dispatcher->{$method}(...$arguments);
}
/**
* Called before dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function preDispatch($eventName, Event $event)
protected function beforeDispatch(string $eventName, object $event)
{
}
/**
* Called after dispatching the event.
*
* @param string $eventName The event name
* @param Event $event The event
*/
protected function postDispatch($eventName, Event $event)
protected function afterDispatch(string $eventName, object $event)
{
}
private function preProcess($eventName)
private function preProcess(string $eventName): void
{
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[$this->currentRequestHash][] = $eventName;
return;
}
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$priority = $this->getListenerPriority($eventName, $listener);
$wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);
$wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
$this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]);
}
}
private function postProcess($eventName)
private function postProcess(string $eventName): void
{
unset($this->wrappedListeners[$eventName]);
$skipped = false;
@@ -272,19 +304,15 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
if (null !== $this->logger) {
$context = array('event' => $eventName, 'listener' => $listener->getPretty());
$context = ['event' => $eventName, 'listener' => $listener->getPretty()];
}
if ($listener->wasCalled()) {
if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
}
if (!isset($this->called[$eventName])) {
$this->called[$eventName] = new \SplObjectStorage();
}
$this->called[$eventName]->attach($listener);
} else {
$this->callStack->detach($listener);
}
if (null !== $this->logger && $skipped) {
@@ -301,13 +329,17 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
}
}
private function sortListenersByPriority($a, $b)
private function sortNotCalledListeners(array $a, array $b)
{
if (is_int($a['priority']) && !is_int($b['priority'])) {
if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
return $cmp;
}
if (\is_int($a['priority']) && !\is_int($b['priority'])) {
return 1;
}
if (!is_int($a['priority']) && is_int($b['priority'])) {
if (!\is_int($a['priority']) && \is_int($b['priority'])) {
return -1;
}

View File

@@ -1,34 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
interface TraceableEventDispatcherInterface extends EventDispatcherInterface
{
/**
* Gets the called listeners.
*
* @return array An array of called listeners
*/
public function getCalledListeners();
/**
* Gets the not called listeners.
*
* @return array An array of not called listeners
*/
public function getNotCalledListeners();
}

View File

@@ -11,44 +11,52 @@
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\EventDispatcher\Event;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Caster\ClassStub;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class WrappedListener
final class WrappedListener
{
private $listener;
private $name;
private $called;
private $stoppedPropagation;
private string|array|object $listener;
private ?\Closure $optimizedListener;
private string $name;
private bool $called = false;
private bool $stoppedPropagation = false;
private $stopwatch;
private $dispatcher;
private $pretty;
private string $pretty;
private $stub;
private static $hasClassStub;
private ?int $priority = null;
private static bool $hasClassStub;
public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
{
$this->listener = $listener;
$this->name = $name;
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->called = false;
$this->stoppedPropagation = false;
if (is_array($listener)) {
$this->name = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
if (\is_array($listener)) {
$this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0];
$this->pretty = $this->name.'::'.$listener[1];
} elseif ($listener instanceof \Closure) {
$this->pretty = $this->name = 'closure';
} elseif (is_string($listener)) {
$r = new \ReflectionFunction($listener);
if (str_contains($r->name, '{closure}')) {
$this->pretty = $this->name = 'closure';
} elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
$this->name = $class->name;
$this->pretty = $this->name.'::'.$r->name;
} else {
$this->pretty = $this->name = $r->name;
}
} elseif (\is_string($listener)) {
$this->pretty = $this->name = $listener;
} else {
$this->name = get_class($listener);
$this->name = get_debug_type($listener);
$this->pretty = $this->name.'::__invoke';
}
@@ -56,58 +64,57 @@ class WrappedListener
$this->name = $name;
}
if (null === self::$hasClassStub) {
self::$hasClassStub = class_exists(ClassStub::class);
}
self::$hasClassStub ??= class_exists(ClassStub::class);
}
public function getWrappedListener()
public function getWrappedListener(): callable|array
{
return $this->listener;
}
public function wasCalled()
public function wasCalled(): bool
{
return $this->called;
}
public function stoppedPropagation()
public function stoppedPropagation(): bool
{
return $this->stoppedPropagation;
}
public function getPretty()
public function getPretty(): string
{
return $this->pretty;
}
public function getInfo($eventName)
public function getInfo(string $eventName): array
{
if (null === $this->stub) {
$this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
}
$this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
return array(
return [
'event' => $eventName,
'priority' => null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null,
'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null),
'pretty' => $this->pretty,
'stub' => $this->stub,
);
];
}
public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void
{
$dispatcher = $this->dispatcher ?: $dispatcher;
$this->called = true;
$this->priority = $dispatcher->getListenerPriority($eventName, $this->listener);
$e = $this->stopwatch->start($this->name, 'event_listener');
call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
if ($e->isStarted()) {
$e->stop();
}
if ($event->isPropagationStopped()) {
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
$this->stoppedPropagation = true;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* This pass allows bundles to extend the list of event aliases.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
class AddEventAliasesPass implements CompilerPassInterface
{
private array $eventAliases;
public function __construct(array $eventAliases)
{
$this->eventAliases = $eventAliases;
}
public function process(ContainerBuilder $container): void
{
$eventAliases = $container->hasParameter('event_dispatcher.event_aliases') ? $container->getParameter('event_dispatcher.event_aliases') : [];
$container->setParameter(
'event_dispatcher.event_aliases',
array_merge($eventAliases, $this->eventAliases)
);
}
}

View File

@@ -12,104 +12,173 @@
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Compiler pass to register tagged services for an event dispatcher.
*/
class RegisterListenersPass implements CompilerPassInterface
{
/**
* @var string
*/
protected $dispatcherService;
private array $hotPathEvents = [];
private array $noPreloadEvents = [];
/**
* @var string
* @return $this
*/
protected $listenerTag;
/**
* @var string
*/
protected $subscriberTag;
/**
* Constructor.
*
* @param string $dispatcherService Service name of the event dispatcher in processed container
* @param string $listenerTag Tag name used for listener
* @param string $subscriberTag Tag name used for subscribers
*/
public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
public function setHotPathEvents(array $hotPathEvents): static
{
$this->dispatcherService = $dispatcherService;
$this->listenerTag = $listenerTag;
$this->subscriberTag = $subscriberTag;
$this->hotPathEvents = array_flip($hotPathEvents);
return $this;
}
/**
* @return $this
*/
public function setNoPreloadEvents(array $noPreloadEvents): static
{
$this->noPreloadEvents = array_flip($noPreloadEvents);
return $this;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) {
return;
}
$definition = $container->findDefinition($this->dispatcherService);
$aliases = [];
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
$def = $container->getDefinition($id);
if ($container->hasParameter('event_dispatcher.event_aliases')) {
$aliases = $container->getParameter('event_dispatcher.event_aliases');
}
$globalDispatcherDefinition = $container->findDefinition('event_dispatcher');
foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) {
$noPreload = 0;
foreach ($events as $event) {
$priority = isset($event['priority']) ? $event['priority'] : 0;
$priority = $event['priority'] ?? 0;
if (!isset($event['event'])) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) {
continue;
}
$event['method'] = $event['method'] ?? '__invoke';
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
}
$event['event'] = $aliases[$event['event']] ?? $event['event'];
if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback(array(
'/(?<=\b)[a-z]/i',
$event['method'] = 'on'.preg_replace_callback([
'/(?<=\b|_)[a-z]/i',
'/[^a-z0-9]/i',
), function ($matches) { return strtoupper($matches[0]); }, $event['event']);
], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
$event['method'] = '__invoke';
}
}
$definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority));
$dispatcherDefinition = $globalDispatcherDefinition;
if (isset($event['dispatcher'])) {
$dispatcherDefinition = $container->getDefinition($event['dispatcher']);
}
$dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
if (isset($this->hotPathEvents[$event['event']])) {
$container->getDefinition($id)->addTag('container.hot_path');
} elseif (isset($this->noPreloadEvents[$event['event']])) {
++$noPreload;
}
}
if ($noPreload && \count($events) === $noPreload) {
$container->getDefinition($id)->addTag('container.no_preload');
}
}
$extractingDispatcher = new ExtractingEventDispatcher();
foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) {
foreach ($container->findTaggedServiceIds('kernel.event_subscriber', true) as $id => $tags) {
$def = $container->getDefinition($id);
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $container->getParameterBag()->resolveValue($def->getClass());
$interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
$class = $def->getClass();
if (!is_subclass_of($class, $interface)) {
if (!class_exists($class, false)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
}
$class = $r->name;
$dispatcherDefinitions = [];
foreach ($tags as $attributes) {
if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
continue;
}
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
$dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']);
}
$container->addObjectResource($class);
if (!$dispatcherDefinitions) {
$dispatcherDefinitions = [$globalDispatcherDefinition];
}
$noPreload = 0;
ExtractingEventDispatcher::$aliases = $aliases;
ExtractingEventDispatcher::$subscriber = $class;
$extractingDispatcher->addSubscriber($extractingDispatcher);
foreach ($extractingDispatcher->listeners as $args) {
$args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]);
$definition->addMethodCall('addListener', $args);
$args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
foreach ($dispatcherDefinitions as $dispatcherDefinition) {
$dispatcherDefinition->addMethodCall('addListener', $args);
}
if (isset($this->hotPathEvents[$args[0]])) {
$container->getDefinition($id)->addTag('container.hot_path');
} elseif (isset($this->noPreloadEvents[$args[0]])) {
++$noPreload;
}
}
$extractingDispatcher->listeners = array();
if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
$container->getDefinition($id)->addTag('container.no_preload');
}
$extractingDispatcher->listeners = [];
ExtractingEventDispatcher::$aliases = [];
}
}
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
{
if (
null === ($class = $container->getDefinition($id)->getClass())
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}
return $name;
}
}
/**
@@ -117,19 +186,24 @@ class RegisterListenersPass implements CompilerPassInterface
*/
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
{
public $listeners = array();
public array $listeners = [];
public static $subscriber;
public static array $aliases = [];
public static string $subscriber;
public function addListener($eventName, $listener, $priority = 0)
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
{
$this->listeners[] = array($eventName, $listener[1], $priority);
$this->listeners[] = [$eventName, $listener[1], $priority];
}
public static function getSubscribedEvents()
public static function getSubscribedEvents(): array
{
$callback = array(self::$subscriber, 'getSubscribedEvents');
$events = [];
return $callback();
foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
$events[self::$aliases[$eventName] ?? $eventName] = $params;
}
return $events;
}
}

View File

@@ -1,58 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Event
{
/**
* @var bool Whether no further event listeners should be triggered
*/
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*
* @see Event::stopPropagation()
*
* @return bool Whether propagation was already stopped for this event
*/
public function isPropagationStopped()
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation()
{
$this->propagationStopped = true;
}
}

View File

@@ -11,6 +11,9 @@
namespace Symfony\Component\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\Debug\WrappedListener;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
*
@@ -28,20 +31,32 @@ namespace Symfony\Component\EventDispatcher;
*/
class EventDispatcher implements EventDispatcherInterface
{
private $listeners = array();
private $sorted = array();
private array $listeners = [];
private array $sorted = [];
private array $optimized;
public function __construct()
{
if (__CLASS__ === static::class) {
$this->optimized = [];
}
}
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
public function dispatch(object $event, string $eventName = null): object
{
if (null === $event) {
$event = new Event();
$eventName = $eventName ?? \get_class($event);
if (isset($this->optimized)) {
$listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
} else {
$listeners = $this->getListeners($eventName);
}
if ($listeners = $this->getListeners($eventName)) {
$this->doDispatch($listeners, $eventName, $event);
if ($listeners) {
$this->callListeners($listeners, $eventName, $event);
}
return $event;
@@ -50,11 +65,11 @@ class EventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
public function getListeners(string $eventName = null): array
{
if (null !== $eventName) {
if (empty($this->listeners[$eventName])) {
return array();
return [];
}
if (!isset($this->sorted[$eventName])) {
@@ -76,33 +91,36 @@ class EventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
if (empty($this->listeners[$eventName])) {
return;
return null;
}
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
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) {
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]();
$this->listeners[$eventName][$priority][$k] = $v;
$v[1] = $v[1] ?? '__invoke';
}
if ($v === $listener) {
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
return $priority;
}
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
public function hasListeners(string $eventName = null): bool
{
if (null !== $eventName) {
return !empty($this->listeners[$eventName]);
@@ -120,40 +138,38 @@ class EventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
{
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName]);
unset($this->sorted[$eventName], $this->optimized[$eventName]);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
public function removeListener(string $eventName, callable|array $listener)
{
if (empty($this->listeners[$eventName])) {
return;
}
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
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) {
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]);
} else {
$listeners[$k] = $v;
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
}
}
if ($listeners) {
$this->listeners[$eventName][$priority] = $listeners;
} else {
if (!$listeners) {
unset($this->listeners[$eventName][$priority]);
}
}
@@ -165,13 +181,13 @@ class EventDispatcher implements EventDispatcherInterface
public function addSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_string($params)) {
$this->addListener($eventName, array($subscriber, $params));
} elseif (is_string($params[0])) {
$this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
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, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
$this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
}
}
}
@@ -183,12 +199,12 @@ class EventDispatcher implements EventDispatcherInterface
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (is_array($params) && is_array($params[0])) {
if (\is_array($params) && \is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($eventName, array($subscriber, $listener[0]));
$this->removeListener($eventName, [$subscriber, $listener[0]]);
}
} else {
$this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
$this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
}
}
}
@@ -201,36 +217,64 @@ class EventDispatcher implements EventDispatcherInterface
*
* @param callable[] $listeners The event listeners
* @param string $eventName The name of the event to dispatch
* @param Event $event The event object to pass to the event handlers/listeners
* @param object $event The event object to pass to the event handlers/listeners
*/
protected function doDispatch($listeners, $eventName, Event $event)
protected function callListeners(iterable $listeners, string $eventName, object $event)
{
$stoppable = $event instanceof StoppableEventInterface;
foreach ($listeners as $listener) {
if ($event->isPropagationStopped()) {
if ($stoppable && $event->isPropagationStopped()) {
break;
}
call_user_func($listener, $event, $eventName, $this);
$listener($event, $eventName, $this);
}
}
/**
* Sorts the internal list of listeners for the given event by priority.
*
* @param string $eventName The name of the event
*/
private function sortListeners($eventName)
private function sortListeners(string $eventName)
{
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = array();
$this->sorted[$eventName] = [];
foreach ($this->listeners[$eventName] as $priority => $listeners) {
foreach ($listeners as $k => $listener) {
if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
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]();
$this->listeners[$eventName][$priority][$k] = $listener;
$listener[1] = $listener[1] ?? '__invoke';
}
$this->sorted[$eventName][] = $listener;
}
}
}
/**
* Optimizes the internal list of listeners for the given event by priority.
*/
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];
}
}

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\EventDispatcher;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
* Listeners are registered on the manager and events are dispatched through the
@@ -18,83 +20,47 @@ namespace Symfony\Component\EventDispatcher;
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventDispatcherInterface
interface EventDispatcherInterface extends ContractsEventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
* @param Event $event The event to pass to the event handlers/listeners
* If not supplied, an empty Event instance is created.
*
* @return Event
*/
public function dispatch($eventName, Event $event = null);
/**
* Adds an event listener that listens on the specified events.
*
* @param string $eventName The event to listen on
* @param callable $listener The listener
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function addListener($eventName, $listener, $priority = 0);
public function addListener(string $eventName, callable $listener, int $priority = 0);
/**
* Adds an event subscriber.
*
* The subscriber is asked for all the events he is
* The subscriber is asked for all the events it is
* interested in and added as a listener for these events.
*
* @param EventSubscriberInterface $subscriber The subscriber
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
/**
* Removes an event listener from the specified events.
*
* @param string $eventName The event to remove a listener from
* @param callable $listener The listener to remove
*/
public function removeListener($eventName, $listener);
public function removeListener(string $eventName, callable $listener);
/**
* Removes an event subscriber.
*
* @param EventSubscriberInterface $subscriber The subscriber
*/
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
* Gets the listeners of a specific event or all listeners sorted by descending priority.
*
* @param string $eventName The name of the event
*
* @return array The event listeners for the specified event, or all event listeners by event name
* @return array<callable[]|callable>
*/
public function getListeners($eventName = null);
public function getListeners(string $eventName = null): array;
/**
* Gets the listener priority for a specific event.
*
* Returns null if the event or the listener does not exist.
*
* @param string $eventName The name of the event
* @param callable $listener The listener
*
* @return int|null The event listener priority
*/
public function getListenerPriority($eventName, $listener);
public function getListenerPriority(string $eventName, callable $listener): ?int;
/**
* Checks whether an event has any registered listeners.
*
* @param string $eventName The name of the event
*
* @return bool true if the specified event has any listeners, false otherwise
*/
public function hasListeners($eventName = null);
public function hasListeners(string $eventName = null): bool;
}

View File

@@ -12,7 +12,7 @@
namespace Symfony\Component\EventDispatcher;
/**
* An EventSubscriber knows himself what events he is interested in.
* An EventSubscriber knows itself what events it is interested in.
* If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events.
@@ -36,11 +36,14 @@ interface EventSubscriberInterface
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
* * ['eventName' => 'methodName']
* * ['eventName' => ['methodName', $priority]]
* * ['eventName' => [['methodName1', $priority], ['methodName2']]]
*
* @return array The event names to listen to
* The code must not depend on runtime state as it will only be called at compile time.
* All logic depending on runtime state must be put into the individual methods handling the events.
*
* @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
*/
public static function getSubscribedEvents();
}

View File

@@ -11,36 +11,30 @@
namespace Symfony\Component\EventDispatcher;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Event encapsulation class.
*
* Encapsulates events thus decoupling the observer from the subject they encapsulate.
*
* @author Drak <drak@zikula.org>
*
* @implements \ArrayAccess<string, mixed>
* @implements \IteratorAggregate<string, mixed>
*/
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
/**
* Event subject.
*
* @var mixed usually object or callable
*/
protected $subject;
/**
* Array of arguments.
*
* @var array
*/
protected $arguments;
/**
* Encapsulate an event with $subject and $args.
*
* @param mixed $subject The subject of the event, usually an object
* @param mixed $subject The subject of the event, usually an object or a callable
* @param array $arguments Arguments to store in the event
*/
public function __construct($subject = null, array $arguments = array())
public function __construct(mixed $subject = null, array $arguments = [])
{
$this->subject = $subject;
$this->arguments = $arguments;
@@ -48,10 +42,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* Getter for subject property.
*
* @return mixed $subject The observer subject
*/
public function getSubject()
public function getSubject(): mixed
{
return $this->subject;
}
@@ -59,13 +51,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* Get argument by key.
*
* @param string $key Key
*
* @return mixed Contents of array key
*
* @throws \InvalidArgumentException If key is not found.
* @throws \InvalidArgumentException if key is not found
*/
public function getArgument($key)
public function getArgument(string $key): mixed
{
if ($this->hasArgument($key)) {
return $this->arguments[$key];
@@ -77,12 +65,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* Add argument to event.
*
* @param string $key Argument name
* @param mixed $value Value
*
* @return $this
*/
public function setArgument($key, $value)
public function setArgument(string $key, mixed $value): static
{
$this->arguments[$key] = $value;
@@ -91,10 +76,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* Getter for all arguments.
*
* @return array
*/
public function getArguments()
public function getArguments(): array
{
return $this->arguments;
}
@@ -102,11 +85,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* Set args property.
*
* @param array $args Arguments
*
* @return $this
*/
public function setArguments(array $args = array())
public function setArguments(array $args = []): static
{
$this->arguments = $args;
@@ -115,14 +96,10 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* Has argument.
*
* @param string $key Key of arguments array
*
* @return bool
*/
public function hasArgument($key)
public function hasArgument(string $key): bool
{
return array_key_exists($key, $this->arguments);
return \array_key_exists($key, $this->arguments);
}
/**
@@ -130,11 +107,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
*
* @param string $key Array key
*
* @return mixed
*
* @throws \InvalidArgumentException If key does not exist in $this->args.
* @throws \InvalidArgumentException if key does not exist in $this->args
*/
public function offsetGet($key)
public function offsetGet(mixed $key): mixed
{
return $this->getArgument($key);
}
@@ -142,10 +117,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* ArrayAccess for argument setter.
*
* @param string $key Array key to set
* @param mixed $value Value
* @param string $key Array key to set
*/
public function offsetSet($key, $value)
public function offsetSet(mixed $key, mixed $value): void
{
$this->setArgument($key, $value);
}
@@ -155,7 +129,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
*
* @param string $key Array key
*/
public function offsetUnset($key)
public function offsetUnset(mixed $key): void
{
if ($this->hasArgument($key)) {
unset($this->arguments[$key]);
@@ -166,10 +140,8 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
* ArrayAccess has argument.
*
* @param string $key Array key
*
* @return bool
*/
public function offsetExists($key)
public function offsetExists(mixed $key): bool
{
return $this->hasArgument($key);
}
@@ -177,9 +149,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
/**
* IteratorAggregate for iterating over the object like an array.
*
* @return \ArrayIterator
* @return \ArrayIterator<string, mixed>
*/
public function getIterator()
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->arguments);
}

View File

@@ -18,18 +18,8 @@ namespace Symfony\Component\EventDispatcher;
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
/**
* The proxied dispatcher.
*
* @var EventDispatcherInterface
*/
private $dispatcher;
/**
* Creates an unmodifiable proxy for an event dispatcher.
*
* @param EventDispatcherInterface $dispatcher The proxied event dispatcher
*/
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
@@ -38,15 +28,15 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function dispatch($eventName, Event $event = null)
public function dispatch(object $event, string $eventName = null): object
{
return $this->dispatcher->dispatch($eventName, $event);
return $this->dispatcher->dispatch($event, $eventName);
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
@@ -62,7 +52,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
public function removeListener(string $eventName, callable|array $listener)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
@@ -78,7 +68,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
public function getListeners(string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
@@ -86,7 +76,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
@@ -94,7 +84,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
public function hasListeners(string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2017 Fabien Potencier
Copyright (c) 2004-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -8,8 +8,8 @@ them.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
* [Documentation](https://symfony.com/doc/current/components/event_dispatcher.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@@ -1,442 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
abstract class AbstractEventDispatcherTest extends TestCase
{
/* Some pseudo events */
const preFoo = 'pre.foo';
const postFoo = 'post.foo';
const preBar = 'pre.bar';
const postBar = 'post.bar';
/**
* @var EventDispatcher
*/
private $dispatcher;
private $listener;
protected function setUp()
{
$this->dispatcher = $this->createEventDispatcher();
$this->listener = new TestEventListener();
}
protected function tearDown()
{
$this->dispatcher = null;
$this->listener = null;
}
abstract protected function createEventDispatcher();
public function testInitialState()
{
$this->assertEquals(array(), $this->dispatcher->getListeners());
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
$this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
}
public function testAddListener()
{
$this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
$this->assertTrue($this->dispatcher->hasListeners());
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
$this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
$this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
$this->assertCount(2, $this->dispatcher->getListeners());
}
public function testGetListenersSortsByPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener1->name = '1';
$listener2->name = '2';
$listener3->name = '3';
$this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10);
$this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10);
$this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo'));
$expected = array(
array($listener2, 'preFoo'),
array($listener3, 'preFoo'),
array($listener1, 'preFoo'),
);
$this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
}
public function testGetAllListenersSortsByPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener4 = new TestEventListener();
$listener5 = new TestEventListener();
$listener6 = new TestEventListener();
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->addListener('post.foo', $listener4, -10);
$this->dispatcher->addListener('post.foo', $listener5);
$this->dispatcher->addListener('post.foo', $listener6, 10);
$expected = array(
'pre.foo' => array($listener3, $listener2, $listener1),
'post.foo' => array($listener6, $listener5, $listener4),
);
$this->assertSame($expected, $this->dispatcher->getListeners());
}
public function testGetListenerPriority()
{
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
$this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
$this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
$this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
}
public function testDispatch()
{
$this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
$this->dispatcher->dispatch(self::preFoo);
$this->assertTrue($this->listener->preFooInvoked);
$this->assertFalse($this->listener->postFooInvoked);
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
$event = new Event();
$return = $this->dispatcher->dispatch(self::preFoo, $event);
$this->assertSame($event, $return);
}
public function testDispatchForClosure()
{
$invoked = 0;
$listener = function () use (&$invoked) {
++$invoked;
};
$this->dispatcher->addListener('pre.foo', $listener);
$this->dispatcher->addListener('post.foo', $listener);
$this->dispatcher->dispatch(self::preFoo);
$this->assertEquals(1, $invoked);
}
public function testStopEventPropagation()
{
$otherListener = new TestEventListener();
// postFoo() stops the propagation, so only one listener should
// be executed
// Manually set priority to enforce $this->listener to be called first
$this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10);
$this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo'));
$this->dispatcher->dispatch(self::postFoo);
$this->assertTrue($this->listener->postFooInvoked);
$this->assertFalse($otherListener->postFooInvoked);
}
public function testDispatchByPriority()
{
$invoked = array();
$listener1 = function () use (&$invoked) {
$invoked[] = '1';
};
$listener2 = function () use (&$invoked) {
$invoked[] = '2';
};
$listener3 = function () use (&$invoked) {
$invoked[] = '3';
};
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->dispatch(self::preFoo);
$this->assertEquals(array('3', '2', '1'), $invoked);
}
public function testRemoveListener()
{
$this->dispatcher->addListener('pre.bar', $this->listener);
$this->assertTrue($this->dispatcher->hasListeners(self::preBar));
$this->dispatcher->removeListener('pre.bar', $this->listener);
$this->assertFalse($this->dispatcher->hasListeners(self::preBar));
$this->dispatcher->removeListener('notExists', $this->listener);
}
public function testAddSubscriber()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
}
public function testAddSubscriberWithPriorities()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $listeners);
$this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
}
public function testAddSubscriberWithMultipleListeners()
{
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $listeners);
$this->assertEquals('preFoo2', $listeners[0][1]);
}
public function testRemoveSubscriber()
{
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
$this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
}
public function testRemoveSubscriberWithPriorities()
{
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
}
public function testRemoveSubscriberWithMultipleListeners()
{
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
$this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
}
public function testEventReceivesTheDispatcherInstanceAsArgument()
{
$listener = new TestWithDispatcher();
$this->dispatcher->addListener('test', array($listener, 'foo'));
$this->assertNull($listener->name);
$this->assertNull($listener->dispatcher);
$this->dispatcher->dispatch('test');
$this->assertEquals('test', $listener->name);
$this->assertSame($this->dispatcher, $listener->dispatcher);
}
/**
* @see https://bugs.php.net/bug.php?id=62976
*
* This bug affects:
* - The PHP 5.3 branch for versions < 5.3.18
* - The PHP 5.4 branch for versions < 5.4.8
* - The PHP 5.5 branch is not affected
*/
public function testWorkaroundForPhpBug62976()
{
$dispatcher = $this->createEventDispatcher();
$dispatcher->addListener('bug.62976', new CallableClass());
$dispatcher->removeListener('bug.62976', function () {});
$this->assertTrue($dispatcher->hasListeners('bug.62976'));
}
public function testHasListenersWhenAddedCallbackListenerIsRemoved()
{
$listener = function () {};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testGetListenersWhenAddedCallbackListenerIsRemoved()
{
$listener = function () {};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertSame(array(), $this->dispatcher->getListeners());
}
public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
{
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testHasListenersIsLazy()
{
$called = 0;
$listener = array(function () use (&$called) { ++$called; }, 'onFoo');
$this->dispatcher->addListener('foo', $listener);
$this->assertTrue($this->dispatcher->hasListeners());
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->assertSame(0, $called);
}
public function testDispatchLazyListener()
{
$called = 0;
$factory = function () use (&$called) {
++$called;
return new TestWithDispatcher();
};
$this->dispatcher->addListener('foo', array($factory, 'foo'));
$this->assertSame(0, $called);
$this->dispatcher->dispatch('foo', new Event());
$this->dispatcher->dispatch('foo', new Event());
$this->assertSame(1, $called);
}
public function testRemoveFindsLazyListeners()
{
$test = new TestWithDispatcher();
$factory = function () use ($test) { return $test; };
$this->dispatcher->addListener('foo', array($factory, 'foo'));
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', array($test, 'foo'));
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->dispatcher->addListener('foo', array($test, 'foo'));
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
$this->assertFalse($this->dispatcher->hasListeners('foo'));
}
public function testPriorityFindsLazyListeners()
{
$test = new TestWithDispatcher();
$factory = function () use ($test) { return $test; };
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', array($test, 'foo')));
$this->dispatcher->removeListener('foo', array($factory, 'foo'));
$this->dispatcher->addListener('foo', array($test, 'foo'), 5);
$this->assertSame(5, $this->dispatcher->getListenerPriority('foo', array($factory, 'foo')));
}
public function testGetLazyListeners()
{
$test = new TestWithDispatcher();
$factory = function () use ($test) { return $test; };
$this->dispatcher->addListener('foo', array($factory, 'foo'), 3);
$this->assertSame(array(array($test, 'foo')), $this->dispatcher->getListeners('foo'));
$this->dispatcher->removeListener('foo', array($test, 'foo'));
$this->dispatcher->addListener('bar', array($factory, 'foo'), 3);
$this->assertSame(array('bar' => array(array($test, 'foo'))), $this->dispatcher->getListeners());
}
}
class CallableClass
{
public function __invoke()
{
}
}
class TestEventListener
{
public $preFooInvoked = false;
public $postFooInvoked = false;
/* Listener methods */
public function preFoo(Event $e)
{
$this->preFooInvoked = true;
}
public function postFoo(Event $e)
{
$this->postFooInvoked = true;
$e->stopPropagation();
}
}
class TestWithDispatcher
{
public $name;
public $dispatcher;
public function foo(Event $e, $name, $dispatcher)
{
$this->name = $name;
$this->dispatcher = $dispatcher;
}
}
class TestEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo');
}
}
class TestEventSubscriberWithPriorities implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'pre.foo' => array('preFoo', 10),
'post.foo' => array('postFoo'),
);
}
}
class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('pre.foo' => array(
array('preFoo1'),
array('preFoo2', 10),
));
}
}

View File

@@ -1,210 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @group legacy
*/
class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
{
protected function createEventDispatcher()
{
$container = new Container();
return new ContainerAwareEventDispatcher($container);
}
public function testAddAListenerService()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->dispatch('onEvent', $event);
}
public function testAddASubscriberService()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock();
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$service
->expects($this->once())
->method('onEventWithPriority')
->with($event)
;
$service
->expects($this->once())
->method('onEventNested')
->with($event)
;
$container = new Container();
$container->set('service.subscriber', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService');
$dispatcher->dispatch('onEvent', $event);
$dispatcher->dispatch('onEventWithPriority', $event);
$dispatcher->dispatch('onEventNested', $event);
}
public function testPreventDuplicateListenerService()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10);
$dispatcher->dispatch('onEvent', $event);
}
public function testHasListenersOnLazyLoad()
{
$event = new Event();
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$service
->expects($this->once())
->method('onEvent')
->with($event)
;
$this->assertTrue($dispatcher->hasListeners());
if ($dispatcher->hasListeners('onEvent')) {
$dispatcher->dispatch('onEvent');
}
}
public function testGetListenersOnLazyLoad()
{
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$listeners = $dispatcher->getListeners();
$this->assertTrue(isset($listeners['onEvent']));
$this->assertCount(1, $dispatcher->getListeners('onEvent'));
}
public function testRemoveAfterDispatch()
{
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->dispatch('onEvent', new Event());
$dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
$this->assertFalse($dispatcher->hasListeners('onEvent'));
}
public function testRemoveBeforeDispatch()
{
$service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
$container = new Container();
$container->set('service.listener', $service);
$dispatcher = new ContainerAwareEventDispatcher($container);
$dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
$dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
$this->assertFalse($dispatcher->hasListeners('onEvent'));
}
}
class Service
{
public function onEvent(Event $e)
{
}
}
class SubscriberService implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'onEvent' => 'onEvent',
'onEventWithPriority' => array('onEventWithPriority', 10),
'onEventNested' => array(array('onEventNested')),
);
}
public function onEvent(Event $e)
{
}
public function onEventWithPriority(Event $e)
{
}
public function onEventNested(Event $e)
{
}
}

View File

@@ -1,242 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests\Debug;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Stopwatch\Stopwatch;
class TraceableEventDispatcherTest extends TestCase
{
public function testAddRemoveListener()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', $listener = function () {});
$listeners = $dispatcher->getListeners('foo');
$this->assertCount(1, $listeners);
$this->assertSame($listener, $listeners[0]);
$tdispatcher->removeListener('foo', $listener);
$this->assertCount(0, $dispatcher->getListeners('foo'));
}
public function testGetListeners()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', $listener = function () {});
$this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo'));
}
public function testHasListeners()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$this->assertFalse($dispatcher->hasListeners('foo'));
$this->assertFalse($tdispatcher->hasListeners('foo'));
$tdispatcher->addListener('foo', $listener = function () {});
$this->assertTrue($dispatcher->hasListeners('foo'));
$this->assertTrue($tdispatcher->hasListeners('foo'));
}
public function testGetListenerPriority()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', function () {}, 123);
$listeners = $dispatcher->getListeners('foo');
$this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
// Verify that priority is preserved when listener is removed and re-added
// in preProcess() and postProcess().
$tdispatcher->dispatch('foo', new Event());
$listeners = $dispatcher->getListeners('foo');
$this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
}
public function testGetListenerPriorityWhileDispatching()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$priorityWhileDispatching = null;
$listener = function () use ($tdispatcher, &$priorityWhileDispatching, &$listener) {
$priorityWhileDispatching = $tdispatcher->getListenerPriority('bar', $listener);
};
$tdispatcher->addListener('bar', $listener, 5);
$tdispatcher->dispatch('bar');
$this->assertSame(5, $priorityWhileDispatching);
}
public function testAddRemoveSubscriber()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$subscriber = new EventSubscriber();
$tdispatcher->addSubscriber($subscriber);
$listeners = $dispatcher->getListeners('foo');
$this->assertCount(1, $listeners);
$this->assertSame(array($subscriber, 'call'), $listeners[0]);
$tdispatcher->removeSubscriber($subscriber);
$this->assertCount(0, $dispatcher->getListeners('foo'));
}
public function testGetCalledListeners()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->addListener('foo', function () {}, 5);
$listeners = $tdispatcher->getNotCalledListeners();
$this->assertArrayHasKey('stub', $listeners['foo.closure']);
unset($listeners['foo.closure']['stub']);
$this->assertEquals(array(), $tdispatcher->getCalledListeners());
$this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners);
$tdispatcher->dispatch('foo');
$listeners = $tdispatcher->getCalledListeners();
$this->assertArrayHasKey('stub', $listeners['foo.closure']);
unset($listeners['foo.closure']['stub']);
$this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners);
$this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
}
public function testGetCalledListenersNested()
{
$tdispatcher = null;
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
$tdispatcher = $dispatcher;
$dispatcher->dispatch('bar');
});
$dispatcher->addListener('bar', function (Event $event) {});
$dispatcher->dispatch('foo');
$this->assertSame($dispatcher, $tdispatcher);
$this->assertCount(2, $dispatcher->getCalledListeners());
}
public function testLogger()
{
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
$tdispatcher->addListener('foo', $listener1 = function () {});
$tdispatcher->addListener('foo', $listener2 = function () {});
$logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
$logger->expects($this->at(1))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
$tdispatcher->dispatch('foo');
}
public function testLoggerWithStoppedEvent()
{
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
$tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); });
$tdispatcher->addListener('foo', $listener2 = function () {});
$logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure'));
$logger->expects($this->at(1))->method('debug')->with('Listener "{listener}" stopped propagation of the event "{event}".', array('event' => 'foo', 'listener' => 'closure'));
$logger->expects($this->at(2))->method('debug')->with('Listener "{listener}" was not called for event "{event}".', array('event' => 'foo', 'listener' => 'closure'));
$tdispatcher->dispatch('foo');
}
public function testDispatchCallListeners()
{
$called = array();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10);
$tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20);
$tdispatcher->dispatch('foo');
$this->assertSame(array('foo2', 'foo1'), $called);
}
public function testDispatchNested()
{
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$loop = 1;
$dispatchedEvents = 0;
$dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) {
++$loop;
if (2 == $loop) {
$dispatcher->dispatch('foo');
}
});
$dispatcher->addListener('foo', function () use (&$dispatchedEvents) {
++$dispatchedEvents;
});
$dispatcher->dispatch('foo');
$this->assertSame(2, $dispatchedEvents);
}
public function testDispatchReusedEventNested()
{
$nestedCall = false;
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$dispatcher->addListener('foo', function (Event $e) use ($dispatcher) {
$dispatcher->dispatch('bar', $e);
});
$dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) {
$nestedCall = true;
});
$this->assertFalse($nestedCall);
$dispatcher->dispatch('foo');
$this->assertTrue($nestedCall);
}
public function testListenerCanRemoveItselfWhenExecuted()
{
$eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
$dispatcher->removeListener('foo', $listener1);
};
$eventDispatcher->addListener('foo', $listener1);
$eventDispatcher->addListener('foo', function () {});
$eventDispatcher->dispatch('foo');
$this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
}
}
class EventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('foo' => 'call');
}
}

View File

@@ -1,167 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
class RegisterListenersPassTest extends TestCase
{
/**
* Tests that event subscribers not implementing EventSubscriberInterface
* trigger an exception.
*
* @expectedException \InvalidArgumentException
*/
public function testEventSubscriberWithoutInterface()
{
// one service, not implementing any interface
$services = array(
'my_event_subscriber' => array(0 => array()),
);
$definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock();
$definition->expects($this->atLeastOnce())
->method('getClass')
->will($this->returnValue('stdClass'));
$builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock();
$builder->expects($this->any())
->method('hasDefinition')
->will($this->returnValue(true));
// We don't test kernel.event_listener here
$builder->expects($this->atLeastOnce())
->method('findTaggedServiceIds')
->will($this->onConsecutiveCalls(array(), $services));
$builder->expects($this->atLeastOnce())
->method('getDefinition')
->will($this->returnValue($definition));
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
}
public function testValidEventSubscriber()
{
$services = array(
'my_event_subscriber' => array(0 => array()),
);
$definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock();
$definition->expects($this->atLeastOnce())
->method('getClass')
->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'));
$builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition'))->getMock();
$builder->expects($this->any())
->method('hasDefinition')
->will($this->returnValue(true));
// We don't test kernel.event_listener here
$builder->expects($this->atLeastOnce())
->method('findTaggedServiceIds')
->will($this->onConsecutiveCalls(array(), $services));
$builder->expects($this->atLeastOnce())
->method('getDefinition')
->will($this->returnValue($definition));
$builder->expects($this->atLeastOnce())
->method('findDefinition')
->will($this->returnValue($definition));
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The service "foo" tagged "kernel.event_listener" must not be abstract.
*/
public function testAbstractEventListener()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The service "foo" tagged "kernel.event_subscriber" must not be abstract.
*/
public function testAbstractEventSubscriber()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testEventSubscriberResolvableClassName()
{
$container = new ContainerBuilder();
$container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService');
$container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = array(
array(
'addListener',
array(
'event',
array(new ServiceClosureArgument(new Reference('foo')), 'onEvent'),
0,
),
),
);
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class"
*/
public function testEventSubscriberUnresolvableClassName()
{
$container = new ContainerBuilder();
$container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
}
class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'event' => 'onEvent',
);
}
}

View File

@@ -1,22 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use Symfony\Component\EventDispatcher\EventDispatcher;
class EventDispatcherTest extends AbstractEventDispatcherTest
{
protected function createEventDispatcher()
{
return new EventDispatcher();
}
}

View File

@@ -1,55 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Event;
/**
* Test class for Event.
*/
class EventTest extends TestCase
{
/**
* @var \Symfony\Component\EventDispatcher\Event
*/
protected $event;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->event = new Event();
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
$this->event = null;
}
public function testIsPropagationStopped()
{
$this->assertFalse($this->event->isPropagationStopped());
}
public function testStopPropagationAndIsPropagationStopped()
{
$this->event->stopPropagation();
$this->assertTrue($this->event->isPropagationStopped());
}
}

View File

@@ -1,140 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Test class for Event.
*/
class GenericEventTest extends TestCase
{
/**
* @var GenericEvent
*/
private $event;
private $subject;
/**
* Prepares the environment before running a test.
*/
protected function setUp()
{
parent::setUp();
$this->subject = new \stdClass();
$this->event = new GenericEvent($this->subject, array('name' => 'Event'));
}
/**
* Cleans up the environment after running a test.
*/
protected function tearDown()
{
$this->subject = null;
$this->event = null;
parent::tearDown();
}
public function testConstruct()
{
$this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event')));
}
/**
* Tests Event->getArgs().
*/
public function testGetArguments()
{
// test getting all
$this->assertSame(array('name' => 'Event'), $this->event->getArguments());
}
public function testSetArguments()
{
$result = $this->event->setArguments(array('foo' => 'bar'));
$this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event);
$this->assertSame($this->event, $result);
}
public function testSetArgument()
{
$result = $this->event->setArgument('foo2', 'bar2');
$this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
$this->assertEquals($this->event, $result);
}
public function testGetArgument()
{
// test getting key
$this->assertEquals('Event', $this->event->getArgument('name'));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testGetArgException()
{
$this->event->getArgument('nameNotExist');
}
public function testOffsetGet()
{
// test getting key
$this->assertEquals('Event', $this->event['name']);
// test getting invalid arg
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
$this->assertFalse($this->event['nameNotExist']);
}
public function testOffsetSet()
{
$this->event['foo2'] = 'bar2';
$this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
}
public function testOffsetUnset()
{
unset($this->event['name']);
$this->assertAttributeSame(array(), 'arguments', $this->event);
}
public function testOffsetIsset()
{
$this->assertTrue(isset($this->event['name']));
$this->assertFalse(isset($this->event['nameNotExist']));
}
public function testHasArgument()
{
$this->assertTrue($this->event->hasArgument('name'));
$this->assertFalse($this->event->hasArgument('nameNotExist'));
}
public function testGetSubject()
{
$this->assertSame($this->subject, $this->event->getSubject());
}
public function testHasIterator()
{
$data = array();
foreach ($this->event as $key => $value) {
$data[$key] = $value;
}
$this->assertEquals(array('name' => 'Event'), $data);
}
}

View File

@@ -1,106 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcherTest extends TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $innerDispatcher;
/**
* @var ImmutableEventDispatcher
*/
private $dispatcher;
protected function setUp()
{
$this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
$this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
}
public function testDispatchDelegates()
{
$event = new Event();
$this->innerDispatcher->expects($this->once())
->method('dispatch')
->with('event', $event)
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->dispatch('event', $event));
}
public function testGetListenersDelegates()
{
$this->innerDispatcher->expects($this->once())
->method('getListeners')
->with('event')
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->getListeners('event'));
}
public function testHasListenersDelegates()
{
$this->innerDispatcher->expects($this->once())
->method('hasListeners')
->with('event')
->will($this->returnValue('result'));
$this->assertSame('result', $this->dispatcher->hasListeners('event'));
}
/**
* @expectedException \BadMethodCallException
*/
public function testAddListenerDisallowed()
{
$this->dispatcher->addListener('event', function () { return 'foo'; });
}
/**
* @expectedException \BadMethodCallException
*/
public function testAddSubscriberDisallowed()
{
$subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
$this->dispatcher->addSubscriber($subscriber);
}
/**
* @expectedException \BadMethodCallException
*/
public function testRemoveListenerDisallowed()
{
$this->dispatcher->removeListener('event', function () { return 'foo'; });
}
/**
* @expectedException \BadMethodCallException
*/
public function testRemoveSubscriberDisallowed()
{
$subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
$this->dispatcher->removeSubscriber($subscriber);
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "symfony/event-dispatcher",
"type": "library",
"description": "Symfony EventDispatcher Component",
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
@@ -16,17 +16,25 @@
}
],
"require": {
"php": ">=5.5.9"
"php": ">=8.0.2",
"symfony/event-dispatcher-contracts": "^2|^3"
},
"require-dev": {
"symfony/dependency-injection": "~3.3",
"symfony/expression-language": "~2.8|~3.0",
"symfony/config": "~2.8|~3.0",
"symfony/stopwatch": "~2.8|~3.0",
"psr/log": "~1.0"
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/expression-language": "^5.4|^6.0",
"symfony/config": "^5.4|^6.0",
"symfony/error-handler": "^5.4|^6.0",
"symfony/http-foundation": "^5.4|^6.0",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/stopwatch": "^5.4|^6.0",
"psr/log": "^1|^2|^3"
},
"conflict": {
"symfony/dependency-injection": "<3.3"
"symfony/dependency-injection": "<5.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0|3.0"
},
"suggest": {
"symfony/dependency-injection": "",
@@ -38,10 +46,5 @@
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
}
"minimum-stability": "dev"
}

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony EventDispatcher Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>