PluginProbe
FluentSMTP – WP Mail SMTP Plugin with Amazon SES, SendGrid, Mailgun, Postmark, Cloudflare, toSend, Gmail and Any SMTP / trunk
FluentSMTP – WP Mail SMTP Plugin with Amazon SES, SendGrid, Mailgun, Postmark, Cloudflare, toSend, Gmail and Any SMTP vtrunk
2.4.0 trunk 1.0.1 1.1.0 1.1.1 1.2.0 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.71 2.2.72 2.2.73 2.2.80 2.2.81 All 32 releases
fluent-smtp / includes / Core / Container.php

Container.php in FluentSMTP – WP Mail SMTP Plugin with Amazon SES, SendGrid, Mailgun, Postmark, Cloudflare, toSend, Gmail and Any SMTP trunk, at includes/Core/Container.php

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