Completed
Push — master ( baba12...502b0f )
by Andreas
08:34
created

EntityListenerPass   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 127
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 7
dl 0
loc 127
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
D process() 0 67 18
A attachToListener() 0 22 5
A getResolverClass() 0 11 2
A getConcreteDefinitionClass() 0 18 4
1
<?php
2
3
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
4
5
use Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver;
6
use Doctrine\Bundle\DoctrineBundle\Mapping\EntityListenerServiceResolver;
7
use Symfony\Component\DependencyInjection\ChildDefinition;
8
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
9
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
10
use Symfony\Component\DependencyInjection\ContainerBuilder;
11
use Symfony\Component\DependencyInjection\Definition;
12
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
13
use Symfony\Component\DependencyInjection\Reference;
14
15
/**
16
 * Class for Symfony bundles to register entity listeners
17
 */
18
class EntityListenerPass implements CompilerPassInterface
19
{
20
    /**
21
     * {@inheritDoc}
22
     */
23
    public function process(ContainerBuilder $container)
24
    {
25
        $resolvers = $container->findTaggedServiceIds('doctrine.orm.entity_listener');
26
27
        $lazyServiceReferencesByResolver = [];
28
29
        foreach ($resolvers as $id => $tagAttributes) {
30
            foreach ($tagAttributes as $attributes) {
31
                $name          = isset($attributes['entity_manager']) ? $attributes['entity_manager'] : $container->getParameter('doctrine.default_entity_manager');
32
                $entityManager = sprintf('doctrine.orm.%s_entity_manager', $name);
33
34
                if (! $container->hasDefinition($entityManager)) {
35
                    continue;
36
                }
37
38
                $resolverId = sprintf('doctrine.orm.%s_entity_listener_resolver', $name);
39
40
                if (! $container->has($resolverId)) {
41
                    continue;
42
                }
43
44
                $resolver = $container->findDefinition($resolverId);
45
                $resolver->setPublic(true);
46
47
                if (isset($attributes['entity']) && isset($attributes['event'])) {
48
                    $this->attachToListener($container, $name, $this->getConcreteDefinitionClass($container->findDefinition($id), $container, $id), $attributes);
49
                }
50
51
                $resolverClass                 = $this->getResolverClass($resolver, $container, $resolverId);
52
                $resolverSupportsLazyListeners = is_a($resolverClass, EntityListenerServiceResolver::class, true);
53
54
                $lazyByAttribute = isset($attributes['lazy']) && $attributes['lazy'];
55
                if ($lazyByAttribute && ! $resolverSupportsLazyListeners) {
56
                    throw new InvalidArgumentException(sprintf(
57
                        'Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.',
58
                        EntityListenerServiceResolver::class
59
                    ));
60
                }
61
62
                if (! isset($attributes['lazy']) && $resolverSupportsLazyListeners || $lazyByAttribute) {
63
                    $listener = $container->findDefinition($id);
64
65
                    if ($listener->isAbstract()) {
66
                        throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as this entity listener is lazy-loaded.', $id));
67
                    }
68
69
                    $resolver->addMethodCall('registerService', [$this->getConcreteDefinitionClass($listener, $container, $id), $id]);
70
71
                    // if the resolver uses the default class we will use a service locator for all listeners
72
                    if ($resolverClass === ContainerEntityListenerResolver::class) {
73
                        if (! isset($lazyServiceReferencesByResolver[$resolverId])) {
74
                            $lazyServiceReferencesByResolver[$resolverId] = [];
75
                        }
76
                        $lazyServiceReferencesByResolver[$resolverId][$id] = new Reference($id);
77
                    } else {
78
                        $listener->setPublic(true);
79
                    }
80
                } else {
81
                    $resolver->addMethodCall('register', [new Reference($id)]);
82
                }
83
            }
84
        }
85
86
        foreach ($lazyServiceReferencesByResolver as $resolverId => $listenerReferences) {
87
            $container->findDefinition($resolverId)->setArgument(0, ServiceLocatorTagPass::register($container, $listenerReferences));
88
        }
89
    }
90
91
    private function attachToListener(ContainerBuilder $container, $name, string $class, array $attributes)
92
    {
93
        $listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $name);
94
95
        if (! $container->has($listenerId)) {
96
            return;
97
        }
98
99
        $args = [
100
            $attributes['entity'],
101
            $class,
102
            $attributes['event'],
103
        ];
104
105
        if (isset($attributes['method'])) {
106
            $args[] = $attributes['method'];
107
        } elseif (! method_exists($class, $attributes['event']) && method_exists($class, '__invoke')) {
108
            $args[] = '__invoke';
109
        }
110
111
        $container->findDefinition($listenerId)->addMethodCall('addEntityListener', $args);
112
    }
113
114
    private function getResolverClass(Definition $resolver, ContainerBuilder $container, string $id) : string
115
    {
116
        $resolverClass = $this->getConcreteDefinitionClass($resolver, $container, $id);
117
118
        if (substr($resolverClass, 0, 1) === '%') {
119
            // resolve container parameter first
120
            $resolverClass = $container->getParameterBag()->resolveValue($resolverClass);
121
        }
122
123
        return $resolverClass;
124
    }
125
126
    private function getConcreteDefinitionClass(Definition $definition, ContainerBuilder $container, string $id) : string
127
    {
128
        $class = $definition->getClass();
129
        if ($class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
130
            return $class;
131
        }
132
133
        while ($definition instanceof ChildDefinition) {
134
            $definition = $container->findDefinition($definition->getParent());
135
136
            $class = $definition->getClass();
137
            if ($class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
138
                return $class;
139
            }
140
        }
141
142
        throw new InvalidArgumentException(sprintf('The service "%s" must define its class.', $id));
143
    }
144
}
145