Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5f8f645

Browse files
committed
Fix circular loop with EntityManager
1 parent e6cfa09 commit 5f8f645

File tree

4 files changed

+222
-29
lines changed

4 files changed

+222
-29
lines changed

src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php

Lines changed: 66 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 <[email protected]>
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,67 @@ public function removeEventListener($events, $listener)
136149
}
137150
}
138151

152+
/**
153+
* {@inheritdoc}
154+
*
155+
* @param string|EventSubscriber $subscriber When a string is provided, lazyloads the subscriber from the injected ServiceLocator
156+
*
157+
* @return void
158+
*/
159+
public function addEventSubscriber($subscriber)
160+
{
161+
if (!\is_string($subscriber)) {
162+
parent::addEventSubscriber($subscriber);
163+
164+
return;
165+
}
166+
167+
$this->initializedSubscribers = false;
168+
$this->subscribers[$subscriber] = $subscriber;
169+
}
170+
171+
/**
172+
* {@inheritdoc}
173+
*
174+
* @param string|EventSubscriber $subscriber When a string is provided, lazyloads the subscriber from the injected ServiceLocator
175+
*
176+
* @return void
177+
*/
178+
public function removeEventSubscriber($subscriber)
179+
{
180+
if (!\is_string($subscriber)) {
181+
parent::removeEventSubscriber($subscriber);
182+
183+
return;
184+
}
185+
186+
if (isset($this->subscribers[$subscriber]) && !\is_string($this->subscribers[$subscriber])) {
187+
parent::removeEventSubscriber($this->subscribers[$subscriber]);
188+
}
189+
190+
unset($this->subscribers[$subscriber]);
191+
}
192+
139193
private function initializeListeners(string $eventName)
140194
{
195+
$this->initialized[$eventName] = true;
141196
foreach ($this->listeners[$eventName] as $hash => $listener) {
142197
if (\is_string($listener)) {
143198
$this->listeners[$eventName][$hash] = $listener = $this->container->get($listener);
144199

145200
$this->methods[$eventName][$hash] = $this->getMethod($listener, $eventName);
146201
}
147202
}
148-
$this->initialized[$eventName] = true;
203+
}
204+
205+
private function initializeSubscribers()
206+
{
207+
$this->initializedSubscribers = true;
208+
foreach ($this->subscribers as $id => $subscriber) {
209+
if (\is_string($subscriber)) {
210+
parent::addEventSubscriber($this->subscribers[$id] = $this->container->get($subscriber));
211+
}
212+
}
149213
}
150214

151215
/**

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

Lines 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('addEventSubscriber', [$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

Lines 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->addEventSubscriber('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->addEventSubscriber('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->addEventSubscriber('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->addEventSubscriber('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->removeEventSubscriber('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()
240+
{
241+
++$this->calledSubscribedEventsCount;
242+
243+
return $this->listenedEvents;
244+
}
245+
}

0 commit comments

Comments
 (0)