Completed
Push — 1.0 ( 5e8373...d03f73 )
by David
11:40
created

getDecoratedServiceName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
3
4
namespace TheCodingMachine\Interop\ServiceProviderBridgeBundle;
5
6
7
use Interop\Container\ServiceProvider;
8
use Invoker\Reflection\CallableReflection;
9
use Puli\Discovery\Binding\ClassBinding;
10
use Puli\GeneratedPuliFactory;
11
use Symfony\Component\DependencyInjection\Alias;
12
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
13
use Symfony\Component\DependencyInjection\ContainerBuilder;
14
use Symfony\Component\DependencyInjection\Definition;
15
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16
use Symfony\Component\DependencyInjection\Reference;
17
use TheCodingMachine\ServiceProvider\Registry;
18
19
class ServiceProviderCompilationPass implements CompilerPassInterface
20
{
21
    private $registryId;
22
23
    /**
24
     * @var array
25
     */
26
    private $serviceProvidersLazyArray;
27
28
    /**
29
     * @var bool
30
     */
31
    private $usePuli;
32
33
    private $bundle;
34
35
    /**
36
     * @param int $registryId
37
     * @param array $serviceProvidersLazyArray
38
     * @param bool $usePuli
39
     */
40
    public function __construct($registryId, array $serviceProvidersLazyArray, $usePuli, InteropServiceProviderBridgeBundle $bundle)
41
    {
42
        $this->registryId = $registryId;
43
        $this->serviceProvidersLazyArray = $serviceProvidersLazyArray;
44
        $this->usePuli = $usePuli;
45
        $this->bundle = $bundle;
46
    }
47
48
49
    /**
50
     * You can modify the container here before it is dumped to PHP code.
51
     *
52
     * @param ContainerBuilder $container
53
     */
54
    public function process(ContainerBuilder $container)
55
    {
56
        // Now, let's store the registry in the container (an empty version of it... it will be dynamically added at runtime):
57
        $this->registerRegistry($container);
58
59
        $registry = $this->bundle->getRegistry($container);
60
61
        // Note: in the 'boot' method of a bundle, the container is available.
62
        // We use that to push the lazy array in the container.
63
        // The lazy array can be used by the registry that is also part of the container.
64
        // The registry can itself be used by a factory that creates services!
65
66
        $this->registerAcclimatedContainer($container);
67
68
        foreach ($registry as $serviceProviderKey => $serviceProvider) {
69
            $this->registerProvider($serviceProviderKey, $serviceProvider, $container);
70
        }
71
    }
72
73
74
    private function registerRegistry(ContainerBuilder $container)
75
    {
76
        $definition = new Definition(Registry::class);
77
        $definition->setSynthetic(true);
78
79
        $container->setDefinition('service_provider_registry_'.$this->registryId, $definition);
80
    }
81
82
    private function registerAcclimatedContainer(ContainerBuilder $container) {
83
        $definition = new Definition('TheCodingMachine\\Interop\\ServiceProviderBridgeBundle\\SymfonyContainerAdapter');
84
        $definition->addArgument(new Reference("service_container"));
85
86
        $container->setDefinition('interop_service_provider_acclimated_container', $definition);
87
    }
88
89
    private function registerProvider($serviceProviderKey, ServiceProvider $serviceProvider, ContainerBuilder $container) {
90
        $serviceFactories = $serviceProvider->getServices();
91
92
        foreach ($serviceFactories as $serviceName => $callable) {
93
            $this->registerService($serviceName, $serviceProviderKey, $serviceProvider, $callable, $container);
94
        }
95
    }
96
97
    private function registerService($serviceName, $serviceProviderKey, ServiceProvider $serviceProvider, $callable, ContainerBuilder $container) {
0 ignored issues
show
Unused Code introduced by
The parameter $serviceProvider is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
98
        $factoryDefinition = $this->getServiceDefinitionFromCallable($serviceName, $serviceProviderKey, $callable);
99
100
        if (!$container->has($serviceName)) {
101
            $container->setDefinition($serviceName, $factoryDefinition);
102
        } else {
103
            // The new service will be created under the name 'xxx_decorated_y'
104
            // The old service will be moved to the name 'xxx_decorated_y.inner'
105
            // This old service will be accessible through a callback represented by 'xxx_decorated_y.callbackwrapper'
106
            // The $servicename becomes an alias pointing to 'xxx_decorated_y'
107
108
            $oldServiceName = $serviceName;
109
            $serviceName = $this->getDecoratedServiceName($serviceName, $container);
110
111
            $innerName = $serviceName.'.inner';
112
            $innerDefinition = $container->findDefinition($oldServiceName);
113
            $container->setDefinition($innerName, $innerDefinition);
114
115
            $callbackWrapperName = $serviceName.'.callbackwrapper';
116
            $callbackWrapperDefinition = new Definition('TheCodingMachine\\Interop\\ServiceProviderBridgeBundle\\CallableService', [
117
                new Reference('service_container'),
118
                $innerName
119
            ]);
120
121
            $factoryDefinition->addArgument(new Reference($callbackWrapperName));
122
123
            $container->setDefinition($serviceName, $factoryDefinition);
124
            $container->setDefinition($innerName, $innerDefinition);
125
            $container->setDefinition($callbackWrapperName, $callbackWrapperDefinition);
126
127
            $container->setAlias($oldServiceName, new Alias($serviceName));
128
        }
129
130
    }
131
132
    private function getDecoratedServiceName($serviceName, ContainerBuilder $container) {
133
        $counter = 1;
134
        while ($container->has($serviceName.'_decorated_'.$counter)) {
135
            $counter++;
136
        }
137
        return $serviceName.'_decorated_'.$counter;
138
    }
139
140
    private function getServiceDefinitionFromCallable($serviceName, $serviceProviderKey, callable $callable)
141
    {
142
        /*if ($callable instanceof DefinitionInterface) {
143
            // TODO: plug the definition-interop converter here!
144
        }*/
145
        $factoryDefinition = new Definition('Class'); // TODO: in PHP7, we can get the return type of the function!
146
        $containerDefinition = new Reference('interop_service_provider_acclimated_container');
147
148
        if ((is_array($callable) && is_string($callable[0])) || is_string($callable)) {
149
            $factoryDefinition->setFactory($callable);
150
            $factoryDefinition->addArgument(new Reference('interop_service_provider_acclimated_container'));
151
        } else {
152
            $factoryDefinition->setFactory([ new Reference('service_provider_registry_'.$this->registryId), 'createService' ]);
153
            $factoryDefinition->addArgument($serviceProviderKey);
154
            $factoryDefinition->addArgument($serviceName);
155
            $factoryDefinition->addArgument($containerDefinition);
156
        }
157
158
        return $factoryDefinition;
159
    }
160
}
161