ServiceProviderCompilationPass::process()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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