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 fefa4d1

Browse filesBrowse files
committed
Fix circular loop with EntityManager
1 parent e6cfa09 commit fefa4d1
Copy full SHA for fefa4d1

File tree

4 files changed

+193
-31
lines changed
Filter options

4 files changed

+193
-31
lines changed

‎src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php
+34-2Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313

1414
use Doctrine\Common\EventArgs;
1515
use Doctrine\Common\EventManager;
16+
use Doctrine\Common\EventSubscriber;
1617
use Psr\Container\ContainerInterface;
1718

1819
/**
19-
* Allows lazy loading of listener services.
20+
* Allows lazy loading of listener and subscriber services.
2021
*
2122
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
2223
*/
@@ -28,7 +29,9 @@ class ContainerAwareEventManager extends EventManager
2829
* <event> => <listeners>
2930
*/
3031
private $listeners = [];
32+
private $subscribers = [];
3133
private $initialized = [];
34+
private $initializedSubscribers = false;
3235
private $methods = [];
3336
private $container;
3437

@@ -44,6 +47,9 @@ public function __construct(ContainerInterface $container)
4447
*/
4548
public function dispatchEvent($eventName, EventArgs $eventArgs = null)
4649
{
50+
if (!$this->initializedSubscribers) {
51+
$this->initializeSubscribers();
52+
}
4753
if (!isset($this->listeners[$eventName])) {
4854
return;
4955
}
@@ -66,6 +72,9 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null)
6672
*/
6773
public function getListeners($event = null)
6874
{
75+
if (!$this->initializedSubscribers) {
76+
$this->initializeSubscribers();
77+
}
6978
if (null !== $event) {
7079
if (!isset($this->initialized[$event])) {
7180
$this->initializeListeners($event);
@@ -90,6 +99,10 @@ public function getListeners($event = null)
9099
*/
91100
public function hasListeners($event)
92101
{
102+
if (!$this->initializedSubscribers) {
103+
$this->initializeSubscribers();
104+
}
105+
93106
return isset($this->listeners[$event]) && $this->listeners[$event];
94107
}
95108

@@ -136,16 +149,35 @@ public function removeEventListener($events, $listener)
136149
}
137150
}
138151

152+
/**
153+
* Adds an EventSubscriber by its identifier in the ServiceLocator injected in the constructor.
154+
*/
155+
public function addEventSubscriberId(string $subscriber): void
156+
{
157+
$this->initializedSubscribers = false;
158+
$this->subscribers[$subscriber] = $subscriber;
159+
}
160+
139161
private function initializeListeners(string $eventName)
140162
{
163+
$this->initialized[$eventName] = true;
141164
foreach ($this->listeners[$eventName] as $hash => $listener) {
142165
if (\is_string($listener)) {
143166
$this->listeners[$eventName][$hash] = $listener = $this->container->get($listener);
144167

145168
$this->methods[$eventName][$hash] = $this->getMethod($listener, $eventName);
146169
}
147170
}
148-
$this->initialized[$eventName] = true;
171+
}
172+
173+
private function initializeSubscribers()
174+
{
175+
$this->initializedSubscribers = true;
176+
foreach ($this->subscribers as $id => $subscriber) {
177+
if (\is_string($subscriber)) {
178+
parent::addEventSubscriber($this->subscribers[$id] = $this->container->get($subscriber));
179+
}
180+
}
149181
}
150182

151183
/**

‎src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php
+25-14Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
1313

14+
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
15+
use Symfony\Component\DependencyInjection\ChildDefinition;
1416
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1517
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
1618
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -55,11 +57,19 @@ public function process(ContainerBuilder $container)
5557
}
5658

5759
$this->connections = $container->getParameter($this->connections);
58-
$this->addTaggedSubscribers($container);
59-
$this->addTaggedListeners($container);
60+
$listenerRefs = [];
61+
$this->addTaggedSubscribers($container, $listenerRefs);
62+
$this->addTaggedListeners($container, $listenerRefs);
63+
64+
// replace service container argument of event managers with smaller service locator
65+
// so services can even remain private
66+
foreach ($listenerRefs as $connection => $refs) {
67+
$this->getEventManagerDef($container, $connection)
68+
->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs));
69+
}
6070
}
6171

62-
private function addTaggedSubscribers(ContainerBuilder $container)
72+
private function addTaggedSubscribers(ContainerBuilder $container, array &$listenerRefs)
6373
{
6474
$subscriberTag = $this->tagPrefix.'.event_subscriber';
6575
$taggedSubscribers = $this->findAndSortTags($subscriberTag, $container);
@@ -71,17 +81,25 @@ private function addTaggedSubscribers(ContainerBuilder $container)
7181
if (!isset($this->connections[$con])) {
7282
throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: "%s".', $con, $id, implode('", "', array_keys($this->connections))));
7383
}
74-
75-
$this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', [new Reference($id)]);
84+
$managerDef = $parentDef = $this->getEventManagerDef($container, $con);
85+
while ($parentDef instanceof ChildDefinition) {
86+
$parentDef = $container->findDefinition($parentDef->getParent());
87+
}
88+
$class = $container->getParameterBag()->resolveValue($parentDef->getClass());
89+
if (ContainerAwareEventManager::class === $class || is_subclass_of($class, ContainerAwareEventManager::class)) {
90+
$listenerRefs[$con][$id] = new Reference($id);
91+
$managerDef->addMethodCall('addEventSubscriberId', [$id]);
92+
} else {
93+
$managerDef->addMethodCall('addEventSubscriber', [new Reference($id)]);
94+
}
7695
}
7796
}
7897
}
7998

80-
private function addTaggedListeners(ContainerBuilder $container)
99+
private function addTaggedListeners(ContainerBuilder $container, array &$listenerRefs)
81100
{
82101
$listenerTag = $this->tagPrefix.'.event_listener';
83102
$taggedListeners = $this->findAndSortTags($listenerTag, $container);
84-
$listenerRefs = [];
85103

86104
foreach ($taggedListeners as $taggedListener) {
87105
[$id, $tag] = $taggedListener;
@@ -100,13 +118,6 @@ private function addTaggedListeners(ContainerBuilder $container)
100118
$this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', [[$tag['event']], $id]);
101119
}
102120
}
103-
104-
// replace service container argument of event managers with smaller service locator
105-
// so services can even remain private
106-
foreach ($listenerRefs as $connection => $refs) {
107-
$this->getEventManagerDef($container, $connection)
108-
->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs));
109-
}
110121
}
111122

112123
private function getEventManagerDef(ContainerBuilder $container, string $name)

‎src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php
+78-1Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bridge\Doctrine\Tests;
1313

14+
use Doctrine\Common\EventSubscriber;
1415
use PHPUnit\Framework\TestCase;
1516
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
1617
use Symfony\Component\DependencyInjection\Container;
@@ -37,10 +38,19 @@ public function testDispatchEvent()
3738
$this->container->set('lazy3', $listener5 = new MyListener());
3839
$this->evm->addEventListener('foo', $listener5 = new MyListener());
3940
$this->evm->addEventListener('bar', $listener5);
41+
$this->container->set('lazy4', $subscriber1 = new MySubscriber(['foo']));
42+
$this->evm->addEventSubscriberId('lazy4');
43+
$this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar']));
44+
45+
$this->assertSame(0, $subscriber1->calledSubscribedEventsCount);
46+
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
4047

4148
$this->evm->dispatchEvent('foo');
4249
$this->evm->dispatchEvent('bar');
4350

51+
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
52+
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
53+
4454
$this->assertSame(0, $listener1->calledByInvokeCount);
4555
$this->assertSame(1, $listener1->calledByEventNameCount);
4656
$this->assertSame(0, $listener2->calledByInvokeCount);
@@ -51,9 +61,13 @@ public function testDispatchEvent()
5161
$this->assertSame(0, $listener4->calledByEventNameCount);
5262
$this->assertSame(1, $listener5->calledByInvokeCount);
5363
$this->assertSame(1, $listener5->calledByEventNameCount);
64+
$this->assertSame(0, $subscriber1->calledByInvokeCount);
65+
$this->assertSame(1, $subscriber1->calledByEventNameCount);
66+
$this->assertSame(1, $subscriber2->calledByInvokeCount);
67+
$this->assertSame(0, $subscriber2->calledByEventNameCount);
5468
}
5569

56-
public function testAddEventListenerAfterDispatchEvent()
70+
public function testAddEventListenerAndSubscriberAfterDispatchEvent()
5771
{
5872
$this->container->set('lazy1', $listener1 = new MyListener());
5973
$this->evm->addEventListener('foo', 'lazy1');
@@ -64,10 +78,19 @@ public function testAddEventListenerAfterDispatchEvent()
6478
$this->container->set('lazy3', $listener5 = new MyListener());
6579
$this->evm->addEventListener('foo', $listener5 = new MyListener());
6680
$this->evm->addEventListener('bar', $listener5);
81+
$this->container->set('lazy7', $subscriber1 = new MySubscriber(['foo']));
82+
$this->evm->addEventSubscriberId('lazy7');
83+
$this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar']));
84+
85+
$this->assertSame(0, $subscriber1->calledSubscribedEventsCount);
86+
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
6787

6888
$this->evm->dispatchEvent('foo');
6989
$this->evm->dispatchEvent('bar');
7090

91+
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
92+
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
93+
7194
$this->container->set('lazy4', $listener6 = new MyListener());
7295
$this->evm->addEventListener('foo', 'lazy4');
7396
$this->evm->addEventListener('foo', $listener7 = new MyListener());
@@ -77,10 +100,23 @@ public function testAddEventListenerAfterDispatchEvent()
77100
$this->container->set('lazy6', $listener10 = new MyListener());
78101
$this->evm->addEventListener('foo', $listener10 = new MyListener());
79102
$this->evm->addEventListener('bar', $listener10);
103+
$this->container->set('lazy8', $subscriber3 = new MySubscriber(['foo']));
104+
$this->evm->addEventSubscriberId('lazy8');
105+
$this->evm->addEventSubscriber($subscriber4 = new MySubscriber(['bar']));
106+
107+
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
108+
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
109+
$this->assertSame(0, $subscriber3->calledSubscribedEventsCount);
110+
$this->assertSame(1, $subscriber4->calledSubscribedEventsCount);
80111

81112
$this->evm->dispatchEvent('foo');
82113
$this->evm->dispatchEvent('bar');
83114

115+
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
116+
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
117+
$this->assertSame(1, $subscriber3->calledSubscribedEventsCount);
118+
$this->assertSame(1, $subscriber4->calledSubscribedEventsCount);
119+
84120
$this->assertSame(0, $listener1->calledByInvokeCount);
85121
$this->assertSame(2, $listener1->calledByEventNameCount);
86122
$this->assertSame(0, $listener2->calledByInvokeCount);
@@ -91,6 +127,10 @@ public function testAddEventListenerAfterDispatchEvent()
91127
$this->assertSame(0, $listener4->calledByEventNameCount);
92128
$this->assertSame(2, $listener5->calledByInvokeCount);
93129
$this->assertSame(2, $listener5->calledByEventNameCount);
130+
$this->assertSame(0, $subscriber1->calledByInvokeCount);
131+
$this->assertSame(2, $subscriber1->calledByEventNameCount);
132+
$this->assertSame(2, $subscriber2->calledByInvokeCount);
133+
$this->assertSame(0, $subscriber2->calledByEventNameCount);
94134

95135
$this->assertSame(0, $listener6->calledByInvokeCount);
96136
$this->assertSame(1, $listener6->calledByEventNameCount);
@@ -102,6 +142,10 @@ public function testAddEventListenerAfterDispatchEvent()
102142
$this->assertSame(0, $listener9->calledByEventNameCount);
103143
$this->assertSame(1, $listener10->calledByInvokeCount);
104144
$this->assertSame(1, $listener10->calledByEventNameCount);
145+
$this->assertSame(0, $subscriber3->calledByInvokeCount);
146+
$this->assertSame(1, $subscriber3->calledByEventNameCount);
147+
$this->assertSame(1, $subscriber4->calledByInvokeCount);
148+
$this->assertSame(0, $subscriber4->calledByEventNameCount);
105149
}
106150

107151
public function testGetListenersForEvent()
@@ -135,6 +179,21 @@ public function testRemoveEventListener()
135179
$this->assertSame([], $this->evm->getListeners('foo'));
136180
}
137181

182+
public function testRemoveEventSubscriber()
183+
{
184+
$this->container->set('lazy', $subscriber1 = new MySubscriber(['foo']));
185+
$this->evm->addEventSubscriberId('lazy');
186+
$this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['foo']));
187+
188+
$this->assertSame([$subscriber2, $subscriber1], array_values($this->evm->getListeners('foo')));
189+
190+
$this->evm->removeEventSubscriber($subscriber2);
191+
$this->assertSame([$subscriber1], array_values($this->evm->getListeners('foo')));
192+
193+
$this->evm->removeEventSubscriberId('lazy');
194+
$this->assertSame([], $this->evm->getListeners('foo'));
195+
}
196+
138197
public function testRemoveEventListenerAfterDispatchEvent()
139198
{
140199
$this->container->set('lazy', $listener1 = new MyListener());
@@ -166,3 +225,21 @@ public function foo()
166225
++$this->calledByEventNameCount;
167226
}
168227
}
228+
229+
class MySubscriber extends MyListener implements EventSubscriber
230+
{
231+
public $calledSubscribedEventsCount = 0;
232+
private $listenedEvents;
233+
234+
public function __construct(array $listenedEvents)
235+
{
236+
$this->listenedEvents = $listenedEvents;
237+
}
238+
239+
public function getSubscribedEvents(): array
240+
{
241+
++$this->calledSubscribedEventsCount;
242+
243+
return $this->listenedEvents;
244+
}
245+
}

0 commit comments

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