1
|
|
|
<?php |
2
|
|
|
namespace EventEspresso\core\services\container; |
3
|
|
|
|
4
|
|
|
use UnexpectedValueException; |
5
|
|
|
|
6
|
|
|
if ( ! defined('EVENT_ESPRESSO_VERSION')) { |
7
|
|
|
exit('No direct script access allowed'); |
8
|
|
|
} |
9
|
|
|
|
10
|
|
|
|
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Class DependencyInjector |
14
|
|
|
* Responsible for recursively resolving and injecting dependencies |
15
|
|
|
* into the arguments array passed to a class upon instantiation. |
16
|
|
|
* Caches all Reflection objects generated so that this work isn't duplicated. |
17
|
|
|
* |
18
|
|
|
* @package Event Espresso |
19
|
|
|
* @author Brent Christensen |
20
|
|
|
* @since 4.9.1 |
21
|
|
|
*/ |
22
|
|
|
class DependencyInjector implements InjectorInterface |
23
|
|
|
{ |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var CoffeePotInterface $coffee_pot |
27
|
|
|
*/ |
28
|
|
|
private $coffee_pot; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var \EEH_Array $array_helper |
32
|
|
|
*/ |
33
|
|
|
private $array_helper; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var \ReflectionClass[] $reflectors |
37
|
|
|
*/ |
38
|
|
|
private $reflectors; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var \ReflectionMethod[] $constructors |
42
|
|
|
*/ |
43
|
|
|
private $constructors; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var \ReflectionParameter[] $parameters |
47
|
|
|
*/ |
48
|
|
|
private $parameters; |
49
|
|
|
|
50
|
|
|
|
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* DependencyInjector constructor |
54
|
|
|
* |
55
|
|
|
* @param CoffeePotInterface $coffee_pot |
56
|
|
|
* @param \EEH_Array $array_helper |
57
|
|
|
*/ |
58
|
|
|
public function __construct(CoffeePotInterface $coffee_pot, \EEH_Array $array_helper) |
59
|
|
|
{ |
60
|
|
|
$this->coffee_pot = $coffee_pot; |
61
|
|
|
$this->array_helper = $array_helper; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
|
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* getReflectionClass |
68
|
|
|
* checks if a ReflectionClass object has already been generated for a class |
69
|
|
|
* and returns that instead of creating a new one |
70
|
|
|
* |
71
|
|
|
* @param string $class_name |
72
|
|
|
* @return \ReflectionClass |
73
|
|
|
*/ |
74
|
|
View Code Duplication |
public function getReflectionClass($class_name) |
|
|
|
|
75
|
|
|
{ |
76
|
|
|
if ( |
77
|
|
|
! isset($this->reflectors[$class_name]) |
78
|
|
|
|| ! $this->reflectors[$class_name] instanceof \ReflectionClass |
79
|
|
|
) { |
80
|
|
|
$this->reflectors[$class_name] = new \ReflectionClass($class_name); |
81
|
|
|
} |
82
|
|
|
return $this->reflectors[$class_name]; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
|
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* getConstructor |
89
|
|
|
* checks if a ReflectionMethod object has already been generated for the class constructor |
90
|
|
|
* and returns that instead of creating a new one |
91
|
|
|
* |
92
|
|
|
* @param \ReflectionClass $reflector |
93
|
|
|
* @return \ReflectionMethod |
94
|
|
|
*/ |
95
|
|
|
protected function getConstructor(\ReflectionClass $reflector) |
96
|
|
|
{ |
97
|
|
|
if ( |
98
|
|
|
! isset($this->constructors[$reflector->getName()]) |
99
|
|
|
|| ! $this->constructors[$reflector->getName()] instanceof \ReflectionMethod |
100
|
|
|
) { |
101
|
|
|
$this->constructors[$reflector->getName()] = $reflector->getConstructor(); |
102
|
|
|
} |
103
|
|
|
return $this->constructors[$reflector->getName()]; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
|
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* getParameters |
110
|
|
|
* checks if an array of ReflectionParameter objects has already been generated for the class constructor |
111
|
|
|
* and returns that instead of creating a new one |
112
|
|
|
* |
113
|
|
|
* @param \ReflectionMethod $constructor |
114
|
|
|
* @return \ReflectionParameter[] |
115
|
|
|
*/ |
116
|
|
|
protected function getParameters(\ReflectionMethod $constructor) |
117
|
|
|
{ |
118
|
|
|
if ( ! isset($this->parameters[$constructor->class])) { |
119
|
|
|
$this->parameters[$constructor->class] = $constructor->getParameters(); |
120
|
|
|
} |
121
|
|
|
return $this->parameters[$constructor->class]; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
|
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* resolveDependencies |
128
|
|
|
* examines the constructor for the requested class to determine |
129
|
|
|
* if any dependencies exist, and if they can be injected. |
130
|
|
|
* If so, then those classes will be added to the array of arguments passed to the constructor |
131
|
|
|
* PLZ NOTE: this is achieved by type hinting the constructor params |
132
|
|
|
* For example: |
133
|
|
|
* if attempting to load a class "Foo" with the following constructor: |
134
|
|
|
* __construct( Bar $bar_class, Fighter $grohl_class ) |
135
|
|
|
* then $bar_class and $grohl_class will be added to the $arguments array, |
136
|
|
|
* but only IF they are NOT already present in the incoming arguments array, |
137
|
|
|
* and the correct classes can be loaded |
138
|
|
|
* |
139
|
|
|
* @param RecipeInterface $recipe |
140
|
|
|
* @param \ReflectionClass $reflector |
141
|
|
|
* @param array $arguments |
142
|
|
|
* @return array |
143
|
|
|
*/ |
144
|
|
|
public function resolveDependencies(RecipeInterface $recipe, \ReflectionClass $reflector, $arguments = array()) |
145
|
|
|
{ |
146
|
|
|
// if arguments array is numerically and sequentially indexed, then we want it to remain as is, |
147
|
|
|
// else wrap it in an additional array so that it doesn't get split into multiple parameters |
148
|
|
|
$arguments = $this->array_helper->is_array_numerically_and_sequentially_indexed($arguments) |
149
|
|
|
? $arguments |
150
|
|
|
: array($arguments); |
151
|
|
|
$resolved_parameters = array(); |
152
|
|
|
// let's examine the constructor |
153
|
|
|
// let's examine the constructor |
154
|
|
|
$constructor = $this->getConstructor($reflector); |
155
|
|
|
// whu? huh? nothing? |
156
|
|
|
if ( ! $constructor) { |
157
|
|
|
return $arguments; |
158
|
|
|
} |
159
|
|
|
// get constructor parameters |
160
|
|
|
$params = $this->getParameters($constructor); |
161
|
|
|
if (empty($params)) { |
162
|
|
|
return $resolved_parameters; |
163
|
|
|
} |
164
|
|
|
$ingredients = $recipe->ingredients(); |
165
|
|
|
// and the keys for the incoming arguments array so that we can compare existing arguments with what is expected |
166
|
|
|
$argument_keys = array_keys($arguments); |
167
|
|
|
// now loop thru all of the constructors expected parameters |
168
|
|
|
foreach ($params as $index => $param) { |
|
|
|
|
169
|
|
|
if ( ! $param instanceof \ReflectionParameter) { |
170
|
|
|
continue; |
171
|
|
|
} |
172
|
|
|
// is this a dependency for a specific class ? |
173
|
|
|
$param_class = $param->getClass() ? $param->getClass()->name : null; |
174
|
|
|
if ( |
175
|
|
|
// param is specified in the list of ingredients for this Recipe |
176
|
|
|
isset($ingredients[$param_class]) |
177
|
|
|
) { |
178
|
|
|
// attempt to inject the dependency |
179
|
|
|
$resolved_parameters[$index] = $this->injectDependency($ingredients[$param_class]); |
180
|
|
|
} else if ( |
181
|
|
|
// param is not even a class |
182
|
|
|
empty($param_class) |
183
|
|
|
// and something already exists in the incoming arguments for this param |
184
|
|
|
&& isset($argument_keys[$index], $arguments[$argument_keys[$index]]) |
185
|
|
|
) { |
186
|
|
|
// add parameter from incoming arguments |
187
|
|
|
$resolved_parameters[$index] = $arguments[$argument_keys[$index]]; |
188
|
|
|
} else if ( |
189
|
|
|
// parameter is type hinted as a class, exists as an incoming argument, AND it's the correct class |
190
|
|
|
! empty($param_class) |
191
|
|
|
&& isset($argument_keys[$index], $arguments[$argument_keys[$index]]) |
192
|
|
|
&& $arguments[$argument_keys[$index]] instanceof $param_class |
193
|
|
|
) { |
194
|
|
|
// add parameter from incoming arguments |
195
|
|
|
$resolved_parameters[$index] = $arguments[$argument_keys[$index]]; |
196
|
|
|
} else if ( |
197
|
|
|
// parameter is type hinted as a class, and should be injected |
198
|
|
|
! empty($param_class) |
199
|
|
|
) { |
200
|
|
|
// attempt to inject the dependency |
201
|
|
|
$resolved_parameters[$index] = $this->injectDependency($param_class); |
202
|
|
|
} else if ($param->isOptional()) { |
203
|
|
|
$resolved_parameters[$index] = $param->getDefaultValue(); |
204
|
|
|
} else { |
205
|
|
|
$resolved_parameters[$index] = null; |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
return $resolved_parameters; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
|
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @param string $param_class |
215
|
|
|
* @return mixed |
216
|
|
|
*/ |
217
|
|
|
private function injectDependency($param_class) |
218
|
|
|
{ |
219
|
|
|
$dependency = $this->coffee_pot->brew($param_class); |
220
|
|
|
if ( ! $dependency instanceof $param_class) { |
221
|
|
|
throw new UnexpectedValueException( |
222
|
|
|
sprintf( |
223
|
|
|
__( |
224
|
|
|
'Could not resolve dependency for "%1$s" for the "%2$s" class constructor.', |
225
|
|
|
'event_espresso' |
226
|
|
|
), |
227
|
|
|
$param_class |
228
|
|
|
) |
229
|
|
|
); |
230
|
|
|
} |
231
|
|
|
return $dependency; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
} |
235
|
|
|
// End of file DependencyInjector.php |
236
|
|
|
// Location: /DependencyInjector.php |
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.