Passed
Push — master ( 2e04a1...503be9 )
by Benjamin
04:20
created

ServiceProviderCompilationPass   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 67
dl 0
loc 152
rs 10
c 0
b 0
f 0
wmc 30

10 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 18 3
A getDecoratedServiceName() 0 8 3
A registerExtensions() 0 5 2
A registerRegistry() 0 7 1
A registerService() 0 2 1
A registerFactories() 0 5 2
A __construct() 0 4 1
A extendService() 0 2 1
B addServiceDefinitionFromCallable() 0 42 9
B getReturnType() 0 21 7
1
<?php
2
3
4
namespace Bnf\Interop\ServiceProviderBridgeBundle;
5
6
7
use Interop\Container\ServiceProviderInterface;
8
use Symfony\Component\DependencyInjection\Alias;
9
use Symfony\Component\DependencyInjection\ChildDefinition;
10
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
11
use Symfony\Component\DependencyInjection\ContainerBuilder;
12
use Symfony\Component\DependencyInjection\Definition;
13
use Symfony\Component\DependencyInjection\Reference;
14
15
class ServiceProviderCompilationPass implements CompilerPassInterface
16
{
17
    private $registryId;
18
19
    private $registryProvider;
20
21
    /**
22
     * @param int $registryId
23
     * @param RegistryProviderInterface $registryProvider
24
     */
25
    public function __construct($registryId, RegistryProviderInterface $registryProvider)
26
    {
27
        $this->registryId = $registryId;
28
        $this->registryProvider = $registryProvider;
29
    }
30
31
    /**
32
     * You can modify the container here before it is dumped to PHP code.
33
     *
34
     * @param ContainerBuilder $container
35
     */
36
    public function process(ContainerBuilder $container)
37
    {
38
        // Now, let's store the registry in the container (an empty version of it... it will be dynamically added at runtime):
39
        $this->registerRegistry($container);
40
41
        $registry = $this->registryProvider->getRegistry($container);
42
43
        // Note: in the 'boot' method of a bundle, the container is available.
44
        // We use that to push the lazy array in the container.
45
        // The lazy array can be used by the registry that is also part of the container.
46
        // The registry can itself be used by a factory that creates services!
47
48
        foreach ($registry as $serviceProviderKey => $serviceProvider) {
49
            $this->registerFactories($serviceProviderKey, $serviceProvider, $container);
50
        }
51
52
        foreach ($registry as $serviceProviderKey => $serviceProvider) {
53
            $this->registerExtensions($serviceProviderKey, $serviceProvider, $container);
54
        }
55
    }
56
57
58
    private function registerRegistry(ContainerBuilder $container)
59
    {
60
        $definition = new Definition(Registry::class);
61
        $definition->setSynthetic(true);
62
        $definition->setPublic(true);
63
64
        $container->setDefinition('service_provider_registry_'.$this->registryId, $definition);
65
    }
66
67
    private function registerFactories($serviceProviderKey, ServiceProviderInterface $serviceProvider, ContainerBuilder $container) {
68
        $serviceFactories = $serviceProvider->getFactories();
69
70
        foreach ($serviceFactories as $serviceName => $callable) {
71
            $this->registerService($serviceName, $serviceProviderKey, $callable, $container);
72
        }
73
    }
74
75
    private function registerExtensions($serviceProviderKey, ServiceProviderInterface $serviceProvider, ContainerBuilder $container) {
76
        $serviceFactories = $serviceProvider->getExtensions();
77
78
        foreach ($serviceFactories as $serviceName => $callable) {
79
            $this->extendService($serviceName, $serviceProviderKey, $callable, $container);
80
        }
81
    }
82
83
    private function registerService($serviceName, $serviceProviderKey, $callable, ContainerBuilder $container) {
84
        $this->addServiceDefinitionFromCallable($serviceName, $serviceProviderKey, $callable, $container);
85
    }
86
87
    private function extendService($serviceName, $serviceProviderKey, $callable, ContainerBuilder $container) {
88
        $this->addServiceDefinitionFromCallable($serviceName, $serviceProviderKey, $callable, $container, true);
89
    }
90
91
    private function getDecoratedServiceName($serviceName, ContainerBuilder $container) {
92
        $counter = 1;
93
        while ($container->has($serviceName.'_decorated_'.$counter)) {
94
            $counter++;
95
        }
96
        return [
97
            $serviceName.'_decorated_'.$counter,
98
            $counter === 1 ? $serviceName : $serviceName.'_decorated_'.($counter-1)
99
        ];
100
    }
101
102
    private function addServiceDefinitionFromCallable($serviceName, $serviceProviderKey, callable $callable, ContainerBuilder $container, bool $extension = false)
103
    {
104
        /*if ($callable instanceof DefinitionInterface) {
105
            // TODO: plug the definition-interop converter here!
106
        }*/
107
108
        $innerName = null;
109
        $decoratedServiceName = null;
110
        $factoryDefinition = new Definition($this->getReturnType($callable, $serviceName));
111
        $factoryDefinition->setPublic(true);
112
        if ($extension && $container->has($serviceName)) {
113
            // TODO: Use a ChildDefinition? $factoryDefinition = new ChildDefinition($serviceName);
114
            list($decoratedServiceName, $previousServiceName) = $this->getDecoratedServiceName($serviceName, $container);
115
            $innerName = $decoratedServiceName . '.inner';
116
117
            $factoryDefinition->setDecoratedService($previousServiceName, $innerName);
118
        }
119
120
        $containerDefinition = new Reference('service_container');
121
122
        if ((is_array($callable) && is_string($callable[0])) || is_string($callable)) {
123
            $factoryDefinition->setFactory($callable);
124
            $factoryDefinition->addArgument($containerDefinition);
125
        } else {
126
            $registryMethod = $extension ? 'extendService' : 'createService';
127
            $factoryDefinition->setFactory([ new Reference('service_provider_registry_'.$this->registryId), $registryMethod ]);
128
            $factoryDefinition->addArgument($serviceProviderKey);
129
            $factoryDefinition->addArgument($serviceName);
130
            $factoryDefinition->addArgument($containerDefinition);
131
        }
132
133
        if ($innerName) {
134
            $factoryDefinition->addArgument(new Reference($innerName));
135
        }
136
137
        if ($decoratedServiceName) {
138
            $container->setDefinition($decoratedServiceName, $factoryDefinition);
139
        } else {
140
            $container->setDefinition($serviceName, $factoryDefinition);
141
        }
142
143
        return $factoryDefinition;
144
    }
145
146
    private function getReturnType(callable $callable, string $serviceName): string
147
    {
148
        $reflection = null;
149
        if (is_array($callable)) {
150
            $reflection = new \ReflectionMethod($callable[0], $callable[1]);
151
        } elseif (is_object($callable)) {
152
            if ($callable instanceof \Closure) {
153
                $reflection = new \ReflectionFunction($callable);
154
            } else {
155
                $reflection = new \ReflectionMethod($callable, '__invoke');
156
            }
157
        } elseif (is_string($callable)) {
158
            $reflection = new \ReflectionFunction($callable);
159
        }
160
161
        if ($reflection) {
162
            return (string) $reflection->getReturnType() ?: $serviceName;
163
        }
164
165
        // If we cannot reflect a return type, assume the serviceName is the FQCN
166
        return $serviceName;
167
    }
168
}
169