Test Failed
Push — master ( 509c92...191bfb )
by
unknown
05:20
created

Container::isConcreteExists()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.2
cc 4
eloc 6
nc 6
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
        return $this->resolve($instance, is_array($parameters) ? $parameters
40
            : array_slice(func_get_args(), 1));
41
    }
42
43
    /**
44
     * Register a service alias.
45
     *
46
     * @param string $alias The alias name.
47
     * @param string $abstract The class name.
48
     */
49
    public function register($alias, $abstract)
50
    {
51
        if (!is_string($alias) || !is_string($abstract)) {
52
            throw new \InvalidArgumentException(
53
                sprintf("Parameter 1 and 2 of %s must be a string.", __METHOD__)
54
            );
55
        }
56
57
        if (!isset($this->aliases[$alias])) {
58
            $this->aliases[$alias] = $this->make($abstract);
59
        }
60
61
        return $this;
62
    }
63
64
    /**
65
     * Determine if registered alias were exists.
66
     *
67
     * @param string $alias The alias name.
68
     */
69
    public function isAliasExists($alias)
70
    {
71
        return isset($this->aliases[$alias]);
72
    }
73
74
    /**
75
     * Finds an entry of the container by its identifier and returns it.
76
     *
77
     * @param string $id Identifier of the entry to look for.
78
     *
79
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
80
     * @throws ContainerExceptionInterface Error while retrieving the entry.
81
     *
82
     * @return mixed Entry.
83
     */
84
    public function get($id)
85
    {
86
        if (!$this->isAliasExists($id)) {
87
            throw new NotFoundException(
88
                sprintf("Identifier %s was not found in our service container stack.", $id)
89
            );
90
        }
91
92
        return $this->aliases[$id];
93
    }
94
    
95
    /**
96
     * Returns true if the container can return an entry for the given identifier.
97
     * Returns false otherwise.
98
     *
99
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
100
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
101
     *
102
     * @param string $id Identifier of the entry to look for.
103
     *
104
     * @return bool
105
     */
106
    public function has($id)
107
    {
108
        return $this->isAliasExists($id);
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function offsetExists($offset)
115
    {
116
        return $this->isBound($offset);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function offsetGet($offset)
123
    {
124
        return $this->make($offset);
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function offsetSet($offset, $value)
131
    {
132
        $this->bind($offset, $value instanceof \Closure ? $value : $this->turnIntoResolvableClosure($offset, $value));
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function offsetUnset($offset)
139
    {
140
        unset($this->bindings[$offset], $this->resolved[$offset]);
141
    }
142
143
    /**
144
     * Determine if defined abstract class name were in resolved concrete stack and it was a
145
     * singleton.
146
     *
147
     * @param string $abstract The resolved abstract class name.
148
     */
149
    public function hasResolvedSingleton($abstract)
150
    {
151
        $flag = $this->getResolvedConcreteFlag($abstract);
152
153
        return in_array('singleton', $flag, true);
154
    }
155
156
    /**
157
     * Get singleton resolved concrete from defined abstract class name.
158
     *
159
     * @param string $abstract The resolved abstract class name.
160
     */
161
    public function getResolvedSingleton($abstract)
162
    {
163
        return ($this->hasResolvedSingleton($abstract)
164
            ? $this->resolved[$abstract]['concrete']
165
            : null);
166
    }
167
168
    /**
169
     * Determine if defined abstract class name were in resolved concrete stack.
170
     *
171
     * @param string $abstract The resolved abstract class name.
172
     */
173
    public function hasResolvedConcrete($abstract)
174
    {
175
        return isset($this->resolved[$abstract]);
176
    }
177
178
    /**
179
     * Get flag of resolved concrete behavior on abstract class name.
180
     *
181
     * @param string $abstract The resolved abstract class name.
182
     */
183
    public function getResolvedConcreteFlag($abstract)
184
    {
185
        if (!$this->hasResolvedConcrete($abstract)) {
186
            throw Internal\Exception\ReflectionExceptionFactory::invalidArgument(
187
                sprintf(
188
                    "Parameter 1 of %s must be an abstract class name which exists in resolved concrete stack.",
189
                     __METHOD__
190
                );
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected ';', expecting ',' or ')'
Loading history...
191
            );
192
        }
193
194
        return explode('|', $this->resolved[$abstract]['flag']);
195
    }
196
197
    /**
198
     * Get resolved concrete from defined abstract class name.
199
     *
200
     * @param string $abstract The resolved abstract class name.
201
     */
202
    public function getResolvedConcrete($abstract)
203
    {
204
        return ($this->hasResolvedConcrete($abstract)
205
            ? $this->resolved[$abstract]['concrete']
206
            : null);
207
    }
208
209
    /**
210
     * Get list of unresolved class name from class binding stack.
211
     *
212
     * @return string
213
     */
214
    protected function getAbstracts()
215
    {
216
        return array_keys($this->bindings);
217
    }
218
219
    /**
220
     * Determine if unresolved class name is exists.
221
     *
222
     * @param string $abstract The unresolved class name.
223
     * @return bool
224
     */
225
    public function isAbstractExists($abstract)
226
    {
227
        return array_key_exists($abstract, $this->bindings);
228
    }
229
230
    /**
231
     * Determine if unresolved abstract is an interface.
232
     *
233
     * @param string $abstract The unresolved abstract name.
234
     */
235
    public function isInterface($abstract)
236
    {
237
        $reflector = Internal\ReflectionClassFactory::create($abstract);
238
239
        return $reflector->isInterface();
240
    }
241
242
    /**
243
     * Get concrete list of dependencies based on supplied class name.
244
     *
245
     * @param string $abstract The unresolved class name.
246
     * @return array
247
     */
248
    public function getAbstractDependencies($abstract)
249
    {
250
        return ($this->isAbstractExists($abstract) ? $this->bindings[$abstract] : null);
251
    }
252
253
    /**
254
     * Resolve class dependencies in the supplied class name.
255
     *
256
     * @param string $instance The class name.
257
     * @param array $parameters The needed class dependency.
258
     * @return object
259
     */
260
    protected function resolve($instance, $parameters = [])
261
    {
262
        // If the current abstract is an interface,
263
        // just return the concrete implementation to the callee.
264
        if ($this->isInterface($instance)) {
265
            return $this->getConcreteFromInterface($instance);
266
        }
267
268
        // If the current abstract type being managed as a singleton,
269
        // just return it to the caller instead of reinstantiating it.
270
        try {
271
            return $this->getResolvedSingleton($instance);
272
        } catch (\Exception $e) {
273
        }
274
        
275
        $concrete = $this->getConcrete($instance);
276
277
        if (!is_null($concrete)) {
278
            $object = $this->build($instance,
279
                $concrete instanceof \Closure ? $concrete($this) : $concrete);
280
281
            if ($this->isShared($instance)) {
282
                $this->markAsResolved($instance, $object, 'singleton');
283
            } else {
284
                $this->markAsResolved($instance, $object);
285
            }
286
        } else {
287
            $object = $this->build($instance, $parameters);
288
        }
289
290
        return $object;
291
    }
292
293
    protected function build($instance, $parameters = [])
294
    {
295
        $parameters = (is_null($parameters)
296
            ? []
297
            : (is_array($parameters)
298
                ? $parameters
299
                : array_slice(func_get_args(), 1)));
300
301
        var_dump($parameters);
302
303
        $reflector = Internal\ReflectionClassFactory::create($instance);
304
305
        if (!$this->hasConstructor($reflector)) {
306
            return $this->resolveInstanceWithoutConstructor($reflector);
307
        }
308
309
        if (is_array($parameters) && empty(sizeof($parameters))) {
310
            $constructorParams = $this->getMethodParameters($reflector, '__construct');
311
312
            if (!is_null($constructorParams)) {
313
                $params = $this->resolveMethodParameters($constructorParams);
314
            }
315
        } elseif (is_array($parameters) && !empty(sizeof($parameters))) {
316
            $params = $this->resolveMethodParameters($parameters);
317
        }
318
319
        return $reflector->newInstanceArgs(!empty($parameters) ? $parameters : $params);
320
    }
321
322
    /**
323
     * Resolve method parameters.
324
     *
325
     * @param array $params The unresolvable method.
326
     * @return array
327
     */
328
    protected function resolveMethodParameters($params = [])
329
    {
330
        if (!is_array($params)) {
331
            throw new \InvalidArgumentException(
332
                sprintf("Parameter 1 of %s must be an array.", __METHOD__)
333
            );
334
        }
335
336
        foreach ($params as $key => $value) {
337
            if ($value instanceof \ReflectionParameter) {
338
                $class = $value->getClass();
339
340
                if ($class instanceof \ReflectionClass) {
341
                    if ($class->isInterface()) {
342
                        $params[$key] = $this->getConcreteFromInterface($class->getName());
343
                    } else {
344
                        $params[$key] = $this->circularDependencyResolver($class->getName());
345
                    }
346
                } else {
347
                    $params[$key] = ($value->isDefaultValueAvailable()
348
                        ? $value->getDefaultValue() : null);
349
                }
350
            } else {
351
                if (is_string($value) && class_exists($value)) {
352
                    $params[$key] = $this->circularDependencyResolver($value);
353
                } elseif ($value instanceof \Closure) {
354
                    $params[$key] = $value($this);
355
                } else {
356
                    $params[$key] = $value;
357
                }
358
            }
359
        }
360
361
        return $params;
362
    }
363
364
    /**
365
     * Recursively resolving class dependency.
366
     *
367
     * @param string $class The valid class name.
368
     * @return object
369
     */
370
    protected function circularDependencyResolver($class)
371
    {
372
        if (!is_string($class) && !class_exists($class)) {
373
            throw Internal\Exception\ReflectionExceptionFactory::invalidArgument(
374
                sprintf("Parameter 1 of %s must be a string of valid class name.", __METHOD__)
375
            );
376
        }
377
378
        $reflector = Internal\ReflectionClassFactory::create($class);
379
380
        if (!$this->hasConstructor($reflector)) {
381
            return $this->resolveInstanceWithoutConstructor($reflector);
382
        } else {
383
            $param = $this->getMethodParameters($reflector, '__construct');
384
385
            if (empty($param)) {
386
                return $reflector->newInstance();
387
            } else {
388
                foreach ($param as $key => $value) {
389
                    $class = $value->getClass();
390
391
                    if ($class instanceof \ReflectionClass) {
392
                        if ($class->isInterface()) {
393
                            $param[$key] = $this->getConcreteFromInterface($class->getName());
394
                        } else {
395
                            $param[$key] = $this->circularDependencyResolver($class->getName());
396
                        }
397
                    }
398
                }
399
400
                return $reflector->newInstanceArgs($param);
401
            }
402
        }
403
    }
404
405
    /**
406
     * Get concrete implementation from given abstract.
407
     *
408
     * @param string $abstract
409
     * @return \Closure|string|null
410
     */
411
    public function getConcrete($abstract)
412
    {
413
        return (isset($this->bindings[$abstract])
414
            ? $this->bindings[$abstract]['concrete']
415
            : null);
416
    }
417
418
    /**
419
     * Get concrete implementation from abstract.
420
     *
421
     * @param string $interface The interface name.
422
     * @return object
423
     */
424
    protected function getConcreteFromInterface($interface)
425
    {
426
        if (!$this->isAbstractExists($interface)) {
427
            throw Internal\Exception\ReflectionExceptionFactory::runtime(
428
                sprintf("%s has no concrete implementation in the class binding stack.", $interface)
429
            );
430
        }
431
432
        try {
433
            return $this->getResolvedSingleton($interface);
434
        } catch (\Exception $e) {
435
        }
436
437
        $concrete = $this->bindings[$interface]['concrete'];
438
439
        $object = $concrete instanceof \Closure ? $concrete($this) : $this->build($concrete);
440
441
        if ($this->isShared($interface)) {
442
            $this->markAsResolved($interface, $object, 'singleton');
443
        } else {
444
            $this->markAsResolved($interface, $object);
445
        }
446
447
        return $object;
448
    }
449
450
    /**
451
     * Determine if current reflection object has constructor.
452
     *
453
     * @param \ReflectionClass $refl The current reflection class object.
454
     * @return boolean
455
     */
456
    protected function hasConstructor(Internal\ReflectionClassFactory $refl)
457
    {
458
        return $refl->hasMethod('__construct');
459
    }
460
461
    /**
462
     * Determine if unresolvable class name has cloneable.
463
     *
464
     * @param \ReflectionClass $refl The current reflection class object.
465
     * @return boolean
466
     */
467
    protected function isCloneable(Internal\ReflectionClassFactory $refl)
468
    {
469
        return $refl->hasMethod('__clone');
470
    }
471
472
    /**
473
     * Determine if unresolvable class name has serializable.
474
     *
475
     * @param \ReflectionClass $refl The current reflection class object.
476
     * @return boolean
477
     */
478
    protected function isSerializable(Internal\ReflectionClassFactory $refl)
479
    {
480
        return $refl->hasMethod('__sleep');
481
    }
482
483
    /**
484
     * Resolving class name without constructor.
485
     *
486
     * @param \ReflectionClass $refl An instance of \ReflectionClass
487
     */
488
    protected function resolveInstanceWithoutConstructor(Internal\ReflectionClassFactory $refl)
489
    {
490
        return $refl->newInstanceWithoutConstructor();
491
    }
492
493
    /**
494
     * Get method parameters.
495
     *
496
     * @param \ReflectionClass $refl An reflection class instance.
497
     * @param string $method The method name.
498
     * @return array
499
     */
500
    protected function getMethodParameters(Internal\ReflectionClassFactory $refl, $method)
501
    {
502
        return ($refl->hasMethod($method) ? $refl->getMethod($method)->getParameters() : null);
503
    }
504
505
    /**
506
     * Mark resolved class name to true.
507
     *
508
     * @param string $abstract The resolved class name.
509
     * @param object $resolvedInstance The object instance of resolved abstract.
510
     * @param mixed $flag The concrete-resolving behavior.
511
     * @return void
512
     */
513
    protected function markAsResolved($abstract, $resolvedInstance, $flag = [])
514
    {
515
        if (!is_array($flag)) {
516
            $flag = array_slice(func_get_args(), 2);
517
        }
518
519
        if ($this->isAbstractExists($abstract)) {
520
            $this->resolved[$abstract] = [
521
                'concrete' => $resolvedInstance,
522
                'resolved' => true,
523
                'flag' => join('|', $flag)
524
            ];
525
        }
526
    }
527
528
    /**
529
     * Register binding into container stack.
530
     *
531
     * @param string $abstract The unresolvable class name.
532
     * @param \Closure|string $concrete Closure or class name being bound to the class name.
533
     */
534
    public function bind($abstract, $concrete = null, $shared = false)
535
    {
536
        if (is_null($concrete)) {
537
            $concrete = $abstract;
538
        }
539
540
        if (!($concrete instanceof \Closure)) {
541
            $concrete = $this->turnIntoResolvableClosure($abstract, $concrete);
542
        }
543
544
        $this->bindings[$abstract] = compact('concrete', 'shared');
545
    }
546
547
    /**
548
     * Register shared binding into container stack.
549
     *
550
     * @param string $abstract The unresolvable abstract
551
     * @param \Closure|string|null The concrete form of supplied abstract.
552
     */
553
    public function singleton($abstract, $concrete = null)
554
    {
555
        $this->bind($abstract, $concrete, true);
556
    }
557
558
    /**
559
     * Bind service into binding container stack if supplied class name
560
     * not being bound.
561
     *
562
     * @param string $abstract The unresolvable class name.
563
     * @param \Closure|string $concrete Closure or class name begin bound to the class name.
564
     */
565
    public function bindIf($abstract, $concrete)
566
    {
567
        if (!$this->isBound($abstract)) {
568
            $this->bind($abstract, $concrete);
569
        }
570
    }
571
572
    /**
573
     * Call defined instance.
574
     *
575
     * @param string $instance The class name to invoke/call.
576
     * @param array $args The class name __invoke method argument.
577
     * @return mixed|void
578
     */
579
    public function callInstance($instance, $args = [])
580
    {
581
        $args = (is_array($args) ? $args : array_slice(func_get_args(), 1));
582
        
583
        $current = $this->make($instance);
584
585
        var_dump($current);
586
587
        return call_user_func_array($current, $args);
588
    }
589
590
    /**
591
     * Determine if class name has been bound or not.
592
     *
593
     * @param string $abstract The unresolvable class name.
594
     * @return bool
595
     */
596
    public function isBound($abstract)
597
    {
598
        return $this->isAbstractExists($abstract);
599
    }
600
601
    /**
602
     * Determine if a given type is shared.
603
     *
604
     * @param string $abstract
605
     * @return bool
606
     */
607
    public function isShared($abstract)
608
    {
609
        if (!isset($this->bindings[$abstract])) {
610
            throw Internal\Exception\ReflectionExceptionFactory::invalidArgument(
611
                sprintf("Parameter 1 of %s must be valid keys in binding container stack.", __METHOD__)
612
            );
613
        }
614
615
        return ($this->bindings[$abstract]['shared'] ? true : false);
616
    }
617
618
    /**
619
     * Turn class name into resolvable closure.
620
     *
621
     * @param string $abstract The class name
622
     * @param \Closure|string $concrete Can be instance of \Closure or class name.
623
     * @return \Closure
624
     */
625
    protected function turnIntoResolvableClosure($abstract, $concrete)
626
    {
627
        return function (Container $container, $parameters = []) use ($abstract, $concrete) {
628
            return ($abstract == $concrete ? $container->resolve($abstract)
629
                : $container->resolve($concrete, $parameters));
630
        };
631
    }
632
}
633