Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d5309f3

Browse filesBrowse files
committed
[EventDispatcher] Freeze events.
1 parent 8c80c5b commit d5309f3
Copy full SHA for d5309f3

24 files changed

+428
-10
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public function getConfigTreeBuilder()
8888
->scalarNode('error_controller')
8989
->defaultValue('error_controller')
9090
->end()
91+
->booleanNode('freeze_kernel_events')->defaultValue(false)->end()
9192
->end()
9293
;
9394

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
7070
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
7171
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
72+
use Symfony\Component\HttpKernel\KernelEvents;
7273
use Symfony\Component\Lock\Lock;
7374
use Symfony\Component\Lock\LockFactory;
7475
use Symfony\Component\Lock\LockInterface;
@@ -223,6 +224,19 @@ public function load(array $configs, ContainerBuilder $container)
223224
$container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
224225
$container->setParameter('kernel.default_locale', $config['default_locale']);
225226
$container->setParameter('kernel.error_controller', $config['error_controller']);
227+
$container->setParameter(
228+
'event_dispatcher.freeze_events',
229+
$config['freeze_kernel_events'] ? [
230+
KernelEvents::REQUEST,
231+
KernelEvents::EXCEPTION,
232+
KernelEvents::VIEW,
233+
KernelEvents::CONTROLLER,
234+
KernelEvents::CONTROLLER_ARGUMENTS,
235+
KernelEvents::RESPONSE,
236+
KernelEvents::TERMINATE,
237+
KernelEvents::FINISH_REQUEST,
238+
] : []
239+
);
226240

227241
if (!$container->hasParameter('debug.file_link_format')) {
228242
$links = [

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<xsd:attribute name="default-locale" type="xsd:string" />
4242
<xsd:attribute name="test" type="xsd:boolean" />
4343
<xsd:attribute name="error-controller" type="xsd:string" />
44+
<xsd:attribute name="freeze-kernel-events" type="xsd:boolean" />
4445
</xsd:complexType>
4546

4647
<xsd:complexType name="form">

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
501501
'notification_on_failed_messages' => false,
502502
],
503503
'error_controller' => 'error_controller',
504+
'freeze_kernel_events' => false,
504505
'secrets' => [
505506
'enabled' => true,
506507
'vault_directory' => '%kernel.project_dir%/config/secrets/%kernel.environment%',
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'freeze_kernel_events' => true,
5+
]);
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:framework="http://symfony.com/schema/dic/symfony"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
7+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
8+
9+
<framework:config freeze-kernel-events="true" />
10+
</container>
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
framework:
2+
freeze_kernel_events: true

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
4040
use Symfony\Component\HttpClient\ScopingHttpClient;
4141
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
42+
use Symfony\Component\HttpKernel\KernelEvents;
4243
use Symfony\Component\Messenger\Transport\TransportFactory;
4344
use Symfony\Component\PropertyAccess\PropertyAccessor;
4445
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
@@ -1384,6 +1385,32 @@ public function testMailerWithSpecificMessageBus(): void
13841385
$this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('mailer.mailer')->getArgument(1));
13851386
}
13861387

1388+
public function testFreezeKernelEvents(): void
1389+
{
1390+
$container = $this->createContainerFromFile('freeze_events');
1391+
1392+
$this->assertSame(
1393+
[
1394+
KernelEvents::REQUEST,
1395+
KernelEvents::EXCEPTION,
1396+
KernelEvents::VIEW,
1397+
KernelEvents::CONTROLLER,
1398+
KernelEvents::CONTROLLER_ARGUMENTS,
1399+
KernelEvents::RESPONSE,
1400+
KernelEvents::TERMINATE,
1401+
KernelEvents::FINISH_REQUEST,
1402+
],
1403+
$container->getParameter('event_dispatcher.freeze_events')
1404+
);
1405+
}
1406+
1407+
public function testDontFreezeKernelEventsByDefault(): void
1408+
{
1409+
$container = $this->createContainerFromFile('full');
1410+
1411+
$this->assertSame([], $container->getParameter('event_dispatcher.freeze_events'));
1412+
}
1413+
13871414
protected function createContainer(array $data = [])
13881415
{
13891416
return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([

‎src/Symfony/Bundle/FrameworkBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"symfony/console": "<4.4",
7474
"symfony/dotenv": "<4.4",
7575
"symfony/dom-crawler": "<4.4",
76+
"symfony/event-dispatcher": "<5.1",
7677
"symfony/http-client": "<4.4",
7778
"symfony/form": "<4.4",
7879
"symfony/lock": "<4.4",

‎src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
+15-1Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111

1212
namespace Symfony\Component\EventDispatcher\Debug;
1313

14+
use Psr\EventDispatcher\ListenerProviderInterface;
1415
use Psr\EventDispatcher\StoppableEventInterface;
1516
use Psr\Log\LoggerInterface;
1617
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1718
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1819
use Symfony\Component\HttpFoundation\Request;
1920
use Symfony\Component\HttpFoundation\RequestStack;
2021
use Symfony\Component\Stopwatch\Stopwatch;
22+
use Symfony\Contracts\EventDispatcher\ListenerProviderAwareInterface;
2123
use Symfony\Contracts\Service\ResetInterface;
2224

2325
/**
@@ -27,7 +29,7 @@
2729
*
2830
* @author Fabien Potencier <fabien@symfony.com>
2931
*/
30-
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
32+
class TraceableEventDispatcher implements EventDispatcherInterface, ListenerProviderAwareInterface, ResetInterface
3133
{
3234
protected $logger;
3335
protected $stopwatch;
@@ -47,6 +49,10 @@ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $sto
4749
$this->wrappedListeners = [];
4850
$this->orphanedEvents = [];
4951
$this->requestStack = $requestStack;
52+
53+
if (!$dispatcher instanceof ListenerProviderAwareInterface) {
54+
@trigger_error(sprintf('Implementing %s without %s is deprecated since Symfony 5.1.', EventDispatcherInterface::class, ListenerProviderAwareInterface::class), E_USER_DEPRECATED);
55+
}
5056
}
5157

5258
/**
@@ -91,6 +97,14 @@ public function removeSubscriber(EventSubscriberInterface $subscriber)
9197
return $this->dispatcher->removeSubscriber($subscriber);
9298
}
9399

100+
/**
101+
* {@inheritdoc}
102+
*/
103+
public function setListenerProvider(string $event, ListenerProviderInterface $listenerProvider): void
104+
{
105+
$this->dispatcher->setListenerProvider($event, $listenerProvider);
106+
}
107+
94108
/**
95109
* {@inheritdoc}
96110
*/

‎src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php
+34-4Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1515
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1819
use Symfony\Component\DependencyInjection\Reference;
1920
use Symfony\Component\EventDispatcher\Event as LegacyEvent;
2021
use Symfony\Component\EventDispatcher\EventDispatcher;
2122
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
23+
use Symfony\Component\EventDispatcher\ListenerProvider\LazyListenerProvider;
24+
use Symfony\Component\EventDispatcher\ListenerProvider\SimpleListenerProvider;
2225
use Symfony\Contracts\EventDispatcher\Event;
2326

2427
/**
@@ -30,16 +33,18 @@ class RegisterListenersPass implements CompilerPassInterface
3033
protected $listenerTag;
3134
protected $subscriberTag;
3235
protected $eventAliasesParameter;
36+
protected $freezeEventsParameter;
3337

3438
private $hotPathEvents = [];
3539
private $hotPathTagName;
3640

37-
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
41+
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases', string $freezeEventsParameter = 'event_dispatcher.freeze_events')
3842
{
3943
$this->dispatcherService = $dispatcherService;
4044
$this->listenerTag = $listenerTag;
4145
$this->subscriberTag = $subscriberTag;
4246
$this->eventAliasesParameter = $eventAliasesParameter;
47+
$this->freezeEventsParameter = $freezeEventsParameter;
4348
}
4449

4550
public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path')
@@ -64,6 +69,13 @@ public function process(ContainerBuilder $container)
6469
}
6570
$definition = $container->findDefinition($this->dispatcherService);
6671

72+
$frozenEvents = [];
73+
if ($container->hasParameter($this->freezeEventsParameter)) {
74+
foreach ($container->getParameter($this->freezeEventsParameter) as $event) {
75+
$frozenEvents[$event] = [];
76+
}
77+
}
78+
6779
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
6880
foreach ($events as $event) {
6981
$priority = isset($event['priority']) ? $event['priority'] : 0;
@@ -91,7 +103,11 @@ public function process(ContainerBuilder $container)
91103
}
92104
}
93105

94-
$definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
106+
if (isset($frozenEvents[$event['event']])) {
107+
$frozenEvents[$event['event']][$priority][] = [new Reference($id), $event['method']];
108+
} else {
109+
$definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
110+
}
95111

96112
if (isset($this->hotPathEvents[$event['event']])) {
97113
$container->getDefinition($id)->addTag($this->hotPathTagName);
@@ -119,8 +135,12 @@ public function process(ContainerBuilder $container)
119135
ExtractingEventDispatcher::$subscriber = $class;
120136
$extractingDispatcher->addSubscriber($extractingDispatcher);
121137
foreach ($extractingDispatcher->listeners as $args) {
122-
$args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
123-
$definition->addMethodCall('addListener', $args);
138+
if (isset($frozenEvents[$args[0]])) {
139+
$frozenEvents[$args[0]][$args[2]][] = [new Reference($id), $args[1]];
140+
} else {
141+
$args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
142+
$definition->addMethodCall('addListener', $args);
143+
}
124144

125145
if (isset($this->hotPathEvents[$args[0]])) {
126146
$container->getDefinition($id)->addTag($this->hotPathTagName);
@@ -129,6 +149,16 @@ public function process(ContainerBuilder $container)
129149
$extractingDispatcher->listeners = [];
130150
ExtractingEventDispatcher::$aliases = [];
131151
}
152+
153+
foreach ($frozenEvents as $event => $listeners) {
154+
krsort($listeners, SORT_NUMERIC);
155+
$listeners = array_values($listeners);
156+
$providerId = sprintf('__%s.listener_provider.%s', $this->dispatcherService, $event);
157+
$container->register($providerId, SimpleListenerProvider::class)
158+
->setArguments([array_merge(... array_values($listeners))]);
159+
160+
$definition->addMethodCall('setListenerProvider', [$event, new Definition(LazyListenerProvider::class, [new ServiceClosureArgument(new Reference($providerId))])]);
161+
}
132162
}
133163

134164
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string

‎src/Symfony/Component/EventDispatcher/EventDispatcher.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/EventDispatcher/EventDispatcher.php
+27-2Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111

1212
namespace Symfony\Component\EventDispatcher;
1313

14+
use Psr\EventDispatcher\ListenerProviderInterface;
1415
use Psr\EventDispatcher\StoppableEventInterface;
1516
use Symfony\Component\EventDispatcher\Debug\WrappedListener;
17+
use Symfony\Component\EventDispatcher\Exception\RuntimeException;
18+
use Symfony\Contracts\EventDispatcher\ListenerProviderAwareInterface;
1619

1720
/**
1821
* The EventDispatcherInterface is the central point of Symfony's event listener system.
@@ -29,11 +32,15 @@
2932
* @author Jordan Alliot <jordan.alliot@gmail.com>
3033
* @author Nicolas Grekas <p@tchwork.com>
3134
*/
32-
class EventDispatcher implements EventDispatcherInterface
35+
class EventDispatcher implements EventDispatcherInterface, ListenerProviderAwareInterface
3336
{
3437
private $listeners = [];
3538
private $sorted = [];
3639
private $optimized;
40+
/**
41+
* @var ListenerProviderInterface[]
42+
*/
43+
private $listenerProviders = [];
3744

3845
public function __construct()
3946
{
@@ -49,7 +56,9 @@ public function dispatch(object $event, string $eventName = null): object
4956
{
5057
$eventName = $eventName ?? \get_class($event);
5158

52-
if (null !== $this->optimized && null !== $eventName) {
59+
if (isset($this->listenerProviders[$eventName])) {
60+
$listeners = $this->listenerProviders[$eventName]->getListenersForEvent($event);
61+
} elseif (null !== $this->optimized && null !== $eventName) {
5362
$listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
5463
} else {
5564
$listeners = $this->getListeners($eventName);
@@ -140,6 +149,10 @@ public function hasListeners(string $eventName = null)
140149
*/
141150
public function addListener(string $eventName, $listener, int $priority = 0)
142151
{
152+
if (isset($this->listenerProviders[$eventName])) {
153+
throw new RuntimeException(sprintf('The "%s" event is frozen. You cannot attache listeners to it.', $eventName));
154+
}
155+
143156
$this->listeners[$eventName][$priority][] = $listener;
144157
unset($this->sorted[$eventName], $this->optimized[$eventName]);
145158
}
@@ -149,6 +162,10 @@ public function addListener(string $eventName, $listener, int $priority = 0)
149162
*/
150163
public function removeListener(string $eventName, $listener)
151164
{
165+
if (isset($this->listenerProviders[$eventName])) {
166+
throw new RuntimeException(sprintf('The "%s" event is frozen. You cannot remove listeners from it.', $eventName));
167+
}
168+
152169
if (empty($this->listeners[$eventName])) {
153170
return;
154171
}
@@ -209,6 +226,14 @@ public function removeSubscriber(EventSubscriberInterface $subscriber)
209226
}
210227
}
211228

229+
/**
230+
* {@inheritdoc}
231+
*/
232+
public function setListenerProvider(string $event, ListenerProviderInterface $listenerProvider): void
233+
{
234+
$this->listenerProviders[$event] = $listenerProvider;
235+
}
236+
212237
/**
213238
* Triggers the listeners of an event.
214239
*
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Symfony\Component\EventDispatcher\Exception;
4+
5+
interface ExceptionInterface
6+
{
7+
}
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Symfony\Component\EventDispatcher\Exception;
4+
5+
final class RuntimeException extends \RuntimeException implements ExceptionInterface
6+
{
7+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.