Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ContainerEntityListenerResolver + load listeners lazy by default #951

Merged
merged 8 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
$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));
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
}
}

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.
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
*/
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);
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
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
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
{
/** @var ContainerInterface */
alcaeus marked this conversation as resolved.
Show resolved Hide resolved
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])) {
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
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
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
*
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
* @return object
*/
private function resolveService($serviceId)
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
{
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
*/
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
private function normalizeClassName($className)
dmaicher marked this conversation as resolved.
Show resolved Hide resolved
{
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