Container::rebound()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
rs 10
1
<?php
2
/**
3
 * Framy Framework
4
 *
5
 * @copyright Copyright Framy
6
 * @Author Marco Bier <[email protected]>
7
 */
8
9
namespace app\framework\Component\Container;
10
11
use ArrayAccess;
12
use Closure;
13
use ReflectionClass;
14
use ReflectionFunction;
15
use ReflectionMethod;
16
use ReflectionParameter;
17
use InvalidArgumentException;
18
19
/**
20
 * Class Container
21
 * @package app\framework\Component\Container
22
 */
23
class Container implements ArrayAccess, ContainerInterface
24
{
25
    /**
26
     * The current globally available container (if any).
27
     *
28
     * @var static
29
     */
30
    protected static $instance;
31
32
    /**
33
     * An array of the types that have been resolved.
34
     *
35
     * @var array
36
     */
37
    protected $resolved = [];
38
39
    /**
40
     * The container's bindings.
41
     *
42
     * @var array
43
     */
44
    protected $bindings = [];
45
46
    /**
47
     * The container's shared instances.
48
     *
49
     * @var array
50
     */
51
    protected $instances = [];
52
53
    /**
54
     * The registered type aliases.
55
     *
56
     * @var array
57
     */
58
    protected $aliases = [];
59
60
    /**
61
     * The extension closures for services.
62
     *
63
     * @var array
64
     */
65
    protected $extenders = [];
66
67
    /**
68
     * All of the registered tags.
69
     *
70
     * @var array
71
     */
72
    protected $tags = [];
73
74
    /**
75
     * The stack of concretions currently being built.
76
     *
77
     * @var array
78
     */
79
    protected $buildStack = [];
80
81
    /**
82
     * The contextual binding map.
83
     *
84
     * @var array
85
     */
86
    public $contextual = [];
87
88
    /**
89
     * All of the registered rebound callbacks.
90
     *
91
     * @var array
92
     */
93
    protected $reboundCallbacks = [];
94
95
    /**
96
     * All of the global resolving callbacks.
97
     *
98
     * @var array
99
     */
100
    protected $globalResolvingCallbacks = [];
101
102
    /**
103
     * All of the global after resolving callbacks.
104
     *
105
     * @var array
106
     */
107
    protected $globalAfterResolvingCallbacks = [];
108
109
    /**
110
     * All of the after resolving callbacks by class type.
111
     *
112
     * @var array
113
     */
114
    protected $resolvingCallbacks = [];
115
116
    /**
117
     * All of the after resolving callbacks by class type.
118
     *
119
     * @var array
120
     */
121
    protected $afterResolvingCallbacks = [];
122
123
    /**
124
     * Define a contextual binding.
125
     *
126
     * @param  string  $concrete
127
     * @return ContextualBindingBuilder
128
     */
129
    public function when($concrete)
130
    {
131
        $concrete = $this->normalize($concrete);
132
133
        return new ContextualBindingBuilder($this, $concrete);
134
    }
135
136
    /**
137
     * Determine if the given abstract type has been bound.
138
     *
139
     * @param  string  $abstract
140
     * @return bool
141
     */
142
    public function bound($abstract)
143
    {
144
        $abstract = $this->normalize($abstract);
145
146
        return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]) || $this->isAlias($abstract);
147
    }
148
149
    /**
150
     * Determine if the given abstract type has been resolved.
151
     *
152
     * @param  string  $abstract
153
     * @return bool
154
     */
155
    public function resolved($abstract)
156
    {
157
        $abstract = $this->normalize($abstract);
158
159
        if ($this->isAlias($abstract)) {
160
            $abstract = $this->getAlias($abstract);
161
        }
162
163
        return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]);
164
    }
165
166
    /**
167
     * Determine if a given string is an alias.
168
     *
169
     * @param  string  $name
170
     * @return bool
171
     */
172
    public function isAlias($name)
173
    {
174
        return isset($this->aliases[$this->normalize($name)]);
175
    }
176
177
    /**
178
     * Register a binding with the container.
179
     *
180
     * @param  string|array  $abstract
181
     * @param  \Closure|string|null  $concrete
182
     * @param  bool  $shared
183
     * @return void
184
     * @throws BindingResolutionException
185
     * @throws \ReflectionException
186
     */
187
    public function bind($abstract, $concrete = null, $shared = false)
188
    {
189
        $abstract = $this->normalize($abstract);
190
191
        $concrete = $this->normalize($concrete);
192
193
        // If the given types are actually an array, we will assume an alias is being
194
        // defined and will grab this "real" abstract class name and register this
195
        // alias with the container so that it can be used as a shortcut for it.
196
        if (is_array($abstract)) {
197
            list($abstract, $alias) = $this->extractAlias($abstract);
198
199
            $this->alias($abstract, $alias);
200
        }
201
202
        // If no concrete type was given, we will simply set the concrete type to the
203
        // abstract type. This will allow concrete type to be registered as shared
204
        // without being forced to state their classes in both of the parameter.
205
        $this->dropStaleInstances($abstract);
206
207
        if (is_null($concrete)) {
208
            $concrete = $abstract;
209
        }
210
211
        // If the factory is not a Closure, it means it is just a class name which is
212
        // bound into this container to the abstract type and we will just wrap it
213
        // up inside its own Closure to give us more convenience when extending.
214
        if (! $concrete instanceof Closure) {
215
            $concrete = $this->getClosure($abstract, $concrete);
216
        }
217
218
        $this->bindings[$abstract] = compact('concrete', 'shared');
219
220
        // If the abstract type was already resolved in this container we'll fire the
221
        // rebound listener so that any objects which have already gotten resolved
222
        // can have their copy of the object updated via the listener callbacks.
223
        if ($this->resolved($abstract)) {
224
            $this->rebound($abstract);
225
        }
226
    }
227
228
    /**
229
     * Get the Closure to be used when building a type.
230
     *
231
     * @param  string  $abstract
232
     * @param  string  $concrete
233
     * @return \Closure
234
     */
235
    protected function getClosure($abstract, $concrete)
236
    {
237
        return function ($c, $parameters = []) use ($abstract, $concrete) {
238
            $method = ($abstract == $concrete) ? 'build' : 'make';
239
240
            return $c->$method($concrete, $parameters);
241
        };
242
    }
243
244
    /**
245
     * Add a contextual binding to the container.
246
     *
247
     * @param  string  $concrete
248
     * @param  string  $abstract
249
     * @param  \Closure|string  $implementation
250
     * @return void
251
     */
252
    public function addContextualBinding($concrete, $abstract, $implementation)
253
    {
254
        $this->contextual[$this->normalize($concrete)][$this->normalize($abstract)] = $this->normalize($implementation);
255
    }
256
257
    /**
258
     * Register a binding if it hasn't already been registered.
259
     *
260
     * @param  string  $abstract
261
     * @param  \Closure|string|null  $concrete
262
     * @param  bool  $shared
263
     * @return void
264
     * @throws BindingResolutionException
265
     * @throws \ReflectionException
266
     */
267
    public function bindIf($abstract, $concrete = null, $shared = false)
268
    {
269
        if (! $this->bound($abstract)) {
270
            $this->bind($abstract, $concrete, $shared);
271
        }
272
    }
273
274
    /**
275
     * Register a shared binding in the container.
276
     *
277
     * @param  string|array  $abstract
278
     * @param  \Closure|string|null  $concrete
279
     * @return void
280
     * @throws BindingResolutionException
281
     * @throws \ReflectionException
282
     */
283
    public function singleton($abstract, $concrete = null)
284
    {
285
        $this->bind($abstract, $concrete, true);
286
    }
287
288
    /**
289
     * Wrap a Closure such that it is shared.
290
     *
291
     * @param  \Closure  $closure
292
     * @return \Closure
293
     */
294
    public function share(Closure $closure)
295
    {
296
        return function ($container) use ($closure) {
297
            // We'll simply declare a static variable within the Closures and if it has
298
            // not been set we will execute the given Closures to resolve this value
299
            // and return it back to these consumers of the method as an instance.
300
            static $object;
301
302
            if (is_null($object)) {
303
                $object = $closure($container);
304
            }
305
306
            return $object;
307
        };
308
    }
309
310
    /**
311
     * "Extend" an abstract type in the container.
312
     *
313
     * @param  string    $abstract
314
     * @param  \Closure  $closure
315
     * @return void
316
     *
317
     * @throws \InvalidArgumentException
318
     * @throws BindingResolutionException
319
     * @throws \ReflectionException
320
     */
321
    public function extend($abstract, Closure $closure)
322
    {
323
        $abstract = $this->normalize($abstract);
324
325
        if (isset($this->instances[$abstract])) {
326
            $this->instances[$abstract] = $closure($this->instances[$abstract], $this);
327
328
            $this->rebound($abstract);
329
        } else {
330
            $this->extenders[$abstract][] = $closure;
331
        }
332
    }
333
334
    /**
335
     * Register an existing instance as shared in the container.
336
     *
337
     * @param  string  $abstract
338
     * @param  mixed   $instance
339
     * @return void
340
     * @throws BindingResolutionException
341
     * @throws \ReflectionException
342
     */
343
    public function instance($abstract, $instance)
344
    {
345
        $abstract = $this->normalize($abstract);
346
347
        // First, we will extract the alias from the abstract if it is an array so we
348
        // are using the correct name when binding the type. If we get an alias it
349
        // will be registered with the container so we can resolve it out later.
350
        if (is_array($abstract)) {
0 ignored issues
show
introduced by
The condition is_array($abstract) is always false.
Loading history...
351
            list($abstract, $alias) = $this->extractAlias($abstract);
352
353
            $this->alias($abstract, $alias);
354
        }
355
356
        unset($this->aliases[$abstract]);
357
358
        // We'll check to determine if this type has been bound before, and if it has
359
        // we will fire the rebound callbacks registered with the container and it
360
        // can be updated with consuming classes that have gotten resolved here.
361
        $bound = $this->bound($abstract);
362
363
        $this->instances[$abstract] = $instance;
364
365
        if ($bound) {
366
            $this->rebound($abstract);
367
        }
368
    }
369
370
    /**
371
     * Assign a set of tags to a given binding.
372
     *
373
     * @param  array|string  $abstracts
374
     * @param  array|mixed   ...$tags
375
     * @return void
376
     */
377
    public function tag($abstracts, $tags)
378
    {
379
        $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);
380
381
        foreach ($tags as $tag) {
382
            if (! isset($this->tags[$tag])) {
383
                $this->tags[$tag] = [];
384
            }
385
386
            foreach ((array) $abstracts as $abstract) {
387
                $this->tags[$tag][] = $this->normalize($abstract);
388
            }
389
        }
390
    }
391
392
    /**
393
     * Resolve all of the bindings for a given tag.
394
     *
395
     * @param  string  $tag
396
     * @return array
397
     * @throws BindingResolutionException
398
     * @throws \ReflectionException
399
     */
400
    public function tagged($tag)
401
    {
402
        $results = [];
403
404
        if (isset($this->tags[$tag])) {
405
            foreach ($this->tags[$tag] as $abstract) {
406
                $results[] = $this->make($abstract);
407
            }
408
        }
409
410
        return $results;
411
    }
412
413
    /**
414
     * Alias a type to a different name.
415
     *
416
     * @param  string  $abstract
417
     * @param  string  $alias
418
     * @return void
419
     */
420
    public function alias($abstract, $alias)
421
    {
422
        $this->aliases[$alias] = $this->normalize($abstract);
423
    }
424
425
    /**
426
     * Extract the type and alias from a given definition.
427
     *
428
     * @param  array  $definition
429
     * @return array
430
     */
431
    protected function extractAlias(array $definition)
432
    {
433
        return [key($definition), current($definition)];
434
    }
435
436
    /**
437
     * Bind a new callback to an abstract's rebind event.
438
     *
439
     * @param  string    $abstract
440
     * @param  \Closure  $callback
441
     * @return mixed
442
     * @throws BindingResolutionException
443
     * @throws \ReflectionException
444
     */
445
    public function rebinding($abstract, Closure $callback)
446
    {
447
        $this->reboundCallbacks[$this->normalize($abstract)][] = $callback;
448
449
        if ($this->bound($abstract)) {
450
            return $this->make($abstract);
451
        }
452
453
        return null;
454
    }
455
456
    /**
457
     * Refresh an instance on the given target and method.
458
     *
459
     * @param  string  $abstract
460
     * @param  mixed   $target
461
     * @param  string  $method
462
     * @return mixed
463
     * @throws BindingResolutionException
464
     * @throws \ReflectionException
465
     */
466
    public function refresh($abstract, $target, $method)
467
    {
468
        return $this->rebinding($this->normalize($abstract), function ($instance) use ($target, $method) {
469
            $target->{$method}($instance);
470
        });
471
    }
472
473
    /**
474
     * Fire the "rebound" callbacks for the given abstract type.
475
     *
476
     * @param  string  $abstract
477
     * @return void
478
     * @throws BindingResolutionException
479
     * @throws \ReflectionException
480
     */
481
    protected function rebound($abstract)
482
    {
483
        $instance = $this->make($abstract);
484
485
        foreach ($this->getReboundCallbacks($abstract) as $callback) {
486
            call_user_func($callback, $this, $instance);
487
        }
488
    }
489
490
    /**
491
     * Get the rebound callbacks for a given type.
492
     *
493
     * @param  string  $abstract
494
     * @return array
495
     */
496
    protected function getReboundCallbacks($abstract)
497
    {
498
        if (isset($this->reboundCallbacks[$abstract])) {
499
            return $this->reboundCallbacks[$abstract];
500
        }
501
502
        return [];
503
    }
504
505
    /**
506
     * Wrap the given closure such that its dependencies will be injected when executed.
507
     *
508
     * @param  \Closure  $callback
509
     * @param  array  $parameters
510
     * @return \Closure
511
     */
512
    public function wrap(Closure $callback, array $parameters = [])
513
    {
514
        return function () use ($callback, $parameters) {
515
            return $this->call($callback, $parameters);
516
        };
517
    }
518
519
    /**
520
     * Call the given Closure / class@method and inject its dependencies.
521
     *
522
     * @param  callable|string  $callback
523
     * @param  array  $parameters
524
     * @param  string|null  $defaultMethod
525
     * @return mixed
526
     * @throws \ReflectionException
527
     * @throws BindingResolutionException
528
     */
529
    public function call($callback, array $parameters = [], $defaultMethod = null)
530
    {
531
        if ($this->isCallableWithAtSign($callback) || $defaultMethod) {
532
            return $this->callClass($callback, $parameters, $defaultMethod);
0 ignored issues
show
Bug introduced by
It seems like $callback can also be of type callable; however, parameter $target of app\framework\Component\...\Container::callClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

532
            return $this->callClass(/** @scrutinizer ignore-type */ $callback, $parameters, $defaultMethod);
Loading history...
533
        }
534
535
        $dependencies = $this->getMethodDependencies($callback, $parameters);
536
537
        return call_user_func_array($callback, $dependencies);
538
    }
539
540
    /**
541
     * Determine if the given string is in Class@method syntax.
542
     *
543
     * @param  mixed  $callback
544
     * @return bool
545
     */
546
    protected function isCallableWithAtSign($callback)
547
    {
548
        if (! is_string($callback)) {
549
            return false;
550
        }
551
552
        return strpos($callback, '@') !== false;
553
    }
554
555
    /**
556
     * Get all dependencies for a given method.
557
     *
558
     * @param  callable|string  $callback
559
     * @param  array  $parameters
560
     * @return array
561
     * @throws \ReflectionException
562
     * @throws BindingResolutionException
563
     */
564
    protected function getMethodDependencies($callback, array $parameters = [])
565
    {
566
        $dependencies = [];
567
568
        foreach ($this->getCallReflector($callback)->getParameters() as $parameter) {
569
            $this->addDependencyForCallParameter($parameter, $parameters, $dependencies);
570
        }
571
572
        return array_merge($dependencies, $parameters);
573
    }
574
575
    /**
576
     * Get the proper reflection instance for the given callback.
577
     *
578
     * @param  callable|string  $callback
579
     * @return \ReflectionFunctionAbstract
580
     * @throws \ReflectionException
581
     */
582
    protected function getCallReflector($callback)
583
    {
584
        if (is_string($callback) && strpos($callback, '::') !== false) {
585
            $callback = explode('::', $callback);
586
        }
587
588
        if (is_array($callback)) {
589
            return new ReflectionMethod($callback[0], $callback[1]);
590
        }
591
592
        return new ReflectionFunction($callback);
593
    }
594
595
    /**
596
     * Get the dependency for the given call parameter.
597
     *
598
     * @param  \ReflectionParameter  $parameter
599
     * @param  array  $parameters
600
     * @param  array  $dependencies
601
     * @return void
602
     * @throws \ReflectionException
603
     * @throws BindingResolutionException
604
     */
605
    protected function addDependencyForCallParameter(ReflectionParameter $parameter, array &$parameters, &$dependencies)
606
    {
607
        if (array_key_exists($parameter->name, $parameters)) {
608
            $dependencies[] = $parameters[$parameter->name];
609
610
            unset($parameters[$parameter->name]);
611
        } elseif ($parameter->getClass()) {
612
            $dependencies[] = $this->make($parameter->getClass()->name);
613
        } elseif ($parameter->isDefaultValueAvailable()) {
614
            $dependencies[] = $parameter->getDefaultValue();
615
        }
616
    }
617
618
    /**
619
     * Call a string reference to a class using Class@method syntax.
620
     *
621
     * @param  string  $target
622
     * @param  array  $parameters
623
     * @param  string|null  $defaultMethod
624
     * @return mixed
625
     *
626
     * @throws \InvalidArgumentException
627
     * @throws \ReflectionException
628
     * @throws BindingResolutionException
629
     */
630
    protected function callClass($target, array $parameters = [], $defaultMethod = null)
631
    {
632
        $segments = explode('@', $target);
633
634
        // If the listener has an @ sign, we will assume it is being used to delimit
635
        // the class name from the handle method name. This allows for handlers
636
        // to run multiple handler methods in a single class for convenience.
637
        $method = count($segments) == 2 ? $segments[1] : $defaultMethod;
638
639
        if (is_null($method)) {
640
            throw new InvalidArgumentException('Method not provided.');
641
        }
642
643
        return $this->call([$this->make($segments[0]), $method], $parameters);
644
    }
645
646
    /**
647
     * Resolve the given type from the container.
648
     *
649
     * @param  string  $abstract
650
     * @param  array   $parameters
651
     * @return mixed
652
     * @throws BindingResolutionException
653
     * @throws \ReflectionException
654
     */
655
    public function make($abstract, array $parameters = [])
656
    {
657
        $abstract = $this->getAlias($this->normalize($abstract));
658
659
        // If an instance of the type is currently being managed as a singleton we'll
660
        // just return an existing instance instead of instantiating new instances
661
        // so the developer can keep using the same objects instance every time.
662
        if (isset($this->instances[$abstract])) {
663
            return $this->instances[$abstract];
664
        }
665
666
        $concrete = $this->getConcrete($abstract);
667
668
        // We're ready to instantiate an instance of the concrete type registered for
669
        // the binding. This will instantiate the types, as well as resolve any of
670
        // its "nested" dependencies recursively until all have gotten resolved.
671
        if ($this->isBuildable($concrete, $abstract)) {
672
            $object = $this->build($concrete, $parameters);
673
        } else {
674
            $object = $this->make($concrete, $parameters);
675
        }
676
677
        // If we defined any extenders for this type, we'll need to spin through them
678
        // and apply them to the object being built. This allows for the extension
679
        // of services, such as changing configuration or decorating the object.
680
        foreach ($this->getExtenders($abstract) as $extender) {
681
            $object = $extender($object, $this);
682
        }
683
684
        // If the requested type is registered as a singleton we'll want to cache off
685
        // the instances in "memory" so we can return it later without creating an
686
        // entirely new instance of an object on each subsequent request for it.
687
        if ($this->isShared($abstract)) {
688
            $this->instances[$abstract] = $object;
689
        }
690
691
        $this->fireResolvingCallbacks($abstract, $object);
692
693
        $this->resolved[$abstract] = true;
694
695
        return $object;
696
    }
697
698
    /**
699
     * Get the concrete type for a given abstract.
700
     *
701
     * @param  string  $abstract
702
     * @return mixed   $concrete
703
     */
704
    protected function getConcrete($abstract)
705
    {
706
        if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
707
            return $concrete;
708
        }
709
710
        // If we don't have a registered resolver or concrete for the type, we'll just
711
        // assume each type is a concrete name and will attempt to resolve it as is
712
        // since the container should be able to resolve concretes automatically.
713
        if (! isset($this->bindings[$abstract])) {
714
            return $abstract;
715
        }
716
717
        return $this->bindings[$abstract]['concrete'];
718
    }
719
720
    /**
721
     * Get the contextual concrete binding for the given abstract.
722
     *
723
     * @param  string  $abstract
724
     * @return string|null
725
     */
726
    protected function getContextualConcrete($abstract)
727
    {
728
        if (isset($this->contextual[end($this->buildStack)][$abstract])) {
729
            return $this->contextual[end($this->buildStack)][$abstract];
730
        }
731
732
        return null;
733
    }
734
735
    /**
736
     * Normalize the given class name by removing leading slashes.
737
     *
738
     * @param  mixed  $service
739
     * @return mixed
740
     */
741
    protected function normalize($service)
742
    {
743
        return is_string($service) ? ltrim($service, '\\') : $service;
744
    }
745
746
    /**
747
     * Get the extender callbacks for a given type.
748
     *
749
     * @param  string  $abstract
750
     * @return array
751
     */
752
    protected function getExtenders($abstract)
753
    {
754
        if (isset($this->extenders[$abstract])) {
755
            return $this->extenders[$abstract];
756
        }
757
758
        return [];
759
    }
760
761
    /**
762
     * Instantiate a concrete instance of the given type.
763
     *
764
     * @param  string  $concrete
765
     * @param  array   $parameters
766
     * @return mixed
767
     *
768
     * @throws BindingResolutionException
769
     * @throws \ReflectionException
770
     */
771
    public function build($concrete, array $parameters = [])
772
    {
773
        // If the concrete type is actually a Closure, we will just execute it and
774
        // hand back the results of the functions, which allows functions to be
775
        // used as resolvers for more fine-tuned resolution of these objects.
776
        if ($concrete instanceof Closure) {
0 ignored issues
show
introduced by
$concrete is never a sub-type of Closure.
Loading history...
777
            return $concrete($this, $parameters);
778
        }
779
780
        $reflector = new ReflectionClass($concrete);
781
782
        // If the type is not instantiable, the developer is attempting to resolve
783
        // an abstract type such as an Interface of Abstract Class and there is
784
        // no binding registered for the abstractions so we need to bail out.
785
        if (! $reflector->isInstantiable()) {
786
            if (! empty($this->buildStack)) {
787
                $previous = implode(', ', $this->buildStack);
788
789
                $message = "Target [$concrete] is not instantiable while building [$previous].";
790
            } else {
791
                $message = "Target [$concrete] is not instantiable.";
792
            }
793
794
            throw new BindingResolutionException($message);
795
        }
796
797
        $this->buildStack[] = $concrete;
798
799
        $constructor = $reflector->getConstructor();
800
801
        // If there are no constructors, that means there are no dependencies then
802
        // we can just resolve the instances of the objects right away, without
803
        // resolving any other types or dependencies out of these containers.
804
        if (is_null($constructor)) {
805
            array_pop($this->buildStack);
806
807
            return new $concrete;
808
        }
809
810
        $dependencies = $constructor->getParameters();
811
812
        // Once we have all the constructor's parameters we can create each of the
813
        // dependency instances and then use the reflection instances to make a
814
        // new instance of this class, injecting the created dependencies in.
815
        $parameters = $this->keyParametersByArgument(
816
            $dependencies, $parameters
817
        );
818
819
        $instances = $this->getDependencies(
820
            $dependencies, $parameters
821
        );
822
823
        array_pop($this->buildStack);
824
825
        return $reflector->newInstanceArgs($instances);
826
    }
827
828
    /**
829
     * Resolve all of the dependencies from the ReflectionParameters.
830
     *
831
     * @param  array  $parameters
832
     * @param  array  $primitives
833
     * @return array
834
     * @throws \ReflectionException
835
     * @throws BindingResolutionException
836
     */
837
    protected function getDependencies(array $parameters, array $primitives = [])
838
    {
839
        $dependencies = [];
840
841
        foreach ($parameters as $parameter) {
842
            $dependency = $parameter->getClass();
843
844
            // If the class is null, it means the dependency is a string or some other
845
            // primitive type which we can not resolve since it is not a class and
846
            // we will just bomb out with an error since we have no-where to go.
847
            if (array_key_exists($parameter->name, $primitives)) {
848
                $dependencies[] = $primitives[$parameter->name];
849
            } elseif (is_null($dependency)) {
850
                $dependencies[] = $this->resolveNonClass($parameter);
851
            } else {
852
                $dependencies[] = $this->resolveClass($parameter);
853
            }
854
        }
855
856
        return $dependencies;
857
    }
858
859
    /**
860
     * Resolve a non-class hinted dependency.
861
     *
862
     * @param  \ReflectionParameter  $parameter
863
     * @return mixed
864
     *
865
     * @throws BindingResolutionException
866
     * @throws \ReflectionException
867
     */
868
    protected function resolveNonClass(ReflectionParameter $parameter)
869
    {
870
        if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {
871
            if ($concrete instanceof Closure) {
0 ignored issues
show
introduced by
$concrete is never a sub-type of Closure.
Loading history...
872
                return call_user_func($concrete, $this);
873
            } else {
874
                return $concrete;
875
            }
876
        }
877
878
        if ($parameter->isDefaultValueAvailable()) {
879
            return $parameter->getDefaultValue();
880
        }
881
882
        $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";
883
884
        throw new BindingResolutionException($message);
885
    }
886
887
    /**
888
     * Resolve a class based dependency from the container.
889
     *
890
     * @param  \ReflectionParameter  $parameter
891
     * @return mixed
892
     *
893
     * @throws BindingResolutionException
894
     * @throws \ReflectionException
895
     */
896
    protected function resolveClass(ReflectionParameter $parameter)
897
    {
898
        try {
899
            return $this->make($parameter->getClass()->name);
900
        }
901
902
            // If we can not resolve the class instance, we will check to see if the value
903
            // is optional, and if it is we will return the optional parameter value as
904
            // the value of the dependency, similarly to how we do this with scalars.
905
        catch (BindingResolutionException $e) {
906
            if ($parameter->isOptional()) {
907
                return $parameter->getDefaultValue();
908
            }
909
910
            throw $e;
911
        }
912
    }
913
914
    /**
915
     * If extra parameters are passed by numeric ID, rekey them by argument name.
916
     *
917
     * @param  array  $dependencies
918
     * @param  array  $parameters
919
     * @return array
920
     */
921
    protected function keyParametersByArgument(array $dependencies, array $parameters)
922
    {
923
        foreach ($parameters as $key => $value) {
924
            if (is_numeric($key)) {
925
                unset($parameters[$key]);
926
927
                $parameters[$dependencies[$key]->name] = $value;
928
            }
929
        }
930
931
        return $parameters;
932
    }
933
934
    /**
935
     * Register a new resolving callback.
936
     *
937
     * @param  string    $abstract
938
     * @param  \Closure|null  $callback
939
     * @return void
940
     * @throws \ReflectionException
941
     */
942
    public function resolving($abstract, Closure $callback = null)
943
    {
944
        if ($callback === null && $abstract instanceof Closure) {
0 ignored issues
show
introduced by
$abstract is never a sub-type of Closure.
Loading history...
945
            $this->resolvingCallback($abstract);
946
        } else {
947
            $this->resolvingCallbacks[$this->normalize($abstract)][] = $callback;
948
        }
949
    }
950
951
    /**
952
     * Register a new after resolving callback for all types.
953
     *
954
     * @param  string   $abstract
955
     * @param  \Closure|null $callback
956
     * @return void
957
     * @throws \ReflectionException
958
     */
959
    public function afterResolving($abstract, Closure $callback = null)
960
    {
961
        if ($abstract instanceof Closure && $callback === null) {
0 ignored issues
show
introduced by
$abstract is never a sub-type of Closure.
Loading history...
962
            $this->afterResolvingCallback($abstract);
963
        } else {
964
            $this->afterResolvingCallbacks[$this->normalize($abstract)][] = $callback;
965
        }
966
    }
967
968
    /**
969
     * Register a new resolving callback by type of its first argument.
970
     *
971
     * @param  \Closure  $callback
972
     * @return void
973
     * @throws \ReflectionException
974
     */
975
    protected function resolvingCallback(Closure $callback)
976
    {
977
        $abstract = $this->getFunctionHint($callback);
978
979
        if ($abstract) {
980
            $this->resolvingCallbacks[$abstract][] = $callback;
981
        } else {
982
            $this->globalResolvingCallbacks[] = $callback;
983
        }
984
    }
985
986
    /**
987
     * Register a new after resolving callback by type of its first argument.
988
     *
989
     * @param  \Closure  $callback
990
     * @return void
991
     * @throws \ReflectionException
992
     */
993
    protected function afterResolvingCallback(Closure $callback)
994
    {
995
        $abstract = $this->getFunctionHint($callback);
996
997
        if ($abstract) {
998
            $this->afterResolvingCallbacks[$abstract][] = $callback;
999
        } else {
1000
            $this->globalAfterResolvingCallbacks[] = $callback;
1001
        }
1002
    }
1003
1004
    /**
1005
     * Get the type hint for this closure's first argument.
1006
     *
1007
     * @param  \Closure  $callback
1008
     * @return mixed
1009
     * @throws \ReflectionException
1010
     */
1011
    protected function getFunctionHint(Closure $callback)
1012
    {
1013
        $function = new ReflectionFunction($callback);
1014
1015
        if ($function->getNumberOfParameters() == 0) {
1016
            return;
1017
        }
1018
1019
        $expected = $function->getParameters()[0];
1020
1021
        if (! $expected->getClass()) {
1022
            return;
1023
        }
1024
1025
        return $expected->getClass()->name;
1026
    }
1027
1028
    /**
1029
     * Fire all of the resolving callbacks.
1030
     *
1031
     * @param  string  $abstract
1032
     * @param  mixed   $object
1033
     * @return void
1034
     */
1035
    protected function fireResolvingCallbacks($abstract, $object)
1036
    {
1037
        $this->fireCallbackArray($object, $this->globalResolvingCallbacks);
1038
1039
        $this->fireCallbackArray(
1040
            $object, $this->getCallbacksForType(
1041
            $abstract, $object, $this->resolvingCallbacks
1042
        )
1043
        );
1044
1045
        $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);
1046
1047
        $this->fireCallbackArray(
1048
            $object, $this->getCallbacksForType(
1049
            $abstract, $object, $this->afterResolvingCallbacks
1050
        )
1051
        );
1052
    }
1053
1054
    /**
1055
     * Get all callbacks for a given type.
1056
     *
1057
     * @param  string  $abstract
1058
     * @param  object  $object
1059
     * @param  array   $callbacksPerType
1060
     *
1061
     * @return array
1062
     */
1063
    protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
1064
    {
1065
        $results = [];
1066
1067
        foreach ($callbacksPerType as $type => $callbacks) {
1068
            if ($type === $abstract || $object instanceof $type) {
1069
                $results = array_merge($results, $callbacks);
1070
            }
1071
        }
1072
1073
        return $results;
1074
    }
1075
1076
    /**
1077
     * Fire an array of callbacks with an object.
1078
     *
1079
     * @param  mixed  $object
1080
     * @param  array  $callbacks
1081
     * @return void
1082
     */
1083
    protected function fireCallbackArray($object, array $callbacks)
1084
    {
1085
        foreach ($callbacks as $callback) {
1086
            $callback($object, $this);
1087
        }
1088
    }
1089
1090
    /**
1091
     * Determine if a given type is shared.
1092
     *
1093
     * @param  string  $abstract
1094
     * @return bool
1095
     */
1096
    public function isShared($abstract)
1097
    {
1098
        $abstract = $this->normalize($abstract);
1099
1100
        if (isset($this->instances[$abstract])) {
1101
            return true;
1102
        }
1103
1104
        if (! isset($this->bindings[$abstract]['shared'])) {
1105
            return false;
1106
        }
1107
1108
        return $this->bindings[$abstract]['shared'] === true;
1109
    }
1110
1111
    /**
1112
     * Determine if the given concrete is buildable.
1113
     *
1114
     * @param  mixed   $concrete
1115
     * @param  string  $abstract
1116
     * @return bool
1117
     */
1118
    protected function isBuildable($concrete, $abstract)
1119
    {
1120
        return $concrete === $abstract || $concrete instanceof Closure;
1121
    }
1122
1123
    /**
1124
     * Get the alias for an abstract if available.
1125
     *
1126
     * @param  string  $abstract
1127
     * @return string
1128
     */
1129
    protected function getAlias($abstract)
1130
    {
1131
        return isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract;
1132
    }
1133
1134
    /**
1135
     * Get the container's bindings.
1136
     *
1137
     * @return array
1138
     */
1139
    public function getBindings()
1140
    {
1141
        return $this->bindings;
1142
    }
1143
1144
    /**
1145
     * Drop all of the stale instances and aliases.
1146
     *
1147
     * @param  string  $abstract
1148
     * @return void
1149
     */
1150
    protected function dropStaleInstances($abstract)
1151
    {
1152
        unset($this->instances[$abstract], $this->aliases[$abstract]);
1153
    }
1154
1155
    /**
1156
     * Remove a resolved instance from the instance cache.
1157
     *
1158
     * @param  string  $abstract
1159
     * @return void
1160
     */
1161
    public function forgetInstance($abstract)
1162
    {
1163
        unset($this->instances[$this->normalize($abstract)]);
1164
    }
1165
1166
    /**
1167
     * Clear all of the instances from the container.
1168
     *
1169
     * @return void
1170
     */
1171
    public function forgetInstances()
1172
    {
1173
        $this->instances = [];
1174
    }
1175
1176
    /**
1177
     * Flush the container of all bindings and resolved instances.
1178
     *
1179
     * @return void
1180
     */
1181
    public function flush()
1182
    {
1183
        $this->aliases = [];
1184
        $this->resolved = [];
1185
        $this->bindings = [];
1186
        $this->instances = [];
1187
    }
1188
1189
    /**
1190
     * Set the globally available instance of the container.
1191
     *
1192
     * @return static
1193
     */
1194
    public static function getInstance()
1195
    {
1196
        return static::$instance;
1197
    }
1198
1199
    /**
1200
     * Set the shared instance of the container.
1201
     *
1202
     * @param  ContainerInterface  $container
1203
     * @return void
1204
     */
1205
    public static function setInstance(ContainerInterface $container)
1206
    {
1207
        static::$instance = $container;
0 ignored issues
show
Documentation Bug introduced by
$container is of type app\framework\Component\...iner\ContainerInterface, but the property $instance was declared to be of type app\framework\Component\Container\Container. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
1208
    }
1209
1210
    /**
1211
     * Determine if a given offset exists.
1212
     *
1213
     * @param  string  $key
1214
     * @return bool
1215
     */
1216
    public function offsetExists($key)
1217
    {
1218
        return isset($this->bindings[$this->normalize($key)]);
1219
    }
1220
1221
    /**
1222
     * Get the value at a given offset.
1223
     *
1224
     * @param  string  $key
1225
     * @return mixed
1226
     * @throws BindingResolutionException
1227
     * @throws \ReflectionException
1228
     */
1229
    public function offsetGet($key)
1230
    {
1231
        return $this->make($key);
1232
    }
1233
1234
    /**
1235
     * Set the value at a given offset.
1236
     *
1237
     * @param  string  $key
1238
     * @param  mixed   $value
1239
     * @return void
1240
     * @throws BindingResolutionException
1241
     * @throws \ReflectionException
1242
     */
1243
    public function offsetSet($key, $value)
1244
    {
1245
        // If the value is not a Closure, we will make it one. This simply gives
1246
        // more "drop-in" replacement functionality for the Pimple which this
1247
        // container's simplest functions are base modeled and built after.
1248
        if (! $value instanceof Closure) {
1249
            $value = function () use ($value) {
1250
                return $value;
1251
            };
1252
        }
1253
1254
        $this->bind($key, $value);
1255
    }
1256
1257
    /**
1258
     * Unset the value at a given offset.
1259
     *
1260
     * @param  string  $key
1261
     * @return void
1262
     */
1263
    public function offsetUnset($key)
1264
    {
1265
        $key = $this->normalize($key);
1266
1267
        unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
1268
    }
1269
1270
    /**
1271
     * Dynamically access container services.
1272
     *
1273
     * @param  string  $key
1274
     * @return mixed
1275
     */
1276
    public function __get($key)
1277
    {
1278
        return $this[$key];
1279
    }
1280
1281
    /**
1282
     * Dynamically set container services.
1283
     *
1284
     * @param  string  $key
1285
     * @param  mixed   $value
1286
     * @return void
1287
     */
1288
    public function __set($key, $value)
1289
    {
1290
        $this[$key] = $value;
1291
    }
1292
}
1293