Completed
Pull Request — 1.2 (#12)
by David
01:36
created

getCreateServiceDefinitionFromCallable()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 18

Duplication

Lines 9
Ratio 50 %

Importance

Changes 0
Metric Value
dl 9
loc 18
rs 9.0444
c 0
b 0
f 0
cc 6
nc 4
nop 5
1
<?php
2
3
declare (strict_types = 1);
4
5
namespace TheCodingMachine\Yaco\ServiceProvider;
6
7
use Interop\Container\Definition\DefinitionInterface;
8
use Interop\Container\ServiceProviderInterface;
9
use TheCodingMachine\ServiceProvider\Registry;
10
use TheCodingMachine\Yaco\Compiler;
11
use TheCodingMachine\Yaco\CompilerException;
12
use TheCodingMachine\Yaco\Definition\AliasDefinition;
13
use TheCodingMachine\Yaco\Definition\DumpableInterface;
14
use TheCodingMachine\Yaco\Definition\FactoryCallDefinition;
15
use TheCodingMachine\Yaco\Definition\Reference;
16
use TheCodingMachine\Yaco\DefinitionConverterInterface;
17
18
class ServiceProviderLoader
19
{
20
    /**
21
     * @var Compiler
22
     */
23
    private $compiler;
24
25
    /**
26
     * @var DefinitionConverterInterface
27
     */
28
    private $converter;
29
30
    /**
31
     * @param Compiler $compiler
32
     */
33
    public function __construct(Compiler $compiler, DefinitionConverterInterface $converter)
34
    {
35
        $this->compiler = $compiler;
36
        $this->converter = $converter;
37
    }
38
39
    /**
40
     * Loads the registry into the container.
41
     *
42
     * @param Registry $registry
43
     */
44
    public function loadFromRegistry(Registry $registry)
45
    {
46
        foreach ($registry as $key => $serviceProvider) {
47
            $this->loadServiceProviderFactories($serviceProvider, $key);
48
        }
49
        foreach ($registry as $key => $serviceProvider) {
50
            $this->loadServiceProviderExtensions($serviceProvider, $key);
51
        }
52
    }
53
54
    /**
55
     * @param ServiceProviderInterface $serviceProvider
56
     * @param int             $serviceProviderKey
57
     */
58
    private function loadServiceProviderFactories(ServiceProviderInterface $serviceProvider, int $serviceProviderKey)
59
    {
60
        $serviceFactories = $serviceProvider->getFactories();
61
62
        foreach ($serviceFactories as $serviceName => $callable) {
63
            $this->registerService($serviceName, $serviceProviderKey, $callable);
64
        }
65
    }
66
67
    /**
68
     * @param ServiceProviderInterface $serviceProvider
69
     * @param int             $serviceProviderKey
70
     */
71
    private function loadServiceProviderExtensions(ServiceProviderInterface $serviceProvider, int $serviceProviderKey)
72
    {
73
        $serviceExtensions = $serviceProvider->getExtensions();
74
75
        foreach ($serviceExtensions as $serviceName => $callable) {
76
            $this->extendService($serviceName, $serviceProviderKey, $callable);
77
        }
78
    }
79
80
    /**
81
     * @param string $serviceName
82
     * @param int $serviceProviderKey
83
     * @param callable $callable
84
     *
85
     */
86
    private function registerService(string $serviceName, int $serviceProviderKey, callable $callable)
87
    {
88
        $definition = $this->getCreateServiceDefinitionFromCallable($serviceName, $serviceName, $serviceProviderKey, $callable, new ContainerDefinition());
89
90
        $this->compiler->addDumpableDefinition($definition);
91
    }
92
93
    /**
94
     * @param string $serviceName
95
     * @param int $serviceProviderKey
96
     * @param callable $callable
97
     *
98
     * @throws CompilerException
99
     */
100
    private function extendService(string $serviceName, int $serviceProviderKey, callable $callable)
101
    {
102
        // TODO: check if $callable as a nullable previous argument!
103
104
        if (!$this->compiler->has($serviceName)) {
105
            // TODO: if $callable as NOT a nullable previous argument, throw an exception.
106
        }
107
108
        // The new service will be created under the name 'xxx_decorated_y'
109
        // The old service will be moved to the name 'xxx_decorated_y.inner'
110
        // This old service will be accessible through a callback represented by 'xxx_decorated_y.callbackwrapper'
111
        // The $servicename becomes an alias pointing to 'xxx_decorated_y'
112
113
        $previousDefinition = $this->compiler->getDumpableDefinition($serviceName);
114
        /*while ($previousDefinition instanceof Reference) {
115
            $previousDefinition = $this->compiler->getDumpableDefinition($previousDefinition->getAlias());
116
        }*/
117
118
        while ($previousDefinition instanceof AliasDefinition) {
119
            $previousDefinition = $this->compiler->getDumpableDefinition($previousDefinition->getAlias());
120
        }
121
122
        $oldServiceName = $serviceName;
123
        $decoratedServiceName = $this->getDecoratedServiceName($serviceName);
124
        //$innerName = $decoratedServiceName.'.inner';
125
        //$callbackWrapperName = $decoratedServiceName.'.callbackwrapper';
126
127
        // TODO: it would be way easier if we could simply rename a definition!!!
128
        if ($previousDefinition instanceof FactoryCallDefinition) {
129
            $innerDefinition = $previousDefinition->cloneWithoutIdentifier();
130
        } elseif ($previousDefinition instanceof CreateServiceFromRegistryDefinition) {
131
            $innerDefinition = $previousDefinition->cloneWithoutIdentifier();
132
        } elseif ($previousDefinition instanceof ExtendServiceFromRegistryDefinition) {
133
            $innerDefinition = $previousDefinition->cloneWithoutIdentifier();
134
        } else {
135
            // @codeCoverageIgnoreStart
136
            throw new CompilerException('Unable to rename definition from class '.get_class($previousDefinition));
137
            // @codeCoverageIgnoreEnd
138
        }
139
140
        $definition = $this->getExtendServiceDefinitionFromCallable($decoratedServiceName, $serviceName, $serviceProviderKey, $callable, new ContainerDefinition(), $innerDefinition);
141
142
        $this->compiler->addDumpableDefinition($definition);
143
        //$this->compiler->addDumpableDefinition($innerDefinition);
144
        //$this->compiler->addDumpableDefinition($callbackWrapperDefinition);
145
        $this->compiler->addDumpableDefinition(new AliasDefinition($oldServiceName, $decoratedServiceName));
146
    }
147
148
    /**
149
     * @param $serviceName
150
     * @param int                            $serviceProviderKey
151
     * @param callable                       $callable
152
     * @param ContainerDefinition            $containerDefinition
153
     *
154
     * @return DumpableInterface
155
     */
156
    private function getCreateServiceDefinitionFromCallable($decoratedServiceName, $serviceName, $serviceProviderKey, callable $callable, ContainerDefinition $containerDefinition): DumpableInterface
157
    {
158
        if ($callable instanceof DefinitionInterface) {
0 ignored issues
show
Bug introduced by
The class Interop\Container\Definition\DefinitionInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
159
            return $this->converter->convert($decoratedServiceName, $callable);
160
        }
161 View Code Duplication
        if (is_array($callable) && is_string($callable[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
            return new FactoryCallDefinition($decoratedServiceName, $callable[0], $callable[1], [$containerDefinition]);
163
        } elseif (is_string($callable) && strpos($callable, '::') !== false) {
164
            $pos = strpos($callable, '::');
165
            $className = substr($callable, 0, $pos);
166
            $methodName = substr($callable, $pos + 2);
167
168
            return new FactoryCallDefinition($decoratedServiceName, $className, $methodName, [$containerDefinition]);
169
        }
170
171
        // This is an object or a callback... we need to call the getServices method of the service provider at runtime.
172
        return new CreateServiceFromRegistryDefinition($decoratedServiceName, $serviceName, $serviceProviderKey);
173
    }
174
175
    /**
176
     * @param $serviceName
177
     * @param int                            $serviceProviderKey
178
     * @param callable                       $callable
179
     * @param ContainerDefinition            $containerDefinition
180
     * @param CallbackWrapperDefinition|null $previousDefinition
181
     *
182
     * @return DumpableInterface
183
     */
184
    private function getExtendServiceDefinitionFromCallable($decoratedServiceName, $serviceName, $serviceProviderKey, callable $callable, ContainerDefinition $containerDefinition, DumpableInterface $previousDefinition = null): DumpableInterface
185
    {
186
        if ($callable instanceof DefinitionInterface) {
0 ignored issues
show
Bug introduced by
The class Interop\Container\Definition\DefinitionInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
187
            return $this->converter->convert($decoratedServiceName, $callable);
188
        }
189 View Code Duplication
        if (is_array($callable) && is_string($callable[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
190
            return new FactoryCallDefinition($decoratedServiceName, $callable[0], $callable[1], [$containerDefinition, $previousDefinition]);
191
        } elseif (is_string($callable) && strpos($callable, '::') !== false) {
192
            $pos = strpos($callable, '::');
193
            $className = substr($callable, 0, $pos);
194
            $methodName = substr($callable, $pos + 2);
195
196
            return new FactoryCallDefinition($decoratedServiceName, $className, $methodName, [$containerDefinition, $previousDefinition]);
197
        }
198
199
        // This is an object or a callback... we need to call the getServices method of the service provider at runtime.
200
        return new ExtendServiceFromRegistryDefinition($decoratedServiceName, $serviceName, $serviceProviderKey, $previousDefinition);
201
    }
202
203
    /**
204
     * @param string $serviceName
205
     *
206
     * @return string
207
     */
208
    private function getDecoratedServiceName(string $serviceName): string
209
    {
210
        $counter = 1;
211
        while ($this->compiler->has($serviceName.'_decorated_'.$counter)) {
212
            ++$counter;
213
        }
214
215
        return $serviceName.'_decorated_'.$counter;
216
    }
217
}
218