Completed
Push — master ( b66b49...202e37 )
by Benjamin
03:02
created

ServiceProviderCompilationPass::getReflection()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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