first commit

This commit is contained in:
Sampanna Rimal
2024-08-27 17:48:06 +05:45
commit 53c0140f58
10839 changed files with 1125847 additions and 0 deletions

View File

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

View File

@ -0,0 +1,96 @@
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

View File

@ -0,0 +1,356 @@
<?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
{
protected ?LoggerInterface $logger;
protected Stopwatch $stopwatch;
/**
* @var \SplObjectStorage<WrappedListener, array{string, string}>|null
*/
private ?\SplObjectStorage $callStack = null;
private EventDispatcherInterface $dispatcher;
private array $wrappedListeners = [];
private array $orphanedEvents = [];
private ?RequestStack $requestStack;
private string $currentRequestHash = '';
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, ?LoggerInterface $logger = null, ?RequestStack $requestStack = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->requestStack = $requestStack;
}
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
{
$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->attach($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 {
$this->callStack->detach($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;
}
}

View File

@ -0,0 +1,144 @@
<?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 Stopwatch $stopwatch;
private ?EventDispatcherInterface $dispatcher;
private string $pretty;
private string $callableRef;
private ClassStub|string $stub;
private ?int $priority = null;
private static bool $hasClassStub;
public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, ?EventDispatcherInterface $dispatcher = null, ?int $priority = null)
{
$this->listener = $listener;
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null);
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->priority = $priority;
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 (str_contains($r->name, '{closure}')) {
$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]];
}
}

View File

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

View File

@ -0,0 +1,213 @@
<?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;
foreach ($events as $event) {
$priority = $event['priority'] ?? 0;
if (!isset($event['event'])) {
if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) {
continue;
}
$event['method'] ??= '__invoke';
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
}
$event['event'] = $aliases[$event['event']] ?? $event['event'];
if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback([
'/(?<=\b|_)[a-z]/i',
'/[^a-z0-9]/i',
], 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 = [];
}
}
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
{
if (
null === ($class = $container->getDefinition($id)->getClass())
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}
return $name;
}
}
/**
* @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;
}
}

View File

@ -0,0 +1,256 @@
<?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];
}
}

View File

@ -0,0 +1,66 @@
<?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 array<callable[]|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;
}

View File

@ -0,0 +1,49 @@
<?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();
}

View File

@ -0,0 +1,158 @@
<?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
{
protected mixed $subject;
protected array $arguments;
/**
* 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(mixed $subject = null, array $arguments = [])
{
$this->subject = $subject;
$this->arguments = $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);
}
}

View File

@ -0,0 +1,67 @@
<?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
{
private EventDispatcherInterface $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $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);
}
}

19
vendor/symfony/event-dispatcher/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
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.

View File

@ -0,0 +1,15 @@
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)

View File

@ -0,0 +1,47 @@
{
"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.2",
"symfony/event-dispatcher-contracts": "^2.5|^3"
},
"require-dev": {
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/expression-language": "^6.4|^7.0",
"symfony/config": "^6.4|^7.0",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/stopwatch": "^6.4|^7.0",
"psr/log": "^1|^2|^3"
},
"conflict": {
"symfony/dependency-injection": "<6.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"
}