Completed
Push — master ( d8cf41...48e44c )
by Benjamin
02:23
created

addServiceDefinitionFromCallable()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 18
nc 8
nop 5
dl 0
loc 29
rs 9.3554
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, 'extendService');
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, string $method = 'createService')
108
    {
109
        $finalServiceName = $serviceName;
110
        $innerName = null;
111
112
        $factoryDefinition = new Definition($this->getReturnType($callable, $serviceName));
113
        $factoryDefinition->setPublic(true);
114
115
        if ($method === 'extendService' && $container->has($serviceName)) {
116
            list($finalServiceName, $previousServiceName) = $this->getDecoratedServiceName($serviceName, $container);
117
            $innerName = $finalServiceName . '.inner';
118
119
            $factoryDefinition->setDecoratedService($previousServiceName, $innerName);
120
        }
121
122
        if ($this->isStaticallyCallable($callable)) {
123
            $factoryDefinition->setFactory($callable);
0 ignored issues
show
Bug introduced by
$callable of type callable is incompatible with the type string|array expected by parameter $factory of Symfony\Component\Depend...efinition::setFactory(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
            $factoryDefinition->setFactory(/** @scrutinizer ignore-type */ $callable);
Loading history...
124
        } else {
125
            $factoryDefinition->setFactory([ new Reference('service_provider_registry_'.$this->registryId), $method ]);
126
            $factoryDefinition->addArgument($serviceProviderKey);
127
            $factoryDefinition->addArgument($serviceName);
128
        }
129
130
        $factoryDefinition->addArgument(new Reference('service_container'));
131
        if ($innerName !== null) {
132
            $factoryDefinition->addArgument(new Reference($innerName));
133
        }
134
135
        $container->setDefinition($finalServiceName, $factoryDefinition);
136
    }
137
138
    private function isStaticallyCallable(callable $callable): bool
139
    {
140
        return (is_array($callable) && is_string($callable[0])) || is_string($callable);
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