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) { |
|
|
|
|
159
|
|
|
return $this->converter->convert($decoratedServiceName, $callable); |
160
|
|
|
} |
161
|
|
View Code Duplication |
if (is_array($callable) && is_string($callable[0])) { |
|
|
|
|
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) { |
|
|
|
|
187
|
|
|
return $this->converter->convert($decoratedServiceName, $callable); |
188
|
|
|
} |
189
|
|
View Code Duplication |
if (is_array($callable) && is_string($callable[0])) { |
|
|
|
|
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
|
|
|
|
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.