PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.21
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.21
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Container / Container.php

Container.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.21, at vendor/wpfluent/framework/src/WPFluent/Container/Container.php

1,472 lines 39.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\Framework\Container;
4
5 use Closure;
6 use Exception;
7 use TypeError;
8 use ArrayAccess;
9 use LogicException;
10 use ReflectionClass;
11 use ReflectionException;
12 use ReflectionParameter;
13 use FluentBooking\Framework\Container\Contracts\BindingResolutionException;
14 use FluentBooking\Framework\Container\Contracts\CircularDependencyException;
15 use FluentBooking\Framework\Container\Contracts\Container as ContainerContract;
16
17 class Container implements ArrayAccess, ContainerContract
18 {
19 /**
20 * The current globally available container (if any).
21 *
22 * @var static
23 */
24 protected static $instance;
25
26 /**
27 * An array of the types that have been resolved.
28 *
29 * @var bool[]
30 */
31 protected $resolved = [];
32
33 /**
34 * The container's bindings.
35 *
36 * @var array[]
37 */
38 protected $bindings = [];
39
40 /**
41 * The container's method bindings.
42 *
43 * @var \Closure[]
44 */
45 protected $methodBindings = [];
46
47 /**
48 * The container's shared instances.
49 *
50 * @var object[]
51 */
52 protected $instances = [];
53
54 /**
55 * The container's scoped instances.
56 *
57 * @var array
58 */
59 protected $scopedInstances = [];
60
61 /**
62 * The registered type aliases.
63 *
64 * @var string[]
65 */
66 protected $aliases = [];
67
68 /**
69 * The registered aliases keyed by the abstract name.
70 *
71 * @var array[]
72 */
73 protected $abstractAliases = [];
74
75 /**
76 * The extension closures for services.
77 *
78 * @var array[]
79 */
80 protected $extenders = [];
81
82 /**
83 * All of the registered tags.
84 *
85 * @var array[]
86 */
87 protected $tags = [];
88
89 /**
90 * The stack of concretions currently being built.
91 *
92 * @var array[]
93 */
94 protected $buildStack = [];
95
96 /**
97 * The parameter override stack.
98 *
99 * @var array[]
100 */
101 protected $with = [];
102
103 /**
104 * The contextual binding map.
105 *
106 * @var array[]
107 */
108 public $contextual = [];
109
110 /**
111 * All of the registered rebound callbacks.
112 *
113 * @var array[]
114 */
115 protected $reboundCallbacks = [];
116
117 /**
118 * All of the global before resolving callbacks.
119 *
120 * @var \Closure[]
121 */
122 protected $globalBeforeResolvingCallbacks = [];
123
124 /**
125 * All of the global resolving callbacks.
126 *
127 * @var \Closure[]
128 */
129 protected $globalResolvingCallbacks = [];
130
131 /**
132 * All of the global after resolving callbacks.
133 *
134 * @var \Closure[]
135 */
136 protected $globalAfterResolvingCallbacks = [];
137
138 /**
139 * All of the before resolving callbacks by class type.
140 *
141 * @var array[]
142 */
143 protected $beforeResolvingCallbacks = [];
144
145 /**
146 * All of the resolving callbacks by class type.
147 *
148 * @var array[]
149 */
150 protected $resolvingCallbacks = [];
151
152 /**
153 * All of the after resolving callbacks by class type.
154 *
155 * @var array[]
156 */
157 protected $afterResolvingCallbacks = [];
158
159 /**
160 * Define a contextual binding.
161 *
162 * @param array|string $concrete
163 * @return \FluentBooking\Framework\Container\Contracts\ContextualBindingBuilder
164 */
165 public function when($concrete)
166 {
167 $aliases = [];
168
169 foreach (Util::arrayWrap($concrete) as $c) {
170 $aliases[] = $this->getAlias($c);
171 }
172
173 return new ContextualBindingBuilder($this, $aliases);
174 }
175
176 /**
177 * Determine if the given abstract type has been bound.
178 *
179 * @param string $abstract
180 * @return bool
181 */
182 public function bound($abstract)
183 {
184 return isset($this->bindings[$abstract]) ||
185 isset($this->instances[$abstract]) ||
186 $this->isAlias($abstract);
187 }
188
189 /**
190 * {@inheritdoc}
191 *
192 * @return bool
193 */
194 public function has($id)
195 {
196 return $this->bound($id);
197 }
198
199 /**
200 * Determine if the given abstract type has been resolved.
201 *
202 * @param string $abstract
203 * @return bool
204 */
205 public function resolved($abstract)
206 {
207 if ($this->isAlias($abstract)) {
208 $abstract = $this->getAlias($abstract);
209 }
210
211 return isset($this->resolved[$abstract]) ||
212 isset($this->instances[$abstract]);
213 }
214
215 /**
216 * Determine if a given type is shared.
217 *
218 * @param string $abstract
219 * @return bool
220 */
221 public function isShared($abstract)
222 {
223 return isset($this->instances[$abstract]) ||
224 (isset($this->bindings[$abstract]['shared']) &&
225 $this->bindings[$abstract]['shared'] === true);
226 }
227
228 /**
229 * Determine if a given string is an alias.
230 *
231 * @param string $name
232 * @return bool
233 */
234 public function isAlias($name)
235 {
236 return isset($this->aliases[$name]);
237 }
238
239 /**
240 * Register a binding with the container.
241 *
242 * @param string $abstract
243 * @param \Closure|string|null $concrete
244 * @param bool $shared
245 * @return void
246 *
247 * @throws \TypeError
248 */
249 public function bind($abstract, $concrete = null, $shared = false)
250 {
251 $this->dropStaleInstances($abstract);
252
253 // If no concrete type was given, we will simply set the concrete type to the
254 // abstract type. After that, the concrete type to be registered as shared
255 // without being forced to state their classes in both of the parameters.
256 if (is_null($concrete)) {
257 $concrete = $abstract;
258 }
259
260 // If the factory is not a Closure, it means it is just a class name which is
261 // bound into this container to the abstract type and we will just wrap it
262 // up inside its own Closure to give us more convenience when extending.
263 if (! $concrete instanceof Closure) {
264 if (! is_string($concrete)) {
265 throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
266 }
267
268 $concrete = $this->getClosure($abstract, $concrete);
269 }
270
271 $this->bindings[$abstract] = compact('concrete', 'shared');
272
273 // If the abstract type was already resolved in this container we'll fire the
274 // rebound listener so that any objects which have already gotten resolved
275 // can have their copy of the object updated via the listener callbacks.
276 if ($this->resolved($abstract)) {
277 $this->rebound($abstract);
278 }
279 }
280
281 /**
282 * Get the Closure to be used when building a type.
283 *
284 * @param string $abstract
285 * @param string $concrete
286 * @return \Closure
287 */
288 protected function getClosure($abstract, $concrete)
289 {
290 return function ($container, $parameters = []) use ($abstract, $concrete) {
291 if ($abstract == $concrete) {
292 return $container->build($concrete);
293 }
294
295 return $container->resolve(
296 $concrete, $parameters, $raiseEvents = false
297 );
298 };
299 }
300
301 /**
302 * Determine if the container has a method binding.
303 *
304 * @param string $method
305 * @return bool
306 */
307 public function hasMethodBinding($method)
308 {
309 return isset($this->methodBindings[$method]);
310 }
311
312 /**
313 * Bind a callback to resolve with Container::call.
314 *
315 * @param array|string $method
316 * @param \Closure $callback
317 * @return void
318 */
319 public function bindMethod($method, $callback)
320 {
321 $this->methodBindings[$this->parseBindMethod($method)] = $callback;
322 }
323
324 /**
325 * Get the method to be bound in class@method format.
326 *
327 * @param array|string $method
328 * @return string
329 */
330 protected function parseBindMethod($method)
331 {
332 if (is_array($method)) {
333 return $method[0].'@'.$method[1];
334 }
335
336 return $method;
337 }
338
339 /**
340 * Get the method binding for the given method.
341 *
342 * @param string $method
343 * @param mixed $instance
344 * @return mixed
345 */
346 public function callMethodBinding($method, $instance)
347 {
348 return call_user_func($this->methodBindings[$method], $instance, $this);
349 }
350
351 /**
352 * Add a contextual binding to the container.
353 *
354 * @param string $concrete
355 * @param string $abstract
356 * @param \Closure|string $implementation
357 * @return void
358 */
359 public function addContextualBinding($concrete, $abstract, $implementation)
360 {
361 $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
362 }
363
364 /**
365 * Register a binding if it hasn't already been registered.
366 *
367 * @param string $abstract
368 * @param \Closure|string|null $concrete
369 * @param bool $shared
370 * @return void
371 */
372 public function bindIf($abstract, $concrete = null, $shared = false)
373 {
374 if (! $this->bound($abstract)) {
375 $this->bind($abstract, $concrete, $shared);
376 }
377 }
378
379 /**
380 * Register a shared binding in the container.
381 *
382 * @param string $abstract
383 * @param \Closure|string|null $concrete
384 * @return void
385 */
386 public function singleton($abstract, $concrete = null)
387 {
388 $this->bind($abstract, $concrete, true);
389 }
390
391 /**
392 * Register a shared binding if it hasn't already been registered.
393 *
394 * @param string $abstract
395 * @param \Closure|string|null $concrete
396 * @return void
397 */
398 public function singletonIf($abstract, $concrete = null)
399 {
400 if (! $this->bound($abstract)) {
401 $this->singleton($abstract, $concrete);
402 }
403 }
404
405 /**
406 * Register a scoped binding in the container.
407 *
408 * @param string $abstract
409 * @param \Closure|string|null $concrete
410 * @return void
411 */
412 public function scoped($abstract, $concrete = null)
413 {
414 $this->scopedInstances[] = $abstract;
415
416 $this->singleton($abstract, $concrete);
417 }
418
419 /**
420 * Register a scoped binding if it hasn't already been registered.
421 *
422 * @param string $abstract
423 * @param \Closure|string|null $concrete
424 * @return void
425 */
426 public function scopedIf($abstract, $concrete = null)
427 {
428 if (! $this->bound($abstract)) {
429 $this->scopedInstances[] = $abstract;
430
431 $this->singleton($abstract, $concrete);
432 }
433 }
434
435 /**
436 * "Extend" an abstract type in the container.
437 *
438 * @param string $abstract
439 * @param \Closure $closure
440 * @return void
441 *
442 * @throws \InvalidArgumentException
443 */
444 public function extend($abstract, Closure $closure)
445 {
446 $abstract = $this->getAlias($abstract);
447
448 if (isset($this->instances[$abstract])) {
449 $this->instances[$abstract] = $closure($this->instances[$abstract], $this);
450
451 $this->rebound($abstract);
452 } else {
453 $this->extenders[$abstract][] = $closure;
454
455 if ($this->resolved($abstract)) {
456 $this->rebound($abstract);
457 }
458 }
459 }
460
461 /**
462 * Register an existing instance as shared in the container.
463 *
464 * @param string $abstract
465 * @param mixed $instance
466 * @return mixed
467 */
468 public function instance($abstract, $instance)
469 {
470 $this->removeAbstractAlias($abstract);
471
472 $isBound = $this->bound($abstract);
473
474 unset($this->aliases[$abstract]);
475
476 // We'll check to determine if this type has been bound before, and if it has
477 // we will fire the rebound callbacks registered with the container and it
478 // can be updated with consuming classes that have gotten resolved here.
479 $this->instances[$abstract] = $instance;
480
481 if ($isBound) {
482 $this->rebound($abstract);
483 }
484
485 return $instance;
486 }
487
488 /**
489 * Remove an alias from the contextual binding alias cache.
490 *
491 * @param string $searched
492 * @return void
493 */
494 protected function removeAbstractAlias($searched)
495 {
496 if (! isset($this->aliases[$searched])) {
497 return;
498 }
499
500 foreach ($this->abstractAliases as $abstract => $aliases) {
501 foreach ($aliases as $index => $alias) {
502 if ($alias == $searched) {
503 unset($this->abstractAliases[$abstract][$index]);
504 }
505 }
506 }
507 }
508
509 /**
510 * Assign a set of tags to a given binding.
511 *
512 * @param array|string $abstracts
513 * @param array|mixed ...$tags
514 * @return void
515 */
516 public function tag($abstracts, $tags)
517 {
518 $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);
519
520 foreach ($tags as $tag) {
521 if (! isset($this->tags[$tag])) {
522 $this->tags[$tag] = [];
523 }
524
525 foreach ((array) $abstracts as $abstract) {
526 $this->tags[$tag][] = $abstract;
527 }
528 }
529 }
530
531 /**
532 * Resolve all of the bindings for a given tag.
533 *
534 * @param string $tag
535 * @return iterable
536 */
537 public function tagged($tag)
538 {
539 if (! isset($this->tags[$tag])) {
540 return [];
541 }
542
543 return new RewindableGenerator(function () use ($tag) {
544 foreach ($this->tags[$tag] as $abstract) {
545 yield $this->make($abstract);
546 }
547 }, count($this->tags[$tag]));
548 }
549
550 /**
551 * Alias a type to a different name.
552 *
553 * @param string $abstract
554 * @param string $alias
555 * @return void
556 *
557 * @throws \LogicException
558 */
559 public function alias($abstract, $alias)
560 {
561 if ($alias === $abstract) {
562 throw new LogicException("[{$abstract}] is aliased to itself.");
563 }
564
565 $this->aliases[$alias] = $abstract;
566
567 $this->abstractAliases[$abstract][] = $alias;
568 }
569
570 /**
571 * Bind a new callback to an abstract's rebind event.
572 *
573 * @param string $abstract
574 * @param \Closure $callback
575 * @return mixed
576 */
577 public function rebinding($abstract, Closure $callback)
578 {
579 $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback;
580
581 if ($this->bound($abstract)) {
582 return $this->make($abstract);
583 }
584 }
585
586 /**
587 * Refresh an instance on the given target and method.
588 *
589 * @param string $abstract
590 * @param mixed $target
591 * @param string $method
592 * @return mixed
593 */
594 public function refresh($abstract, $target, $method)
595 {
596 return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) {
597 $target->{$method}($instance);
598 });
599 }
600
601 /**
602 * Fire the "rebound" callbacks for the given abstract type.
603 *
604 * @param string $abstract
605 * @return void
606 */
607 protected function rebound($abstract)
608 {
609 $instance = $this->make($abstract);
610
611 foreach ($this->getReboundCallbacks($abstract) as $callback) {
612 call_user_func($callback, $this, $instance);
613 }
614 }
615
616 /**
617 * Get the rebound callbacks for a given type.
618 *
619 * @param string $abstract
620 * @return array
621 */
622 protected function getReboundCallbacks($abstract)
623 {
624 return $this->reboundCallbacks[$abstract] ?? [];
625 }
626
627 /**
628 * Wrap the given closure such that its dependencies will be injected when executed.
629 *
630 * @param \Closure $callback
631 * @param array $parameters
632 * @return \Closure
633 */
634 public function wrap(Closure $callback, array $parameters = [])
635 {
636 return function () use ($callback, $parameters) {
637 return $this->call($callback, $parameters);
638 };
639 }
640
641 /**
642 * Call the given Closure / class@method and inject its dependencies.
643 *
644 * @param callable|string $callback
645 * @param array<string, mixed> $parameters
646 * @param string|null $defaultMethod
647 * @return mixed
648 *
649 * @throws \InvalidArgumentException
650 */
651 public function call($callback, array $parameters = [], $defaultMethod = null)
652 {
653 return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
654 }
655
656 /**
657 * Get a closure to resolve the given type from the container.
658 *
659 * @param string $abstract
660 * @return \Closure
661 */
662 public function factory($abstract)
663 {
664 return function () use ($abstract) {
665 return $this->make($abstract);
666 };
667 }
668
669 /**
670 * An alias function name for make().
671 *
672 * @param string|callable $abstract
673 * @param array $parameters
674 * @return mixed
675 *
676 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
677 */
678 public function makeWith($abstract, array $parameters = [])
679 {
680 return $this->make($abstract, $parameters);
681 }
682
683 /**
684 * Resolve the given type from the container.
685 *
686 * @param string|callable $abstract
687 * @param array $parameters
688 * @return mixed
689 *
690 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
691 */
692 public function make($abstract, array $parameters = [])
693 {
694 return $this->resolve($abstract, $parameters);
695 }
696
697 /**
698 * {@inheritdoc}
699 *
700 * @return mixed
701 */
702 public function get($id)
703 {
704 try {
705 return $this->resolve($id);
706 } catch (Exception $e) {
707 if ($this->has($id) || $e instanceof CircularDependencyException) {
708 throw $e;
709 }
710
711 throw new EntryNotFoundException($id, $e->getCode(), $e);
712 }
713 }
714
715 /**
716 * Resolve the given type from the container.
717 *
718 * @param string|callable $abstract
719 * @param array $parameters
720 * @param bool $raiseEvents
721 * @return mixed
722 *
723 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
724 * @throws \FluentBooking\Framework\Container\Contracts\CircularDependencyException
725 */
726 protected function resolve($abstract, $parameters = [], $raiseEvents = true)
727 {
728 $abstract = $this->getAlias($abstract);
729
730 // First we'll fire any event handlers which handle the "before" resolving of
731 // specific types. This gives some hooks the chance to add various extends
732 // calls to change the resolution of objects that they're interested in.
733 if ($raiseEvents) {
734 $this->fireBeforeResolvingCallbacks($abstract, $parameters);
735 }
736
737 $concrete = $this->getContextualConcrete($abstract);
738
739 $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
740
741 // If an instance of the type is currently being managed as a singleton we'll
742 // just return an existing instance instead of instantiating new instances
743 // so the developer can keep using the same objects instance every time.
744 if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
745 return $this->instances[$abstract];
746 }
747
748 $this->with[] = $parameters;
749
750 if (is_null($concrete)) {
751 $concrete = $this->getConcrete($abstract);
752 }
753
754 // We're ready to instantiate an instance of the concrete type registered for
755 // the binding. This will instantiate the types, as well as resolve any of
756 // its "nested" dependencies recursively until all have gotten resolved.
757 if ($this->isBuildable($concrete, $abstract)) {
758 $object = $this->build($concrete);
759 } else {
760 $object = $this->make($concrete);
761 }
762
763 // If we defined any extenders for this type, we'll need to spin through them
764 // and apply them to the object being built. This allows for the extension
765 // of services, such as changing configuration or decorating the object.
766 foreach ($this->getExtenders($abstract) as $extender) {
767 $object = $extender($object, $this);
768 }
769
770 // If the requested type is registered as a singleton we'll want to cache off
771 // the instances in "memory" so we can return it later without creating an
772 // entirely new instance of an object on each subsequent request for it.
773 if ($this->isShared($abstract) && ! $needsContextualBuild) {
774 $this->instances[$abstract] = $object;
775 }
776
777 if ($raiseEvents) {
778 $this->fireResolvingCallbacks($abstract, $object);
779 }
780
781 // Before returning, we will also set the resolved flag to "true" and pop off
782 // the parameter overrides for this build. After those two things are done
783 // we will be ready to return back the fully constructed class instance.
784 $this->resolved[$abstract] = true;
785
786 array_pop($this->with);
787
788 return $object;
789 }
790
791 /**
792 * Get the concrete type for a given abstract.
793 *
794 * @param string|callable $abstract
795 * @return mixed
796 */
797 protected function getConcrete($abstract)
798 {
799 // If we don't have a registered resolver or concrete for the type, we'll just
800 // assume each type is a concrete name and will attempt to resolve it as is
801 // since the container should be able to resolve concretes automatically.
802 if (isset($this->bindings[$abstract])) {
803 return $this->bindings[$abstract]['concrete'];
804 }
805
806 return $abstract;
807 }
808
809 /**
810 * Get the contextual concrete binding for the given abstract.
811 *
812 * @param string|callable $abstract
813 * @return \Closure|string|array|null
814 */
815 protected function getContextualConcrete($abstract)
816 {
817 if (! is_null($binding = $this->findInContextualBindings($abstract))) {
818 return $binding;
819 }
820
821 // Next we need to see if a contextual binding might be bound under an alias of the
822 // given abstract type. So, we will need to check if any aliases exist with this
823 // type and then spin through them and check for contextual bindings on these.
824 if (empty($this->abstractAliases[$abstract])) {
825 return;
826 }
827
828 foreach ($this->abstractAliases[$abstract] as $alias) {
829 if (! is_null($binding = $this->findInContextualBindings($alias))) {
830 return $binding;
831 }
832 }
833 }
834
835 /**
836 * Find the concrete binding for the given abstract in the contextual binding array.
837 *
838 * @param string|callable $abstract
839 * @return \Closure|string|null
840 */
841 protected function findInContextualBindings($abstract)
842 {
843 return $this->contextual[end($this->buildStack)][$abstract] ?? null;
844 }
845
846 /**
847 * Determine if the given concrete is buildable.
848 *
849 * @param mixed $concrete
850 * @param string $abstract
851 * @return bool
852 */
853 protected function isBuildable($concrete, $abstract)
854 {
855 return $concrete === $abstract || $concrete instanceof Closure;
856 }
857
858 /**
859 * Instantiate a concrete instance of the given type.
860 *
861 * @param \Closure|string $concrete
862 * @return mixed
863 *
864 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
865 * @throws \FluentBooking\Framework\Container\Contracts\CircularDependencyException
866 */
867 public function build($concrete)
868 {
869 // If the concrete type is actually a Closure, we will just execute it and
870 // hand back the results of the functions, which allows functions to be
871 // used as resolvers for more fine-tuned resolution of these objects.
872 if ($concrete instanceof Closure) {
873 return $concrete($this, $this->getLastParameterOverride());
874 }
875
876 try {
877 $reflector = new ReflectionClass($concrete);
878 } catch (ReflectionException $e) {
879 throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
880 }
881
882 // If the type is not instantiable, the developer is attempting to resolve
883 // an abstract type such as an Interface or Abstract Class and there is
884 // no binding registered for the abstractions so we need to bail out.
885 if (! $reflector->isInstantiable()) {
886 return $this->notInstantiable($concrete);
887 }
888
889 $this->buildStack[] = $concrete;
890
891 $constructor = $reflector->getConstructor();
892
893 // If there are no constructors, that means there are no dependencies then
894 // we can just resolve the instances of the objects right away, without
895 // resolving any other types or dependencies out of these containers.
896 if (is_null($constructor)) {
897 array_pop($this->buildStack);
898
899 return new $concrete;
900 }
901
902 $dependencies = $constructor->getParameters();
903
904 // Once we have all the constructor's parameters we can create each of the
905 // dependency instances and then use the reflection instances to make a
906 // new instance of this class, injecting the created dependencies in.
907 try {
908 $instances = $this->resolveDependencies($dependencies);
909 } catch (BindingResolutionException $e) {
910 array_pop($this->buildStack);
911
912 throw $e;
913 }
914
915 array_pop($this->buildStack);
916
917 return $reflector->newInstanceArgs($instances);
918 }
919
920 /**
921 * Resolve all of the dependencies from the ReflectionParameters.
922 *
923 * @param \ReflectionParameter[] $dependencies
924 * @return array
925 *
926 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
927 */
928 protected function resolveDependencies(array $dependencies)
929 {
930 $results = [];
931
932 foreach ($dependencies as $dependency) {
933 // If the dependency has an override for this particular build we will use
934 // that instead as the value. Otherwise, we will continue with this run
935 // of resolutions and let reflection attempt to determine the result.
936 if ($this->hasParameterOverride($dependency)) {
937 $results[] = $this->getParameterOverride($dependency);
938
939 continue;
940 }
941
942 // If the class is null, it means the dependency is a string or some other
943 // primitive type which we can not resolve since it is not a class and
944 // we will just bomb out with an error since we have no-where to go.
945 $result = is_null(Util::getParameterClassName($dependency))
946 ? $this->resolvePrimitive($dependency)
947 : $this->resolveClass($dependency);
948
949 if ($dependency->isVariadic()) {
950 $results = array_merge($results, $result);
951 } else {
952 $results[] = $result;
953 }
954 }
955
956 return $results;
957 }
958
959 /**
960 * Determine if the given dependency has a parameter override.
961 *
962 * @param \ReflectionParameter $dependency
963 * @return bool
964 */
965 protected function hasParameterOverride($dependency)
966 {
967 return array_key_exists(
968 $dependency->name, $this->getLastParameterOverride()
969 );
970 }
971
972 /**
973 * Get a parameter override for a dependency.
974 *
975 * @param \ReflectionParameter $dependency
976 * @return mixed
977 */
978 protected function getParameterOverride($dependency)
979 {
980 return $this->getLastParameterOverride()[$dependency->name];
981 }
982
983 /**
984 * Get the last parameter override.
985 *
986 * @return array
987 */
988 protected function getLastParameterOverride()
989 {
990 return count($this->with) ? end($this->with) : [];
991 }
992
993 /**
994 * Resolve a non-class hinted primitive dependency.
995 *
996 * @param \ReflectionParameter $parameter
997 * @return mixed
998 *
999 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
1000 */
1001 protected function resolvePrimitive(ReflectionParameter $parameter)
1002 {
1003 if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) {
1004 return $concrete instanceof Closure ? $concrete($this) : $concrete;
1005 }
1006
1007 if ($parameter->isDefaultValueAvailable()) {
1008 return $parameter->getDefaultValue();
1009 }
1010
1011 $this->unresolvablePrimitive($parameter);
1012 }
1013
1014 /**
1015 * Resolve a class based dependency from the container.
1016 *
1017 * @param \ReflectionParameter $parameter
1018 * @return mixed
1019 *
1020 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
1021 */
1022 protected function resolveClass(ReflectionParameter $parameter)
1023 {
1024 try {
1025 return $parameter->isVariadic()
1026 ? $this->resolveVariadicClass($parameter)
1027 : $this->make(Util::getParameterClassName($parameter));
1028 }
1029
1030 // If we can not resolve the class instance, we will check to see if the value
1031 // is optional, and if it is we will return the optional parameter value as
1032 // the value of the dependency, similarly to how we do this with scalars.
1033 catch (BindingResolutionException $e) {
1034 if ($parameter->isDefaultValueAvailable()) {
1035 array_pop($this->with);
1036
1037 return $parameter->getDefaultValue();
1038 }
1039
1040 if ($parameter->isVariadic()) {
1041 array_pop($this->with);
1042
1043 return [];
1044 }
1045
1046 throw $e;
1047 }
1048 }
1049
1050 /**
1051 * Resolve a class based variadic dependency from the container.
1052 *
1053 * @param \ReflectionParameter $parameter
1054 * @return mixed
1055 */
1056 protected function resolveVariadicClass(ReflectionParameter $parameter)
1057 {
1058 $className = Util::getParameterClassName($parameter);
1059
1060 $abstract = $this->getAlias($className);
1061
1062 if (! is_array($concrete = $this->getContextualConcrete($abstract))) {
1063 return $this->make($className);
1064 }
1065
1066 return array_map(function ($abstract) {
1067 return $this->resolve($abstract);
1068 }, $concrete);
1069 }
1070
1071 /**
1072 * Throw an exception that the concrete is not instantiable.
1073 *
1074 * @param string $concrete
1075 * @return void
1076 *
1077 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
1078 */
1079 protected function notInstantiable($concrete)
1080 {
1081 if (! empty($this->buildStack)) {
1082 $previous = implode(', ', $this->buildStack);
1083
1084 $message = "Target [$concrete] is not instantiable while building [$previous].";
1085 } else {
1086 $message = "Target [$concrete] is not instantiable.";
1087 }
1088
1089 throw new BindingResolutionException($message);
1090 }
1091
1092 /**
1093 * Throw an exception for an unresolvable primitive.
1094 *
1095 * @param \ReflectionParameter $parameter
1096 * @return void
1097 *
1098 * @throws \FluentBooking\Framework\Container\Contracts\BindingResolutionException
1099 */
1100 protected function unresolvablePrimitive(ReflectionParameter $parameter)
1101 {
1102 $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";
1103
1104 throw new BindingResolutionException($message);
1105 }
1106
1107 /**
1108 * Register a new before resolving callback for all types.
1109 *
1110 * @param \Closure|string $abstract
1111 * @param \Closure|null $callback
1112 * @return void
1113 */
1114 public function beforeResolving($abstract, Closure $callback = null)
1115 {
1116 if (is_string($abstract)) {
1117 $abstract = $this->getAlias($abstract);
1118 }
1119
1120 if ($abstract instanceof Closure && is_null($callback)) {
1121 $this->globalBeforeResolvingCallbacks[] = $abstract;
1122 } else {
1123 $this->beforeResolvingCallbacks[$abstract][] = $callback;
1124 }
1125 }
1126
1127 /**
1128 * Register a new resolving callback.
1129 *
1130 * @param \Closure|string $abstract
1131 * @param \Closure|null $callback
1132 * @return void
1133 */
1134 public function resolving($abstract, Closure $callback = null)
1135 {
1136 if (is_string($abstract)) {
1137 $abstract = $this->getAlias($abstract);
1138 }
1139
1140 if (is_null($callback) && $abstract instanceof Closure) {
1141 $this->globalResolvingCallbacks[] = $abstract;
1142 } else {
1143 $this->resolvingCallbacks[$abstract][] = $callback;
1144 }
1145 }
1146
1147 /**
1148 * Register a new after resolving callback for all types.
1149 *
1150 * @param \Closure|string $abstract
1151 * @param \Closure|null $callback
1152 * @return void
1153 */
1154 public function afterResolving($abstract, Closure $callback = null)
1155 {
1156 if (is_string($abstract)) {
1157 $abstract = $this->getAlias($abstract);
1158 }
1159
1160 if ($abstract instanceof Closure && is_null($callback)) {
1161 $this->globalAfterResolvingCallbacks[] = $abstract;
1162 } else {
1163 $this->afterResolvingCallbacks[$abstract][] = $callback;
1164 }
1165 }
1166
1167 /**
1168 * Fire all of the before resolving callbacks.
1169 *
1170 * @param string $abstract
1171 * @param array $parameters
1172 * @return void
1173 */
1174 protected function fireBeforeResolvingCallbacks($abstract, $parameters = [])
1175 {
1176 $this->fireBeforeCallbackArray($abstract, $parameters, $this->globalBeforeResolvingCallbacks);
1177
1178 foreach ($this->beforeResolvingCallbacks as $type => $callbacks) {
1179 if ($type === $abstract || is_subclass_of($abstract, $type)) {
1180 $this->fireBeforeCallbackArray($abstract, $parameters, $callbacks);
1181 }
1182 }
1183 }
1184
1185 /**
1186 * Fire an array of callbacks with an object.
1187 *
1188 * @param string $abstract
1189 * @param array $parameters
1190 * @param array $callbacks
1191 * @return void
1192 */
1193 protected function fireBeforeCallbackArray($abstract, $parameters, array $callbacks)
1194 {
1195 foreach ($callbacks as $callback) {
1196 $callback($abstract, $parameters, $this);
1197 }
1198 }
1199
1200 /**
1201 * Fire all of the resolving callbacks.
1202 *
1203 * @param string $abstract
1204 * @param mixed $object
1205 * @return void
1206 */
1207 protected function fireResolvingCallbacks($abstract, $object)
1208 {
1209 $this->fireCallbackArray($object, $this->globalResolvingCallbacks);
1210
1211 $this->fireCallbackArray(
1212 $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
1213 );
1214
1215 $this->fireAfterResolvingCallbacks($abstract, $object);
1216 }
1217
1218 /**
1219 * Fire all of the after resolving callbacks.
1220 *
1221 * @param string $abstract
1222 * @param mixed $object
1223 * @return void
1224 */
1225 protected function fireAfterResolvingCallbacks($abstract, $object)
1226 {
1227 $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);
1228
1229 $this->fireCallbackArray(
1230 $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
1231 );
1232 }
1233
1234 /**
1235 * Get all callbacks for a given type.
1236 *
1237 * @param string $abstract
1238 * @param object $object
1239 * @param array $callbacksPerType
1240 * @return array
1241 */
1242 protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
1243 {
1244 $results = [];
1245
1246 foreach ($callbacksPerType as $type => $callbacks) {
1247 if ($type === $abstract || $object instanceof $type) {
1248 $results = array_merge($results, $callbacks);
1249 }
1250 }
1251
1252 return $results;
1253 }
1254
1255 /**
1256 * Fire an array of callbacks with an object.
1257 *
1258 * @param mixed $object
1259 * @param array $callbacks
1260 * @return void
1261 */
1262 protected function fireCallbackArray($object, array $callbacks)
1263 {
1264 foreach ($callbacks as $callback) {
1265 $callback($object, $this);
1266 }
1267 }
1268
1269 /**
1270 * Get the container's bindings.
1271 *
1272 * @return array
1273 */
1274 public function getBindings()
1275 {
1276 return $this->bindings;
1277 }
1278
1279 /**
1280 * Get the alias for an abstract if available.
1281 *
1282 * @param string $abstract
1283 * @return string
1284 */
1285 public function getAlias($abstract)
1286 {
1287 return isset($this->aliases[$abstract])
1288 ? $this->getAlias($this->aliases[$abstract])
1289 : $abstract;
1290 }
1291
1292 /**
1293 * Get the extender callbacks for a given type.
1294 *
1295 * @param string $abstract
1296 * @return array
1297 */
1298 protected function getExtenders($abstract)
1299 {
1300 return $this->extenders[$this->getAlias($abstract)] ?? [];
1301 }
1302
1303 /**
1304 * Remove all of the extender callbacks for a given type.
1305 *
1306 * @param string $abstract
1307 * @return void
1308 */
1309 public function forgetExtenders($abstract)
1310 {
1311 unset($this->extenders[$this->getAlias($abstract)]);
1312 }
1313
1314 /**
1315 * Drop all of the stale instances and aliases.
1316 *
1317 * @param string $abstract
1318 * @return void
1319 */
1320 protected function dropStaleInstances($abstract)
1321 {
1322 unset($this->instances[$abstract], $this->aliases[$abstract]);
1323 }
1324
1325 /**
1326 * Remove a resolved instance from the instance cache.
1327 *
1328 * @param string $abstract
1329 * @return void
1330 */
1331 public function forgetInstance($abstract)
1332 {
1333 unset($this->instances[$abstract]);
1334 }
1335
1336 /**
1337 * Clear all of the instances from the container.
1338 *
1339 * @return void
1340 */
1341 public function forgetInstances()
1342 {
1343 $this->instances = [];
1344 }
1345
1346 /**
1347 * Clear all of the scoped instances from the container.
1348 *
1349 * @return void
1350 */
1351 public function forgetScopedInstances()
1352 {
1353 foreach ($this->scopedInstances as $scoped) {
1354 unset($this->instances[$scoped]);
1355 }
1356 }
1357
1358 /**
1359 * Flush the container of all bindings and resolved instances.
1360 *
1361 * @return void
1362 */
1363 public function flush()
1364 {
1365 $this->aliases = [];
1366 $this->resolved = [];
1367 $this->bindings = [];
1368 $this->instances = [];
1369 $this->abstractAliases = [];
1370 $this->scopedInstances = [];
1371 }
1372
1373 /**
1374 * Get the globally available instance of the container.
1375 *
1376 * @return static
1377 */
1378 public static function getInstance()
1379 {
1380 if (is_null(static::$instance)) {
1381 static::$instance = new static;
1382 }
1383
1384 return static::$instance;
1385 }
1386
1387 /**
1388 * Set the shared instance of the container.
1389 *
1390 * @param \FluentBooking\Framework\Container\Contracts\Container|null $container
1391 * @return \FluentBooking\Framework\Container\Contracts\Container|static
1392 */
1393 public static function setInstance(ContainerContract $container = null)
1394 {
1395 return static::$instance = $container;
1396 }
1397
1398 /**
1399 * Determine if a given offset exists.
1400 *
1401 * @param string $key
1402 * @return bool
1403 */
1404 #[\ReturnTypeWillChange]
1405 public function offsetExists($key)
1406 {
1407 return $this->bound($key);
1408 }
1409
1410 /**
1411 * Get the value at a given offset.
1412 *
1413 * @param string $key
1414 * @return mixed
1415 */
1416 #[\ReturnTypeWillChange]
1417 public function offsetGet($key)
1418 {
1419 return $this->make($key);
1420 }
1421
1422 /**
1423 * Set the value at a given offset.
1424 *
1425 * @param string $key
1426 * @param mixed $value
1427 * @return void
1428 */
1429 #[\ReturnTypeWillChange]
1430 public function offsetSet($key, $value)
1431 {
1432 $this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
1433 return $value;
1434 });
1435 }
1436
1437 /**
1438 * Unset the value at a given offset.
1439 *
1440 * @param string $key
1441 * @return void
1442 */
1443 #[\ReturnTypeWillChange]
1444 public function offsetUnset($key)
1445 {
1446 unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
1447 }
1448
1449 /**
1450 * Dynamically access container services.
1451 *
1452 * @param string $key
1453 * @return mixed
1454 */
1455 public function __get($key)
1456 {
1457 return $this[$key];
1458 }
1459
1460 /**
1461 * Dynamically set container services.
1462 *
1463 * @param string $key
1464 * @param mixed $value
1465 * @return void
1466 */
1467 public function __set($key, $value)
1468 {
1469 $this[$key] = $value;
1470 }
1471 }
1472