Repository: symfony/event-dispatcher
Branch: 8.1
Commit: bd57b0a5c68c
Files: 31
Total size: 118.8 KB
Directory structure:
gitextract_2owf2s03/
├── .gitattributes
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── close-pull-request.yml
├── .gitignore
├── Attribute/
│ └── AsEventListener.php
├── CHANGELOG.md
├── Debug/
│ ├── TraceableEventDispatcher.php
│ └── WrappedListener.php
├── DependencyInjection/
│ ├── AddEventAliasesPass.php
│ └── RegisterListenersPass.php
├── EventDispatcher.php
├── EventDispatcherInterface.php
├── EventSubscriberInterface.php
├── GenericEvent.php
├── ImmutableEventDispatcher.php
├── LICENSE
├── README.md
├── Tests/
│ ├── ChildEventDispatcherTest.php
│ ├── Debug/
│ │ ├── TraceableEventDispatcherTest.php
│ │ └── WrappedListenerTest.php
│ ├── DependencyInjection/
│ │ └── RegisterListenersPassTest.php
│ ├── EventDispatcherTest.php
│ ├── Fixtures/
│ │ ├── CustomEvent.php
│ │ ├── DummyEvent.php
│ │ ├── TaggedInvokableListener.php
│ │ ├── TaggedMultiListener.php
│ │ └── TaggedUnionTypeListener.php
│ ├── GenericEventTest.php
│ └── ImmutableEventDispatcherTest.php
├── composer.json
└── phpunit.xml.dist
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
/Tests export-ignore
/phpunit.xml.dist export-ignore
/.git* export-ignore
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
Please do not submit any Pull Requests here. They will be closed.
---
Please submit your PR here instead:
https://github.com/symfony/symfony
This repository is what we call a "subtree split": a read-only subset of that main repository.
We're looking forward to your PR there!
================================================
FILE: .github/workflows/close-pull-request.yml
================================================
name: Close Pull Request
on:
pull_request_target:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: |
Thanks for your Pull Request! We love contributions.
However, you should instead open your PR on the main repository:
https://github.com/symfony/symfony
This repository is what we call a "subtree split": a read-only subset of that main repository.
We're looking forward to your PR there!
================================================
FILE: .gitignore
================================================
vendor/
composer.lock
phpunit.xml
================================================
FILE: Attribute/AsEventListener.php
================================================
<?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
{
/**
* @param string|null $event The event name to listen to
* @param string|null $method The method to run when the listened event is triggered
* @param int $priority The priority of this listener if several are declared for the same event
* @param string|null $dispatcher The service id of the event dispatcher to listen to
*/
public function __construct(
public ?string $event = null,
public ?string $method = null,
public int $priority = 0,
public ?string $dispatcher = null,
) {
}
}
================================================
FILE: CHANGELOG.md
================================================
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.
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.
2.5.0
-----
* added Debug\TraceableEventDispatcher (originally in HttpKernel)
* changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
* added RegisterListenersPass (originally in HttpKernel)
2.1.0
-----
* added TraceableEventDispatcherInterface
* added ContainerAwareEventDispatcher
* added a reference to the EventDispatcher on the Event
* added a reference to the Event name on the event
* added fluid interface to the dispatch() method which now returns the Event
object
* added GenericEvent event class
* added the possibility for subscribers to subscribe several times for the
same event
* added ImmutableEventDispatcher
================================================
FILE: Debug/TraceableEventDispatcher.php
================================================
<?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 Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\Service\ResetInterface;
/**
* Collects some data about event listeners.
*
* This event dispatcher delegates the dispatching to another one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
{
/**
* @var \SplObjectStorage<WrappedListener, array{string, string}>|null
*/
private ?\SplObjectStorage $callStack = null;
private array $wrappedListeners = [];
private array $orphanedEvents = [];
private string $currentRequestHash = '';
public function __construct(
private EventDispatcherInterface $dispatcher,
protected Stopwatch $stopwatch,
protected ?LoggerInterface $logger = null,
private ?RequestStack $requestStack = null,
protected readonly ?\Closure $disabled = null,
) {
}
public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
{
$this->dispatcher->addListener($eventName, $listener, $priority);
}
public function addSubscriber(EventSubscriberInterface $subscriber): void
{
$this->dispatcher->addSubscriber($subscriber);
}
public function removeListener(string $eventName, callable|array $listener): void
{
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
}
}
}
$this->dispatcher->removeListener($eventName, $listener);
}
public function removeSubscriber(EventSubscriberInterface $subscriber): void
{
$this->dispatcher->removeSubscriber($subscriber);
}
public function getListeners(?string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
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 $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
}
}
}
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
public function hasListeners(?string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}
public function dispatch(object $event, ?string $eventName = null): object
{
if ($this->disabled?->__invoke()) {
return $this->dispatcher->dispatch($event, $eventName);
}
$eventName ??= $event::class;
$this->callStack ??= new \SplObjectStorage();
$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);
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);
}
return $event;
}
public function getCalledListeners(?Request $request = null): array
{
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;
}
public function getNotCalledListeners(?Request $request = null): array
{
try {
$allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority();
} catch (\Exception $e) {
$this->logger?->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
// unable to retrieve the uncalled listeners
return [];
}
$hash = $request ? spl_object_hash($request) : null;
$calledListeners = [];
if (null !== $this->callStack) {
foreach ($this->callStack as $calledListener) {
[, $requestHash] = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$calledListeners[] = $calledListener->getWrappedListener();
}
}
}
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as [$listener, $priority]) {
if (!\in_array($listener, $calledListeners, true)) {
if (!$listener instanceof WrappedListener) {
$listener = new WrappedListener($listener, null, $this->stopwatch, $this, $priority);
}
$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(): void
{
$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
*/
public function __call(string $method, array $arguments): mixed
{
return $this->dispatcher->{$method}(...$arguments);
}
/**
* Called before dispatching the event.
*/
protected function beforeDispatch(string $eventName, object $event): void
{
}
/**
* Called after dispatching the event.
*/
protected function afterDispatch(string $eventName, object $event): void
{
}
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 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[$wrappedListener] = [$eventName, $this->currentRequestHash];
}
}
private function postProcess(string $eventName): void
{
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
continue;
}
// Unwrap listener
$priority = $this->getListenerPriority($eventName, $listener);
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
if (null !== $this->logger) {
$context = ['event' => $eventName, 'listener' => $listener->getPretty()];
}
if ($listener->wasCalled()) {
$this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context);
} else {
unset($this->callStack[$listener]);
}
if (null !== $this->logger && $skipped) {
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
}
if ($listener->stoppedPropagation()) {
$this->logger?->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
$skipped = true;
}
}
}
private function sortNotCalledListeners(array $a, array $b): int
{
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'])) {
return -1;
}
if ($a['priority'] === $b['priority']) {
return 0;
}
if ($a['priority'] > $b['priority']) {
return -1;
}
return 1;
}
private function getListenersWithPriority(): array
{
$result = [];
$allListeners = new \ReflectionProperty(EventDispatcher::class, 'listeners');
foreach ($allListeners->getValue($this->dispatcher) as $eventName => $listenersByPriority) {
foreach ($listenersByPriority as $priority => $listeners) {
foreach ($listeners as $listener) {
$result[$eventName][] = [$listener, $priority];
}
}
}
return $result;
}
private function getListenersWithoutPriority(): array
{
$result = [];
foreach ($this->getListeners() as $eventName => $listeners) {
foreach ($listeners as $listener) {
$result[$eventName][] = [$listener, null];
}
}
return $result;
}
}
================================================
FILE: Debug/WrappedListener.php
================================================
<?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 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>
*/
final class WrappedListener
{
private string|array|object $listener;
private ?\Closure $optimizedListener;
private string $name;
private bool $called = false;
private bool $stoppedPropagation = false;
private string $pretty;
private string $callableRef;
private ClassStub|string $stub;
private static bool $hasClassStub;
public function __construct(
callable|array $listener,
?string $name,
private Stopwatch $stopwatch,
private ?EventDispatcherInterface $dispatcher = null,
private ?int $priority = null,
) {
$this->listener = $listener;
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null);
if (\is_array($listener)) {
[$this->name, $this->callableRef] = $this->parseListener($listener);
$this->pretty = $this->name.'::'.$listener[1];
$this->callableRef .= '::'.$listener[1];
} elseif ($listener instanceof \Closure) {
$r = new \ReflectionFunction($listener);
if ($r->isAnonymous()) {
$this->pretty = $this->name = 'closure';
} elseif ($class = $r->getClosureCalledClass()) {
$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_debug_type($listener);
$this->pretty = $this->name.'::__invoke';
$this->callableRef = $listener::class.'::__invoke';
}
if (null !== $name) {
$this->name = $name;
}
self::$hasClassStub ??= class_exists(ClassStub::class);
}
public function getWrappedListener(): callable|array
{
return $this->listener;
}
public function wasCalled(): bool
{
return $this->called;
}
public function stoppedPropagation(): bool
{
return $this->stoppedPropagation;
}
public function getPretty(): string
{
return $this->pretty;
}
public function getInfo(string $eventName): array
{
$this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->callableRef ?? $this->listener) : $this->pretty.'()';
return [
'event' => $eventName,
'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener),
'pretty' => $this->pretty,
'stub' => $this->stub,
];
}
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');
try {
($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
} finally {
if ($e->isStarted()) {
$e->stop();
}
}
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
$this->stoppedPropagation = true;
}
}
private function parseListener(array $listener): array
{
if ($listener[0] instanceof \Closure) {
foreach ((new \ReflectionFunction($listener[0]))->getAttributes(\Closure::class) as $attribute) {
if ($name = $attribute->getArguments()['name'] ?? false) {
return [$name, $attribute->getArguments()['class'] ?? $name];
}
}
}
if (\is_object($listener[0])) {
return [get_debug_type($listener[0]), $listener[0]::class];
}
return [$listener[0], $listener[0]];
}
}
================================================
FILE: DependencyInjection/AddEventAliasesPass.php
================================================
<?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
{
public function __construct(
private array $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)
);
}
}
================================================
FILE: DependencyInjection/RegisterListenersPass.php
================================================
<?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\Argument\ServiceClosureArgument;
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
{
private array $hotPathEvents = [];
private array $noPreloadEvents = [];
/**
* @return $this
*/
public function setHotPathEvents(array $hotPathEvents): static
{
$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): void
{
if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) {
return;
}
$aliases = [];
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;
$resolvedEvents = [];
foreach ($events as $event) {
if (!isset($event['event'])) {
if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) {
continue;
}
$event['method'] ??= '__invoke';
$eventNames = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
} else {
$eventNames = [$event['event']];
}
foreach ($eventNames as $eventName) {
$event['event'] = $aliases[$eventName] ?? $eventName;
$resolvedEvents[] = $event;
}
}
foreach ($resolvedEvents as $event) {
$priority = $event['priority'] ?? 0;
if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback([
'/(?<=\b|_)[a-z]/i',
'/[^a-z0-9]/i',
], static fn ($matches) => 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'])) {
if (!$r->hasMethod('__invoke')) {
throw new InvalidArgumentException(\sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id));
}
$event['method'] = '__invoke';
}
}
$dispatcherDefinition = $globalDispatcherDefinition;
if (isset($event['dispatcher'])) {
$dispatcherDefinition = $container->findDefinition($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('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 = $def->getClass();
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;
}
$dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($attributes['dispatcher']);
}
if (!$dispatcherDefinitions) {
$dispatcherDefinitions = [$globalDispatcherDefinition];
}
$noPreload = 0;
ExtractingEventDispatcher::$aliases = $aliases;
ExtractingEventDispatcher::$subscriber = $class;
$extractingDispatcher->addSubscriber($extractingDispatcher);
foreach ($extractingDispatcher->listeners as $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;
}
}
if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
$container->getDefinition($id)->addTag('container.no_preload');
}
$extractingDispatcher->listeners = [];
ExtractingEventDispatcher::$aliases = [];
}
}
/**
* @return string[]
*/
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): array
{
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 instanceof \ReflectionUnionType)
) {
throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}
$types = $type instanceof \ReflectionUnionType ? $type->getTypes() : [$type];
$names = [];
foreach ($types as $type) {
if (!$type instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
) {
continue;
}
$names[] = $name;
}
if (!$names) {
throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}
return $names;
}
}
/**
* @internal
*/
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
{
public array $listeners = [];
public static array $aliases = [];
public static string $subscriber;
public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
{
$this->listeners[] = [$eventName, $listener[1], $priority];
}
public static function getSubscribedEvents(): array
{
$events = [];
foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
$events[self::$aliases[$eventName] ?? $eventName] = $params;
}
return $events;
}
}
================================================
FILE: EventDispatcher.php
================================================
<?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 Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\Debug\WrappedListener;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
*
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @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>
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Jordan Alliot <jordan.alliot@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class EventDispatcher implements EventDispatcherInterface
{
private array $listeners = [];
private array $sorted = [];
private array $optimized;
public function __construct()
{
if (__CLASS__ === static::class) {
$this->optimized = [];
}
}
public function dispatch(object $event, ?string $eventName = null): object
{
$eventName ??= $event::class;
if (isset($this->optimized)) {
$listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
} else {
$listeners = $this->getListeners($eventName);
}
if ($listeners) {
$this->callListeners($listeners, $eventName, $event);
}
return $event;
}
public function getListeners(?string $eventName = null): array
{
if (null !== $eventName) {
if (empty($this->listeners[$eventName])) {
return [];
}
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
return $this->sorted[$eventName];
}
foreach ($this->listeners as $eventName => $eventListeners) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
}
return array_filter($this->sorted);
}
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
if (empty($this->listeners[$eventName])) {
return null;
}
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] ??= '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] ??= '__invoke';
}
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
return $priority;
}
}
}
return null;
}
public function hasListeners(?string $eventName = null): bool
{
if (null !== $eventName) {
return !empty($this->listeners[$eventName]);
}
foreach ($this->listeners as $eventListeners) {
if ($eventListeners) {
return true;
}
}
return false;
}
public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
{
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName], $this->optimized[$eventName]);
}
public function removeListener(string $eventName, callable|array $listener): void
{
if (empty($this->listeners[$eventName])) {
return;
}
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] ??= '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as $k => &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] ??= '__invoke';
}
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
}
}
if (!$listeners) {
unset($this->listeners[$eventName][$priority]);
}
}
}
public function addSubscriber(EventSubscriberInterface $subscriber): void
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (\is_string($params)) {
$this->addListener($eventName, [$subscriber, $params]);
} elseif (\is_string($params[0])) {
$this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
} else {
foreach ($params as $listener) {
$this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
}
}
}
}
public function removeSubscriber(EventSubscriberInterface $subscriber): void
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (\is_array($params) && \is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($eventName, [$subscriber, $listener[0]]);
}
} else {
$this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
}
}
}
/**
* Triggers the listeners of an event.
*
* This method can be overridden to add functionality that is executed
* for each listener.
*
* @param callable[] $listeners The event listeners
* @param string $eventName The name of the event to dispatch
* @param object $event The event object to pass to the event handlers/listeners
*/
protected function callListeners(iterable $listeners, string $eventName, object $event): void
{
$stoppable = $event instanceof StoppableEventInterface;
foreach ($listeners as $listener) {
if ($stoppable && $event->isPropagationStopped()) {
break;
}
$listener($event, $eventName, $this);
}
}
/**
* Sorts the internal list of listeners for the given event by priority.
*/
private function sortListeners(string $eventName): void
{
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = [];
foreach ($this->listeners[$eventName] as &$listeners) {
foreach ($listeners as &$listener) {
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$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] ??= '__invoke';
}
($closure = $listener(...))(...$args);
};
} else {
$closure = $listener instanceof WrappedListener ? $listener : $listener(...);
}
}
}
return $this->optimized[$eventName];
}
}
================================================
FILE: EventDispatcherInterface.php
================================================
<?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\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
* manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventDispatcherInterface extends ContractsEventDispatcherInterface
{
/**
* Adds an event listener that listens on the specified events.
*
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function addListener(string $eventName, callable $listener, int $priority = 0): void;
/**
* Adds an event subscriber.
*
* The subscriber is asked for all the events it is
* interested in and added as a listener for these events.
*/
public function addSubscriber(EventSubscriberInterface $subscriber): void;
/**
* Removes an event listener from the specified events.
*/
public function removeListener(string $eventName, callable $listener): void;
public function removeSubscriber(EventSubscriberInterface $subscriber): void;
/**
* Gets the listeners of a specific event or all listeners sorted by descending priority.
*
* @return ($eventName is null ? array<callable[]> : array<callable>)
*/
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.
*/
public function getListenerPriority(string $eventName, callable $listener): ?int;
/**
* Checks whether an event has any registered listeners.
*/
public function hasListeners(?string $eventName = null): bool;
}
================================================
FILE: EventSubscriberInterface.php
================================================
<?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;
/**
* 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.
*
* @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>
*/
interface EventSubscriberInterface
{
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * ['eventName' => 'methodName']
* * ['eventName' => ['methodName', $priority]]
* * ['eventName' => [['methodName1', $priority], ['methodName2']]]
*
* 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(): array;
}
================================================
FILE: GenericEvent.php
================================================
<?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\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
{
/**
* Encapsulate an event with $subject and $arguments.
*
* @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(
protected mixed $subject = null,
protected array $arguments = [],
) {
}
/**
* Getter for subject property.
*/
public function getSubject(): mixed
{
return $this->subject;
}
/**
* Get argument by key.
*
* @throws \InvalidArgumentException if key is not found
*/
public function getArgument(string $key): mixed
{
if ($this->hasArgument($key)) {
return $this->arguments[$key];
}
throw new \InvalidArgumentException(\sprintf('Argument "%s" not found.', $key));
}
/**
* Add argument to event.
*
* @return $this
*/
public function setArgument(string $key, mixed $value): static
{
$this->arguments[$key] = $value;
return $this;
}
/**
* Getter for all arguments.
*/
public function getArguments(): array
{
return $this->arguments;
}
/**
* Set args property.
*
* @return $this
*/
public function setArguments(array $args = []): static
{
$this->arguments = $args;
return $this;
}
/**
* Has argument.
*/
public function hasArgument(string $key): bool
{
return \array_key_exists($key, $this->arguments);
}
/**
* ArrayAccess for argument getter.
*
* @param string $key Array key
*
* @throws \InvalidArgumentException if key does not exist in $this->args
*/
public function offsetGet(mixed $key): mixed
{
return $this->getArgument($key);
}
/**
* ArrayAccess for argument setter.
*
* @param string $key Array key to set
*/
public function offsetSet(mixed $key, mixed $value): void
{
$this->setArgument($key, $value);
}
/**
* ArrayAccess for unset argument.
*
* @param string $key Array key
*/
public function offsetUnset(mixed $key): void
{
if ($this->hasArgument($key)) {
unset($this->arguments[$key]);
}
}
/**
* ArrayAccess has argument.
*
* @param string $key Array key
*/
public function offsetExists(mixed $key): bool
{
return $this->hasArgument($key);
}
/**
* IteratorAggregate for iterating over the object like an array.
*
* @return \ArrayIterator<string, mixed>
*/
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->arguments);
}
}
================================================
FILE: ImmutableEventDispatcher.php
================================================
<?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;
/**
* A read-only proxy for an event dispatcher.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
public function __construct(
private EventDispatcherInterface $dispatcher,
) {
}
public function dispatch(object $event, ?string $eventName = null): object
{
return $this->dispatcher->dispatch($event, $eventName);
}
public function addListener(string $eventName, callable|array $listener, int $priority = 0): never
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
public function addSubscriber(EventSubscriberInterface $subscriber): never
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
public function removeListener(string $eventName, callable|array $listener): never
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
public function removeSubscriber(EventSubscriberInterface $subscriber): never
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
public function getListeners(?string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
public function hasListeners(?string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}
}
================================================
FILE: LICENSE
================================================
Copyright (c) 2004-present 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
EventDispatcher Component
=========================
The EventDispatcher component provides tools that allow your application
components to communicate with each other by dispatching events and listening to
them.
Resources
---------
* [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)
================================================
FILE: Tests/ChildEventDispatcherTest.php
================================================
<?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 ChildEventDispatcherTest extends EventDispatcherTest
{
protected function createEventDispatcher()
{
return new ChildEventDispatcher();
}
}
class ChildEventDispatcher extends EventDispatcher
{
}
================================================
FILE: Tests/Debug/TraceableEventDispatcherTest.php
================================================
<?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\ErrorHandler\BufferingLogger;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\EventDispatcher\Event;
class TraceableEventDispatcherTest extends TestCase
{
public function testAddRemoveListener()
{
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', $listener = static 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 = static 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 = static 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', static 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(new Event(), 'foo');
$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 = static function () use ($tdispatcher, &$priorityWhileDispatching, &$listener) {
$priorityWhileDispatching = $tdispatcher->getListenerPriority('bar', $listener);
};
$tdispatcher->addListener('bar', $listener, 5);
$tdispatcher->dispatch(new Event(), '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([$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', static function () {}, 5);
$listeners = $tdispatcher->getNotCalledListeners();
$this->assertArrayHasKey('stub', $listeners[0]);
unset($listeners[0]['stub']);
$this->assertEquals([], $tdispatcher->getCalledListeners());
$this->assertEquals([['event' => 'foo', 'pretty' => 'closure', 'priority' => 5]], $listeners);
$tdispatcher->dispatch(new Event(), 'foo');
$listeners = $tdispatcher->getCalledListeners();
$this->assertArrayHasKey('stub', $listeners[0]);
unset($listeners[0]['stub']);
$this->assertEquals([['event' => 'foo', 'pretty' => 'closure', 'priority' => 5]], $listeners);
$this->assertEquals([], $tdispatcher->getNotCalledListeners());
}
public function testGetNotCalledClosureListeners()
{
$instantiationCount = 0;
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->addListener('foo', [static function () use (&$instantiationCount) { ++$instantiationCount; }, 'onFoo']);
$tdispatcher->getNotCalledListeners();
$this->assertSame(0, $instantiationCount);
}
public function testClearCalledListeners()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->addListener('foo', static function () {}, 5);
$tdispatcher->dispatch(new Event(), 'foo');
$tdispatcher->reset();
$listeners = $tdispatcher->getNotCalledListeners();
$this->assertArrayHasKey('stub', $listeners[0]);
unset($listeners[0]['stub']);
$this->assertEquals([], $tdispatcher->getCalledListeners());
$this->assertEquals([['event' => 'foo', 'pretty' => 'closure', 'priority' => 5]], $listeners);
}
public function testDispatchAfterReset()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->addListener('foo', static function () {}, 5);
$tdispatcher->reset();
$tdispatcher->dispatch(new Event(), 'foo');
$listeners = $tdispatcher->getCalledListeners();
$this->assertArrayHasKey('stub', $listeners[0]);
}
public function testGetCalledListenersNested()
{
$tdispatcher = null;
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$dispatcher->addListener('foo', static function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
$tdispatcher = $dispatcher;
$dispatcher->dispatch(new Event(), 'bar');
});
$dispatcher->addListener('bar', static function (Event $event) {});
$dispatcher->dispatch(new Event(), 'foo');
$this->assertSame($dispatcher, $tdispatcher);
$this->assertCount(2, $dispatcher->getCalledListeners());
}
public function testItReturnsNoOrphanedEventsWhenCreated()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$events = $tdispatcher->getOrphanedEvents();
$this->assertSame([], $events);
}
public function testItReturnsOrphanedEventsAfterDispatch()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->dispatch(new Event(), 'foo');
$events = $tdispatcher->getOrphanedEvents();
$this->assertCount(1, $events);
$this->assertEquals(['foo'], $events);
}
public function testItDoesNotReturnHandledEvents()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->addListener('foo', static function () {});
$tdispatcher->dispatch(new Event(), 'foo');
$events = $tdispatcher->getOrphanedEvents();
$this->assertSame([], $events);
}
public function testLogger()
{
$logger = new BufferingLogger();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
$tdispatcher->addListener('foo', $listener1 = static function () {});
$tdispatcher->addListener('foo', $listener2 = static function () {});
$tdispatcher->dispatch(new Event(), 'foo');
$this->assertSame([
[
'debug',
'Notified event "{event}" to listener "{listener}".',
['event' => 'foo', 'listener' => 'closure'],
],
[
'debug',
'Notified event "{event}" to listener "{listener}".',
['event' => 'foo', 'listener' => 'closure'],
],
], $logger->cleanLogs());
}
public function testLoggerWithStoppedEvent()
{
$logger = new BufferingLogger();
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
$tdispatcher->addListener('foo', $listener1 = static function (Event $event) { $event->stopPropagation(); });
$tdispatcher->addListener('foo', $listener2 = static function () {});
$tdispatcher->dispatch(new Event(), 'foo');
$this->assertSame([
[
'debug',
'Notified event "{event}" to listener "{listener}".',
['event' => 'foo', 'listener' => 'closure'],
],
[
'debug',
'Listener "{listener}" stopped propagation of the event "{event}".',
['event' => 'foo', 'listener' => 'closure'],
],
[
'debug',
'Listener "{listener}" was not called for event "{event}".',
['event' => 'foo', 'listener' => 'closure'],
],
], $logger->cleanLogs());
}
public function testDispatchCallListeners()
{
$called = [];
$dispatcher = new EventDispatcher();
$tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
$tdispatcher->addListener('foo', static function () use (&$called) { $called[] = 'foo1'; }, 10);
$tdispatcher->addListener('foo', static function () use (&$called) { $called[] = 'foo2'; }, 20);
$tdispatcher->dispatch(new Event(), 'foo');
$this->assertSame(['foo2', 'foo1'], $called);
}
public function testDispatchNested()
{
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$loop = 1;
$dispatchedEvents = 0;
$dispatcher->addListener('foo', $listener1 = static function () use ($dispatcher, &$loop) {
++$loop;
if (2 == $loop) {
$dispatcher->dispatch(new Event(), 'foo');
}
});
$dispatcher->addListener('foo', static function () use (&$dispatchedEvents) {
++$dispatchedEvents;
});
$dispatcher->dispatch(new Event(), 'foo');
$this->assertSame(2, $dispatchedEvents);
}
public function testDispatchReusedEventNested()
{
$nestedCall = false;
$dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$dispatcher->addListener('foo', static function (Event $e) use ($dispatcher) {
$dispatcher->dispatch(new Event(), 'bar', $e);
});
$dispatcher->addListener('bar', static function (Event $e) use (&$nestedCall) {
$nestedCall = true;
});
$this->assertFalse($nestedCall);
$dispatcher->dispatch(new Event(), 'foo');
$this->assertTrue($nestedCall);
}
public function testListenerCanRemoveItselfWhenExecuted()
{
$eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$listener1 = static function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
$dispatcher->removeListener('foo', $listener1);
};
$eventDispatcher->addListener('foo', $listener1);
$eventDispatcher->addListener('foo', static function () {});
$eventDispatcher->dispatch(new Event(), 'foo');
$this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
}
public function testClearOrphanedEvents()
{
$tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
$tdispatcher->dispatch(new Event(), 'foo');
$events = $tdispatcher->getOrphanedEvents();
$this->assertCount(1, $events);
$tdispatcher->reset();
$events = $tdispatcher->getOrphanedEvents();
$this->assertCount(0, $events);
}
}
class EventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['foo' => 'call'];
}
}
================================================
FILE: Tests/Debug/WrappedListenerTest.php
================================================
<?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\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\Debug\WrappedListener;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Stopwatch\StopwatchEvent;
class WrappedListenerTest extends TestCase
{
#[DataProvider('provideListenersToDescribe')]
public function testListenerDescription($listener, $expected)
{
$wrappedListener = new WrappedListener($listener, null, new Stopwatch(), new EventDispatcher());
$this->assertStringMatchesFormat($expected, $wrappedListener->getPretty());
}
public static function provideListenersToDescribe()
{
return [
[new FooListener(), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::__invoke'],
[[new FooListener(), 'listen'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'],
[['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'listenStatic'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listenStatic'],
[['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'invalidMethod'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::invalidMethod'],
['var_dump', 'var_dump'],
[static function () {}, 'closure'],
[\Closure::fromCallable([new FooListener(), 'listen']), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'],
[\Closure::fromCallable(['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'listenStatic']), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listenStatic'],
[\Closure::fromCallable(static function () {}), 'closure'],
[[#[\Closure(name: FooListener::class)] static fn () => new FooListener(), 'listen'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'],
];
}
public function testStopwatchEventIsStoppedWhenListenerThrows()
{
$stopwatchEvent = $this->createMock(StopwatchEvent::class);
$stopwatchEvent->expects(self::once())->method('isStarted')->willReturn(true);
$stopwatchEvent->expects(self::once())->method('stop');
$stopwatch = $this->createStub(Stopwatch::class);
$stopwatch->method('start')->willReturn($stopwatchEvent);
$dispatcher = $this->createStub(EventDispatcherInterface::class);
$wrappedListener = new WrappedListener(static fn () => throw new \Exception(), null, $stopwatch, $dispatcher);
try {
$wrappedListener(new \stdClass(), 'foo', $dispatcher);
} catch (\Exception $ex) {
}
}
}
class FooListener
{
public function listen()
{
}
public function __invoke()
{
}
public static function listenStatic()
{
}
}
================================================
FILE: Tests/DependencyInjection/RegisterListenersPassTest.php
================================================
<?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\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\Tests\Fixtures\CustomEvent;
use Symfony\Component\EventDispatcher\Tests\Fixtures\DummyEvent;
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedInvokableListener;
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedMultiListener;
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedUnionTypeListener;
class RegisterListenersPassTest extends TestCase
{
/**
* Tests that event subscribers not implementing EventSubscriberInterface
* trigger an exception.
*/
public function testEventSubscriberWithoutInterface()
{
$this->expectException(\InvalidArgumentException::class);
$builder = new ContainerBuilder();
$builder->register('event_dispatcher');
$builder->register('my_event_subscriber', 'stdClass')
->addTag('kernel.event_subscriber');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
}
public function testValidEventSubscriber()
{
$builder = new ContainerBuilder();
$eventDispatcherDefinition = $builder->register('event_dispatcher');
$builder->register('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')
->addTag('kernel.event_subscriber');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
$expectedCalls = [
[
'addListener',
[
'event',
[new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, $eventDispatcherDefinition->getMethodCalls());
}
public function testAliasedEventSubscriber()
{
$builder = new ContainerBuilder();
$builder->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
$builder->register('event_dispatcher');
$builder->register('my_event_subscriber', AliasedSubscriber::class)
->addTag('kernel.event_subscriber');
$eventAliasPass = new AddEventAliasesPass([CustomEvent::class => 'custom_event']);
$eventAliasPass->process($builder);
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
$expectedCalls = [
[
'addListener',
[
'aliased_event',
[new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onAliasedEvent'],
0,
],
],
[
'addListener',
[
'custom_event',
[new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onCustomEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, $builder->getDefinition('event_dispatcher')->getMethodCalls());
}
public function testAbstractEventListener()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The service "foo" tagged "kernel.event_listener" must not be abstract.');
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', []);
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testAbstractEventSubscriber()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The service "foo" tagged "kernel.event_subscriber" must not be abstract.');
$container = new ContainerBuilder();
$container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', []);
$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', []);
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
'event',
[new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testHotPathEvents()
{
$container = new ContainerBuilder();
$container->register('foo', SubscriberService::class)->addTag('kernel.event_subscriber', []);
$container->register('event_dispatcher', 'stdClass');
(new RegisterListenersPass())->setHotPathEvents(['event'])->process($container);
$this->assertTrue($container->getDefinition('foo')->hasTag('container.hot_path'));
}
public function testNoPreloadEvents()
{
$container = new ContainerBuilder();
$container->register('foo', SubscriberService::class)->addTag('kernel.event_subscriber', []);
$container->register('bar')->addTag('kernel.event_listener', ['event' => 'cold_event']);
$container->register('baz')
->addTag('kernel.event_listener', ['event' => 'event'])
->addTag('kernel.event_listener', ['event' => 'cold_event']);
$container->register('event_dispatcher', 'stdClass');
(new RegisterListenersPass())
->setHotPathEvents(['event'])
->setNoPreloadEvents(['cold_event'])
->process($container);
$this->assertFalse($container->getDefinition('foo')->hasTag('container.no_preload'));
$this->assertTrue($container->getDefinition('bar')->hasTag('container.no_preload'));
$this->assertFalse($container->getDefinition('baz')->hasTag('container.no_preload'));
}
public function testEventSubscriberUnresolvableClassName()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('You have requested a non-existent parameter "subscriber.class"');
$container = new ContainerBuilder();
$container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', []);
$container->register('event_dispatcher', 'stdClass');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testInvokableEventListener()
{
$container = new ContainerBuilder();
$container->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
$container->register('foo', \get_class(new class {
public function onFooBar()
{
}
}))->addTag('kernel.event_listener', ['event' => 'foo.bar']);
$container->register('bar', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => 'foo.bar']);
$container->register('baz', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => 'event']);
$container->register('zar', \get_class(new class {
public function onFooBarZar()
{
}
}))->addTag('kernel.event_listener', ['event' => 'foo.bar_zar']);
$container->register('event_dispatcher', \stdClass::class);
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
'foo.bar',
[new ServiceClosureArgument(new Reference('foo')), 'onFooBar'],
0,
],
],
[
'addListener',
[
'foo.bar',
[new ServiceClosureArgument(new Reference('bar')), '__invoke'],
0,
],
],
[
'addListener',
[
'event',
[new ServiceClosureArgument(new Reference('baz')), 'onEvent'],
0,
],
],
[
'addListener',
[
'foo.bar_zar',
[new ServiceClosureArgument(new Reference('zar')), 'onFooBarZar'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testItThrowsAnExceptionIfTagIsMissingMethodAndClassHasNoValidMethod()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('None of the "onFooBar" or "__invoke" methods exist for the service "foo". Please define the "method" attribute on "kernel.event_listener" tags.');
$container = new ContainerBuilder();
$container->register('foo', \stdClass::class)->addTag('kernel.event_listener', ['event' => 'foo.bar']);
$container->register('event_dispatcher', \stdClass::class);
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testTaggedInvokableEventListener()
{
$container = $this->createContainerBuilder();
$container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true);
$container->register('event_dispatcher', \stdClass::class);
(new AttributeAutoconfigurationPass())->process($container);
(new ResolveInstanceofConditionalsPass())->process($container);
(new RegisterListenersPass())->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
CustomEvent::class,
[new ServiceClosureArgument(new Reference('foo')), '__invoke'],
0,
],
],
];
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
}
public function testTaggedMultiEventListener()
{
$container = $this->createContainerBuilder();
$container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true);
$container->register('event_dispatcher', \stdClass::class);
(new AttributeAutoconfigurationPass())->process($container);
(new ResolveInstanceofConditionalsPass())->process($container);
(new RegisterListenersPass())->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
CustomEvent::class,
[new ServiceClosureArgument(new Reference('foo')), 'onCustomEvent'],
0,
],
],
[
'addListener',
[
'foo',
[new ServiceClosureArgument(new Reference('foo')), 'onFoo'],
42,
],
],
[
'addListener',
[
'bar',
[new ServiceClosureArgument(new Reference('foo')), 'onBarEvent'],
0,
],
],
[
'addListener',
[
'baz',
[new ServiceClosureArgument(new Reference('foo')), 'onBazEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
}
public function testTaggedMethodUnionTypeEventListener()
{
$container = $this->createContainerBuilder();
$container->register('foo', TaggedUnionTypeListener::class)->setAutoconfigured(true);
$container->register('event_dispatcher', \stdClass::class);
(new AttributeAutoconfigurationPass())->process($container);
(new ResolveInstanceofConditionalsPass())->process($container);
(new RegisterListenersPass())->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
CustomEvent::class,
[new ServiceClosureArgument(new Reference('foo')), 'onUnionEvent'],
0,
],
],
[
'addListener',
[
DummyEvent::class,
[new ServiceClosureArgument(new Reference('foo')), 'onUnionEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
}
public function testAliasedEventListener()
{
$container = new ContainerBuilder();
$eventAliases = [AliasedEvent::class => 'aliased_event'];
$container->setParameter('event_dispatcher.event_aliases', $eventAliases);
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => AliasedEvent::class, 'method' => 'onEvent']);
$container->register('bar', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => CustomEvent::class, 'method' => 'onEvent']);
$container->register('event_dispatcher');
$customEventAlias = [CustomEvent::class => 'custom_event'];
$eventAliasPass = new AddEventAliasesPass($customEventAlias);
$eventAliasPass->process($container);
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$this->assertTrue($container->hasParameter('event_dispatcher.event_aliases'));
$this->assertSame(array_merge($eventAliases, $customEventAlias), $container->getParameter('event_dispatcher.event_aliases'));
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
'aliased_event',
[new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
0,
],
],
[
'addListener',
[
'custom_event',
[new ServiceClosureArgument(new Reference('bar')), 'onEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testOmitEventNameOnTypedListener()
{
$container = new ContainerBuilder();
$container->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
$container->register('foo', TypedListener::class)->addTag('kernel.event_listener', ['method' => 'onEvent']);
$container->register('bar', TypedListener::class)->addTag('kernel.event_listener');
$container->register('event_dispatcher');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
CustomEvent::class,
[new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
0,
],
],
[
'addListener',
[
'aliased_event',
[new ServiceClosureArgument(new Reference('bar')), '__invoke'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testOmitEventNameOnUntypedListener()
{
$container = new ContainerBuilder();
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener', ['method' => 'onEvent']);
$container->register('event_dispatcher');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testOmitEventNameAndMethodOnUntypedListener()
{
$container = new ContainerBuilder();
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener');
$container->register('event_dispatcher');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testOmitEventNameAndMethodOnGenericListener()
{
$container = new ContainerBuilder();
$container->register('foo', GenericListener::class)->addTag('kernel.event_listener');
$container->register('event_dispatcher');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
}
public function testOmitEventNameOnSubscriber()
{
$container = new ContainerBuilder();
$container->register('subscriber', IncompleteSubscriber::class)
->addTag('kernel.event_subscriber')
->addTag('kernel.event_listener')
->addTag('kernel.event_listener', ['event' => 'bar', 'method' => 'onBar'])
;
$container->register('event_dispatcher');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
'bar',
[new ServiceClosureArgument(new Reference('subscriber')), 'onBar'],
0,
],
],
[
'addListener',
[
'foo',
[new ServiceClosureArgument(new Reference('subscriber')), 'onFoo'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
private function createContainerBuilder(): ContainerBuilder
{
$container = new ContainerBuilder();
$container->setParameter('kernel.debug', true);
$container->setParameter('kernel.project_dir', __DIR__);
$container->setParameter('kernel.container_class', 'testContainer');
(new FrameworkExtension())->load([], $container);
return $container;
}
}
class SubscriberService implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'event' => 'onEvent',
];
}
}
class InvokableListenerService
{
public function __invoke()
{
}
public function onEvent()
{
}
}
final class AliasedSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
AliasedEvent::class => 'onAliasedEvent',
CustomEvent::class => 'onCustomEvent',
];
}
}
final class AliasedEvent
{
}
final class TypedListener
{
public function __invoke(AliasedEvent $event): void
{
}
public function onEvent(CustomEvent $event): void
{
}
}
final class GenericListener
{
public function __invoke(object $event): void
{
}
}
final class IncompleteSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'foo' => 'onFoo',
];
}
public function onFoo(): void
{
}
public function onBar(): void
{
}
public function __invoke(CustomEvent $event): void
{
}
}
================================================
FILE: Tests/EventDispatcherTest.php
================================================
<?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\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
class EventDispatcherTest extends TestCase
{
/* Some pseudo events */
private const preFoo = 'pre.foo';
private const postFoo = 'post.foo';
private const preBar = 'pre.bar';
private EventDispatcher $dispatcher;
private TestEventListener $listener;
protected function setUp(): void
{
$this->dispatcher = $this->createEventDispatcher();
$this->listener = new TestEventListener();
}
protected function createEventDispatcher()
{
return new EventDispatcher();
}
public function testInitialState()
{
$this->assertEquals([], $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', [$this->listener, 'preFoo']);
$this->dispatcher->addListener('post.foo', $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();
$listener4 = new TestEventListener();
$listener1->name = '1';
$listener2->name = '2';
$listener3->name = '3';
$listener4->name = '4';
$this->dispatcher->addListener('pre.foo', [$listener1, 'preFoo'], -10);
$this->dispatcher->addListener('pre.foo', [$listener2, 'preFoo'], 10);
$this->dispatcher->addListener('pre.foo', [$listener3, 'preFoo']);
$this->dispatcher->addListener('pre.foo', $listener4->preFoo(...), 20);
$expected = [
$listener4->preFoo(...),
[$listener2, 'preFoo'],
[$listener3, 'preFoo'],
[$listener1, 'preFoo'],
];
$this->assertEquals($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 = [
'pre.foo' => [$listener3, $listener2, $listener1],
'post.foo' => [$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', static function () {}));
}
public function testDispatch()
{
$this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
$this->dispatcher->addListener('post.foo', $this->listener->postFoo(...));
$this->dispatcher->dispatch(new Event(), self::preFoo);
$this->assertTrue($this->listener->preFooInvoked);
$this->assertFalse($this->listener->postFooInvoked);
$this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(new Event(), 'noevent'));
$this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(new Event(), self::preFoo));
$event = new Event();
$return = $this->dispatcher->dispatch($event, self::preFoo);
$this->assertSame($event, $return);
}
public function testDispatchForClosure()
{
$invoked = 0;
$listener = static function () use (&$invoked) {
++$invoked;
};
$this->dispatcher->addListener('pre.foo', $listener);
$this->dispatcher->addListener('post.foo', $listener);
$this->dispatcher->dispatch(new Event(), 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', [$this->listener, 'postFoo'], 10);
$this->dispatcher->addListener('post.foo', $otherListener->postFoo(...));
$this->dispatcher->dispatch(new Event(), self::postFoo);
$this->assertTrue($this->listener->postFooInvoked);
$this->assertFalse($otherListener->postFooInvoked);
}
public function testDispatchByPriority()
{
$invoked = [];
$listener1 = static function () use (&$invoked) {
$invoked[] = '1';
};
$listener2 = static function () use (&$invoked) {
$invoked[] = '2';
};
$listener3 = static 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(new Event(), self::preFoo);
$this->assertEquals(['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(TestEventSubscriberWithPriorities::class, $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', [$listener, 'foo']);
$this->assertNull($listener->name);
$this->assertNull($listener->dispatcher);
$this->dispatcher->dispatch(new Event(), 'test');
$this->assertEquals('test', $listener->name);
$this->assertSame($this->dispatcher, $listener->dispatcher);
}
/**
* @see https://bugs.php.net/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', static function () {});
$this->assertTrue($dispatcher->hasListeners('bug.62976'));
}
public function testHasListenersWhenAddedCallbackListenerIsRemoved()
{
$listener = static function () {};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testGetListenersWhenAddedCallbackListenerIsRemoved()
{
$listener = static function () {};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertSame([], $this->dispatcher->getListeners());
}
public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
{
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testHasListenersIsLazy()
{
$called = 0;
$listener = [static 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()
{
$dispatcher = new TestWithDispatcher();
$called = 0;
$factory = static function () use (&$called, $dispatcher) {
++$called;
return $dispatcher;
};
$this->dispatcher->addListener('foo', [$factory, 'foo']);
$this->assertSame(0, $called);
$this->dispatcher->dispatch(new Event(), 'foo');
$this->assertFalse($dispatcher->invoked);
$this->dispatcher->dispatch(new Event(), 'foo');
$this->assertSame(1, $called);
$this->dispatcher->addListener('bar', [$factory]);
$this->assertSame(1, $called);
$this->dispatcher->dispatch(new Event(), 'bar');
$this->assertTrue($dispatcher->invoked);
$this->dispatcher->dispatch(new Event(), 'bar');
$this->assertSame(2, $called);
}
public function testRemoveFindsLazyListeners()
{
$test = new TestWithDispatcher();
$factory = static fn () => $test;
$this->dispatcher->addListener('foo', [$factory, 'foo']);
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', [$test, 'foo']);
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->dispatcher->addListener('foo', [$test, 'foo']);
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', [$factory, 'foo']);
$this->assertFalse($this->dispatcher->hasListeners('foo'));
}
public function testPriorityFindsLazyListeners()
{
$test = new TestWithDispatcher();
$factory = static fn () => $test;
$this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo']));
$this->dispatcher->removeListener('foo', [$factory, 'foo']);
$this->dispatcher->addListener('foo', [$test, 'foo'], 5);
$this->assertSame(5, $this->dispatcher->getListenerPriority('foo', [$factory, 'foo']));
}
public function testGetLazyListeners()
{
$test = new TestWithDispatcher();
$factory = static fn () => $test;
$this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
$this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo'));
$this->dispatcher->removeListener('foo', [$test, 'foo']);
$this->dispatcher->addListener('bar', [$factory, 'foo'], 3);
$this->assertSame(['bar' => [[$test, 'foo']]], $this->dispatcher->getListeners());
}
public function testMutatingWhilePropagationIsStopped()
{
$testLoaded = false;
$test = new TestEventListener();
$this->dispatcher->addListener('foo', $test->postFoo(...));
$this->dispatcher->addListener('foo', [static function () use ($test, &$testLoaded) {
$testLoaded = true;
return $test;
}, 'preFoo']);
$this->dispatcher->dispatch(new Event(), 'foo');
$this->assertTrue($test->postFooInvoked);
$this->assertFalse($test->preFooInvoked);
$this->assertEquals(0, $this->dispatcher->getListenerPriority('foo', $test->postFoo(...)));
$test->preFoo(new Event());
$this->dispatcher->dispatch(new Event(), 'foo');
$this->assertTrue($testLoaded);
}
public function testNamedClosures()
{
$listener = new TestEventListener();
$callback1 = $listener(...);
$callback2 = $listener(...);
$callback3 = (new TestEventListener())(...);
$this->assertNotSame($callback1, $callback2);
$this->assertNotSame($callback1, $callback3);
$this->assertNotSame($callback2, $callback3);
$this->dispatcher->addListener('foo', $callback1, 3);
$this->dispatcher->addListener('foo', $callback2, 2);
$this->dispatcher->addListener('foo', $callback3, 1);
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', $callback1));
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', $callback2));
$this->dispatcher->removeListener('foo', $callback1);
$this->assertSame(['foo' => [$callback3]], $this->dispatcher->getListeners());
}
}
class CallableClass
{
public function __invoke()
{
}
}
class TestEventListener
{
public string $name;
public bool $preFooInvoked = false;
public bool $postFooInvoked = false;
/* Listener methods */
public function preFoo($e)
{
$this->preFooInvoked = true;
}
public function postFoo($e)
{
$this->postFooInvoked = true;
if (!$this->preFooInvoked) {
$e->stopPropagation();
}
}
public function __invoke()
{
}
}
class TestWithDispatcher
{
public ?string $name = null;
public ?EventDispatcher $dispatcher = null;
public bool $invoked = false;
public function foo($e, $name, $dispatcher)
{
$this->name = $name;
$this->dispatcher = $dispatcher;
}
public function __invoke($e, $name, $dispatcher)
{
$this->name = $name;
$this->dispatcher = $dispatcher;
$this->invoked = true;
}
}
class TestEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['pre.foo' => 'preFoo', 'post.foo' => 'postFoo'];
}
}
class TestEventSubscriberWithPriorities implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'pre.foo' => ['preFoo', 10],
'post.foo' => ['postFoo'],
];
}
}
class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['pre.foo' => [
['preFoo1'],
['preFoo2', 10],
]];
}
}
================================================
FILE: Tests/Fixtures/CustomEvent.php
================================================
<?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\Fixtures;
final class CustomEvent
{
}
================================================
FILE: Tests/Fixtures/DummyEvent.php
================================================
<?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\Fixtures;
final class DummyEvent
{
}
================================================
FILE: Tests/Fixtures/TaggedInvokableListener.php
================================================
<?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\Fixtures;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener]
final class TaggedInvokableListener
{
public function __invoke(CustomEvent $event): void
{
}
}
================================================
FILE: Tests/Fixtures/TaggedMultiListener.php
================================================
<?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\Fixtures;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: CustomEvent::class, method: 'onCustomEvent')]
#[AsEventListener(event: 'foo', priority: 42)]
#[AsEventListener(event: 'bar', method: 'onBarEvent')]
final class TaggedMultiListener
{
public function onCustomEvent(CustomEvent $event): void
{
}
public function onFoo(): void
{
}
public function onBarEvent(): void
{
}
#[AsEventListener(event: 'baz')]
public function onBazEvent(): void
{
}
}
================================================
FILE: Tests/Fixtures/TaggedUnionTypeListener.php
================================================
<?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\Fixtures;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
final class TaggedUnionTypeListener
{
#[AsEventListener]
public function onUnionEvent(CustomEvent|DummyEvent $event): void
{
}
}
================================================
FILE: Tests/GenericEventTest.php
================================================
<?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
{
private GenericEvent $event;
private \stdClass $subject;
protected function setUp(): void
{
$this->subject = new \stdClass();
$this->event = new GenericEvent($this->subject, ['name' => 'Event']);
}
public function testConstruct()
{
$this->assertEquals($this->event, new GenericEvent($this->subject, ['name' => 'Event']));
}
/**
* Tests Event->getArgs().
*/
public function testGetArguments()
{
// test getting all
$this->assertSame(['name' => 'Event'], $this->event->getArguments());
}
public function testSetArguments()
{
$result = $this->event->setArguments(['foo' => 'bar']);
$this->assertSame(['foo' => 'bar'], $this->event->getArguments());
$this->assertSame($this->event, $result);
}
public function testSetArgument()
{
$result = $this->event->setArgument('foo2', 'bar2');
$this->assertSame(['name' => 'Event', 'foo2' => 'bar2'], $this->event->getArguments());
$this->assertEquals($this->event, $result);
}
public function testGetArgument()
{
// test getting key
$this->assertEquals('Event', $this->event->getArgument('name'));
}
public function testGetArgException()
{
$this->expectException(\InvalidArgumentException::class);
$this->event->getArgument('nameNotExist');
}
public function testOffsetGet()
{
// test getting key
$this->assertEquals('Event', $this->event['name']);
// test getting invalid arg
$this->expectException(\InvalidArgumentException::class);
$this->assertFalse($this->event['nameNotExist']);
}
public function testOffsetSet()
{
$this->event['foo2'] = 'bar2';
$this->assertSame(['name' => 'Event', 'foo2' => 'bar2'], $this->event->getArguments());
}
public function testOffsetUnset()
{
unset($this->event['name']);
$this->assertSame([], $this->event->getArguments());
}
public function testOffsetIsset()
{
$this->assertArrayHasKey('name', $this->event);
$this->assertArrayNotHasKey('nameNotExist', $this->event);
}
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 = [];
foreach ($this->event as $key => $value) {
$data[$key] = $value;
}
$this->assertEquals(['name' => 'Event'], $data);
}
}
================================================
FILE: Tests/ImmutableEventDispatcherTest.php
================================================
<?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\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
use Symfony\Contracts\EventDispatcher\Event;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcherTest extends TestCase
{
public function testDispatchDelegates()
{
$innerDispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher = new ImmutableEventDispatcher($innerDispatcher);
$event = new Event();
$resultEvent = new Event();
$innerDispatcher->expects($this->once())
->method('dispatch')
->with($event, 'event')
->willReturn($resultEvent);
$this->assertSame($resultEvent, $dispatcher->dispatch($event, 'event'));
}
public function testGetListenersDelegates()
{
$innerDispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher = new ImmutableEventDispatcher($innerDispatcher);
$innerDispatcher->expects($this->once())
->method('getListeners')
->with('event')
->willReturn(['result']);
$this->assertSame(['result'], $dispatcher->getListeners('event'));
}
public function testHasListenersDelegates()
{
$innerDispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher = new ImmutableEventDispatcher($innerDispatcher);
$innerDispatcher->expects($this->once())
->method('hasListeners')
->with('event')
->willReturn(true);
$this->assertTrue($dispatcher->hasListeners('event'));
}
public function testAddListenerDisallowed()
{
$dispatcher = new ImmutableEventDispatcher(new EventDispatcher());
$this->expectException(\BadMethodCallException::class);
$dispatcher->addListener('event', static fn () => 'foo');
}
public function testAddSubscriberDisallowed()
{
$dispatcher = new ImmutableEventDispatcher(new EventDispatcher());
$this->expectException(\BadMethodCallException::class);
$subscriber = $this->createStub(EventSubscriberInterface::class);
$dispatcher->addSubscriber($subscriber);
}
public function testRemoveListenerDisallowed()
{
$dispatcher = new ImmutableEventDispatcher(new EventDispatcher());
$this->expectException(\BadMethodCallException::class);
$dispatcher->removeListener('event', static fn () => 'foo');
}
public function testRemoveSubscriberDisallowed()
{
$dispatcher = new ImmutableEventDispatcher(new EventDispatcher());
$this->expectException(\BadMethodCallException::class);
$subscriber = $this->createStub(EventSubscriberInterface::class);
$dispatcher->removeSubscriber($subscriber);
}
}
================================================
FILE: composer.json
================================================
{
"name": "symfony/event-dispatcher",
"type": "library",
"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",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=8.4",
"symfony/event-dispatcher-contracts": "^2.5|^3"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^7.4|^8.0",
"symfony/dependency-injection": "^7.4|^8.0",
"symfony/error-handler": "^7.4|^8.0",
"symfony/expression-language": "^7.4|^8.0",
"symfony/framework-bundle": "^7.4|^8.0",
"symfony/http-foundation": "^7.4|^8.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/stopwatch": "^7.4|^8.0"
},
"conflict": {
"symfony/security-http": "<7.4",
"symfony/service-contracts": "<2.5"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0|3.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}
================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.3/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnDeprecation="true"
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>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory>./</directory>
</include>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</source>
<extensions>
<bootstrap class="Symfony\Bridge\PhpUnit\SymfonyExtension" />
</extensions>
</phpunit>
gitextract_2owf2s03/ ├── .gitattributes ├── .github/ │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── close-pull-request.yml ├── .gitignore ├── Attribute/ │ └── AsEventListener.php ├── CHANGELOG.md ├── Debug/ │ ├── TraceableEventDispatcher.php │ └── WrappedListener.php ├── DependencyInjection/ │ ├── AddEventAliasesPass.php │ └── RegisterListenersPass.php ├── EventDispatcher.php ├── EventDispatcherInterface.php ├── EventSubscriberInterface.php ├── GenericEvent.php ├── ImmutableEventDispatcher.php ├── LICENSE ├── README.md ├── Tests/ │ ├── ChildEventDispatcherTest.php │ ├── Debug/ │ │ ├── TraceableEventDispatcherTest.php │ │ └── WrappedListenerTest.php │ ├── DependencyInjection/ │ │ └── RegisterListenersPassTest.php │ ├── EventDispatcherTest.php │ ├── Fixtures/ │ │ ├── CustomEvent.php │ │ ├── DummyEvent.php │ │ ├── TaggedInvokableListener.php │ │ ├── TaggedMultiListener.php │ │ └── TaggedUnionTypeListener.php │ ├── GenericEventTest.php │ └── ImmutableEventDispatcherTest.php ├── composer.json └── phpunit.xml.dist
SYMBOL INDEX (245 symbols across 22 files)
FILE: Attribute/AsEventListener.php
class AsEventListener (line 19) | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Att...
method __construct (line 28) | public function __construct(
FILE: Debug/TraceableEventDispatcher.php
class TraceableEventDispatcher (line 31) | class TraceableEventDispatcher implements EventDispatcherInterface, Rese...
method __construct (line 41) | public function __construct(
method addListener (line 50) | public function addListener(string $eventName, callable|array $listene...
method addSubscriber (line 55) | public function addSubscriber(EventSubscriberInterface $subscriber): void
method removeListener (line 60) | public function removeListener(string $eventName, callable|array $list...
method removeSubscriber (line 75) | public function removeSubscriber(EventSubscriberInterface $subscriber)...
method getListeners (line 80) | public function getListeners(?string $eventName = null): array
method getListenerPriority (line 85) | public function getListenerPriority(string $eventName, callable|array ...
method hasListeners (line 100) | public function hasListeners(?string $eventName = null): bool
method dispatch (line 105) | public function dispatch(object $event, ?string $eventName = null): ob...
method getCalledListeners (line 143) | public function getCalledListeners(?Request $request = null): array
method getNotCalledListeners (line 161) | public function getNotCalledListeners(?Request $request = null): array
method getOrphanedEvents (line 203) | public function getOrphanedEvents(?Request $request = null): array
method reset (line 216) | public function reset(): void
method __call (line 229) | public function __call(string $method, array $arguments): mixed
method beforeDispatch (line 237) | protected function beforeDispatch(string $eventName, object $event): void
method afterDispatch (line 244) | protected function afterDispatch(string $eventName, object $event): void
method preProcess (line 248) | private function preProcess(string $eventName): void
method postProcess (line 266) | private function postProcess(string $eventName): void
method sortNotCalledListeners (line 301) | private function sortNotCalledListeners(array $a, array $b): int
method getListenersWithPriority (line 326) | private function getListenersWithPriority(): array
method getListenersWithoutPriority (line 343) | private function getListenersWithoutPriority(): array
FILE: Debug/WrappedListener.php
class WrappedListener (line 22) | final class WrappedListener
method __construct (line 34) | public function __construct(
method getWrappedListener (line 73) | public function getWrappedListener(): callable|array
method wasCalled (line 78) | public function wasCalled(): bool
method stoppedPropagation (line 83) | public function stoppedPropagation(): bool
method getPretty (line 88) | public function getPretty(): string
method getInfo (line 93) | public function getInfo(string $eventName): array
method __invoke (line 105) | public function __invoke(object $event, string $eventName, EventDispat...
method parseListener (line 127) | private function parseListener(array $listener): array
FILE: DependencyInjection/AddEventAliasesPass.php
class AddEventAliasesPass (line 22) | class AddEventAliasesPass implements CompilerPassInterface
method __construct (line 24) | public function __construct(
method process (line 29) | public function process(ContainerBuilder $container): void
FILE: DependencyInjection/RegisterListenersPass.php
class RegisterListenersPass (line 26) | class RegisterListenersPass implements CompilerPassInterface
method setHotPathEvents (line 34) | public function setHotPathEvents(array $hotPathEvents): static
method setNoPreloadEvents (line 44) | public function setNoPreloadEvents(array $noPreloadEvents): static
method process (line 51) | public function process(ContainerBuilder $container): void
method getEventFromTypeDeclaration (line 181) | private function getEventFromTypeDeclaration(ContainerBuilder $contain...
class ExtractingEventDispatcher (line 218) | class ExtractingEventDispatcher extends EventDispatcher implements Event...
method addListener (line 225) | public function addListener(string $eventName, callable|array $listene...
method getSubscribedEvents (line 230) | public static function getSubscribedEvents(): array
FILE: EventDispatcher.php
class EventDispatcher (line 32) | class EventDispatcher implements EventDispatcherInterface
method __construct (line 38) | public function __construct()
method dispatch (line 45) | public function dispatch(object $event, ?string $eventName = null): ob...
method getListeners (line 62) | public function getListeners(?string $eventName = null): array
method getListenerPriority (line 85) | public function getListenerPriority(string $eventName, callable|array ...
method hasListeners (line 111) | public function hasListeners(?string $eventName = null): bool
method addListener (line 126) | public function addListener(string $eventName, callable|array $listene...
method removeListener (line 132) | public function removeListener(string $eventName, callable|array $list...
method addSubscriber (line 160) | public function addSubscriber(EventSubscriberInterface $subscriber): void
method removeSubscriber (line 175) | public function removeSubscriber(EventSubscriberInterface $subscriber)...
method callListeners (line 198) | protected function callListeners(iterable $listeners, string $eventNam...
method sortListeners (line 213) | private function sortListeners(string $eventName): void
method optimizeListeners (line 232) | private function optimizeListeners(string $eventName): array
FILE: EventDispatcherInterface.php
type EventDispatcherInterface (line 23) | interface EventDispatcherInterface extends ContractsEventDispatcherInter...
method addListener (line 31) | public function addListener(string $eventName, callable $listener, int...
method addSubscriber (line 39) | public function addSubscriber(EventSubscriberInterface $subscriber): v...
method removeListener (line 44) | public function removeListener(string $eventName, callable $listener):...
method removeSubscriber (line 46) | public function removeSubscriber(EventSubscriberInterface $subscriber)...
method getListeners (line 53) | public function getListeners(?string $eventName = null): array;
method getListenerPriority (line 60) | public function getListenerPriority(string $eventName, callable $liste...
method hasListeners (line 65) | public function hasListeners(?string $eventName = null): bool;
FILE: EventSubscriberInterface.php
type EventSubscriberInterface (line 25) | interface EventSubscriberInterface
method getSubscribedEvents (line 48) | public static function getSubscribedEvents(): array;
FILE: GenericEvent.php
class GenericEvent (line 26) | class GenericEvent extends Event implements \ArrayAccess, \IteratorAggre...
method __construct (line 34) | public function __construct(
method getSubject (line 43) | public function getSubject(): mixed
method getArgument (line 53) | public function getArgument(string $key): mixed
method setArgument (line 67) | public function setArgument(string $key, mixed $value): static
method getArguments (line 77) | public function getArguments(): array
method setArguments (line 87) | public function setArguments(array $args = []): static
method hasArgument (line 97) | public function hasArgument(string $key): bool
method offsetGet (line 109) | public function offsetGet(mixed $key): mixed
method offsetSet (line 119) | public function offsetSet(mixed $key, mixed $value): void
method offsetUnset (line 129) | public function offsetUnset(mixed $key): void
method offsetExists (line 141) | public function offsetExists(mixed $key): bool
method getIterator (line 151) | public function getIterator(): \ArrayIterator
FILE: ImmutableEventDispatcher.php
class ImmutableEventDispatcher (line 19) | class ImmutableEventDispatcher implements EventDispatcherInterface
method __construct (line 21) | public function __construct(
method dispatch (line 26) | public function dispatch(object $event, ?string $eventName = null): ob...
method addListener (line 31) | public function addListener(string $eventName, callable|array $listene...
method addSubscriber (line 36) | public function addSubscriber(EventSubscriberInterface $subscriber): n...
method removeListener (line 41) | public function removeListener(string $eventName, callable|array $list...
method removeSubscriber (line 46) | public function removeSubscriber(EventSubscriberInterface $subscriber)...
method getListeners (line 51) | public function getListeners(?string $eventName = null): array
method getListenerPriority (line 56) | public function getListenerPriority(string $eventName, callable|array ...
method hasListeners (line 61) | public function hasListeners(?string $eventName = null): bool
FILE: Tests/ChildEventDispatcherTest.php
class ChildEventDispatcherTest (line 16) | class ChildEventDispatcherTest extends EventDispatcherTest
method createEventDispatcher (line 18) | protected function createEventDispatcher()
class ChildEventDispatcher (line 24) | class ChildEventDispatcher extends EventDispatcher
FILE: Tests/Debug/TraceableEventDispatcherTest.php
class TraceableEventDispatcherTest (line 23) | class TraceableEventDispatcherTest extends TestCase
method testAddRemoveListener (line 25) | public function testAddRemoveListener()
method testGetListeners (line 39) | public function testGetListeners()
method testHasListeners (line 48) | public function testHasListeners()
method testGetListenerPriority (line 61) | public function testGetListenerPriority()
method testGetListenerPriorityWhileDispatching (line 78) | public function testGetListenerPriorityWhileDispatching()
method testAddRemoveSubscriber (line 92) | public function testAddRemoveSubscriber()
method testGetCalledListeners (line 108) | public function testGetCalledListeners()
method testGetNotCalledClosureListeners (line 128) | public function testGetNotCalledClosureListeners()
method testClearCalledListeners (line 140) | public function testClearCalledListeners()
method testDispatchAfterReset (line 155) | public function testDispatchAfterReset()
method testGetCalledListenersNested (line 167) | public function testGetCalledListenersNested()
method testItReturnsNoOrphanedEventsWhenCreated (line 181) | public function testItReturnsNoOrphanedEventsWhenCreated()
method testItReturnsOrphanedEventsAfterDispatch (line 188) | public function testItReturnsOrphanedEventsAfterDispatch()
method testItDoesNotReturnHandledEvents (line 197) | public function testItDoesNotReturnHandledEvents()
method testLogger (line 206) | public function testLogger()
method testLoggerWithStoppedEvent (line 231) | public function testLoggerWithStoppedEvent()
method testDispatchCallListeners (line 261) | public function testDispatchCallListeners()
method testDispatchNested (line 275) | public function testDispatchNested()
method testDispatchReusedEventNested (line 295) | public function testDispatchReusedEventNested()
method testListenerCanRemoveItselfWhenExecuted (line 311) | public function testListenerCanRemoveItselfWhenExecuted()
method testClearOrphanedEvents (line 324) | public function testClearOrphanedEvents()
class EventSubscriber (line 336) | class EventSubscriber implements EventSubscriberInterface
method getSubscribedEvents (line 338) | public static function getSubscribedEvents(): array
FILE: Tests/Debug/WrappedListenerTest.php
class WrappedListenerTest (line 22) | class WrappedListenerTest extends TestCase
method testListenerDescription (line 24) | #[DataProvider('provideListenersToDescribe')]
method provideListenersToDescribe (line 32) | public static function provideListenersToDescribe()
method testStopwatchEventIsStoppedWhenListenerThrows (line 48) | public function testStopwatchEventIsStoppedWhenListenerThrows()
class FooListener (line 68) | class FooListener
method listen (line 70) | public function listen()
method __invoke (line 74) | public function __invoke()
method listenStatic (line 78) | public static function listenStatic()
FILE: Tests/DependencyInjection/RegisterListenersPassTest.php
class RegisterListenersPassTest (line 31) | class RegisterListenersPassTest extends TestCase
method testEventSubscriberWithoutInterface (line 37) | public function testEventSubscriberWithoutInterface()
method testValidEventSubscriber (line 49) | public function testValidEventSubscriber()
method testAliasedEventSubscriber (line 72) | public function testAliasedEventSubscriber()
method testAbstractEventListener (line 107) | public function testAbstractEventListener()
method testAbstractEventSubscriber (line 119) | public function testAbstractEventSubscriber()
method testEventSubscriberResolvableClassName (line 131) | public function testEventSubscriberResolvableClassName()
method testHotPathEvents (line 156) | public function testHotPathEvents()
method testNoPreloadEvents (line 168) | public function testNoPreloadEvents()
method testEventSubscriberUnresolvableClassName (line 189) | public function testEventSubscriberUnresolvableClassName()
method testInvokableEventListener (line 201) | public function testInvokableEventListener()
method testItThrowsAnExceptionIfTagIsMissingMethodAndClassHasNoValidMethod (line 261) | public function testItThrowsAnExceptionIfTagIsMissingMethodAndClassHas...
method testTaggedInvokableEventListener (line 275) | public function testTaggedInvokableEventListener()
method testTaggedMultiEventListener (line 299) | public function testTaggedMultiEventListener()
method testTaggedMethodUnionTypeEventListener (line 348) | public function testTaggedMethodUnionTypeEventListener()
method testAliasedEventListener (line 382) | public function testAliasedEventListener()
method testOmitEventNameOnTypedListener (line 423) | public function testOmitEventNameOnTypedListener()
method testOmitEventNameOnUntypedListener (line 456) | public function testOmitEventNameOnUntypedListener()
method testOmitEventNameAndMethodOnUntypedListener (line 469) | public function testOmitEventNameAndMethodOnUntypedListener()
method testOmitEventNameAndMethodOnGenericListener (line 482) | public function testOmitEventNameAndMethodOnGenericListener()
method testOmitEventNameOnSubscriber (line 495) | public function testOmitEventNameOnSubscriber()
method createContainerBuilder (line 530) | private function createContainerBuilder(): ContainerBuilder
class SubscriberService (line 542) | class SubscriberService implements EventSubscriberInterface
method getSubscribedEvents (line 544) | public static function getSubscribedEvents(): array
class InvokableListenerService (line 552) | class InvokableListenerService
method __invoke (line 554) | public function __invoke()
method onEvent (line 558) | public function onEvent()
class AliasedSubscriber (line 563) | final class AliasedSubscriber implements EventSubscriberInterface
method getSubscribedEvents (line 565) | public static function getSubscribedEvents(): array
class AliasedEvent (line 574) | final class AliasedEvent
class TypedListener (line 578) | final class TypedListener
method __invoke (line 580) | public function __invoke(AliasedEvent $event): void
method onEvent (line 584) | public function onEvent(CustomEvent $event): void
class GenericListener (line 589) | final class GenericListener
method __invoke (line 591) | public function __invoke(object $event): void
class IncompleteSubscriber (line 596) | final class IncompleteSubscriber implements EventSubscriberInterface
method getSubscribedEvents (line 598) | public static function getSubscribedEvents(): array
method onFoo (line 605) | public function onFoo(): void
method onBar (line 609) | public function onBar(): void
method __invoke (line 613) | public function __invoke(CustomEvent $event): void
FILE: Tests/EventDispatcherTest.php
class EventDispatcherTest (line 19) | class EventDispatcherTest extends TestCase
method setUp (line 29) | protected function setUp(): void
method createEventDispatcher (line 35) | protected function createEventDispatcher()
method testInitialState (line 40) | public function testInitialState()
method testAddListener (line 47) | public function testAddListener()
method testGetListenersSortsByPriority (line 59) | public function testGetListenersSortsByPriority()
method testGetAllListenersSortsByPriority (line 85) | public function testGetAllListenersSortsByPriority()
method testGetListenerPriority (line 109) | public function testGetListenerPriority()
method testDispatch (line 123) | public function testDispatch()
method testDispatchForClosure (line 137) | public function testDispatchForClosure()
method testStopEventPropagation (line 149) | public function testStopEventPropagation()
method testDispatchByPriority (line 163) | public function testDispatchByPriority()
method testRemoveListener (line 182) | public function testRemoveListener()
method testAddSubscriber (line 191) | public function testAddSubscriber()
method testAddSubscriberWithPriorities (line 199) | public function testAddSubscriberWithPriorities()
method testAddSubscriberWithMultipleListeners (line 213) | public function testAddSubscriberWithMultipleListeners()
method testRemoveSubscriber (line 224) | public function testRemoveSubscriber()
method testRemoveSubscriberWithPriorities (line 235) | public function testRemoveSubscriberWithPriorities()
method testRemoveSubscriberWithMultipleListeners (line 244) | public function testRemoveSubscriberWithMultipleListeners()
method testEventReceivesTheDispatcherInstanceAsArgument (line 254) | public function testEventReceivesTheDispatcherInstanceAsArgument()
method testWorkaroundForPhpBug62976 (line 273) | public function testWorkaroundForPhpBug62976()
method testHasListenersWhenAddedCallbackListenerIsRemoved (line 281) | public function testHasListenersWhenAddedCallbackListenerIsRemoved()
method testGetListenersWhenAddedCallbackListenerIsRemoved (line 289) | public function testGetListenersWhenAddedCallbackListenerIsRemoved()
method testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled (line 297) | public function testHasListenersWithoutEventsReturnsFalseAfterHasListe...
method testHasListenersIsLazy (line 303) | public function testHasListenersIsLazy()
method testDispatchLazyListener (line 313) | public function testDispatchLazyListener()
method testRemoveFindsLazyListeners (line 337) | public function testRemoveFindsLazyListeners()
method testPriorityFindsLazyListeners (line 353) | public function testPriorityFindsLazyListeners()
method testGetLazyListeners (line 366) | public function testGetLazyListeners()
method testMutatingWhilePropagationIsStopped (line 379) | public function testMutatingWhilePropagationIsStopped()
method testNamedClosures (line 403) | public function testNamedClosures()
class CallableClass (line 428) | class CallableClass
method __invoke (line 430) | public function __invoke()
class TestEventListener (line 435) | class TestEventListener
method preFoo (line 443) | public function preFoo($e)
method postFoo (line 448) | public function postFoo($e)
method __invoke (line 457) | public function __invoke()
class TestWithDispatcher (line 462) | class TestWithDispatcher
method foo (line 468) | public function foo($e, $name, $dispatcher)
method __invoke (line 474) | public function __invoke($e, $name, $dispatcher)
class TestEventSubscriber (line 482) | class TestEventSubscriber implements EventSubscriberInterface
method getSubscribedEvents (line 484) | public static function getSubscribedEvents(): array
class TestEventSubscriberWithPriorities (line 490) | class TestEventSubscriberWithPriorities implements EventSubscriberInterface
method getSubscribedEvents (line 492) | public static function getSubscribedEvents(): array
class TestEventSubscriberWithMultipleListeners (line 501) | class TestEventSubscriberWithMultipleListeners implements EventSubscribe...
method getSubscribedEvents (line 503) | public static function getSubscribedEvents(): array
FILE: Tests/Fixtures/CustomEvent.php
class CustomEvent (line 14) | final class CustomEvent
FILE: Tests/Fixtures/DummyEvent.php
class DummyEvent (line 14) | final class DummyEvent
FILE: Tests/Fixtures/TaggedInvokableListener.php
class TaggedInvokableListener (line 16) | #[AsEventListener]
method __invoke (line 19) | public function __invoke(CustomEvent $event): void
FILE: Tests/Fixtures/TaggedMultiListener.php
class TaggedMultiListener (line 16) | #[AsEventListener(event: CustomEvent::class, method: 'onCustomEvent')]
method onCustomEvent (line 21) | public function onCustomEvent(CustomEvent $event): void
method onFoo (line 25) | public function onFoo(): void
method onBarEvent (line 29) | public function onBarEvent(): void
method onBazEvent (line 33) | #[AsEventListener(event: 'baz')]
FILE: Tests/Fixtures/TaggedUnionTypeListener.php
class TaggedUnionTypeListener (line 16) | final class TaggedUnionTypeListener
method onUnionEvent (line 18) | #[AsEventListener]
FILE: Tests/GenericEventTest.php
class GenericEventTest (line 20) | class GenericEventTest extends TestCase
method setUp (line 25) | protected function setUp(): void
method testConstruct (line 31) | public function testConstruct()
method testGetArguments (line 39) | public function testGetArguments()
method testSetArguments (line 45) | public function testSetArguments()
method testSetArgument (line 52) | public function testSetArgument()
method testGetArgument (line 59) | public function testGetArgument()
method testGetArgException (line 65) | public function testGetArgException()
method testOffsetGet (line 71) | public function testOffsetGet()
method testOffsetSet (line 81) | public function testOffsetSet()
method testOffsetUnset (line 87) | public function testOffsetUnset()
method testOffsetIsset (line 93) | public function testOffsetIsset()
method testHasArgument (line 99) | public function testHasArgument()
method testGetSubject (line 105) | public function testGetSubject()
method testHasIterator (line 110) | public function testHasIterator()
FILE: Tests/ImmutableEventDispatcherTest.php
class ImmutableEventDispatcherTest (line 24) | class ImmutableEventDispatcherTest extends TestCase
method testDispatchDelegates (line 26) | public function testDispatchDelegates()
method testGetListenersDelegates (line 42) | public function testGetListenersDelegates()
method testHasListenersDelegates (line 55) | public function testHasListenersDelegates()
method testAddListenerDisallowed (line 68) | public function testAddListenerDisallowed()
method testAddSubscriberDisallowed (line 76) | public function testAddSubscriberDisallowed()
method testRemoveListenerDisallowed (line 86) | public function testRemoveListenerDisallowed()
method testRemoveSubscriberDisallowed (line 94) | public function testRemoveSubscriberDisallowed()
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (128K chars).
[
{
"path": ".gitattributes",
"chars": 74,
"preview": "/Tests export-ignore\n/phpunit.xml.dist export-ignore\n/.git* export-ignore\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 278,
"preview": "Please do not submit any Pull Requests here. They will be closed.\n---\n\nPlease submit your PR here instead:\nhttps://githu"
},
{
"path": ".github/workflows/close-pull-request.yml",
"chars": 544,
"preview": "name: Close Pull Request\n\non:\n pull_request_target:\n types: [opened]\n\njobs:\n run:\n runs-on: ubuntu-latest\n st"
},
{
"path": ".gitignore",
"chars": 34,
"preview": "vendor/\ncomposer.lock\nphpunit.xml\n"
},
{
"path": "Attribute/AsEventListener.php",
"chars": 1100,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "CHANGELOG.md",
"chars": 2909,
"preview": "CHANGELOG\n=========\n\n6.0\n---\n\n * Remove `LegacyEventDispatcherProxy`\n\n5.4\n---\n\n * Allow `#[AsEventListener]` attribute o"
},
{
"path": "Debug/TraceableEventDispatcher.php",
"chars": 12142,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Debug/WrappedListener.php",
"chars": 4599,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "DependencyInjection/AddEventAliasesPass.php",
"chars": 1087,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "DependencyInjection/RegisterListenersPass.php",
"chars": 9290,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "EventDispatcher.php",
"chars": 8858,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "EventDispatcherInterface.php",
"chars": 2187,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "EventSubscriberInterface.php",
"chars": 1771,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "GenericEvent.php",
"chars": 3457,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "ImmutableEventDispatcher.php",
"chars": 1980,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "Copyright (c) 2004-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 599,
"preview": "EventDispatcher Component\n=========================\n\nThe EventDispatcher component provides tools that allow your applic"
},
{
"path": "Tests/ChildEventDispatcherTest.php",
"chars": 567,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Debug/TraceableEventDispatcherTest.php",
"chars": 13138,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Debug/WrappedListenerTest.php",
"chars": 3231,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/DependencyInjection/RegisterListenersPassTest.php",
"chars": 22677,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/EventDispatcherTest.php",
"chars": 18509,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Fixtures/CustomEvent.php",
"chars": 327,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Fixtures/DummyEvent.php",
"chars": 326,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Fixtures/TaggedInvokableListener.php",
"chars": 491,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Fixtures/TaggedMultiListener.php",
"chars": 834,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Fixtures/TaggedUnionTypeListener.php",
"chars": 510,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/GenericEventTest.php",
"chars": 3197,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/ImmutableEventDispatcherTest.php",
"chars": 3303,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "composer.json",
"chars": 1523,
"preview": "{\n \"name\": \"symfony/event-dispatcher\",\n \"type\": \"library\",\n \"description\": \"Provides tools that allow your appl"
},
{
"path": "phpunit.xml.dist",
"chars": 1050,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:noNa"
}
]
About this extraction
This page contains the full source code of the symfony/event-dispatcher GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (118.8 KB), approximately 27.8k tokens, and a symbol index with 245 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.