Completed
Push — master ( fa80df...2a61b3 )
by Changwan
03:32
created

Container::applyWire()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 15
ccs 12
cts 12
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace Wandu\DI;
3
4
use Closure;
5
use Doctrine\Common\Annotations\Reader;
6
use Interop\Container\ContainerInterface as InteropContainerInterface;
7
use ReflectionClass;
8
use ReflectionException;
9
use ReflectionFunctionAbstract;
10
use ReflectionObject;
11
use ReflectionProperty;
12
use Wandu\DI\Annotations\AutoWired;
13
use Wandu\DI\Containee\BindContainee;
14
use Wandu\DI\Containee\ClosureContainee;
15
use Wandu\DI\Containee\InstanceContainee;
16
use Wandu\DI\Exception\CannotChangeException;
17
use Wandu\DI\Exception\CannotFindParameterException;
18
use Wandu\DI\Exception\CannotResolveException;
19
use Wandu\DI\Exception\NullReferenceException;
20
use Wandu\Reflection\ReflectionCallable;
21
22
class Container implements ContainerInterface
23
{
24
    /** @var \Wandu\DI\Containee\ContaineeAbstract[] */
25
    protected $containees = [];
26
    
27
    /** @var \Wandu\DI\ServiceProviderInterface[] */
28
    protected $providers = [];
29
30
    /** @var array */
31
    protected $extenders = [];
32
    
33
    /** @var array */
34
    protected $indexOfAliases = [];
35
    
36
    /** @var bool */
37
    protected $isBooted = false;
38
39 54
    public function __construct()
40
    {
41 54
        $this->instance(Container::class, $this)->freeze();
42 54
        $this->instance(ContainerInterface::class, $this)->freeze();
43 54
        $this->instance(InteropContainerInterface::class, $this)->freeze();
44 54
        $this->instance('container', $this)->freeze();
45 54
    }
46
47 5
    public function __clone()
48
    {
49
        // direct remove instance because of frozen
50
        unset(
51 5
            $this->containees[Container::class],
52 5
            $this->containees[ContainerInterface::class],
53 5
            $this->containees[InteropContainerInterface::class],
54 5
            $this->containees['container']
55
        );
56 5
        $this->instance(Container::class, $this)->freeze();
57 5
        $this->instance(ContainerInterface::class, $this)->freeze();
58 5
        $this->instance(InteropContainerInterface::class, $this)->freeze();
59 5
        $this->instance('container', $this)->freeze();
60 5
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function __call($name, array $arguments)
66
    {
67
        return $this->call($this->get($name), $arguments);
0 ignored issues
show
Documentation introduced by
$this->get($name) is of type *, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 2
    public function offsetExists($name)
74
    {
75 2
        return $this->has($name) && $this->get($name) !== null;
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 16
    public function offsetGet($name)
82
    {
83 16
        return $this->get($name);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 8
    public function offsetSet($name, $value)
90
    {
91 8
        $this->set($name, $value);
92 8
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 1
    public function offsetUnset($name)
98
    {
99 1
        $this->destroy($name);
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 14
    public function has($name)
106
    {
107 14
        return array_key_exists($name, $this->containees) || class_exists($name);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 54
    public function destroy(...$names)
114
    {
115 54
        foreach ($names as $name) {
116 54
            if (array_key_exists($name, $this->containees)) {
117 4
                if ($this->containees[$name]->isFrozen()) {
118 2
                    throw new CannotChangeException($name);
119
                }
120 4
            }
121 54
            unset($this->containees[$name]);
122 54
        }
123 54
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128 39
    public function containee($name)
129
    {
130 39
        if (!array_key_exists($name, $this->containees)) {
131 7
            if (class_exists($name)) {
132 6
                $this->bind($name);
133 6
            } else {
134 1
                throw new NullReferenceException($name);
135
            }
136 6
        }
137 38
        return $this->containees[$name];
138
    }
139
    
140
    /**
141
     * @param string $name
142
     * @return mixed
143
     */
144 39
    public function getRawItem($name)
145
    {
146 39
        return $this->containee($name)->get($this);
147
    }
148
    
149
    /**
150
     * {@inheritdoc}
151
     */
152 39
    public function get($name)
153
    {
154 39
        $instance = $this->getRawItem($name);
155 37
        if ($this->containees[$name]->isWireEnabled()) {
156 1
            $this->applyWire($instance);
157 1
        }
158
159 37
        foreach ($this->getExtenders($name) as $extender) {
160 4
            $instance = $extender->__invoke($instance);
161 37
        }
162 37
        return $instance;
163
    }
164
165
    /**
166
     * @param string $name
167
     * @return \Closure[]
168
     */
169 37
    protected function getExtenders($name)
170
    {
171 37
        $extenders = [];
172 37
        if (isset($this->extenders[$name])) {
173 4
            $extenders = array_merge($extenders, $this->extenders[$name]);
174 4
        }
175
176
        // extend propagation
177 37
        if (isset($this->indexOfAliases[$name])) {
178 16
            foreach ($this->indexOfAliases[$name] as $aliasName) {
179 16
                $extenders = array_merge($extenders, $this->getExtenders($aliasName));
180 16
            }
181 16
        }
182 37
        return $extenders;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188 8
    public function set($name, $value)
189
    {
190 8
        if (!($value instanceof ContaineeInterface)) {
191 7
            $value = new InstanceContainee($value);
192 7
        }
193 8
        return $this->addContainee($name, $value);
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 54
    public function instance($name, $value)
200
    {
201 54
        return $this->addContainee($name, new InstanceContainee($value));
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 25
    public function closure($name, callable $handler)
208
    {
209 25
        return $this->addContainee($name, new ClosureContainee($handler));
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215 21
    public function alias($name, $origin)
216
    {
217 21
        if (!array_key_exists($origin, $this->indexOfAliases)) {
218 21
            $this->indexOfAliases[$origin] = [];
219 21
        }
220 21
        $this->indexOfAliases[$origin][] = $name;
221 21
        return $this->closure($name, function (ContainerInterface $container) use ($origin) {
0 ignored issues
show
Bug introduced by
It seems like $name defined by parameter $name on line 215 can also be of type array; however, Wandu\DI\Container::closure() does only seem to accept string, maybe add an additional type check?

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.

Loading history...
222 10
            return $container->get($origin); // proxy
223 21
        })->factory(true);
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 15
    public function bind($name, $class = null)
230
    {
231 15
        if (isset($class)) {
232 10
            $this->alias($class, $name);
233 10
            return $this->addContainee($name, new BindContainee($class));
234
        }
235 6
        return $this->addContainee($name, new BindContainee($name));
236
    }
237
    
238
    /**
239
     * @param string $name
240
     * @param \Wandu\DI\ContaineeInterface $containee
241
     * @return \Wandu\DI\ContaineeInterface
242
     */
243 54
    public function addContainee($name, ContaineeInterface $containee)
244
    {
245 54
        $this->destroy($name);
246 54
        return $this->containees[$name] = $containee;
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252 5
    public function extend($name, Closure $handler)
253
    {
254 5
        if (!array_key_exists($name, $this->extenders)) {
255 5
            $this->extenders[$name] = [];
256 5
        }
257 5
        $this->extenders[$name][] = $handler;
258 5
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 7
    public function register(ServiceProviderInterface $provider)
264
    {
265 7
        $provider->register($this);
266 7
        $this->providers[] = $provider;
267 7
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272 1
    public function boot()
273
    {
274 1
        if (!$this->isBooted) {
275 1
            foreach ($this->providers as $provider) {
276 1
                $provider->boot($this);
277 1
            }
278 1
            $this->isBooted = true;
279 1
        }
280 1
        return $this;
281
    }
282
    
283
    /**
284
     * {@inheritdoc}
285
     */
286
    public function freeze($name)
287
    {
288
        $this->containees[$name]->freeze();
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 5
    public function with(array $arguments = [])
295
    {
296 5
        $new = clone $this;
297 5
        foreach ($arguments as $name => $argument) {
298 2
            if ($argument instanceof ContaineeInterface) {
299
                $new->addContainee($name, $argument);
300
            } else {
301 2
                $new->instance($name, $argument);
302
            }
303 5
        }
304 5
        return $new;
305
    }
306
    
307
    /**
308
     * {@inheritdoc}
309
     */
310 17
    public function create($class, array $arguments = [])
311
    {
312 17
        $reflectionClass = new ReflectionClass($class);
313 17
        $reflectionMethod = $reflectionClass->getConstructor();
314 17
        if (!$reflectionMethod) {
315 13
            return $reflectionClass->newInstance();
316
        }
317
        try {
318 7
            $parameters = $this->getParameters($reflectionMethod, $arguments);
319 7
        } catch (CannotFindParameterException $e) {
320 5
            throw new CannotResolveException($class, $e->getParameter());
321
        }
322 6
        return $reflectionClass->newInstanceArgs($parameters);
323
    }
324
325
    /**
326
     * {@inheritdoc}
327
     */
328 8
    public function call(callable $callee, array $arguments = [])
329
    {
330
        try {
331 8
            return call_user_func_array(
332 8
                $callee,
333 8
                $this->getParameters(new ReflectionCallable($callee), $arguments)
334 8
            );
335 4
        } catch (CannotFindParameterException $e) {
336 4
            throw new CannotResolveException(null, $e->getParameter());
337
        }
338
    }
339
340
    /**
341
     * {@inheritdoc}
342
     */
343 2
    public function inject($object, array $properties = [])
344
    {
345 2
        $reflectionObject = new ReflectionObject($object);
346 2
        foreach ($properties as $property => $value) {
347 2
            $this->injectProperty($reflectionObject->getProperty($property), $object, $value);
348 2
        }
349 2
    }
350
351
    /**
352
     * @param \ReflectionProperty $property
353
     * @param object $object
354
     * @param mixed $target
355
     */
356 2
    private function injectProperty(ReflectionProperty $property, $object, $target)
357
    {
358 2
        $property->setAccessible(true);
359 2
        $property->setValue($object, $target);
360 2
    }
361
362
    /**
363
     * @param \ReflectionFunctionAbstract $reflectionFunction
364
     * @param array $arguments
365
     * @return array
366
     */
367 13
    protected function getParameters(ReflectionFunctionAbstract $reflectionFunction, array $arguments = [])
368
    {
369 13
        $parametersToReturn = static::getSeqArray($arguments);
370
371 13
        $reflectionParameters = array_slice($reflectionFunction->getParameters(), count($parametersToReturn));
372 13
        if (!count($reflectionParameters)) {
373 6
            return $parametersToReturn; 
374
        }
375
        
376
        try {
377
            /* @var \ReflectionParameter $param */
378 10
            foreach ($reflectionParameters as $param) {
379
                /*
380
                 * #1. search in arguments by parameter name
381
                 * #2. if parameter has type hint
382
                 * #2.1. search in container by class name
383
                 * #3. if has default value, insert default value.
384
                 * #4. exception
385
                 */
386 10
                $paramName = $param->getName();
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
387 10
                if (array_key_exists($paramName, $arguments)) { // #1.
388 4
                    $parametersToReturn[] = $arguments[$paramName];
389 4
                    continue;
390
                }
391 10
                $paramClass = $param->getClass();
392 10
                if ($paramClass) { // #2.
393 8
                    $paramClassName = $paramClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $paramClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
394 8
                    if ($this->has($paramClassName)) { // #2.1.
395 8
                        $parametersToReturn[] = $this->get($paramClassName);
396 7
                        continue;
397
                    }
398 6
                }
399 8
                if ($param->isDefaultValueAvailable()) { // #3.
400 4
                    $parametersToReturn[] = $param->getDefaultValue();
401 4
                    continue;
402
                }
403 7
                throw new CannotFindParameterException($paramName); // #4.
404 9
            }
405 10
        } catch (ReflectionException $e) {
406 1
            throw new CannotFindParameterException($paramName);
407
        }
408 9
        return $parametersToReturn;
409
    }
410
411
    /**
412
     * @param array $array
413
     * @return array
414
     */
415 13
    protected static function getSeqArray(array $array)
416
    {
417 13
        $arrayToReturn = [];
418 13
        foreach ($array as $key => $item) {
419 5
            if (is_int($key)) {
420 5
                $arrayToReturn[] = $item;
421 5
            }
422 13
        }
423 13
        return $arrayToReturn;
424
    }
425
    
426 1
    protected function applyWire($instance)
427
    {
428
        /* @var \Doctrine\Common\Annotations\Reader $reader */
429 1
        $reader = $this->get(Reader::class);
430 1
        class_exists(AutoWired::class);
431 1
        $reflObject = new ReflectionObject($instance);
432 1
        foreach ($reflObject->getProperties() as $reflProperty) {
433
            /* @var \Wandu\DI\Annotations\AutoWired $autoWired */
434 1
            if ($autoWired = $reader->getPropertyAnnotation($reflProperty, AutoWired::class)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $autoWired is correct as $reader->getPropertyAnno...tions\AutoWired::class) (which targets Doctrine\Common\Annotati...getPropertyAnnotation()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
435 1
                $this->inject($instance, [
436 1
                    $reflProperty->getName() => $this->get($autoWired->name),
437 1
                ]);
438 1
            }
439 1
        }
440 1
    }
441
}
442