Completed
Push — master ( 46c707...bf5fd8 )
by Benjamin
01:34
created

getStaticallyCallable()   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
namespace Bnf\SymfonyServiceProviderCompilerPass;
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 Registry
15
     */
16
    private $registry;
17
18
    /**
19
     * @var string
20
     */
21
    private $registryServiceName;
22
23
    /**
24
     * @param Registry $registry
25
     * @param string $registryServiceName
26
     */
27
    public function __construct(Registry $registry, string $registryServiceName = 'service_provider_registry')
28
    {
29
        $this->registry = $registry;
30
        $this->registryServiceName = $registryServiceName;
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 has to be dynamically added at runtime):
41
        $this->registerRegistry($container);
42
43
        foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
44
            $this->registerFactories($serviceProviderKey, $serviceProvider, $container);
45
        }
46
47
        foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
48
            $this->registerExtensions($serviceProviderKey, $serviceProvider, $container);
49
        }
50
    }
51
52
    private function registerRegistry(ContainerBuilder $container)
53
    {
54
        $definition = new Definition(Registry::class);
55
        $definition->setSynthetic(true);
56
        $definition->setPublic(true);
57
58
        $container->setDefinition($this->registryServiceName, $definition);
59
    }
60
61
    private function registerFactories($serviceProviderKey, ServiceProviderInterface $serviceProvider, ContainerBuilder $container)
62
    {
63
        $serviceFactories = $serviceProvider->getFactories();
64
65
        foreach ($serviceFactories as $serviceName => $callable) {
66
            $this->registerService($serviceName, $serviceProviderKey, $callable, $container);
67
        }
68
    }
69
70
    private function registerExtensions($serviceProviderKey, ServiceProviderInterface $serviceProvider, ContainerBuilder $container)
71
    {
72
        $serviceFactories = $serviceProvider->getExtensions();
73
74
        foreach ($serviceFactories as $serviceName => $callable) {
75
            $this->extendService($serviceName, $serviceProviderKey, $callable, $container);
76
        }
77
    }
78
79
    private function registerService($serviceName, $serviceProviderKey, $callable, ContainerBuilder $container)
80
    {
81
        $this->addServiceDefinitionFromCallable($serviceName, $serviceProviderKey, $callable, $container);
82
    }
83
84
    private function extendService($serviceName, $serviceProviderKey, $callable, ContainerBuilder $container)
85
    {
86
        $this->addServiceDefinitionFromCallable($serviceName, $serviceProviderKey, $callable, $container, 'extendService');
87
    }
88
89
    private function getDecoratedServiceName($serviceName, ContainerBuilder $container)
90
    {
91
        $counter = 1;
92
        while ($container->has($serviceName . '_decorated_' . $counter)) {
93
            $counter++;
94
        }
95
        return [
96
            $serviceName . '_decorated_' . $counter,
97
            $counter === 1 ? $serviceName : $serviceName . '_decorated_' . ($counter-1)
98
        ];
99
    }
100
101
    private function addServiceDefinitionFromCallable($serviceName, $serviceProviderKey, callable $callable, ContainerBuilder $container, string $method = 'createService')
102
    {
103
        $finalServiceName = $serviceName;
104
        $innerName = null;
105
106
        $factoryDefinition = new Definition($this->getReturnType($callable, $serviceName));
107
        $factoryDefinition->setPublic(true);
108
109
        if ($method === 'extendService' && $container->has($serviceName)) {
110
            list($finalServiceName, $previousServiceName) = $this->getDecoratedServiceName($serviceName, $container);
111
            $innerName = $finalServiceName . '.inner';
112
113
            $factoryDefinition->setDecoratedService($previousServiceName, $innerName);
114
        }
115
116
        $staticallyCallable = $this->getStaticallyCallable($callable);
117
        if ($staticallyCallable !== null) {
118
            $factoryDefinition->setFactory($staticallyCallable);
119
        } else {
120
            $factoryDefinition->setFactory([ new Reference($this->registryServiceName), $method ]);
121
            $factoryDefinition->addArgument($serviceProviderKey);
122
            $factoryDefinition->addArgument($serviceName);
123
        }
124
125
        $factoryDefinition->addArgument(new Reference('service_container'));
126
        if ($innerName !== null) {
127
            $factoryDefinition->addArgument(new Reference($innerName));
128
        }
129
130
        $container->setDefinition($finalServiceName, $factoryDefinition);
131
    }
132
133
    /**
134
     * @param callable $callable
135
     * @return array|string|null
136
     */
137
    private function getStaticallyCallable(callable $callable)
138
    {
139
        if (is_string($callable)) {
140
            return $callable;
141
        }
142
        if (is_array($callable) && isset($callable[0]) && is_string($callable[0])) {
143
            return $callable;
144
        }
145
146
        return null;
147
    }
148
149
    private function getReturnType(callable $callable, string $serviceName): string
150
    {
151
        return $this->getReflection($callable)->getReturnType() ?: $serviceName;
152
    }
153
154
    private function getReflection(callable $callable): \ReflectionFunctionAbstract
155
    {
156
        if (is_array($callable) && count($callable) === 2) {
157
            return new \ReflectionMethod($callable[0], $callable[1]);
158
        }
159
        if (is_object($callable) && !$callable instanceof \Closure) {
160
            return new \ReflectionMethod($callable, '__invoke');
161
        }
162
163
        return new \ReflectionFunction($callable);
164
    }
165
}
166