1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace Spires\Container; |
5
|
|
|
|
6
|
|
|
use Closure; |
7
|
|
|
use ArrayAccess; |
8
|
|
|
use ReflectionClass; |
9
|
|
|
use ReflectionMethod; |
10
|
|
|
use ReflectionFunction; |
11
|
|
|
use ReflectionParameter; |
12
|
|
|
use Spires\Contracts\Container\Container as ContainerContract; |
13
|
|
|
use Spires\Contracts\Container\BindingResolutionException; |
14
|
|
|
|
15
|
|
|
class Container implements ArrayAccess, ContainerContract |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* The current globally available container (if any). |
19
|
|
|
* |
20
|
|
|
* @var static |
21
|
|
|
*/ |
22
|
|
|
protected static $instance; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* The container's bindings. |
26
|
|
|
* |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
protected $bindings = []; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* The container's shared instances. |
33
|
|
|
* |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
protected $instances = []; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* The stack of concretions currently being built. |
40
|
|
|
* |
41
|
|
|
* @var array |
42
|
|
|
*/ |
43
|
|
|
protected $buildStack = []; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Register a binding with the container. |
47
|
|
|
* |
48
|
|
|
* @param string $abstract |
49
|
|
|
* @param \Closure|string|null $concrete |
50
|
|
|
* @param bool $shared |
51
|
|
|
* @return void |
52
|
|
|
*/ |
53
|
|
|
public function bind(string $abstract, $concrete = null, bool $shared = false) |
54
|
|
|
{ |
55
|
|
|
$abstract = $this->normalize($abstract); |
56
|
|
|
|
57
|
|
|
$concrete = $this->normalize($concrete); |
58
|
|
|
|
59
|
|
|
// If no concrete type was given, we will simply set the concrete type to the |
60
|
|
|
// abstract type. After that, the concrete type to be registered as shared |
61
|
|
|
// without being forced to state their classes in both of the parameters. |
62
|
|
|
$this->dropStaleInstances($abstract); |
63
|
|
|
|
64
|
|
|
if (is_null($concrete)) { |
65
|
|
|
$concrete = $abstract; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
// If the factory is not a Closure, it means it is just a class name which is |
69
|
|
|
// bound into this container to the abstract type and we will just wrap it |
70
|
|
|
// up inside its own Closure to give us more convenience when extending. |
71
|
|
|
if (!$concrete instanceof Closure) { |
72
|
|
|
$concrete = $this->getClosure($abstract, $concrete); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
$this->bindings[$abstract] = compact('concrete', 'shared'); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Register a shared binding in the container. |
80
|
|
|
* |
81
|
|
|
* @param string|array $abstract |
82
|
|
|
* @param \Closure|string|null $concrete |
83
|
|
|
* @return void |
84
|
|
|
*/ |
85
|
|
|
public function singleton(string $abstract, $concrete = null) |
86
|
|
|
{ |
87
|
|
|
$this->bind($abstract, $concrete, true); |
|
|
|
|
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Register an existing instance as shared in the container. |
92
|
|
|
* |
93
|
|
|
* @param string $abstract |
94
|
|
|
* @param mixed $instance |
95
|
|
|
* @return void |
96
|
|
|
*/ |
97
|
|
|
public function instance(string $abstract, $instance) |
98
|
|
|
{ |
99
|
|
|
$abstract = $this->normalize($abstract); |
100
|
|
|
|
101
|
|
|
$this->instances[$abstract] = $instance; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Determine if the given abstract type has been bound. |
106
|
|
|
* |
107
|
|
|
* @param string $abstract |
108
|
|
|
* @return bool |
109
|
|
|
*/ |
110
|
|
|
public function bound(string $abstract) |
111
|
|
|
{ |
112
|
|
|
$abstract = $this->normalize($abstract); |
113
|
|
|
|
114
|
|
|
return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Resolve the given type from the container. |
119
|
|
|
* |
120
|
|
|
* @param string $abstract |
121
|
|
|
* @param array $parameters |
122
|
|
|
* @return mixed |
123
|
|
|
*/ |
124
|
|
|
public function make(string $abstract, array $parameters = []) |
125
|
|
|
{ |
126
|
|
|
$abstract = $this->normalize($abstract); |
127
|
|
|
|
128
|
|
|
// If an instance of the type is currently being managed as a singleton we'll |
129
|
|
|
// just return an existing instance instead of instantiating new instances |
130
|
|
|
// so the developer can keep using the same objects instance every time. |
131
|
|
|
if (isset($this->instances[$abstract])) { |
132
|
|
|
return $this->instances[$abstract]; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$concrete = $this->getConcrete($abstract); |
136
|
|
|
|
137
|
|
|
// We're ready to instantiate an instance of the concrete type registered for |
138
|
|
|
// the binding. This will instantiate the types, as well as resolve any of |
139
|
|
|
// its "nested" dependencies recursively until all have gotten resolved. |
140
|
|
|
if ($this->isBuildable($concrete, $abstract)) { |
141
|
|
|
$object = $this->build($concrete, $parameters); |
142
|
|
|
} else { |
143
|
|
|
$object = $this->make($concrete, $parameters); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
// If the requested type is registered as a singleton we'll want to cache off |
147
|
|
|
// the instances in "memory" so we can return it later without creating an |
148
|
|
|
// entirely new instance of an object on each subsequent request for it. |
149
|
|
|
if ($this->isShared($abstract)) { |
150
|
|
|
$this->instances[$abstract] = $object; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return $object; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Instantiate a concrete instance of the given type. |
158
|
|
|
* |
159
|
|
|
* @param \Closure|string $concrete |
160
|
|
|
* @param array $parameters |
161
|
|
|
* @return mixed |
162
|
|
|
* |
163
|
|
|
* @throws \Spires\Contracts\Container\BindingResolutionException |
164
|
|
|
*/ |
165
|
|
|
public function build($concrete, array $parameters = []) |
166
|
|
|
{ |
167
|
|
|
// If the concrete type is actually a Closure, we will just execute it and |
168
|
|
|
// hand back the results of the functions, which allows functions to be |
169
|
|
|
// used as resolvers for more fine-tuned resolution of these objects. |
170
|
|
|
if ($concrete instanceof Closure) { |
171
|
|
|
return $concrete($this, $parameters); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
$reflector = new ReflectionClass($concrete); |
175
|
|
|
|
176
|
|
|
// If the type is not instantiable, the developer is attempting to resolve |
177
|
|
|
// an abstract type such as an Interface of Abstract Class and there is |
178
|
|
|
// no binding registered for the abstractions so we need to bail out. |
179
|
|
|
if (!$reflector->isInstantiable()) { |
180
|
|
|
if (!empty($this->buildStack)) { |
181
|
|
|
$previous = implode(', ', $this->buildStack); |
182
|
|
|
|
183
|
|
|
$message = "Target [$concrete] is not instantiable while building [$previous]."; |
184
|
|
|
} else { |
185
|
|
|
$message = "Target [$concrete] is not instantiable."; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
throw new BindingResolutionException($message); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
$this->buildStack[] = $concrete; |
192
|
|
|
|
193
|
|
|
$constructor = $reflector->getConstructor(); |
194
|
|
|
|
195
|
|
|
// If there are no constructors, that means there are no dependencies then |
196
|
|
|
// we can just resolve the instances of the objects right away, without |
197
|
|
|
// resolving any other types or dependencies out of these containers. |
198
|
|
|
if (is_null($constructor)) { |
199
|
|
|
array_pop($this->buildStack); |
200
|
|
|
|
201
|
|
|
return new $concrete; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
$dependencies = $constructor->getParameters(); |
205
|
|
|
|
206
|
|
|
// Once we have all the constructor's parameters we can create each of the |
207
|
|
|
// dependency instances and then use the reflection instances to make a |
208
|
|
|
// new instance of this class, injecting the created dependencies in. |
209
|
|
|
$parameters = $this->keyParametersByArgument( |
210
|
|
|
$dependencies, |
211
|
|
|
$parameters |
212
|
|
|
); |
213
|
|
|
|
214
|
|
|
$instances = $this->getDependencies( |
215
|
|
|
$dependencies, |
216
|
|
|
$parameters |
217
|
|
|
); |
218
|
|
|
|
219
|
|
|
array_pop($this->buildStack); |
220
|
|
|
|
221
|
|
|
return $reflector->newInstanceArgs($instances); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Call the given Closure / [object, method] and inject its dependencies. |
226
|
|
|
* |
227
|
|
|
* @param callable|array $callable |
228
|
|
|
* @param array $parameters |
229
|
|
|
* @return mixed |
230
|
|
|
*/ |
231
|
|
|
public function call($callable, array $parameters = []) |
232
|
|
|
{ |
233
|
|
|
$injected = $this->getInjectedMethodParameters($callable, $parameters); |
234
|
|
|
|
235
|
|
|
return call_user_func_array($callable, $injected); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Set the globally available instance of the container. |
240
|
|
|
* |
241
|
|
|
* @return static |
242
|
|
|
*/ |
243
|
|
|
public static function getInstance() |
244
|
|
|
{ |
245
|
|
|
return static::$instance; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Set the shared instance of the container. |
250
|
|
|
* |
251
|
|
|
* @param \Spires\Contracts\Container\Container $container |
252
|
|
|
* @return void |
253
|
|
|
*/ |
254
|
|
|
public static function setInstance(ContainerContract $container) |
255
|
|
|
{ |
256
|
|
|
static::$instance = $container; |
|
|
|
|
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Normalize the given class name by removing leading slashes. |
261
|
|
|
* |
262
|
|
|
* @param mixed $service |
263
|
|
|
* @return mixed |
264
|
|
|
*/ |
265
|
|
|
protected function normalize($service) |
266
|
|
|
{ |
267
|
|
|
return is_string($service) ? ltrim($service, '\\') : $service; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Drop all of the stale instances and aliases. |
272
|
|
|
* |
273
|
|
|
* @param string $abstract |
274
|
|
|
* @return void |
275
|
|
|
*/ |
276
|
|
|
protected function dropStaleInstances(string $abstract) |
277
|
|
|
{ |
278
|
|
|
unset($this->instances[$abstract]); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Get the Closure to be used when building a type. |
283
|
|
|
* |
284
|
|
|
* @param string $abstract |
285
|
|
|
* @param string $concrete |
286
|
|
|
* @return \Closure |
287
|
|
|
*/ |
288
|
|
|
protected function getClosure(string $abstract, string $concrete) |
289
|
|
|
{ |
290
|
|
|
return function ($c, $parameters = []) use ($abstract, $concrete) { |
291
|
|
|
$method = ($abstract == $concrete) ? 'build' : 'make'; |
292
|
|
|
|
293
|
|
|
return $c->$method($concrete, $parameters); |
294
|
|
|
}; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Get the concrete type for a given abstract. |
299
|
|
|
* |
300
|
|
|
* @param string $abstract |
301
|
|
|
* @return mixed $concrete |
302
|
|
|
*/ |
303
|
|
|
protected function getConcrete(string $abstract) |
304
|
|
|
{ |
305
|
|
|
// If we don't have a registered resolver or concrete for the type, we'll just |
306
|
|
|
// assume each type is a concrete name and will attempt to resolve it as is |
307
|
|
|
// since the container should be able to resolve concretes automatically. |
308
|
|
|
if (!isset($this->bindings[$abstract])) { |
309
|
|
|
return $abstract; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
return $this->bindings[$abstract]['concrete']; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Determine if the given concrete is buildable. |
317
|
|
|
* |
318
|
|
|
* @param mixed $concrete |
319
|
|
|
* @param string $abstract |
320
|
|
|
* @return bool |
321
|
|
|
*/ |
322
|
|
|
protected function isBuildable($concrete, string $abstract) |
323
|
|
|
{ |
324
|
|
|
return $concrete === $abstract || $concrete instanceof Closure; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Determine if a given type is shared. |
329
|
|
|
* |
330
|
|
|
* @param string $abstract |
331
|
|
|
* @return bool |
332
|
|
|
*/ |
333
|
|
|
protected function isShared(string $abstract) |
334
|
|
|
{ |
335
|
|
|
$abstract = $this->normalize($abstract); |
336
|
|
|
|
337
|
|
|
if (isset($this->instances[$abstract])) { |
338
|
|
|
return true; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
if (!isset($this->bindings[$abstract]['shared'])) { |
342
|
|
|
return false; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
return $this->bindings[$abstract]['shared'] === true; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* If extra parameters are passed by numeric ID, rekey them by argument name. |
350
|
|
|
* |
351
|
|
|
* @param array $dependencies |
352
|
|
|
* @param array $parameters |
353
|
|
|
* @return array |
354
|
|
|
*/ |
355
|
|
|
protected function keyParametersByArgument(array $dependencies, array $parameters) |
356
|
|
|
{ |
357
|
|
|
foreach ($parameters as $key => $value) { |
358
|
|
|
if (is_numeric($key)) { |
359
|
|
|
unset($parameters[$key]); |
360
|
|
|
|
361
|
|
|
$parameters[$dependencies[$key]->name] = $value; |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
return $parameters; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* Resolve all of the dependencies from the ReflectionParameters. |
370
|
|
|
* |
371
|
|
|
* @param array $parameters |
372
|
|
|
* @param array $primitives |
373
|
|
|
* @return array |
374
|
|
|
*/ |
375
|
|
|
protected function getDependencies(array $parameters, array $primitives = []) |
376
|
|
|
{ |
377
|
|
|
$dependencies = []; |
378
|
|
|
|
379
|
|
|
foreach ($parameters as $parameter) { |
380
|
|
|
$dependency = $parameter->getClass(); |
381
|
|
|
|
382
|
|
|
// If the class is null, it means the dependency is a string or some other |
383
|
|
|
// primitive type which we can not resolve since it is not a class and |
384
|
|
|
// we will just bomb out with an error since we have no-where to go. |
385
|
|
|
if (array_key_exists($parameter->name, $primitives)) { |
386
|
|
|
$dependencies[] = $primitives[$parameter->name]; |
387
|
|
|
} elseif (is_null($dependency)) { |
388
|
|
|
$dependencies[] = $this->resolveNonClass($parameter); |
389
|
|
|
} else { |
390
|
|
|
$dependencies[] = $this->resolveClass($parameter); |
391
|
|
|
} |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
return $dependencies; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Resolve a non-class hinted dependency. |
399
|
|
|
* |
400
|
|
|
* @param \ReflectionParameter $parameter |
401
|
|
|
* @return mixed |
402
|
|
|
* |
403
|
|
|
* @throws \Spires\Contracts\Container\BindingResolutionException |
404
|
|
|
*/ |
405
|
|
|
protected function resolveNonClass(ReflectionParameter $parameter) |
406
|
|
|
{ |
407
|
|
|
if ($parameter->isDefaultValueAvailable()) { |
408
|
|
|
return $parameter->getDefaultValue(); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
$message = "Unresolvable dependency resolving [$parameter] " . |
412
|
|
|
"in class {$parameter->getDeclaringClass()->getName()}"; |
413
|
|
|
|
414
|
|
|
throw new BindingResolutionException($message); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Resolve a class based dependency from the container. |
419
|
|
|
* |
420
|
|
|
* @param \ReflectionParameter $parameter |
421
|
|
|
* @return mixed |
422
|
|
|
* |
423
|
|
|
* @throws \Spires\Contracts\Container\BindingResolutionException |
424
|
|
|
*/ |
425
|
|
|
protected function resolveClass(ReflectionParameter $parameter) |
426
|
|
|
{ |
427
|
|
|
try { |
428
|
|
|
return $this->make($parameter->getClass()->name); |
429
|
|
|
} catch (BindingResolutionException $e) { |
430
|
|
|
// If we can not resolve the class instance, we will check to see if the value |
431
|
|
|
// is optional, and if it is we will return the optional parameter value as |
432
|
|
|
// the value of the dependency, similarly to how we do this with scalars. |
433
|
|
|
if ($parameter->isOptional()) { |
434
|
|
|
return $parameter->getDefaultValue(); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
throw $e; |
438
|
|
|
} |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* Get all dependencies for a given method. |
443
|
|
|
* |
444
|
|
|
* @param callable|array $callable |
445
|
|
|
* @param array $parameters |
446
|
|
|
* @return array |
447
|
|
|
*/ |
448
|
|
|
protected function getInjectedMethodParameters($callable, array $parameters = []) |
449
|
|
|
{ |
450
|
|
|
$injected = []; |
451
|
|
|
|
452
|
|
|
foreach ($this->getCallReflector($callable)->getParameters() as $parameter) { |
453
|
|
|
$injected[$parameter->name] = $this->addDependencyForCallParameter($parameter, $parameters); |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
return $injected; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Get the proper reflection instance for the given callback. |
461
|
|
|
* |
462
|
|
|
* @param callable|array $callable |
463
|
|
|
* @return \ReflectionFunctionAbstract |
464
|
|
|
*/ |
465
|
|
|
protected function getCallReflector($callable) |
466
|
|
|
{ |
467
|
|
|
if (is_array($callable)) { |
468
|
|
|
return new ReflectionMethod($callable[0], $callable[1]); |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
return new ReflectionFunction($callable); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
/** |
475
|
|
|
* Get the dependency for the given call parameter. |
476
|
|
|
* |
477
|
|
|
* @param \ReflectionParameter $parameter |
478
|
|
|
* @param array $parameters |
479
|
|
|
* @return mixed |
480
|
|
|
*/ |
481
|
|
|
protected function addDependencyForCallParameter(ReflectionParameter $parameter, array &$parameters) |
482
|
|
|
{ |
483
|
|
|
if (array_key_exists($parameter->name, $parameters)) { |
484
|
|
|
$value = $parameters[$parameter->name]; |
485
|
|
|
unset($parameters[$parameter->name]); |
486
|
|
|
return $value; |
487
|
|
|
} elseif ($parameter->getClass()) { |
488
|
|
|
return $this->make($parameter->getClass()->name); |
489
|
|
|
} elseif ($parameter->isDefaultValueAvailable()) { |
490
|
|
|
return $parameter->getDefaultValue(); |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
return array_shift($parameters); |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Determine if a given offset exists. |
498
|
|
|
* |
499
|
|
|
* @param string $key |
500
|
|
|
* @return bool |
501
|
|
|
*/ |
502
|
|
|
public function offsetExists($key) |
503
|
|
|
{ |
504
|
|
|
return $this->bound($key); |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Get the value at a given offset. |
509
|
|
|
* |
510
|
|
|
* @param string $key |
511
|
|
|
* @return mixed |
512
|
|
|
*/ |
513
|
|
|
public function offsetGet($key) |
514
|
|
|
{ |
515
|
|
|
return $this->make($key); |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
/** |
519
|
|
|
* Set the value at a given offset. |
520
|
|
|
* |
521
|
|
|
* @param string $key |
522
|
|
|
* @param mixed $value |
523
|
|
|
* @return void |
524
|
|
|
*/ |
525
|
|
|
public function offsetSet($key, $value) |
526
|
|
|
{ |
527
|
|
|
// If the value is not a Closure, we will make it one. This simply gives |
528
|
|
|
// more "drop-in" replacement functionality for the Pimple which this |
529
|
|
|
// container's simplest functions are base modeled and built after. |
530
|
|
|
if (!$value instanceof Closure) { |
531
|
|
|
$value = function () use ($value) { |
532
|
|
|
return $value; |
533
|
|
|
}; |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
$this->bind($key, $value); |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
/** |
540
|
|
|
* Unset the value at a given offset. |
541
|
|
|
* |
542
|
|
|
* @param string $key |
543
|
|
|
* @return void |
544
|
|
|
*/ |
545
|
|
|
public function offsetUnset($key) |
546
|
|
|
{ |
547
|
|
|
$key = $this->normalize($key); |
548
|
|
|
|
549
|
|
|
unset($this->bindings[$key], $this->instances[$key]); |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
/** |
553
|
|
|
* Dynamically access container services. |
554
|
|
|
* |
555
|
|
|
* @param string $key |
556
|
|
|
* @return mixed |
557
|
|
|
*/ |
558
|
|
|
public function __get($key) |
559
|
|
|
{ |
560
|
|
|
return $this[$key]; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* Dynamically set container services. |
565
|
|
|
* |
566
|
|
|
* @param string $key |
567
|
|
|
* @param mixed $value |
568
|
|
|
* @return void |
569
|
|
|
*/ |
570
|
|
|
public function __set($key, $value) |
571
|
|
|
{ |
572
|
|
|
$this[$key] = $value; |
573
|
|
|
} |
574
|
|
|
} |
575
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.