1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the slince/di package. |
5
|
|
|
* |
6
|
|
|
* (c) Slince <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Slince\Di; |
13
|
|
|
|
14
|
|
|
use Slince\Di\Exception\ConfigException; |
15
|
|
|
use Slince\Di\Exception\DependencyInjectionException; |
16
|
|
|
use Slince\Di\Exception\NotFoundException; |
17
|
|
|
|
18
|
|
|
class DefinitionResolver |
19
|
|
|
{ |
20
|
|
|
/** |
21
|
|
|
* @var Container |
22
|
|
|
*/ |
23
|
|
|
protected $container; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @param Container $container |
27
|
|
|
*/ |
28
|
|
|
public function __construct(Container $container) |
29
|
|
|
{ |
30
|
|
|
$this->container = $container; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @param Definition $definition |
35
|
|
|
* @return mixed |
36
|
|
|
*/ |
37
|
|
|
public function resolve(Definition $definition) |
38
|
|
|
{ |
39
|
|
|
if (null !== $definition->getFactory()) { |
40
|
|
|
$instance = $this->createFromFactory($definition); |
41
|
|
|
$reflection = new \ReflectionObject($instance); |
42
|
|
|
} else { |
43
|
|
|
list($reflection, $instance) = $this->createFromClass($definition); |
44
|
|
|
|
45
|
|
|
} |
46
|
|
|
$this->invokeMethods($definition, $instance, $reflection); |
47
|
|
|
$this->invokeProperties($definition, $instance); |
48
|
|
|
|
49
|
|
|
return $instance; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
protected function createFromClass(Definition $definition) |
53
|
|
|
{ |
54
|
|
|
$class = $definition->getClass(); |
55
|
|
|
if ($class === null) { |
56
|
|
|
throw new ConfigException('You must set a class or factory for definition.'); |
57
|
|
|
} |
58
|
|
|
try { |
59
|
|
|
$reflection = new \ReflectionClass($definition->getClass()); |
60
|
|
|
} catch (\ReflectionException $e) { |
61
|
|
|
throw new ConfigException(sprintf('Class "%s" is invalid', $definition->getClass())); |
62
|
|
|
} |
63
|
|
|
if (!$reflection->isInstantiable()) { |
64
|
|
|
throw new ConfigException(sprintf('Can not instantiate "%s"', $definition->getClass())); |
65
|
|
|
} |
66
|
|
|
$constructor = $reflection->getConstructor(); |
67
|
|
|
if (is_null($constructor)) { |
68
|
|
|
$instance = $reflection->newInstanceWithoutConstructor(); |
69
|
|
|
} else { |
70
|
|
|
$arguments = $this->resolveFunctionArguments($constructor, $definition->getArguments()); |
71
|
|
|
$instance = $reflection->newInstanceArgs($arguments); |
72
|
|
|
} |
73
|
|
|
return [$reflection, $instance]; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
protected function createFromFactory(Definition $definition) |
77
|
|
|
{ |
78
|
|
|
$factory = $definition->getFactory(); |
79
|
|
|
|
80
|
|
|
try { |
81
|
|
|
|
82
|
|
|
$reflection = is_array($factory) |
83
|
|
|
? new \ReflectionMethod($factory[0], $factory[1]) |
84
|
|
|
: new \ReflectionFunction($factory); |
85
|
|
|
|
86
|
|
|
if ($reflection->getNumberOfParameters() > 0) { |
87
|
|
|
$arguments = $this->resolveFunctionArguments($reflection, $definition->getArguments()); |
88
|
|
|
} else { |
89
|
|
|
$arguments = []; |
90
|
|
|
} |
91
|
|
|
return call_user_func_array($factory, $arguments); |
92
|
|
|
|
93
|
|
|
} catch (\ReflectionException $exception) { |
94
|
|
|
throw new ConfigException('The factory is invalid.'); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
return $instance; |
|
|
|
|
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @param Definition $definition |
102
|
|
|
* @param object $instance |
103
|
|
|
* @param \ReflectionClass $reflection |
104
|
|
|
*/ |
105
|
|
|
protected function invokeMethods(Definition $definition, $instance, \ReflectionClass $reflection) |
106
|
|
|
{ |
107
|
|
|
foreach ($definition->getMethodCalls() as $method) { |
108
|
|
|
try { |
109
|
|
|
$reflectionMethod = $reflection->getMethod($method[0]); |
110
|
|
|
} catch (\ReflectionException $e) { |
111
|
|
|
throw new DependencyInjectionException(sprintf( |
112
|
|
|
'Class "%s" has no method "%s"', |
113
|
|
|
$definition->getClass(), |
114
|
|
|
$method[0] |
115
|
|
|
)); |
116
|
|
|
} |
117
|
|
|
$reflectionMethod->invokeArgs($instance, $this->resolveFunctionArguments( |
118
|
|
|
$reflectionMethod, |
119
|
|
|
$method[1] |
120
|
|
|
)); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @param Definition $definition |
126
|
|
|
* @param object $instance |
127
|
|
|
*/ |
128
|
|
|
protected function invokeProperties(Definition $definition, $instance) |
129
|
|
|
{ |
130
|
|
|
foreach ($definition->getProperties() as $propertyName => $propertyValue) { |
131
|
|
|
if (property_exists($instance, $propertyName)) { |
132
|
|
|
$instance->$propertyName = $propertyValue; |
133
|
|
|
} else { |
134
|
|
|
throw new DependencyInjectionException(sprintf( |
135
|
|
|
"Class '%s' has no property '%s'", |
136
|
|
|
$definition->getClass(), |
137
|
|
|
$propertyName |
138
|
|
|
)); |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Resolves all arguments for the function or method. |
145
|
|
|
* |
146
|
|
|
* @param \ReflectionFunctionAbstract $method |
147
|
|
|
* @param array $arguments |
148
|
|
|
* @throws DependencyInjectionException |
149
|
|
|
* @return array |
150
|
|
|
*/ |
151
|
|
|
public function resolveFunctionArguments( |
152
|
|
|
\ReflectionFunctionAbstract $method, |
153
|
|
|
array $arguments |
154
|
|
|
) { |
155
|
|
|
$functionArguments = []; |
156
|
|
|
$arguments = $this->resolveParameters($arguments); |
157
|
|
|
foreach ($method->getParameters() as $parameter) { |
158
|
|
|
//If the dependency is provided directly |
159
|
|
|
if (isset($arguments[$parameter->getPosition()])) { |
160
|
|
|
$functionArguments[] = $arguments[$parameter->getPosition()]; |
161
|
|
|
} elseif (isset($arguments[$parameter->name])) { |
162
|
|
|
$functionArguments[] = $arguments[$parameter->name]; |
163
|
|
|
} elseif (($dependency = $parameter->getClass()) != null) { |
164
|
|
|
$dependencyName = $dependency->name; |
165
|
|
|
try { |
166
|
|
|
$functionArguments[] = $this->container->get($dependencyName); |
167
|
|
|
} catch (NotFoundException $exception) { |
168
|
|
|
if ($parameter->isOptional()) { |
169
|
|
|
$functionArguments[] = $parameter->getDefaultValue(); |
170
|
|
|
} else { |
171
|
|
|
throw $exception; |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
} elseif ($parameter->isOptional()) { |
175
|
|
|
$functionArguments[] = $parameter->getDefaultValue(); |
176
|
|
|
} else { |
177
|
|
|
throw new DependencyInjectionException(sprintf( |
178
|
|
|
'Missing required parameter "%s" when calling "%s"', |
179
|
|
|
$parameter->name, |
180
|
|
|
$method->getName() |
181
|
|
|
)); |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
return $functionArguments; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Resolves array of parameters |
189
|
|
|
* @param array $parameters |
190
|
|
|
* @return array |
191
|
|
|
*/ |
192
|
|
|
protected function resolveParameters($parameters) |
193
|
|
|
{ |
194
|
|
|
return array_map(function($parameter) { |
195
|
|
|
if (is_array($parameter)) { |
196
|
|
|
return $this->resolveParameters($parameter); |
197
|
|
|
} else { |
198
|
|
|
return $this->resolveParameter($parameter); |
199
|
|
|
} |
200
|
|
|
}, $parameters); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Formats parameter value |
205
|
|
|
* |
206
|
|
|
* @param string|Reference $value |
207
|
|
|
* @return string |
208
|
|
|
* @throws DependencyInjectionException |
209
|
|
|
*/ |
210
|
|
|
protected function resolveParameter($value) |
211
|
|
|
{ |
212
|
|
|
//Reference |
213
|
|
|
if ($value instanceof Reference) { |
214
|
|
|
return $this->container->get($value->getId()); |
215
|
|
|
} |
216
|
|
|
if ('@' === $value[0]) { |
217
|
|
|
return $this->container->get(substr($value, 1)); |
218
|
|
|
} |
219
|
|
|
//"fool%bar%baz" |
220
|
|
|
return preg_replace_callback("#%([^%\s]+)%#", function ($matches) { |
221
|
|
|
$key = $matches[1]; |
222
|
|
|
if ($parameter = $this->container->getParameter($key)) { |
223
|
|
|
return $parameter; |
224
|
|
|
} |
225
|
|
|
throw new DependencyInjectionException(sprintf("Parameter [%s] is not defined", $key)); |
226
|
|
|
}, $value); |
227
|
|
|
} |
228
|
|
|
} |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.