PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 2.17.0
GiveWP – Donation Plugin and Fundraising Platform v2.17.0
4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 All 253 releases
give / src / Container / Container.php
Container.php
1,186 lines 27.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\Container;
4
5 use ArrayAccess;
6 use Closure;
7 use Exception;
8 use Give\Container\Exceptions\BindingResolutionException;
9 use Give\Framework\Exceptions\Primitives\InvalidArgumentException;
10 use Give\Framework\Exceptions\Primitives\LogicException;
11 use ReflectionClass;
12 use ReflectionException;
13 use ReflectionParameter;
14
15 class Container implements ArrayAccess {
16 /**
17 * An array of the types that have been resolved.
18 *
19 * @var bool[]
20 */
21 protected $resolved = [];
22
23 /**
24 * The container's bindings.
25 *
26 * @var array[]
27 */
28 protected $bindings = [];
29
30 /**
31 * The container's method bindings.
32 *
33 * @var Closure[]
34 */
35 protected $methodBindings = [];
36
37 /**
38 * The container's shared instances.
39 *
40 * @var object[]
41 */
42 protected $instances = [];
43
44 /**
45 * The registered type aliases.
46 *
47 * @var string[]
48 */
49 protected $aliases = [];
50
51 /**
52 * The registered aliases keyed by the abstract name.
53 *
54 * @var array[]
55 */
56 protected $abstractAliases = [];
57
58 /**
59 * The extension closures for services.
60 *
61 * @var array[]
62 */
63 protected $extenders = [];
64
65 /**
66 * All of the registered tags.
67 *
68 * @var array[]
69 */
70 protected $tags = [];
71
72 /**
73 * The stack of concretions currently being built.
74 *
75 * @var array[]
76 */
77 protected $buildStack = [];
78
79 /**
80 * The parameter override stack.
81 *
82 * @var array[]
83 */
84 protected $with = [];
85
86 /**
87 * All of the registered rebound callbacks.
88 *
89 * @var array[]
90 */
91 protected $reboundCallbacks = [];
92
93 /**
94 * All of the global resolving callbacks.
95 *
96 * @var Closure[]
97 */
98 protected $globalResolvingCallbacks = [];
99
100 /**
101 * All of the global after resolving callbacks.
102 *
103 * @var Closure[]
104 */
105 protected $globalAfterResolvingCallbacks = [];
106
107 /**
108 * All of the resolving callbacks by class type.
109 *
110 * @var array[]
111 */
112 protected $resolvingCallbacks = [];
113
114 /**
115 * All of the after resolving callbacks by class type.
116 *
117 * @var array[]
118 */
119 protected $afterResolvingCallbacks = [];
120
121 /**
122 * Determine if the given abstract type has been bound.
123 *
124 * @param string $abstract
125 *
126 * @return bool
127 */
128 public function bound( $abstract ) {
129 return isset( $this->bindings[ $abstract ] ) ||
130 isset( $this->instances[ $abstract ] ) ||
131 $this->isAlias( $abstract );
132 }
133
134 /**
135 * Returns true if the container can return an entry for the given identifier.
136 * Returns false otherwise.
137 *
138 * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
139 * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
140 *
141 * @param string $id Identifier of the entry to look for.
142 *
143 * @return bool
144 */
145 public function has( $id ) {
146 return $this->bound( $id );
147 }
148
149 /**
150 * Determine if the given abstract type has been resolved.
151 *
152 * @param string $abstract
153 *
154 * @return bool
155 */
156 public function resolved( $abstract ) {
157 if ( $this->isAlias( $abstract ) ) {
158 $abstract = $this->getAlias( $abstract );
159 }
160
161 return isset( $this->resolved[ $abstract ] ) ||
162 isset( $this->instances[ $abstract ] );
163 }
164
165 /**
166 * Determine if a given type is shared.
167 *
168 * @param string $abstract
169 *
170 * @return bool
171 */
172 public function isShared( $abstract ) {
173 return isset( $this->instances[ $abstract ] ) ||
174 ( isset( $this->bindings[ $abstract ]['shared'] ) &&
175 $this->bindings[ $abstract ]['shared'] === true );
176 }
177
178 /**
179 * Determine if a given string is an alias.
180 *
181 * @param string $name
182 *
183 * @return bool
184 */
185 public function isAlias( $name ) {
186 return isset( $this->aliases[ $name ] );
187 }
188
189 /**
190 * Register a binding with the container.
191 *
192 * @param string $abstract
193 * @param Closure|string|null $concrete
194 * @param bool $shared
195 *
196 * @return void
197 */
198 public function bind( $abstract, $concrete = null, $shared = false ) {
199 $this->dropStaleInstances( $abstract );
200
201 // If no concrete type was given, we will simply set the concrete type to the
202 // abstract type. After that, the concrete type to be registered as shared
203 // without being forced to state their classes in both of the parameters.
204 if ( is_null( $concrete ) ) {
205 $concrete = $abstract;
206 }
207
208 // If the factory is not a Closure, it means it is just a class name which is
209 // bound into this container to the abstract type and we will just wrap it
210 // up inside its own Closure to give us more convenience when extending.
211 if ( ! $concrete instanceof Closure ) {
212 $concrete = $this->getClosure( $abstract, $concrete );
213 }
214
215 $this->bindings[ $abstract ] = compact( 'concrete', 'shared' );
216
217 // If the abstract type was already resolved in this container we'll fire the
218 // rebound listener so that any objects which have already gotten resolved
219 // can have their copy of the object updated via the listener callbacks.
220 if ( $this->resolved( $abstract ) ) {
221 $this->rebound( $abstract );
222 }
223 }
224
225 /**
226 * Get the Closure to be used when building a type.
227 *
228 * @param string $abstract
229 * @param string $concrete
230 *
231 * @return Closure
232 */
233 protected function getClosure( $abstract, $concrete ) {
234 return function ( $container, $parameters = [] ) use ( $abstract, $concrete ) {
235 if ( $abstract == $concrete ) {
236 return $container->build( $concrete );
237 }
238
239 return $container->resolve(
240 $concrete,
241 $parameters,
242 $raiseEvents = false
243 );
244 };
245 }
246
247 /**
248 * Determine if the container has a method binding.
249 *
250 * @param string $method
251 *
252 * @return bool
253 */
254 public function hasMethodBinding( $method ) {
255 return isset( $this->methodBindings[ $method ] );
256 }
257
258 /**
259 * Bind a callback to resolve with Container::call.
260 *
261 * @param array|string $method
262 * @param Closure $callback
263 *
264 * @return void
265 */
266 public function bindMethod( $method, $callback ) {
267 $this->methodBindings[ $this->parseBindMethod( $method ) ] = $callback;
268 }
269
270 /**
271 * Get the method to be bound in class@method format.
272 *
273 * @param array|string $method
274 *
275 * @return string
276 */
277 protected function parseBindMethod( $method ) {
278 if ( is_array( $method ) ) {
279 return $method[0] . '@' . $method[1];
280 }
281
282 return $method;
283 }
284
285 /**
286 * Get the method binding for the given method.
287 *
288 * @param string $method
289 * @param mixed $instance
290 *
291 * @return mixed
292 */
293 public function callMethodBinding( $method, $instance ) {
294 return call_user_func( $this->methodBindings[ $method ], $instance, $this );
295 }
296
297 /**
298 * Register a binding if it hasn't already been registered.
299 *
300 * @param string $abstract
301 * @param Closure|string|null $concrete
302 * @param bool $shared
303 *
304 * @return void
305 */
306 public function bindIf( $abstract, $concrete = null, $shared = false ) {
307 if ( ! $this->bound( $abstract ) ) {
308 $this->bind( $abstract, $concrete, $shared );
309 }
310 }
311
312 /**
313 * Register a shared binding in the container.
314 *
315 * @param string $abstract
316 * @param Closure|string|null $concrete
317 *
318 * @return void
319 */
320 public function singleton( $abstract, $concrete = null ) {
321 $this->bind( $abstract, $concrete, true );
322 }
323
324 /**
325 * Register a shared binding if it hasn't already been registered.
326 *
327 * @param string $abstract
328 * @param Closure|string|null $concrete
329 *
330 * @return void
331 */
332 public function singletonIf( $abstract, $concrete = null ) {
333 if ( ! $this->bound( $abstract ) ) {
334 $this->singleton( $abstract, $concrete );
335 }
336 }
337
338 /**
339 * "Extend" an abstract type in the container.
340 *
341 * @param string $abstract
342 * @param Closure $closure
343 *
344 * @return void
345 *
346 * @throws \InvalidArgumentException
347 */
348 public function extend( $abstract, Closure $closure ) {
349 $abstract = $this->getAlias( $abstract );
350
351 if ( isset( $this->instances[ $abstract ] ) ) {
352 $this->instances[ $abstract ] = $closure( $this->instances[ $abstract ], $this );
353
354 $this->rebound( $abstract );
355 } else {
356 $this->extenders[ $abstract ][] = $closure;
357
358 if ( $this->resolved( $abstract ) ) {
359 $this->rebound( $abstract );
360 }
361 }
362 }
363
364 /**
365 * Register an existing instance as shared in the container.
366 *
367 * @param string $abstract
368 * @param mixed $instance
369 *
370 * @return mixed
371 */
372 public function instance( $abstract, $instance ) {
373 $this->removeAbstractAlias( $abstract );
374
375 $isBound = $this->bound( $abstract );
376
377 unset( $this->aliases[ $abstract ] );
378
379 // We'll check to determine if this type has been bound before, and if it has
380 // we will fire the rebound callbacks registered with the container and it
381 // can be updated with consuming classes that have gotten resolved here.
382 $this->instances[ $abstract ] = $instance;
383
384 if ( $isBound ) {
385 $this->rebound( $abstract );
386 }
387
388 return $instance;
389 }
390
391 /**
392 * Remove an alias from the contextual binding alias cache.
393 *
394 * @param string $searched
395 *
396 * @return void
397 */
398 protected function removeAbstractAlias( $searched ) {
399 if ( ! isset( $this->aliases[ $searched ] ) ) {
400 return;
401 }
402
403 foreach ( $this->abstractAliases as $abstract => $aliases ) {
404 foreach ( $aliases as $index => $alias ) {
405 if ( $alias == $searched ) {
406 unset( $this->abstractAliases[ $abstract ][ $index ] );
407 }
408 }
409 }
410 }
411
412 /**
413 * Assign a set of tags to a given binding.
414 *
415 * @param array|string $abstracts
416 * @param array|mixed ...$tags
417 *
418 * @return void
419 */
420 public function tag( $abstracts, $tags ) {
421 $tags = is_array( $tags ) ? $tags : array_slice( func_get_args(), 1 );
422
423 foreach ( $tags as $tag ) {
424 if ( ! isset( $this->tags[ $tag ] ) ) {
425 $this->tags[ $tag ] = [];
426 }
427
428 foreach ( (array) $abstracts as $abstract ) {
429 $this->tags[ $tag ][] = $abstract;
430 }
431 }
432 }
433
434 /**
435 * Alias a type to a different name.
436 *
437 * @param string $abstract
438 * @param string $alias
439 *
440 * @return void
441 *
442 * @throws LogicException
443 */
444 public function alias( $abstract, $alias ) {
445 if ( $alias === $abstract ) {
446 throw new LogicException( "[{$abstract}] is aliased to itself." );
447 }
448
449 $this->aliases[ $alias ] = $abstract;
450
451 $this->abstractAliases[ $abstract ][] = $alias;
452 }
453
454 /**
455 * Bind a new callback to an abstract's rebind event.
456 *
457 * @param string $abstract
458 * @param Closure $callback
459 *
460 * @return mixed
461 */
462 public function rebinding( $abstract, Closure $callback ) {
463 $this->reboundCallbacks[ $abstract = $this->getAlias( $abstract ) ][] = $callback;
464
465 if ( $this->bound( $abstract ) ) {
466 return $this->make( $abstract );
467 }
468
469 return null;
470 }
471
472 /**
473 * Refresh an instance on the given target and method.
474 *
475 * @param string $abstract
476 * @param mixed $target
477 * @param string $method
478 *
479 * @return mixed
480 */
481 public function refresh( $abstract, $target, $method ) {
482 return $this->rebinding(
483 $abstract,
484 function ( $app, $instance ) use ( $target, $method ) {
485 $target->{$method}( $instance );
486 }
487 );
488 }
489
490 /**
491 * Fire the "rebound" callbacks for the given abstract type.
492 *
493 * @param string $abstract
494 *
495 * @return void
496 */
497 protected function rebound( $abstract ) {
498 $instance = $this->make( $abstract );
499
500 foreach ( $this->getReboundCallbacks( $abstract ) as $callback ) {
501 $callback( $this, $instance );
502 }
503 }
504
505 /**
506 * Get the rebound callbacks for a given type.
507 *
508 * @param string $abstract
509 *
510 * @return array
511 */
512 protected function getReboundCallbacks( $abstract ) {
513 return isset( $this->reboundCallbacks[ $abstract ] ) ? $this->reboundCallbacks[ $abstract ] : [];
514 }
515
516 /**
517 * Get a closure to resolve the given type from the container.
518 *
519 * @param string $abstract
520 *
521 * @return Closure
522 */
523 public function factory( $abstract ) {
524 return function () use ( $abstract ) {
525 return $this->make( $abstract );
526 };
527 }
528
529 /**
530 * Resolve the given type from the container.
531 *
532 * @param string $abstract
533 * @param array $parameters
534 *
535 * @return mixed
536 */
537 public function make( $abstract, array $parameters = [] ) {
538 return $this->resolve( $abstract, $parameters );
539 }
540
541 /**
542 * Finds an entry of the container by its identifier and returns it.
543 *
544 * @param string $id Identifier of the entry to look for.
545 *
546 * @return mixed Entry.
547 * @throws InvalidArgumentException|BindingResolutionException
548 */
549 public function get( $id ) {
550 try {
551 return $this->resolve( $id );
552 } catch ( Exception $e ) {
553 if ( $this->has( $id ) ) {
554 throw $e;
555 }
556
557 throw new InvalidArgumentException( $id, $e->getCode(), $e );
558 }
559 }
560
561 /**
562 * Resolve the given type from the container.
563 *
564 * @param string $abstract
565 * @param array $parameters
566 * @param bool $raiseEvents
567 *
568 * @return mixed
569 * @throws BindingResolutionException
570 */
571 protected function resolve( $abstract, $parameters = [], $raiseEvents = true ) {
572 $abstract = $this->getAlias( $abstract );
573
574 $concrete = null;
575
576 $needsContextualBuild = false;
577
578 // If an instance of the type is currently being managed as a singleton we'll
579 // just return an existing instance instead of instantiating new instances
580 // so the developer can keep using the same objects instance every time.
581 if ( isset( $this->instances[ $abstract ] ) && ! $needsContextualBuild ) {
582 return $this->instances[ $abstract ];
583 }
584
585 $this->with[] = $parameters;
586
587 if ( is_null( $concrete ) ) {
588 $concrete = $this->getConcrete( $abstract );
589 }
590
591 // We're ready to instantiate an instance of the concrete type registered for
592 // the binding. This will instantiate the types, as well as resolve any of
593 // its "nested" dependencies recursively until all have gotten resolved.
594 if ( $this->isBuildable( $concrete, $abstract ) ) {
595 $object = $this->build( $concrete );
596 } else {
597 $object = $this->make( $concrete );
598 }
599
600 // If we defined any extenders for this type, we'll need to spin through them
601 // and apply them to the object being built. This allows for the extension
602 // of services, such as changing configuration or decorating the object.
603 foreach ( $this->getExtenders( $abstract ) as $extender ) {
604 $object = $extender( $object, $this );
605 }
606
607 // If the requested type is registered as a singleton we'll want to cache off
608 // the instances in "memory" so we can return it later without creating an
609 // entirely new instance of an object on each subsequent request for it.
610 if ( $this->isShared( $abstract ) ) {
611 $this->instances[ $abstract ] = $object;
612 }
613
614 if ( $raiseEvents ) {
615 $this->fireResolvingCallbacks( $abstract, $object );
616 }
617
618 // Before returning, we will also set the resolved flag to "true" and pop off
619 // the parameter overrides for this build. After those two things are done
620 // we will be ready to return back the fully constructed class instance.
621 $this->resolved[ $abstract ] = true;
622
623 array_pop( $this->with );
624
625 return $object;
626 }
627
628 /**
629 * Get the concrete type for a given abstract.
630 *
631 * @param string $abstract
632 *
633 * @return mixed
634 */
635 protected function getConcrete( $abstract ) {
636 // If we don't have a registered resolver or concrete for the type, we'll just
637 // assume each type is a concrete name and will attempt to resolve it as is
638 // since the container should be able to resolve concretes automatically.
639 if ( isset( $this->bindings[ $abstract ] ) ) {
640 return $this->bindings[ $abstract ]['concrete'];
641 }
642
643 return $abstract;
644 }
645
646 /**
647 * Determine if the given concrete is buildable.
648 *
649 * @param mixed $concrete
650 * @param string $abstract
651 *
652 * @return bool
653 */
654 protected function isBuildable( $concrete, $abstract ) {
655 return $concrete === $abstract || $concrete instanceof Closure;
656 }
657
658 /**
659 * Instantiate a concrete instance of the given type.
660 *
661 * @param Closure|string $concrete
662 *
663 * @return mixed
664 * @throws BindingResolutionException
665 * @throws ReflectionException
666 */
667 public function build( $concrete ) {
668 // If the concrete type is actually a Closure, we will just execute it and
669 // hand back the results of the functions, which allows functions to be
670 // used as resolvers for more fine-tuned resolution of these objects.
671 if ( $concrete instanceof Closure ) {
672 return $concrete( $this, $this->getLastParameterOverride() );
673 }
674
675 try {
676 $reflector = new ReflectionClass( $concrete );
677 } catch ( ReflectionException $e ) {
678 throw new InvalidArgumentException( "Target class [$concrete] does not exist.", 0, $e );
679 }
680
681 // If the type is not instantiable, the developer is attempting to resolve
682 // an abstract type such as an Interface or Abstract Class and there is
683 // no binding registered for the abstractions so we need to bail out.
684 if ( ! $reflector->isInstantiable() ) {
685 $this->notInstantiable( $concrete );
686 }
687
688 $this->buildStack[] = $concrete;
689
690 $constructor = $reflector->getConstructor();
691
692 // If there are no constructors, that means there are no dependencies then
693 // we can just resolve the instances of the objects right away, without
694 // resolving any other types or dependencies out of these containers.
695 if ( is_null( $constructor ) ) {
696 array_pop( $this->buildStack );
697
698 return new $concrete;
699 }
700
701 $dependencies = $constructor->getParameters();
702
703 // Once we have all the constructor's parameters we can create each of the
704 // dependency instances and then use the reflection instances to make a
705 // new instance of this class, injecting the created dependencies in.
706 try {
707 $instances = $this->resolveDependencies( $dependencies );
708 } catch ( BindingResolutionException $e ) {
709 array_pop( $this->buildStack );
710
711 throw $e;
712 }
713
714 array_pop( $this->buildStack );
715
716 return $reflector->newInstanceArgs( $instances );
717 }
718
719 /**
720 * Resolve all of the dependencies from the ReflectionParameters.
721 *
722 * @param array $dependencies
723 *
724 * @return array
725 * @throws BindingResolutionException
726 * @throws ReflectionException
727 */
728 protected function resolveDependencies( array $dependencies ) {
729 $results = [];
730
731 foreach ( $dependencies as $dependency ) {
732 // If this dependency has a override for this particular build we will use
733 // that instead as the value. Otherwise, we will continue with this run
734 // of resolutions and let reflection attempt to determine the result.
735 if ( $this->hasParameterOverride( $dependency ) ) {
736 $results[] = $this->getParameterOverride( $dependency );
737
738 continue;
739 }
740
741 $name = $this->getParameterClassName($dependency);
742
743 // If the class is null, it means the dependency is a string or some other
744 // primitive type which we can not resolve since it is not a class and
745 // we will just bomb out with an error since we have no-where to go.
746 $result = is_null( $name )
747 ? $this->resolvePrimitive( $dependency )
748 : $this->resolveClass( $dependency );
749
750 if ( $dependency->isVariadic() ) {
751 array_push( $results, ...$result );
752 } else {
753 $results[] = $result;
754 }
755 }
756
757 return $results;
758 }
759
760 /**
761 * Determine if the given dependency has a parameter override.
762 *
763 * @param ReflectionParameter $dependency
764 *
765 * @return bool
766 */
767 protected function hasParameterOverride( $dependency ) {
768 return array_key_exists(
769 $dependency->name,
770 $this->getLastParameterOverride()
771 );
772 }
773
774 /**
775 * Get a parameter override for a dependency.
776 *
777 * @param ReflectionParameter $dependency
778 *
779 * @return mixed
780 */
781 protected function getParameterOverride( $dependency ) {
782 return $this->getLastParameterOverride()[ $dependency->name ];
783 }
784
785 /**
786 * Get the last parameter override.
787 *
788 * @return array
789 */
790 protected function getLastParameterOverride() {
791 return count( $this->with ) ? end( $this->with ) : [];
792 }
793
794 /**
795 * Resolve a non-class hinted primitive dependency.
796 *
797 * @param ReflectionParameter $parameter
798 *
799 * @return mixed
800 * @throws ReflectionException
801 */
802 protected function resolvePrimitive( ReflectionParameter $parameter ) {
803 if ( $parameter->isDefaultValueAvailable() ) {
804 return $parameter->getDefaultValue();
805 }
806
807 $this->unresolvablePrimitive( $parameter );
808
809 return null;
810 }
811
812 /**
813 * Resolve a class based dependency from the container.
814 *
815 * @param ReflectionParameter $parameter
816 *
817 * @return mixed
818 * @throws BindingResolutionException
819 * @throws ReflectionException
820 */
821 protected function resolveClass( ReflectionParameter $parameter ) {
822 try {
823 $class = $this->getParameterClassName($parameter);
824
825 if ( is_null( $class ) ) {
826 throw new BindingResolutionException();
827 }
828
829 return $this->make($class);
830 }
831
832 // If we can not resolve the class instance, we will check to see if the value
833 // is optional, and if it is we will return the optional parameter value as
834 // the value of the dependency, similarly to how we do this with scalars.
835 catch ( BindingResolutionException $e ) {
836 if ( $parameter->isDefaultValueAvailable() ) {
837 return $parameter->getDefaultValue();
838 }
839
840 if ( $parameter->isVariadic() ) {
841 return [];
842 }
843
844 throw $e;
845 }
846 }
847
848 /**
849 * Throw an exception that the concrete is not instantiable.
850 *
851 * @param string $concrete
852 *
853 * @return void
854 * @throws BindingResolutionException
855 */
856 protected function notInstantiable( $concrete ) {
857 if ( ! empty( $this->buildStack ) ) {
858 $previous = implode( ', ', $this->buildStack );
859
860 $message = "Target [$concrete] is not instantiable while building [$previous].";
861 } else {
862 $message = "Target [$concrete] is not instantiable.";
863 }
864
865 throw new BindingResolutionException( $message );
866 }
867
868 /**
869 * Throw an exception for an unresolvable primitive.
870 *
871 * @param ReflectionParameter $parameter
872 *
873 * @return void
874 * @throws BindingResolutionException
875 */
876 protected function unresolvablePrimitive( ReflectionParameter $parameter ) {
877 $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";
878
879 throw new BindingResolutionException( $message );
880 }
881
882 /**
883 * Register a new resolving callback.
884 *
885 * @param Closure|string $abstract
886 * @param Closure|null $callback
887 *
888 * @return void
889 */
890 public function resolving( $abstract, Closure $callback = null ) {
891 if ( is_string( $abstract ) ) {
892 $abstract = $this->getAlias( $abstract );
893 }
894
895 if ( is_null( $callback ) && $abstract instanceof Closure ) {
896 $this->globalResolvingCallbacks[] = $abstract;
897 } else {
898 $this->resolvingCallbacks[ $abstract ][] = $callback;
899 }
900 }
901
902 /**
903 * Register a new after resolving callback for all types.
904 *
905 * @param Closure|string $abstract
906 * @param Closure|null $callback
907 *
908 * @return void
909 */
910 public function afterResolving( $abstract, Closure $callback = null ) {
911 if ( is_string( $abstract ) ) {
912 $abstract = $this->getAlias( $abstract );
913 }
914
915 if ( $abstract instanceof Closure && is_null( $callback ) ) {
916 $this->globalAfterResolvingCallbacks[] = $abstract;
917 } else {
918 $this->afterResolvingCallbacks[ $abstract ][] = $callback;
919 }
920 }
921
922 /**
923 * Fire all of the resolving callbacks.
924 *
925 * @param string $abstract
926 * @param mixed $object
927 *
928 * @return void
929 */
930 protected function fireResolvingCallbacks( $abstract, $object ) {
931 $this->fireCallbackArray( $object, $this->globalResolvingCallbacks );
932
933 $this->fireCallbackArray(
934 $object,
935 $this->getCallbacksForType( $abstract, $object, $this->resolvingCallbacks )
936 );
937
938 $this->fireAfterResolvingCallbacks( $abstract, $object );
939 }
940
941 /**
942 * Fire all of the after resolving callbacks.
943 *
944 * @param string $abstract
945 * @param mixed $object
946 *
947 * @return void
948 */
949 protected function fireAfterResolvingCallbacks( $abstract, $object ) {
950 $this->fireCallbackArray( $object, $this->globalAfterResolvingCallbacks );
951
952 $this->fireCallbackArray(
953 $object,
954 $this->getCallbacksForType( $abstract, $object, $this->afterResolvingCallbacks )
955 );
956 }
957
958 /**
959 * Get all callbacks for a given type.
960 *
961 * @param string $abstract
962 * @param object $object
963 * @param array $callbacksPerType
964 *
965 * @return array
966 */
967 protected function getCallbacksForType( $abstract, $object, array $callbacksPerType ) {
968 $results = [];
969
970 foreach ( $callbacksPerType as $type => $callbacks ) {
971 if ( $type === $abstract || $object instanceof $type ) {
972 array_push( $results, ...$callbacks );
973 }
974 }
975
976 return $results;
977 }
978
979 /**
980 * Fire an array of callbacks with an object.
981 *
982 * @param mixed $object
983 * @param array $callbacks
984 *
985 * @return void
986 */
987 protected function fireCallbackArray( $object, array $callbacks ) {
988 foreach ( $callbacks as $callback ) {
989 $callback( $object, $this );
990 }
991 }
992
993 /**
994 * Retrieves the class name of a given parameter with respect to the PHP version
995 *
996 * @param ReflectionParameter $parameter
997 *
998 * @return string
999 */
1000 protected function getParameterClassName( ReflectionParameter $parameter ) {
1001 // Use ReflectionParameter::getClass() prior to its replacement in PHP 7.1
1002 if ( version_compare( PHP_VERSION, '7.1', '<' ) ) {
1003 $class = $parameter->getClass();
1004 return $class ? $class->name : null;
1005 }
1006
1007 return $parameter->hasType() ? $parameter->getType()->getName() : null;
1008 }
1009
1010 /**
1011 * Get the container's bindings.
1012 *
1013 * @return array
1014 */
1015 public function getBindings() {
1016 return $this->bindings;
1017 }
1018
1019 /**
1020 * Get the alias for an abstract if available.
1021 *
1022 * @param string $abstract
1023 *
1024 * @return string
1025 */
1026 public function getAlias( $abstract ) {
1027 if ( ! isset( $this->aliases[ $abstract ] ) ) {
1028 return $abstract;
1029 }
1030
1031 return $this->getAlias( $this->aliases[ $abstract ] );
1032 }
1033
1034 /**
1035 * Get the extender callbacks for a given type.
1036 *
1037 * @param string $abstract
1038 *
1039 * @return array
1040 */
1041 protected function getExtenders( $abstract ) {
1042 $abstract = $this->getAlias( $abstract );
1043
1044 return isset( $this->extenders[ $abstract ] ) ? $this->extenders[ $abstract ] : [];
1045 }
1046
1047 /**
1048 * Remove all of the extender callbacks for a given type.
1049 *
1050 * @param string $abstract
1051 *
1052 * @return void
1053 */
1054 public function forgetExtenders( $abstract ) {
1055 unset( $this->extenders[ $this->getAlias( $abstract ) ] );
1056 }
1057
1058 /**
1059 * Drop all of the stale instances and aliases.
1060 *
1061 * @param string $abstract
1062 *
1063 * @return void
1064 */
1065 protected function dropStaleInstances( $abstract ) {
1066 unset( $this->instances[ $abstract ], $this->aliases[ $abstract ] );
1067 }
1068
1069 /**
1070 * Remove a resolved instance from the instance cache.
1071 *
1072 * @param string $abstract
1073 *
1074 * @return void
1075 */
1076 public function forgetInstance( $abstract ) {
1077 unset( $this->instances[ $abstract ] );
1078 }
1079
1080 /**
1081 * Clear all of the instances from the container.
1082 *
1083 * @return void
1084 */
1085 public function forgetInstances() {
1086 $this->instances = [];
1087 }
1088
1089 /**
1090 * Flush the container of all bindings and resolved instances.
1091 *
1092 * @return void
1093 */
1094 public function flush() {
1095 $this->aliases = [];
1096 $this->resolved = [];
1097 $this->bindings = [];
1098 $this->instances = [];
1099 $this->abstractAliases = [];
1100 }
1101
1102 /**
1103 * Determine if a given offset exists.
1104 *
1105 * @param string $key
1106 *
1107 * @return bool
1108 */
1109 public function offsetExists( $key ) {
1110 return $this->bound( $key );
1111 }
1112
1113 /**
1114 * Get the value at a given offset.
1115 *
1116 * @param string $key
1117 *
1118 * @return mixed
1119 */
1120 public function offsetGet( $key ) {
1121 return $this->make( $key );
1122 }
1123
1124 /**
1125 * Set the value at a given offset.
1126 *
1127 * @param string $key
1128 * @param mixed $value
1129 *
1130 * @return void
1131 */
1132 public function offsetSet( $key, $value ) {
1133 $this->bind(
1134 $key,
1135 $value instanceof Closure ? $value : function () use ( $value ) {
1136 return $value;
1137 }
1138 );
1139 }
1140
1141 /**
1142 * Unset the value at a given offset.
1143 *
1144 * @param string $key
1145 *
1146 * @return void
1147 */
1148 public function offsetUnset( $key ) {
1149 unset( $this->bindings[ $key ], $this->instances[ $key ], $this->resolved[ $key ] );
1150 }
1151
1152 /**
1153 * Dynamically access container services.
1154 *
1155 * @param string $key
1156 *
1157 * @return mixed
1158 */
1159 public function __get( $key ) {
1160 return $this[ $key ];
1161 }
1162
1163 /**
1164 * Dynamically set container services.
1165 *
1166 * @param string $key
1167 * @param mixed $value
1168 *
1169 * @return void
1170 */
1171 public function __set( $key, $value ) {
1172 $this[ $key ] = $value;
1173 }
1174
1175 /**
1176 * Checks to see if the key exists.
1177 *
1178 * @param $key
1179 *
1180 * @return bool
1181 */
1182 public function __isset( $key ) {
1183 return isset( $this[ $key ] );
1184 }
1185 }
1186