1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace danmurf\DependencyInjection; |
4
|
|
|
|
5
|
|
|
use danmurf\DependencyInjection\Exception\CircularReferenceException; |
6
|
|
|
use danmurf\DependencyInjection\Exception\ContainerException; |
7
|
|
|
use danmurf\DependencyInjection\Exception\NotFoundException; |
8
|
|
|
use Psr\Container\ContainerInterface; |
9
|
|
|
use ReflectionClass; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Service locator which can get find and build services using |
13
|
|
|
* pre-defined configuration. |
14
|
|
|
* |
15
|
|
|
* @author Dan Murfitt <[email protected]> |
16
|
|
|
*/ |
17
|
|
|
class ConfigurableServiceLocator implements ServiceLocatorInterface |
18
|
|
|
{ |
19
|
|
|
const ARGUMENT_TYPE_SCALAR = 'scalar'; |
20
|
|
|
const ARGUMENT_TYPE_SERVICE = 'service'; |
21
|
|
|
|
22
|
|
|
const ARGUMENT_TYPES = [ |
23
|
|
|
self::ARGUMENT_TYPE_SCALAR, |
24
|
|
|
self::ARGUMENT_TYPE_SERVICE, |
25
|
|
|
]; |
26
|
|
|
|
27
|
|
|
/** @var array */ |
28
|
|
|
private $config = []; |
29
|
|
|
|
30
|
|
|
/** @var array */ |
31
|
|
|
private $locateCalls = []; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @param array $config |
35
|
|
|
*/ |
36
|
20 |
|
public function __construct(array $config) |
37
|
|
|
{ |
38
|
20 |
|
$this->validateConfig($config); |
39
|
16 |
|
$this->config = $config; |
40
|
16 |
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Get a new instance of a service. |
44
|
|
|
* |
45
|
|
|
* @param string $id The service id |
46
|
|
|
* @param ContainerInterface $container The container to get service dependencies from |
47
|
|
|
* |
48
|
|
|
* @throws ContainerException |
49
|
|
|
* @throws NotFoundException |
50
|
|
|
*/ |
51
|
9 |
|
public function locate($id, ContainerInterface $container) |
52
|
|
|
{ |
53
|
9 |
|
$this->validateRequest($id); |
54
|
|
|
|
55
|
9 |
|
if (!isset($this->config[$id])) { |
56
|
5 |
|
throw new NotFoundException(sprintf('Unable to locate service `%s` from configuration.', $id)); |
57
|
|
|
} |
58
|
|
|
|
59
|
4 |
|
$class = new ReflectionClass($this->config[$id]['class']); |
60
|
|
|
|
61
|
4 |
|
if (!isset($this->config[$id]['arguments'])) { |
62
|
1 |
|
return $class->newInstance(); |
63
|
|
|
} |
64
|
|
|
|
65
|
3 |
|
$args = []; |
66
|
3 |
|
foreach ($this->config[$id]['arguments'] as $argumentConfig) { |
67
|
3 |
|
switch ($argumentConfig['type']) { |
68
|
3 |
|
case self::ARGUMENT_TYPE_SCALAR: |
69
|
1 |
|
$args[] = $argumentConfig['value']; |
70
|
1 |
|
break; |
71
|
|
|
|
72
|
2 |
|
case self::ARGUMENT_TYPE_SERVICE: |
73
|
2 |
|
$args[] = $container->get($argumentConfig['value']); |
74
|
2 |
|
break; |
75
|
|
|
} |
76
|
|
|
} |
77
|
|
|
|
78
|
2 |
|
return $class->newInstanceArgs($args); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Ensure the config is in a valid format. |
83
|
|
|
* |
84
|
|
|
* @param array $config |
85
|
|
|
* |
86
|
|
|
* @throws ContainerException |
87
|
|
|
*/ |
88
|
20 |
|
private function validateConfig(array $config) |
89
|
|
|
{ |
90
|
20 |
|
foreach ($config as $id => $definition) { |
91
|
9 |
|
if (isset($definition['class'])) { |
92
|
6 |
|
return $this->validateClassDefinition($definition, $id); |
|
|
|
|
93
|
|
|
} |
94
|
|
|
|
95
|
3 |
|
if (isset($definition['interface'])) { |
96
|
2 |
|
return $this->validateInterfaceDefinition($definition, $id); |
|
|
|
|
97
|
|
|
} |
98
|
|
|
|
99
|
1 |
|
throw new ContainerException(sprintf('Configured service `%s` has no `class` or `interface` value.', $id)); |
100
|
|
|
if (!isset($definition['class'])) { |
|
|
|
|
101
|
|
|
throw new ContainerException(sprintf('Configured service `%s` has no `class` value.', $id)); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
if (isset($definition['arguments'])) { |
105
|
|
|
} |
106
|
|
|
} |
107
|
11 |
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Ensure a class definied service has valid config. |
111
|
|
|
* |
112
|
|
|
* @param array $definition |
113
|
|
|
* @param string $id |
114
|
|
|
*/ |
115
|
6 |
|
private function validateClassDefinition(array $definition, string $id) |
116
|
|
|
{ |
117
|
6 |
|
if (isset($definition['arguments'])) { |
118
|
4 |
|
$this->validateArguments($definition['arguments'], $id); |
119
|
|
|
} |
120
|
4 |
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Ensure an interface defined service has valid config. |
124
|
|
|
* |
125
|
|
|
* @param array $definition |
126
|
|
|
* @param string $id |
127
|
|
|
*/ |
128
|
2 |
|
private function validateInterfaceDefinition(array $definition, string $id) |
129
|
|
|
{ |
130
|
2 |
|
if (!isset($definition['service'])) { |
131
|
1 |
|
throw new ContainerException(sprintf('Configured interface definition `%s` has no `service` value.', $id)); |
132
|
|
|
} |
133
|
1 |
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Ensure the arguments config is in a valid format. |
137
|
|
|
* |
138
|
|
|
* @param array $arguments |
139
|
|
|
* @param string $id |
140
|
|
|
* |
141
|
|
|
* @throws ContainerException |
142
|
|
|
*/ |
143
|
4 |
|
private function validateArguments(array $arguments, string $id) |
144
|
|
|
{ |
145
|
4 |
|
foreach ($arguments as $argument) { |
146
|
4 |
|
$this->validateArgumentProperties($argument, $id); |
147
|
3 |
|
$this->validateArgumentType($argument['type'], $id); |
148
|
|
|
} |
149
|
2 |
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Ensure the argument has valid properties. |
153
|
|
|
* |
154
|
|
|
* @param array $argument |
155
|
|
|
* @param string $id |
156
|
|
|
* |
157
|
|
|
* @throws ContainerException |
158
|
|
|
*/ |
159
|
4 |
|
private function validateArgumentProperties(array $argument, string $id) |
160
|
|
|
{ |
161
|
4 |
|
if (!isset($argument['type']) || !isset($argument['value'])) { |
162
|
1 |
|
throw new ContainerException(sprintf( |
163
|
1 |
|
'Configuration for service `%s` must have `type` and `value` values.', |
164
|
1 |
|
$id |
165
|
|
|
)); |
166
|
|
|
} |
167
|
3 |
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Ensure the argument type is valid. |
171
|
|
|
* |
172
|
|
|
* @param string $type |
173
|
|
|
* @param string $id |
174
|
|
|
* |
175
|
|
|
* @throws ContainerException |
176
|
|
|
*/ |
177
|
3 |
|
private function validateArgumentType(string $type, string $id) |
178
|
|
|
{ |
179
|
3 |
|
if (false === array_search($type, self::ARGUMENT_TYPES)) { |
180
|
1 |
|
throw new ContainerException(sprintf( |
181
|
1 |
|
'Unknown argument type `%s` for service `%s`. Accepted values are `scalar` and `service`.', |
182
|
1 |
|
$type, |
183
|
1 |
|
$id |
184
|
|
|
)); |
185
|
|
|
} |
186
|
2 |
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* @param string $id |
190
|
|
|
* |
191
|
|
|
* @throws CircularReferenceException |
192
|
|
|
*/ |
193
|
9 |
|
private function validateRequest(string $id) |
194
|
|
|
{ |
195
|
9 |
|
if (false !== array_search($id, $this->locateCalls)) { |
196
|
1 |
|
throw new CircularReferenceException(sprintf('Circular dependency reference detected for `%s`', $id)); |
197
|
|
|
} |
198
|
|
|
|
199
|
9 |
|
$this->locateCalls[] = $id; |
200
|
9 |
|
} |
201
|
|
|
} |
202
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.