Skip to content

Commit

Permalink
Add ContainerEntityListenerResolver + load listeners lazy by default
Browse files Browse the repository at this point in the history
  • Loading branch information
dmaicher committed Apr 7, 2019
1 parent eb88c42 commit 0882e9a
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 126 deletions.
44 changes: 28 additions & 16 deletions DependencyInjection/Compiler/EntityListenerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
Expand All @@ -19,6 +20,8 @@ public function process(ContainerBuilder $container)
{
$resolvers = $container->findTaggedServiceIds('doctrine.orm.entity_listener');

$lazyServiceReferencesByResolver = [];

foreach ($resolvers as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
$name = isset($attributes['entity_manager']) ? $attributes['entity_manager'] : $container->getParameter('doctrine.default_entity_manager');
Expand All @@ -41,35 +44,44 @@ public function process(ContainerBuilder $container)
$this->attachToListener($container, $name, $id, $attributes);
}

if (isset($attributes['lazy']) && $attributes['lazy']) {
$interface = 'Doctrine\\Bundle\\DoctrineBundle\\Mapping\\EntityListenerServiceResolver';
$class = $resolver->getClass();

if (substr($class, 0, 1) === '%') {
// resolve container parameter first
$class = $container->getParameterBag()->resolveValue($resolver->getClass());
}
$resolverSupportsLazyListeners = is_a($class, $interface, true);

$lazyByAttribute = isset($attributes['lazy']) && $attributes['lazy'];
if ($lazyByAttribute && ! $resolverSupportsLazyListeners) {
throw new InvalidArgumentException(
sprintf('Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.', $interface)
);
}

if (! isset($attributes['lazy']) && $resolverSupportsLazyListeners || $lazyByAttribute) {
$listener = $container->findDefinition($id);

if ($listener->isAbstract()) {
throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as this entity listener is lazy-loaded.', $id));
}

$interface = 'Doctrine\\Bundle\\DoctrineBundle\\Mapping\\EntityListenerServiceResolver';
$class = $resolver->getClass();

if (substr($class, 0, 1) === '%') {
// resolve container parameter first
$class = $container->getParameterBag()->resolveValue($resolver->getClass());
}
$resolver->addMethodCall('registerService', [$listener->getClass(), $id]);

if (! is_a($class, $interface, true)) {
throw new InvalidArgumentException(
sprintf('Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.', $interface)
);
if (! isset($lazyServiceReferencesByResolver[$resolverId])) {
$lazyServiceReferencesByResolver[$resolverId] = [];
}

$listener->setPublic(true);

$resolver->addMethodCall('registerService', [$listener->getClass(), $id]);
$lazyServiceReferencesByResolver[$resolverId][$id] = new Reference($id);
} else {
$resolver->addMethodCall('register', [new Reference($id)]);
}
}
}

foreach ($lazyServiceReferencesByResolver as $resolverId => $listenerReferences) {
$container->findDefinition($resolverId)->replaceArgument(0, ServiceLocatorTagPass::register($container, $listenerReferences));
}
}

private function attachToListener(ContainerBuilder $container, $name, $id, array $attributes)
Expand Down
103 changes: 6 additions & 97 deletions Mapping/ContainerAwareEntityListenerResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,107 +2,16 @@

namespace Doctrine\Bundle\DoctrineBundle\Mapping;

use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\DependencyInjection\ContainerInterface;

class ContainerAwareEntityListenerResolver implements EntityListenerServiceResolver
/**
* @deprecated since 1.11 and will be removed in 2.0. Use ContainerEntityListenerResolver instead.
*/
class ContainerAwareEntityListenerResolver extends ContainerEntityListenerResolver
{
/** @var ContainerInterface */
private $container;

/** @var object[] Map to store entity listener instances. */
private $instances = [];

/** @var string[] Map to store registered service ids */
private $serviceIds = [];

public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

/**
* {@inheritdoc}
*/
public function clear($className = null)
{
if ($className === null) {
$this->instances = [];

return;
}

$className = $this->normalizeClassName($className);

if (! isset($this->instances[$className])) {
return;
}

unset($this->instances[$className]);
}

/**
* {@inheritdoc}
*/
public function register($object)
{
if (! is_object($object)) {
throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
}

$className = $this->normalizeClassName(get_class($object));

$this->instances[$className] = $object;
}

/**
* {@inheritdoc}
*/
public function registerService($className, $serviceId)
{
$this->serviceIds[$this->normalizeClassName($className)] = $serviceId;
}

/**
* {@inheritdoc}
*/
public function resolve($className)
{
$className = $this->normalizeClassName($className);

if (! isset($this->instances[$className])) {
if (isset($this->serviceIds[$className])) {
$this->instances[$className] = $this->resolveService($this->serviceIds[$className]);
} else {
$this->instances[$className] = new $className();
}
}

return $this->instances[$className];
}

/**
* @param string $serviceId
*
* @return object
*/
private function resolveService($serviceId)
{
if (! $this->container->has($serviceId)) {
throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId));
}

return $this->container->get($serviceId);
}

/**
* @param string $className
*
* @return string
*/
private function normalizeClassName($className)
{
return trim($className, '\\');
@trigger_error(sprintf('The class "%s" is deprecated since 1.11 and will be removed in 2.0 Use "%s" instead.', self::class, 'Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver'), E_USER_DEPRECATED);
parent::__construct($container);
}
}
111 changes: 111 additions & 0 deletions Mapping/ContainerEntityListenerResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Mapping;

use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use RuntimeException;

class ContainerEntityListenerResolver implements EntityListenerServiceResolver
{
/** @var ContainerInterface */
private $container;

/** @var object[] Map to store entity listener instances. */
private $instances = [];

/** @var string[] Map to store registered service ids */
private $serviceIds = [];

/**
* @param ContainerInterface $container a service locator for listeners
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

/**
* {@inheritdoc}
*/
public function clear($className = null)
{
if ($className === null) {
$this->instances = [];

return;
}

$className = $this->normalizeClassName($className);

if (! isset($this->instances[$className])) {
return;
}

unset($this->instances[$className]);
}

/**
* {@inheritdoc}
*/
public function register($object)
{
if (! is_object($object)) {
throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
}

$className = $this->normalizeClassName(get_class($object));

$this->instances[$className] = $object;
}

/**
* {@inheritdoc}
*/
public function registerService($className, $serviceId)
{
$this->serviceIds[$this->normalizeClassName($className)] = $serviceId;
}

/**
* {@inheritdoc}
*/
public function resolve($className)
{
$className = $this->normalizeClassName($className);

if (! isset($this->instances[$className])) {
if (isset($this->serviceIds[$className])) {
$this->instances[$className] = $this->resolveService($this->serviceIds[$className]);
} else {
$this->instances[$className] = new $className();
}
}

return $this->instances[$className];
}

/**
* @param string $serviceId
*
* @return object
*/
private function resolveService($serviceId)
{
if (! $this->container->has($serviceId)) {
throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId));
}

return $this->container->get($serviceId);
}

/**
* @param string $className
*
* @return string
*/
private function normalizeClassName($className)
{
return trim($className, '\\');
}
}
2 changes: 1 addition & 1 deletion Resources/config/orm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<parameter key="doctrine.orm.quote_strategy.ansi.class">Doctrine\ORM\Mapping\AnsiQuoteStrategy</parameter>

<!-- entity listener resolver -->
<parameter key="doctrine.orm.entity_listener_resolver.class">Doctrine\Bundle\DoctrineBundle\Mapping\ContainerAwareEntityListenerResolver</parameter>
<parameter key="doctrine.orm.entity_listener_resolver.class">Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver</parameter>

<!-- second level cache -->
<parameter key="doctrine.orm.second_level_cache.default_cache_factory.class">Doctrine\ORM\Cache\DefaultCacheFactory</parameter>
Expand Down
7 changes: 4 additions & 3 deletions Tests/DependencyInjection/AbstractDoctrineExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ public function testEntityListenerResolver()
$this->assertDICDefinitionMethodCallOnce($definition, 'setEntityListenerResolver', [new Reference('doctrine.orm.em2_entity_listener_resolver')]);

$listener = $container->getDefinition('doctrine.orm.em1_entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener1')]);
$this->assertDICDefinitionMethodCallOnce($listener, 'registerService', ['EntityListener', 'entity_listener1']);

$listener = $container->getDefinition('entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener2')]);
Expand All @@ -849,10 +849,10 @@ public function testAttachEntityListenerTag()
$this->compileContainer($container);

$listener = $container->getDefinition('doctrine.orm.em1_entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener1')]);
$this->assertDICDefinitionMethodCallOnce($listener, 'registerService', ['EntityListener1', 'entity_listener1']);

$listener = $container->getDefinition('doctrine.orm.em2_entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($listener, 'register', [new Reference('entity_listener2')]);
$this->assertDICDefinitionMethodCallOnce($listener, 'registerService', ['EntityListener2', 'entity_listener2']);

$attachListener = $container->getDefinition('doctrine.orm.em1_listeners.attach_entity_listeners');
$this->assertDICDefinitionMethodCallOnce($attachListener, 'addEntityListener', ['My/Entity1', 'EntityListener1', 'postLoad']);
Expand Down Expand Up @@ -894,6 +894,7 @@ public function testAttachLazyEntityListener()

$resolver1 = $container->getDefinition('doctrine.orm.em1_entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($resolver1, 'registerService', ['EntityListener1', 'entity_listener1']);
$this->assertDICDefinitionMethodCallOnce($resolver1, 'register', [new Reference('entity_listener3')]);

$resolver2 = $container->findDefinition('custom_entity_listener_resolver');
$this->assertDICDefinitionMethodCallOnce($resolver2, 'registerService', ['EntityListener2', 'entity_listener2']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">

<services>
<service id="custom_entity_listener_resolver" class="Doctrine\Bundle\DoctrineBundle\Mapping\ContainerAwareEntityListenerResolver" >
<service id="custom_entity_listener_resolver" class="Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver" >
<argument type="service" id="service_container"/>
</service>

Expand All @@ -17,6 +17,9 @@
<service id="entity_listener2" class="EntityListener2">
<tag name="doctrine.orm.entity_listener" entity_manager="em2" lazy="true" />
</service>
<service id="entity_listener3" class="EntityListener3">
<tag name="doctrine.orm.entity_listener" lazy="false" />
</service>
</services>

<doctrine:config>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
custom_entity_listener_resolver:
class: Doctrine\Bundle\DoctrineBundle\Mapping\ContainerAwareEntityListenerResolver
class: Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver
arguments:
- '@service_container'

Expand All @@ -14,6 +14,11 @@ services:
tags:
- { name: doctrine.orm.entity_listener, entity_manager: em2, lazy: true }

entity_listener3:
class: EntityListener3
tags:
- { name: doctrine.orm.entity_listener, lazy: false }

doctrine:
dbal:
default_connection: default
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
services:
entity_listener_resolver:
class: \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
class: Doctrine\ORM\Mapping\DefaultEntityListenerResolver

entity_listener1:
class: \EntityListener
class: EntityListener
tags:
- { name: doctrine.orm.entity_listener }

entity_listener2:
class: \EntityListener
class: EntityListener
tags:
- { name: doctrine.orm.entity_listener, entity_manager: em2 }

Expand Down
Loading

0 comments on commit 0882e9a

Please sign in to comment.