Merge branch '4.4' into 5.1

* 4.4:
  [travis] always install ext-mongodb
  Fix circular loop with EntityManager
This commit is contained in:
Nicolas Grekas 2021-01-14 21:09:08 +01:00
commit a0ac3b0f76
5 changed files with 179 additions and 35 deletions

View File

@ -103,7 +103,7 @@ before_install:
else
rm ~/.pearrc /tmp/pear 2>/dev/null || true
mkdir -p $ext_cache
echo $input | pecl install -f $ext_name &&
echo $input | pecl -q install -f $ext_name &&
cp $ext_dir/$ext_so $ext_cache
fi
}
@ -147,11 +147,11 @@ before_install:
if [[ $PHP = 8.* ]]; then
tfold ext.memcached tpecl memcached-3.1.5 memcached.so $INI
else
tfold ext.mongodb tpecl mongodb-1.8.1 mongodb.so $INI
tfold ext.zookeeper tpecl zookeeper-0.7.2 zookeeper.so $INI
tfold ext.amqp tpecl amqp-1.10.2 amqp.so $INI
fi
tfold ext.mongodb tpecl mongodb-1.9.0 mongodb.so $INI
tfold ext.apcu tpecl apcu-5.1.19 apcu.so $INI
tfold ext.igbinary tpecl igbinary-3.1.6 igbinary.so $INI
tfold ext.redis tpecl redis-5.2.3 redis.so $INI "no"
@ -253,7 +253,7 @@ install:
fi
phpenv global $PHP
rm vendor/composer/package-versions-deprecated -Rf
([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; cp composer.json composer.bak; composer config platform.ext-mongodb 1.8.99; composer require --dev --no-update mongodb/mongodb ^1.8.1)
([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; cp composer.json composer.bak; composer require --dev --no-update mongodb/mongodb ^1.9.0)
tfold 'composer update' $COMPOSER_UP
tfold 'phpunit install' ./phpunit install
if [[ $deps = high ]]; then
@ -269,7 +269,7 @@ install:
git fetch --depth=2 origin $SYMFONY_VERSION
git checkout -m FETCH_HEAD
COMPONENTS=$(echo "$COMPONENTS" | xargs dirname | xargs -n1 -I{} bash -c "[ -e '{}/phpunit.xml.dist' ] && echo '{}'" | sort)
(cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.8.99; composer require --dev --no-update mongodb/mongodb)
(cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb)
[[ ! $COMPONENTS ]] || tfold 'phpunit install' SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 ./phpunit install
[[ ! $COMPONENTS ]] || echo "$COMPONENTS" | parallel --gnu "tfold {} 'cd {} && rm composer.lock vendor/ -Rf && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" || X=1
fi

View File

@ -16,7 +16,7 @@ use Doctrine\Common\EventManager;
use Psr\Container\ContainerInterface;
/**
* Allows lazy loading of listener services.
* Allows lazy loading of listener and subscriber services.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
@ -28,13 +28,16 @@ class ContainerAwareEventManager extends EventManager
* <event> => <listeners>
*/
private $listeners = [];
private $subscribers;
private $initialized = [];
private $initializedSubscribers = false;
private $methods = [];
private $container;
public function __construct(ContainerInterface $container)
public function __construct(ContainerInterface $container, array $subscriberIds = [])
{
$this->container = $container;
$this->subscribers = $subscriberIds;
}
/**
@ -44,6 +47,9 @@ class ContainerAwareEventManager extends EventManager
*/
public function dispatchEvent($eventName, EventArgs $eventArgs = null)
{
if (!$this->initializedSubscribers) {
$this->initializeSubscribers();
}
if (!isset($this->listeners[$eventName])) {
return;
}
@ -66,6 +72,9 @@ class ContainerAwareEventManager extends EventManager
*/
public function getListeners($event = null)
{
if (!$this->initializedSubscribers) {
$this->initializeSubscribers();
}
if (null !== $event) {
if (!isset($this->initialized[$event])) {
$this->initializeListeners($event);
@ -90,6 +99,10 @@ class ContainerAwareEventManager extends EventManager
*/
public function hasListeners($event)
{
if (!$this->initializedSubscribers) {
$this->initializeSubscribers();
}
return isset($this->listeners[$event]) && $this->listeners[$event];
}
@ -138,6 +151,7 @@ class ContainerAwareEventManager extends EventManager
private function initializeListeners(string $eventName)
{
$this->initialized[$eventName] = true;
foreach ($this->listeners[$eventName] as $hash => $listener) {
if (\is_string($listener)) {
$this->listeners[$eventName][$hash] = $listener = $this->container->get($listener);
@ -145,7 +159,16 @@ class ContainerAwareEventManager extends EventManager
$this->methods[$eventName][$hash] = $this->getMethod($listener, $eventName);
}
}
$this->initialized[$eventName] = true;
}
private function initializeSubscribers()
{
$this->initializedSubscribers = true;
foreach ($this->subscribers as $id => $subscriber) {
if (\is_string($subscriber)) {
parent::addEventSubscriber($this->subscribers[$id] = $this->container->get($subscriber));
}
}
}
/**

View File

@ -11,6 +11,8 @@
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -55,15 +57,24 @@ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
}
$this->connections = $container->getParameter($this->connections);
$this->addTaggedSubscribers($container);
$this->addTaggedListeners($container);
$listenerRefs = [];
$this->addTaggedSubscribers($container, $listenerRefs);
$this->addTaggedListeners($container, $listenerRefs);
// replace service container argument of event managers with smaller service locator
// so services can even remain private
foreach ($listenerRefs as $connection => $refs) {
$this->getEventManagerDef($container, $connection)
->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs));
}
}
private function addTaggedSubscribers(ContainerBuilder $container)
private function addTaggedSubscribers(ContainerBuilder $container, array &$listenerRefs)
{
$subscriberTag = $this->tagPrefix.'.event_subscriber';
$taggedSubscribers = $this->findAndSortTags($subscriberTag, $container);
$managerDefs = [];
foreach ($taggedSubscribers as $taggedSubscriber) {
[$id, $tag] = $taggedSubscriber;
$connections = isset($tag['connection']) ? [$tag['connection']] : array_keys($this->connections);
@ -72,16 +83,33 @@ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
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))));
}
$this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', [new Reference($id)]);
if (!isset($managerDefs[$con])) {
$managerDef = $parentDef = $this->getEventManagerDef($container, $con);
while ($parentDef instanceof ChildDefinition) {
$parentDef = $container->findDefinition($parentDef->getParent());
}
$managerClass = $container->getParameterBag()->resolveValue($parentDef->getClass());
$managerDefs[$con] = [$managerDef, $managerClass];
} else {
[$managerDef, $managerClass] = $managerDefs[$con];
}
if (ContainerAwareEventManager::class === $managerClass) {
$listenerRefs[$con][$id] = new Reference($id);
$refs = $managerDef->getArguments()[1] ?? [];
$refs[] = $id;
$managerDef->setArgument(1, $refs);
} else {
$managerDef->addMethodCall('addEventSubscriber', [new Reference($id)]);
}
}
}
}
private function addTaggedListeners(ContainerBuilder $container)
private function addTaggedListeners(ContainerBuilder $container, array &$listenerRefs)
{
$listenerTag = $this->tagPrefix.'.event_listener';
$taggedListeners = $this->findAndSortTags($listenerTag, $container);
$listenerRefs = [];
foreach ($taggedListeners as $taggedListener) {
[$id, $tag] = $taggedListener;
@ -100,13 +128,6 @@ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
$this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', [[$tag['event']], $id]);
}
}
// replace service container argument of event managers with smaller service locator
// so services can even remain private
foreach ($listenerRefs as $connection => $refs) {
$this->getEventManagerDef($container, $connection)
->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs));
}
}
private function getEventManagerDef(ContainerBuilder $container, string $name)

View File

@ -11,6 +11,7 @@
namespace Symfony\Bridge\Doctrine\Tests;
use Doctrine\Common\EventSubscriber;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
use Symfony\Component\DependencyInjection\Container;
@ -28,6 +29,8 @@ class ContainerAwareEventManagerTest extends TestCase
public function testDispatchEvent()
{
$this->evm = new ContainerAwareEventManager($this->container, ['lazy4']);
$this->container->set('lazy1', $listener1 = new MyListener());
$this->evm->addEventListener('foo', 'lazy1');
$this->evm->addEventListener('foo', $listener2 = new MyListener());
@ -37,10 +40,18 @@ class ContainerAwareEventManagerTest extends TestCase
$this->container->set('lazy3', $listener5 = new MyListener());
$this->evm->addEventListener('foo', $listener5 = new MyListener());
$this->evm->addEventListener('bar', $listener5);
$this->container->set('lazy4', $subscriber1 = new MySubscriber(['foo']));
$this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar']));
$this->assertSame(0, $subscriber1->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
$this->evm->dispatchEvent('foo');
$this->evm->dispatchEvent('bar');
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
$this->assertSame(0, $listener1->calledByInvokeCount);
$this->assertSame(1, $listener1->calledByEventNameCount);
$this->assertSame(0, $listener2->calledByInvokeCount);
@ -51,10 +62,16 @@ class ContainerAwareEventManagerTest extends TestCase
$this->assertSame(0, $listener4->calledByEventNameCount);
$this->assertSame(1, $listener5->calledByInvokeCount);
$this->assertSame(1, $listener5->calledByEventNameCount);
$this->assertSame(0, $subscriber1->calledByInvokeCount);
$this->assertSame(1, $subscriber1->calledByEventNameCount);
$this->assertSame(1, $subscriber2->calledByInvokeCount);
$this->assertSame(0, $subscriber2->calledByEventNameCount);
}
public function testAddEventListenerAfterDispatchEvent()
public function testAddEventListenerAndSubscriberAfterDispatchEvent()
{
$this->evm = new ContainerAwareEventManager($this->container, ['lazy7']);
$this->container->set('lazy1', $listener1 = new MyListener());
$this->evm->addEventListener('foo', 'lazy1');
$this->evm->addEventListener('foo', $listener2 = new MyListener());
@ -64,10 +81,18 @@ class ContainerAwareEventManagerTest extends TestCase
$this->container->set('lazy3', $listener5 = new MyListener());
$this->evm->addEventListener('foo', $listener5 = new MyListener());
$this->evm->addEventListener('bar', $listener5);
$this->container->set('lazy7', $subscriber1 = new MySubscriber(['foo']));
$this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar']));
$this->assertSame(0, $subscriber1->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
$this->evm->dispatchEvent('foo');
$this->evm->dispatchEvent('bar');
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
$this->container->set('lazy4', $listener6 = new MyListener());
$this->evm->addEventListener('foo', 'lazy4');
$this->evm->addEventListener('foo', $listener7 = new MyListener());
@ -77,10 +102,19 @@ class ContainerAwareEventManagerTest extends TestCase
$this->container->set('lazy6', $listener10 = new MyListener());
$this->evm->addEventListener('foo', $listener10 = new MyListener());
$this->evm->addEventListener('bar', $listener10);
$this->evm->addEventSubscriber($subscriber3 = new MySubscriber(['bar']));
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber3->calledSubscribedEventsCount);
$this->evm->dispatchEvent('foo');
$this->evm->dispatchEvent('bar');
$this->assertSame(1, $subscriber1->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber2->calledSubscribedEventsCount);
$this->assertSame(1, $subscriber3->calledSubscribedEventsCount);
$this->assertSame(0, $listener1->calledByInvokeCount);
$this->assertSame(2, $listener1->calledByEventNameCount);
$this->assertSame(0, $listener2->calledByInvokeCount);
@ -91,6 +125,10 @@ class ContainerAwareEventManagerTest extends TestCase
$this->assertSame(0, $listener4->calledByEventNameCount);
$this->assertSame(2, $listener5->calledByInvokeCount);
$this->assertSame(2, $listener5->calledByEventNameCount);
$this->assertSame(0, $subscriber1->calledByInvokeCount);
$this->assertSame(2, $subscriber1->calledByEventNameCount);
$this->assertSame(2, $subscriber2->calledByInvokeCount);
$this->assertSame(0, $subscriber2->calledByEventNameCount);
$this->assertSame(0, $listener6->calledByInvokeCount);
$this->assertSame(1, $listener6->calledByEventNameCount);
@ -102,6 +140,8 @@ class ContainerAwareEventManagerTest extends TestCase
$this->assertSame(0, $listener9->calledByEventNameCount);
$this->assertSame(1, $listener10->calledByInvokeCount);
$this->assertSame(1, $listener10->calledByEventNameCount);
$this->assertSame(1, $subscriber3->calledByInvokeCount);
$this->assertSame(0, $subscriber3->calledByEventNameCount);
}
public function testGetListenersForEvent()
@ -166,3 +206,21 @@ class MyListener
++$this->calledByEventNameCount;
}
}
class MySubscriber extends MyListener implements EventSubscriber
{
public $calledSubscribedEventsCount = 0;
private $listenedEvents;
public function __construct(array $listenedEvents)
{
$this->listenedEvents = $listenedEvents;
}
public function getSubscribedEvents(): array
{
++$this->calledSubscribedEventsCount;
return $this->listenedEvents;
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bridge\Doctrine\Tests\DependencyInjection\CompilerPass;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -209,20 +210,46 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
$this->process($container);
$eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager');
// first connection
$this->assertEquals(
[
['addEventSubscriber', [new Reference('a')]],
['addEventSubscriber', [new Reference('b')]],
'a',
'b',
],
$container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls()
$eventManagerDef->getArgument(1)
);
$serviceLocatorDef = $container->getDefinition((string) $eventManagerDef->getArgument(0));
$this->assertSame(ServiceLocator::class, $serviceLocatorDef->getClass());
$this->assertEquals(
[
['addEventSubscriber', [new Reference('a')]],
['addEventSubscriber', [new Reference('c')]],
'a' => new ServiceClosureArgument(new Reference('a')),
'b' => new ServiceClosureArgument(new Reference('b')),
],
$container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls()
$serviceLocatorDef->getArgument(0)
);
$eventManagerDef = $container->getDefinition('doctrine.dbal.second_connection.event_manager');
// second connection
$this->assertEquals(
[
'a',
'c',
],
$eventManagerDef->getArgument(1)
);
$serviceLocatorDef = $container->getDefinition((string) $eventManagerDef->getArgument(0));
$this->assertSame(ServiceLocator::class, $serviceLocatorDef->getClass());
$this->assertEquals(
[
'a' => new ServiceClosureArgument(new Reference('a')),
'c' => new ServiceClosureArgument(new Reference('c')),
],
$serviceLocatorDef->getArgument(0)
);
}
@ -261,15 +288,30 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
$this->process($container);
$eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager');
$this->assertEquals(
[
['addEventSubscriber', [new Reference('c')]],
['addEventSubscriber', [new Reference('d')]],
['addEventSubscriber', [new Reference('e')]],
['addEventSubscriber', [new Reference('b')]],
['addEventSubscriber', [new Reference('a')]],
'c',
'd',
'e',
'b',
'a',
],
$container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls()
$eventManagerDef->getArgument(1)
);
$serviceLocatorDef = $container->getDefinition((string) $eventManagerDef->getArgument(0));
$this->assertSame(ServiceLocator::class, $serviceLocatorDef->getClass());
$this->assertEquals(
[
'a' => new ServiceClosureArgument(new Reference('a')),
'b' => new ServiceClosureArgument(new Reference('b')),
'c' => new ServiceClosureArgument(new Reference('c')),
'd' => new ServiceClosureArgument(new Reference('d')),
'e' => new ServiceClosureArgument(new Reference('e')),
],
$serviceLocatorDef->getArgument(0)
);
}
@ -296,12 +338,12 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
$connections = ['default' => 'doctrine.dbal.default_connection'];
$container->register('doctrine.dbal.default_connection.event_manager', 'stdClass')
$container->register('doctrine.dbal.default_connection.event_manager', ContainerAwareEventManager::class)
->addArgument(new Reference('service_container'));
$container->register('doctrine.dbal.default_connection', 'stdClass');
if ($multipleConnections) {
$container->register('doctrine.dbal.second_connection.event_manager', 'stdClass')
$container->register('doctrine.dbal.second_connection.event_manager', ContainerAwareEventManager::class)
->addArgument(new Reference('service_container'));
$container->register('doctrine.dbal.second_connection', 'stdClass');
$connections['second'] = 'doctrine.dbal.second_connection';