Completed
Push — master ( 6b95b2...3ff006 )
by
unknown
05:25
created

Container::has()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * (c) Paulus Gandung Prakosa ([email protected])
5
 */
6
7
namespace DependencyInjection;
8
9
use Psr\Container\ContainerInterface;
10
use DependencyInjection\Exception\ContainerException;
11
use DependencyInjection\Exception\NotFoundException;
12
13
class Container implements \ArrayAccess, ContainerInterface
14
{
15
    /**
16
     * @var array
17
     */
18
    private $bindings = [];
19
20
    /**
21
     * @var array
22
     */
23
    private $resolved = [];
24
25
    /**
26
     * @var array
27
     */
28
    private $aliases = [];
29
30
    /**
31
     * Resolving all dependencies in the supplied class or object instance constructor.
32
     *
33
     * @param string $instance The class name.
34
     * @param array $parameters List of needed class dependency.
35
     * @return object
36
     */
37
    public function make($instance, $parameters = [])
38
    {
39
        if ($this->isAbstractExists($instance)) {
40
            return $this->resolve($instance, $this->getConcrete($instance));
41
        }
42
43
        return $this->resolve($instance, is_array($parameters) ? $parameters
44
            : array_slice(func_get_args(), 1));
45
    }
46
47
    /**
48
     * Register a service alias.
49
     *
50
     * @param string $alias The alias name.
51
     * @param string $abstract The class name.
52
     */
53
    public function register($alias, $abstract)
54
    {
55
        if (!is_string($alias) || !is_string($abstract)) {
56
            throw new \InvalidArgumentException(
57
                sprintf("Parameter 1 and 2 of %s must be a string.", __METHOD__)
58
            );
59
        }
60
61
        if (!isset($this->aliases[$alias])) {
62
            $this->aliases[$alias] = $this->make($abstract);
63
        }
64
65
        return $this;
66
    }
67
68
    /**
69
     * Determine if registered alias were exists.
70
     *
71
     * @param string $alias The alias name.
72
     */
73
    public function isAliasExists($alias)
74
    {
75
        return isset($this->aliases[$alias]);
76
    }
77
78
    /**
79
     * Finds an entry of the container by its identifier and returns it.
80
     *
81
     * @param string $id Identifier of the entry to look for.
82
     *
83
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
84
     * @throws ContainerExceptionInterface Error while retrieving the entry.
85
     *
86
     * @return mixed Entry.
87
     */
88
    public function get($id)
89
    {
90
        if (!$this->isAliasExists($id)) {
91
            throw new NotFoundException(
92
                sprintf("Identifier %s was not found in our service container stack.", $id)
93
            );
94
        }
95
96
        if (empty($this->aliases[$id])) {
97
            throw new ContainerException(
98
                sprintf("Unable to get concrete implementation for identifier %s", $id)
99
            );
100
        }
101
102
        return $this->aliases[$id];
103
    }
104
    
105
    /**
106
     * Returns true if the container can return an entry for the given identifier.
107
     * Returns false otherwise.
108
     *
109
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
110
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
111
     *
112
     * @param string $id Identifier of the entry to look for.
113
     *
114
     * @return bool
115
     */
116
    public function has($id)
117
    {
118
        return $this->isAliasExists($id);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function offsetExists($offset)
125
    {
126
        return $this->isBound($offset);
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function offsetGet($offset)
133
    {
134
        return $this->make($offset);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function offsetSet($offset, $value)
141
    {
142
        $this->bind($offset, $value instanceof \Closure ? $value : $this->turnIntoResolvableClosure($offset, $value));
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function offsetUnset($offset)
149
    {
150
        unset($this->bindings[$offset], $this->resolved[$offset]);
151
    }
152
153
    /**
154
     * Get list of unresolved class name from class binding stack.
155
     *
156
     * @return string
157
     */
158
    protected function getAbstracts()
159
    {
160
        return array_keys($this->bindings);
161
    }
162
163
    /**
164
     * Determine if unresolved class name is exists.
165
     *
166
     * @param string $abstract The unresolved class name.
167
     * @return bool
168
     */
169
    public function isAbstractExists($abstract)
170
    {
171
        return array_key_exists($abstract, $this->bindings);
172
    }
173
174
    /**
175
     * Determine if concrete dependency is exists.
176
     *
177
     * @param mixed $concrete The concrete dependency.
178
     * @return bool
179
     */
180
    public function isConcreteExists($concrete)
181
    {
182
        foreach (array_values($this->bindings) as $value) {
183
            if (in_array($concrete, $value, true)) {
184
                $isConcreteExists = true;
185
186
                break;
187
            }
188
        }
189
190
        return (isset($isConcreteExists) ? $isConcreteExists : false);
191
    }
192
193
    /**
194
     * Determine if unresolved abstract is an interface.
195
     *
196
     * @param string $abstract The unresolved abstract name.
197
     */
198
    public function isInterface($abstract)
199
    {
200
        $reflector = Internal\ReflectionClassFactory::create($abstract);
201
202
        return $reflector->isInterface();
203
    }
204
205
    /**
206
     * Get concrete list of dependencies based on supplied class name.
207
     *
208
     * @param string $abstract The unresolved class name.
209
     * @return array
210
     */
211
    public function getConcrete($abstract)
212
    {
213
        return ($this->isAbstractExists($abstract) ? $this->bindings[$abstract] : null);
214
    }
215
216
    /**
217
     * Resolve class dependencies in the supplied class name.
218
     *
219
     * @param string $instance The class name.
220
     * @param array $parameters The needed class dependency.
221
     * @return object
222
     */
223
    protected function resolve($instance, $parameters = [])
224
    {
225
        $reflector = Internal\ReflectionClassFactory::create($instance);
226
227
        if (!$this->hasConstructor($reflector)) {
228
            $this->markAsResolved($instance);
229
            
230
            return $this->resolveInstanceWithoutConstructor($reflector);
231
        }
232
233
        if (is_array($parameters) && empty(sizeof($parameters))) {
234
            $constructorParams = $this->getMethodParameters($reflector, '__construct');
235
236
            if (!is_null($constructorParams)) {
237
                $params = $this->resolveMethodParameters($constructorParams);
238
            }
239
        } elseif (is_array($parameters) && !empty(sizeof($parameters))) {
240
            $params = $this->resolveMethodParameters($parameters);
241
        }
242
243
        $this->markAsResolved($instance);
244
245
        return $reflector->newInstanceArgs($params);
0 ignored issues
show
Bug introduced by
The variable $params does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
246
    }
247
248
    /**
249
     * Resolve method parameters.
250
     *
251
     * @param array $params The unresolvable method.
252
     * @return array
253
     */
254
    protected function resolveMethodParameters($params = [])
255
    {
256
        if (!is_array($params)) {
257
            throw new \InvalidArgumentException(
258
                sprintf("Parameter 1 of %s must be an array.", __METHOD__)
259
            );
260
        }
261
262
        foreach ($params as $key => $value) {
263
            if ($value instanceof \ReflectionParameter) {
264
                $class = $value->getClass();
265
266
                if ($class instanceof \ReflectionClass) {
267
                    if ($class->isInterface()) {
268
                        $params[$key] = $this->getConcreteFromInterface($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
269
270
                        $this->markAsResolved($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
271
                    } else {
272
                        $params[$key] = $this->circularDependencyResolver($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
273
                    }
274
                } else {
275
                    $params[$key] = ($value->isDefaultValueAvailable()
276
                        ? $value->getDefaultValue() : null);
277
                }
278
            } else {
279
                if (is_string($value) && class_exists($value)) {
280
                    $params[$key] = $this->circularDependencyResolver($value);
281
                } elseif ($value instanceof \Closure) {
282
                    $params[$key] = ($this->isConcreteExists($value) ? $value($this) : $value);
283
                } else {
284
                    $params[$key] = $value;
285
                }
286
            }
287
        }
288
289
        return $params;
290
    }
291
292
    /**
293
     * Recursively resolving class dependency.
294
     *
295
     * @param string $class The valid class name.
296
     * @return object
297
     */
298
    protected function circularDependencyResolver($class)
299
    {
300
        if (!is_string($class) && !class_exists($class)) {
301
            throw Internal\Exception\ReflectionExceptionFactory::invalidArgument(
302
                sprintf("Parameter 1 of %s must be a string of valid class name.", __METHOD__)
303
            );
304
        }
305
306
        $reflector = Internal\ReflectionClassFactory::create($class);
307
308
        if (!$this->hasConstructor($reflector)) {
309
            return $this->resolveInstanceWithoutConstructor($reflector);
310
        } else {
311
            $param = $this->getMethodParameters($reflector, '__construct');
312
313
            if (empty($param)) {
314
                return $reflector->newInstance();
315
            } else {
316
                foreach ($param as $key => $value) {
317
                    $class = $value->getClass();
318
319
                    if ($class instanceof \ReflectionClass) {
320
                        if ($class->isInterface()) {
321
                            $param[$key] = $this->getConcreteFromInterface($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
322
323
                            $this->markAsResolved($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
324
                        } else {
325
                            $param[$key] = $this->circularDependencyResolver($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
326
                        }
327
                    }
328
                }
329
330
                return $reflector->newInstanceArgs($param);
331
            }
332
        }
333
    }
334
335
    /**
336
     * Get concrete implementation from abstract.
337
     *
338
     * @param string $interface The interface name.
339
     * @return object
340
     */
341
    protected function getConcreteFromInterface($interface)
342
    {
343
        if (!$this->isAbstractExists($interface)) {
344
            throw Internal\Exception\ReflectionExceptionFactory::runtime(
345
                sprintf("%s has no concrete implementation in the clas binding stack.", $interface)
346
            );
347
        }
348
349
        if (sizeof($this->bindings[$interface]) > 1) {
350
            throw Internal\Exception\ReflectionExceptionFactory::logic(
351
                "An interface must only have 1 concrete implementation."
352
            );
353
        }
354
355
        $concrete = $this->bindings[$interface][0];
356
357
        return ($concrete instanceof \Closure ? $concrete($this)
358
            : (is_string($concrete) && class_exists($concrete)
359
                ? $this->resolve($concrete) : $concrete));
360
    }
361
362
    /**
363
     * Determine if current reflection object has constructor.
364
     *
365
     * @param \ReflectionClass $refl The current reflection class object.
366
     * @return boolean
367
     */
368
    protected function hasConstructor(Internal\ReflectionClassFactory $refl)
369
    {
370
        return $refl->hasMethod('__construct');
371
    }
372
373
    /**
374
     * Determine if unresolvable class name has cloneable.
375
     *
376
     * @param \ReflectionClass $refl The current reflection class object.
377
     * @return boolean
378
     */
379
    protected function isCloneable(Internal\ReflectionClassFactory $refl)
380
    {
381
        return $refl->hasMethod('__clone');
382
    }
383
384
    /**
385
     * Determine if unresolvable class name has serializable.
386
     *
387
     * @param \ReflectionClass $refl The current reflection class object.
388
     * @return boolean
389
     */
390
    protected function isSerializable(Internal\ReflectionClassFactory $refl)
391
    {
392
        return $refl->hasMethod('__sleep');
393
    }
394
395
    /**
396
     * Resolving class name without constructor.
397
     *
398
     * @param \ReflectionClass $refl An instance of \ReflectionClass
399
     */
400
    protected function resolveInstanceWithoutConstructor(Internal\ReflectionClassFactory $refl)
401
    {
402
        return $refl->newInstanceWithoutConstructor();
403
    }
404
405
    /**
406
     * Get method parameters.
407
     *
408
     * @param \ReflectionClass $refl An reflection class instance.
409
     * @param string $method The method name.
410
     * @return array
411
     */
412
    protected function getMethodParameters(Internal\ReflectionClassFactory $refl, $method)
413
    {
414
        return ($refl->hasMethod($method) ? $refl->getMethod($method)->getParameters() : null);
415
    }
416
417
    /**
418
     * Mark resolved class name to true.
419
     *
420
     * @param string $abstract The resolved class name.
421
     * @return void
422
     */
423
    protected function markAsResolved($abstract)
424
    {
425
        if ($this->isAbstractExists($abstract)) {
426
            $this->resolved[$abstract] = true;
427
428
            unset($this->bindings[$abstract]);
429
        }
430
    }
431
432
    /**
433
     * Bind service into binding container stack.
434
     *
435
     * @param string $abstract The unresolvable class name.
436
     * @param \Closure|string $concrete Closure or class name being bound to the class name.
437
     */
438
    public function bind($abstract, $concrete = null)
439
    {
440
        if (is_null($concrete)) {
441
            $concrete = $abstract;
442
        }
443
444
        if (!($concrete instanceof \Closure)) {
445
            $concrete = $this->turnIntoResolvableClosure($abstract, $concrete);
446
        }
447
448
        if (isset($this->bindings[$abstract])) {
449
            array_push($this->bindings[$abstract], $concrete);
450
        } else {
451
            $this->bindings[$abstract] = [$concrete];
452
        }
453
    }
454
455
    /**
456
     * Bind service into binding container stack if supplied class name
457
     * not being bound.
458
     *
459
     * @param string $abstract The unresolvable class name.
460
     * @param \Closure|string $concrete Closure or class name begin bound to the class name.
461
     */
462
    public function bindIf($abstract, $concrete)
463
    {
464
        if (!$this->isBound($abstract)) {
465
            $this->bind($abstract, $concrete);
466
        }
467
    }
468
469
    /**
470
     * Call defined instance.
471
     *
472
     * @param string $instance The class name to invoke/call.
473
     * @param array $args The class name __invoke method argument.
474
     * @return mixed|void
475
     */
476
    public function callInstance($instance, $args = [])
477
    {
478
        $args = (is_array($args) ? $args : array_slice(func_get_args(), 1));
479
        
480
        $current = $this->make($instance);
481
482
        $this->markAsResolved($instance);
483
484
        return call_user_func_array($current, $args);
485
    }
486
487
    /**
488
     * Determine if class name has been bound or not.
489
     *
490
     * @param string $abstract The unresolvable class name.
491
     * @return bool
492
     */
493
    public function isBound($abstract)
494
    {
495
        return $this->isAbstractExists($abstract);
496
    }
497
498
    /**
499
     * Turn class name into resolvable closure.
500
     *
501
     * @param string $abstract The class name
502
     * @param \Closure|string $concrete Can be instance of \Closure or class name.
503
     * @return \Closure
504
     */
505
    protected function turnIntoResolvableClosure($abstract, $concrete)
506
    {
507
        return function (Container $container, $parameters = []) use ($abstract, $concrete) {
508
            return ($abstract == $concrete ? $container->resolve($abstract)
509
                : $container->resolve($concrete, $parameters));
0 ignored issues
show
Bug introduced by
It seems like $concrete defined by parameter $concrete on line 505 can also be of type object<Closure>; however, DependencyInjection\Container::resolve() 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...
510
        };
511
    }
512
}
513