Permalink
<?php | |
namespace Illuminate\Container; | |
use ArrayAccess; | |
use Closure; | |
use Exception; | |
use Illuminate\Contracts\Container\BindingResolutionException; | |
use Illuminate\Contracts\Container\Container as ContainerContract; | |
use LogicException; | |
use ReflectionClass; | |
use ReflectionException; | |
use ReflectionParameter; | |
class Container implements ArrayAccess, ContainerContract | |
{ | |
/** | |
* The current globally available container (if any). | |
* | |
* @var static | |
*/ | |
protected static $instance; | |
/** | |
* An array of the types that have been resolved. | |
* | |
* @var bool[] | |
*/ | |
protected $resolved = []; | |
/** | |
* The container's bindings. | |
* | |
* @var array[] | |
*/ | |
protected $bindings = []; | |
/** | |
* The container's method bindings. | |
* | |
* @var \Closure[] | |
*/ | |
protected $methodBindings = []; | |
/** | |
* The container's shared instances. | |
* | |
* @var object[] | |
*/ | |
protected $instances = []; | |
/** | |
* The registered type aliases. | |
* | |
* @var string[] | |
*/ | |
protected $aliases = []; | |
/** | |
* The registered aliases keyed by the abstract name. | |
* | |
* @var array[] | |
*/ | |
protected $abstractAliases = []; | |
/** | |
* The extension closures for services. | |
* | |
* @var array[] | |
*/ | |
protected $extenders = []; | |
/** | |
* All of the registered tags. | |
* | |
* @var array[] | |
*/ | |
protected $tags = []; | |
/** | |
* The stack of concretions currently being built. | |
* | |
* @var array[] | |
*/ | |
protected $buildStack = []; | |
/** | |
* The parameter override stack. | |
* | |
* @var array[] | |
*/ | |
protected $with = []; | |
/** | |
* The contextual binding map. | |
* | |
* @var array[] | |
*/ | |
public $contextual = []; | |
/** | |
* All of the registered rebound callbacks. | |
* | |
* @var array[] | |
*/ | |
protected $reboundCallbacks = []; | |
/** | |
* All of the global before resolving callbacks. | |
* | |
* @var \Closure[] | |
*/ | |
protected $globalBeforeResolvingCallbacks = []; | |
/** | |
* All of the global resolving callbacks. | |
* | |
* @var \Closure[] | |
*/ | |
protected $globalResolvingCallbacks = []; | |
/** | |
* All of the global after resolving callbacks. | |
* | |
* @var \Closure[] | |
*/ | |
protected $globalAfterResolvingCallbacks = []; | |
/** | |
* All of the before resolving callbacks by class type. | |
* | |
* @var array[] | |
*/ | |
protected $beforeResolvingCallbacks = []; | |
/** | |
* All of the resolving callbacks by class type. | |
* | |
* @var array[] | |
*/ | |
protected $resolvingCallbacks = []; | |
/** | |
* All of the after resolving callbacks by class type. | |
* | |
* @var array[] | |
*/ | |
protected $afterResolvingCallbacks = []; | |
/** | |
* Define a contextual binding. | |
* | |
* @param array|string $concrete | |
* @return \Illuminate\Contracts\Container\ContextualBindingBuilder | |
*/ | |
public function when($concrete) | |
{ | |
$aliases = []; | |
foreach (Util::arrayWrap($concrete) as $c) { | |
$aliases[] = $this->getAlias($c); | |
} | |
return new ContextualBindingBuilder($this, $aliases); | |
} | |
/** | |
* Determine if the given abstract type has been bound. | |
* | |
* @param string $abstract | |
* @return bool | |
*/ | |
public function bound($abstract) | |
{ | |
return isset($this->bindings[$abstract]) || | |
isset($this->instances[$abstract]) || | |
$this->isAlias($abstract); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function has($id) | |
{ | |
return $this->bound($id); | |
} | |
/** | |
* Determine if the given abstract type has been resolved. | |
* | |
* @param string $abstract | |
* @return bool | |
*/ | |
public function resolved($abstract) | |
{ | |
if ($this->isAlias($abstract)) { | |
$abstract = $this->getAlias($abstract); | |
} | |
return isset($this->resolved[$abstract]) || | |
isset($this->instances[$abstract]); | |
} | |
/** | |
* Determine if a given type is shared. | |
* | |
* @param string $abstract | |
* @return bool | |
*/ | |
public function isShared($abstract) | |
{ | |
return isset($this->instances[$abstract]) || | |
(isset($this->bindings[$abstract]['shared']) && | |
$this->bindings[$abstract]['shared'] === true); | |
} | |
/** | |
* Determine if a given string is an alias. | |
* | |
* @param string $name | |
* @return bool | |
*/ | |
public function isAlias($name) | |
{ | |
return isset($this->aliases[$name]); | |
} | |
/** | |
* Register a binding with the container. | |
* | |
* @param string $abstract | |
* @param \Closure|string|null $concrete | |
* @param bool $shared | |
* @return void | |
*/ | |
public function bind($abstract, $concrete = null, $shared = false) | |
{ | |
$this->dropStaleInstances($abstract); | |
// If no concrete type was given, we will simply set the concrete type to the | |
// abstract type. After that, the concrete type to be registered as shared | |
// without being forced to state their classes in both of the parameters. | |
if (is_null($concrete)) { | |
$concrete = $abstract; | |
} | |
// If the factory is not a Closure, it means it is just a class name which is | |
// bound into this container to the abstract type and we will just wrap it | |
// up inside its own Closure to give us more convenience when extending. | |
if (! $concrete instanceof Closure) { | |
if (! is_string($concrete)) { | |
throw new \TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null'); | |
} | |
$concrete = $this->getClosure($abstract, $concrete); | |
} | |
$this->bindings[$abstract] = compact('concrete', 'shared'); | |
// If the abstract type was already resolved in this container we'll fire the | |
// rebound listener so that any objects which have already gotten resolved | |
// can have their copy of the object updated via the listener callbacks. | |
if ($this->resolved($abstract)) { | |
$this->rebound($abstract); | |
} | |
} | |
/** | |
* Get the Closure to be used when building a type. | |
* | |
* @param string $abstract | |
* @param string $concrete | |
* @return \Closure | |
*/ | |
protected function getClosure($abstract, $concrete) | |
{ | |
return function ($container, $parameters = []) use ($abstract, $concrete) { | |
if ($abstract == $concrete) { | |
return $container->build($concrete); | |
} | |
return $container->resolve( | |
$concrete, $parameters, $raiseEvents = false | |
); | |
}; | |
} | |
/** | |
* Determine if the container has a method binding. | |
* | |
* @param string $method | |
* @return bool | |
*/ | |
public function hasMethodBinding($method) | |
{ | |
return isset($this->methodBindings[$method]); | |
} | |
/** | |
* Bind a callback to resolve with Container::call. | |
* | |
* @param array|string $method | |
* @param \Closure $callback | |
* @return void | |
*/ | |
public function bindMethod($method, $callback) | |
{ | |
$this->methodBindings[$this->parseBindMethod($method)] = $callback; | |
} | |
/** | |
* Get the method to be bound in class@method format. | |
* | |
* @param array|string $method | |
* @return string | |
*/ | |
protected function parseBindMethod($method) | |
{ | |
if (is_array($method)) { | |
return $method[0].'@'.$method[1]; | |
} | |
return $method; | |
} | |
/** | |
* Get the method binding for the given method. | |
* | |
* @param string $method | |
* @param mixed $instance | |
* @return mixed | |
*/ | |
public function callMethodBinding($method, $instance) | |
{ | |
return call_user_func($this->methodBindings[$method], $instance, $this); | |
} | |
/** | |
* Add a contextual binding to the container. | |
* | |
* @param string $concrete | |
* @param string $abstract | |
* @param \Closure|string $implementation | |
* @return void | |
*/ | |
public function addContextualBinding($concrete, $abstract, $implementation) | |
{ | |
$this->contextual[$concrete][$this->getAlias($abstract)] = $implementation; | |
} | |
/** | |
* Register a binding if it hasn't already been registered. | |
* | |
* @param string $abstract | |
* @param \Closure|string|null $concrete | |
* @param bool $shared | |
* @return void | |
*/ | |
public function bindIf($abstract, $concrete = null, $shared = false) | |
{ | |
if (! $this->bound($abstract)) { | |
$this->bind($abstract, $concrete, $shared); | |
} | |
} | |
/** | |
* Register a shared binding in the container. | |
* | |
* @param string $abstract | |
* @param \Closure|string|null $concrete | |
* @return void | |
*/ | |
public function singleton($abstract, $concrete = null) | |
{ | |
$this->bind($abstract, $concrete, true); | |
} | |
/** | |
* Register a shared binding if it hasn't already been registered. | |
* | |
* @param string $abstract | |
* @param \Closure|string|null $concrete | |
* @return void | |
*/ | |
public function singletonIf($abstract, $concrete = null) | |
{ | |
if (! $this->bound($abstract)) { | |
$this->singleton($abstract, $concrete); | |
} | |
} | |
/** | |
* "Extend" an abstract type in the container. | |
* | |
* @param string $abstract | |
* @param \Closure $closure | |
* @return void | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
public function extend($abstract, Closure $closure) | |
{ | |
$abstract = $this->getAlias($abstract); | |
if (isset($this->instances[$abstract])) { | |
$this->instances[$abstract] = $closure($this->instances[$abstract], $this); | |
$this->rebound($abstract); | |
} else { | |
$this->extenders[$abstract][] = $closure; | |
if ($this->resolved($abstract)) { | |
$this->rebound($abstract); | |
} | |
} | |
} | |
/** | |
* Register an existing instance as shared in the container. | |
* | |
* @param string $abstract | |
* @param mixed $instance | |
* @return mixed | |
*/ | |
public function instance($abstract, $instance) | |
{ | |
$this->removeAbstractAlias($abstract); | |
$isBound = $this->bound($abstract); | |
unset($this->aliases[$abstract]); | |
// We'll check to determine if this type has been bound before, and if it has | |
// we will fire the rebound callbacks registered with the container and it | |
// can be updated with consuming classes that have gotten resolved here. | |
$this->instances[$abstract] = $instance; | |
if ($isBound) { | |
$this->rebound($abstract); | |
} | |
return $instance; | |
} | |
/** | |
* Remove an alias from the contextual binding alias cache. | |
* | |
* @param string $searched | |
* @return void | |
*/ | |
protected function removeAbstractAlias($searched) | |
{ | |
if (! isset($this->aliases[$searched])) { | |
return; | |
} | |
foreach ($this->abstractAliases as $abstract => $aliases) { | |
foreach ($aliases as $index => $alias) { | |
if ($alias == $searched) { | |
unset($this->abstractAliases[$abstract][$index]); | |
} | |
} | |
} | |
} | |
/** | |
* Assign a set of tags to a given binding. | |
* | |
* @param array|string $abstracts | |
* @param array|mixed ...$tags | |
* @return void | |
*/ | |
public function tag($abstracts, $tags) | |
{ | |
$tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1); | |
foreach ($tags as $tag) { | |
if (! isset($this->tags[$tag])) { | |
$this->tags[$tag] = []; | |
} | |
foreach ((array) $abstracts as $abstract) { | |
$this->tags[$tag][] = $abstract; | |
} | |
} | |
} | |
/** | |
* Resolve all of the bindings for a given tag. | |
* | |
* @param string $tag | |
* @return iterable | |
*/ | |
public function tagged($tag) | |
{ | |
if (! isset($this->tags[$tag])) { | |
return []; | |
} | |
return new RewindableGenerator(function () use ($tag) { | |
foreach ($this->tags[$tag] as $abstract) { | |
yield $this->make($abstract); | |
} | |
}, count($this->tags[$tag])); | |
} | |
/** | |
* Alias a type to a different name. | |
* | |
* @param string $abstract | |
* @param string $alias | |
* @return void | |
* | |
* @throws \LogicException | |
*/ | |
public function alias($abstract, $alias) | |
{ | |
if ($alias === $abstract) { | |
throw new LogicException("[{$abstract}] is aliased to itself."); | |
} | |
$this->aliases[$alias] = $abstract; | |
$this->abstractAliases[$abstract][] = $alias; | |
} | |
/** | |
* Bind a new callback to an abstract's rebind event. | |
* | |
* @param string $abstract | |
* @param \Closure $callback | |
* @return mixed | |
*/ | |
public function rebinding($abstract, Closure $callback) | |
{ | |
$this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; | |
if ($this->bound($abstract)) { | |
return $this->make($abstract); | |
} | |
} | |
/** | |
* Refresh an instance on the given target and method. | |
* | |
* @param string $abstract | |
* @param mixed $target | |
* @param string $method | |
* @return mixed | |
*/ | |
public function refresh($abstract, $target, $method) | |
{ | |
return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) { | |
$target->{$method}($instance); | |
}); | |
} | |
/** | |
* Fire the "rebound" callbacks for the given abstract type. | |
* | |
* @param string $abstract | |
* @return void | |
*/ | |
protected function rebound($abstract) | |
{ | |
$instance = $this->make($abstract); | |
foreach ($this->getReboundCallbacks($abstract) as $callback) { | |
call_user_func($callback, $this, $instance); | |
} | |
} | |
/** | |
* Get the rebound callbacks for a given type. | |
* | |
* @param string $abstract | |
* @return array | |
*/ | |
protected function getReboundCallbacks($abstract) | |
{ | |
return $this->reboundCallbacks[$abstract] ?? []; | |
} | |
/** | |
* Wrap the given closure such that its dependencies will be injected when executed. | |
* | |
* @param \Closure $callback | |
* @param array $parameters | |
* @return \Closure | |
*/ | |
public function wrap(Closure $callback, array $parameters = []) | |
{ | |
return function () use ($callback, $parameters) { | |
return $this->call($callback, $parameters); | |
}; | |
} | |
/** | |
* Call the given Closure / class@method and inject its dependencies. | |
* | |
* @param callable|string $callback | |
* @param array<string, mixed> $parameters | |
* @param string|null $defaultMethod | |
* @return mixed | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
public function call($callback, array $parameters = [], $defaultMethod = null) | |
{ | |
return BoundMethod::call($this, $callback, $parameters, $defaultMethod); | |
} | |
/** | |
* Get a closure to resolve the given type from the container. | |
* | |
* @param string $abstract | |
* @return \Closure | |
*/ | |
public function factory($abstract) | |
{ | |
return function () use ($abstract) { | |
return $this->make($abstract); | |
}; | |
} | |
/** | |
* An alias function name for make(). | |
* | |
* @param string|callable $abstract | |
* @param array $parameters | |
* @return mixed | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
public function makeWith($abstract, array $parameters = []) | |
{ | |
return $this->make($abstract, $parameters); | |
} | |
/** | |
* Resolve the given type from the container. | |
* | |
* @param string|callable $abstract | |
* @param array $parameters | |
* @return mixed | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
public function make($abstract, array $parameters = []) | |
{ | |
return $this->resolve($abstract, $parameters); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function get($id) | |
{ | |
try { | |
return $this->resolve($id); | |
} catch (Exception $e) { | |
if ($this->has($id)) { | |
throw $e; | |
} | |
throw new EntryNotFoundException($id, $e->getCode(), $e); | |
} | |
} | |
/** | |
* Resolve the given type from the container. | |
* | |
* @param string|callable $abstract | |
* @param array $parameters | |
* @param bool $raiseEvents | |
* @return mixed | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
protected function resolve($abstract, $parameters = [], $raiseEvents = true) | |
{ | |
$abstract = $this->getAlias($abstract); | |
// First we'll fire any event handlers which handle the "before" resolving of | |
// specific types. This gives some hooks the chance to add various extends | |
// calls to change the resolution of objects that they're interested in. | |
if ($raiseEvents) { | |
$this->fireBeforeResolvingCallbacks($abstract, $parameters); | |
} | |
$concrete = $this->getContextualConcrete($abstract); | |
$needsContextualBuild = ! empty($parameters) || ! is_null($concrete); | |
// If an instance of the type is currently being managed as a singleton we'll | |
// just return an existing instance instead of instantiating new instances | |
// so the developer can keep using the same objects instance every time. | |
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { | |
return $this->instances[$abstract]; | |
} | |
$this->with[] = $parameters; | |
if (is_null($concrete)) { | |
$concrete = $this->getConcrete($abstract); | |
} | |
// We're ready to instantiate an instance of the concrete type registered for | |
// the binding. This will instantiate the types, as well as resolve any of | |
// its "nested" dependencies recursively until all have gotten resolved. | |
if ($this->isBuildable($concrete, $abstract)) { | |
$object = $this->build($concrete); | |
} else { | |
$object = $this->make($concrete); | |
} | |
// If we defined any extenders for this type, we'll need to spin through them | |
// and apply them to the object being built. This allows for the extension | |
// of services, such as changing configuration or decorating the object. | |
foreach ($this->getExtenders($abstract) as $extender) { | |
$object = $extender($object, $this); | |
} | |
// If the requested type is registered as a singleton we'll want to cache off | |
// the instances in "memory" so we can return it later without creating an | |
// entirely new instance of an object on each subsequent request for it. | |
if ($this->isShared($abstract) && ! $needsContextualBuild) { | |
$this->instances[$abstract] = $object; | |
} | |
if ($raiseEvents) { | |
$this->fireResolvingCallbacks($abstract, $object); | |
} | |
// Before returning, we will also set the resolved flag to "true" and pop off | |
// the parameter overrides for this build. After those two things are done | |
// we will be ready to return back the fully constructed class instance. | |
$this->resolved[$abstract] = true; | |
array_pop($this->with); | |
return $object; | |
} | |
/** | |
* Get the concrete type for a given abstract. | |
* | |
* @param string|callable $abstract | |
* @return mixed | |
*/ | |
protected function getConcrete($abstract) | |
{ | |
// If we don't have a registered resolver or concrete for the type, we'll just | |
// assume each type is a concrete name and will attempt to resolve it as is | |
// since the container should be able to resolve concretes automatically. | |
if (isset($this->bindings[$abstract])) { | |
return $this->bindings[$abstract]['concrete']; | |
} | |
return $abstract; | |
} | |
/** | |
* Get the contextual concrete binding for the given abstract. | |
* | |
* @param string|callable $abstract | |
* @return \Closure|string|array|null | |
*/ | |
protected function getContextualConcrete($abstract) | |
{ | |
if (! is_null($binding = $this->findInContextualBindings($abstract))) { | |
return $binding; | |
} | |
// Next we need to see if a contextual binding might be bound under an alias of the | |
// given abstract type. So, we will need to check if any aliases exist with this | |
// type and then spin through them and check for contextual bindings on these. | |
if (empty($this->abstractAliases[$abstract])) { | |
return; | |
} | |
foreach ($this->abstractAliases[$abstract] as $alias) { | |
if (! is_null($binding = $this->findInContextualBindings($alias))) { | |
return $binding; | |
} | |
} | |
} | |
/** | |
* Find the concrete binding for the given abstract in the contextual binding array. | |
* | |
* @param string|callable $abstract | |
* @return \Closure|string|null | |
*/ | |
protected function findInContextualBindings($abstract) | |
{ | |
return $this->contextual[end($this->buildStack)][$abstract] ?? null; | |
} | |
/** | |
* Determine if the given concrete is buildable. | |
* | |
* @param mixed $concrete | |
* @param string $abstract | |
* @return bool | |
*/ | |
protected function isBuildable($concrete, $abstract) | |
{ | |
return $concrete === $abstract || $concrete instanceof Closure; | |
} | |
/** | |
* Instantiate a concrete instance of the given type. | |
* | |
* @param \Closure|string $concrete | |
* @return mixed | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
public function build($concrete) | |
{ | |
// If the concrete type is actually a Closure, we will just execute it and | |
// hand back the results of the functions, which allows functions to be | |
// used as resolvers for more fine-tuned resolution of these objects. | |
if ($concrete instanceof Closure) { | |
return $concrete($this, $this->getLastParameterOverride()); | |
} | |
try { | |
$reflector = new ReflectionClass($concrete); | |
} catch (ReflectionException $e) { | |
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e); | |
} | |
// If the type is not instantiable, the developer is attempting to resolve | |
// an abstract type such as an Interface or Abstract Class and there is | |
// no binding registered for the abstractions so we need to bail out. | |
if (! $reflector->isInstantiable()) { | |
return $this->notInstantiable($concrete); | |
} | |
$this->buildStack[] = $concrete; | |
$constructor = $reflector->getConstructor(); | |
// If there are no constructors, that means there are no dependencies then | |
// we can just resolve the instances of the objects right away, without | |
// resolving any other types or dependencies out of these containers. | |
if (is_null($constructor)) { | |
array_pop($this->buildStack); | |
return new $concrete; | |
} | |
$dependencies = $constructor->getParameters(); | |
// Once we have all the constructor's parameters we can create each of the | |
// dependency instances and then use the reflection instances to make a | |
// new instance of this class, injecting the created dependencies in. | |
try { | |
$instances = $this->resolveDependencies($dependencies); | |
} catch (BindingResolutionException $e) { | |
array_pop($this->buildStack); | |
throw $e; | |
} | |
array_pop($this->buildStack); | |
return $reflector->newInstanceArgs($instances); | |
} | |
/** | |
* Resolve all of the dependencies from the ReflectionParameters. | |
* | |
* @param \ReflectionParameter[] $dependencies | |
* @return array | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
protected function resolveDependencies(array $dependencies) | |
{ | |
$results = []; | |
foreach ($dependencies as $dependency) { | |
// If the dependency has an override for this particular build we will use | |
// that instead as the value. Otherwise, we will continue with this run | |
// of resolutions and let reflection attempt to determine the result. | |
if ($this->hasParameterOverride($dependency)) { | |
$results[] = $this->getParameterOverride($dependency); | |
continue; | |
} | |
// If the class is null, it means the dependency is a string or some other | |
// primitive type which we can not resolve since it is not a class and | |
// we will just bomb out with an error since we have no-where to go. | |
$result = is_null(Util::getParameterClassName($dependency)) | |
? $this->resolvePrimitive($dependency) | |
: $this->resolveClass($dependency); | |
if ($dependency->isVariadic()) { | |
$results = array_merge($results, $result); | |
} else { | |
$results[] = $result; | |
} | |
} | |
return $results; | |
} | |
/** | |
* Determine if the given dependency has a parameter override. | |
* | |
* @param \ReflectionParameter $dependency | |
* @return bool | |
*/ | |
protected function hasParameterOverride($dependency) | |
{ | |
return array_key_exists( | |
$dependency->name, $this->getLastParameterOverride() | |
); | |
} | |
/** | |
* Get a parameter override for a dependency. | |
* | |
* @param \ReflectionParameter $dependency | |
* @return mixed | |
*/ | |
protected function getParameterOverride($dependency) | |
{ | |
return $this->getLastParameterOverride()[$dependency->name]; | |
} | |
/** | |
* Get the last parameter override. | |
* | |
* @return array | |
*/ | |
protected function getLastParameterOverride() | |
{ | |
return count($this->with) ? end($this->with) : []; | |
} | |
/** | |
* Resolve a non-class hinted primitive dependency. | |
* | |
* @param \ReflectionParameter $parameter | |
* @return mixed | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
protected function resolvePrimitive(ReflectionParameter $parameter) | |
{ | |
if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) { | |
return $concrete instanceof Closure ? $concrete($this) : $concrete; | |
} | |
if ($parameter->isDefaultValueAvailable()) { | |
return $parameter->getDefaultValue(); | |
} | |
$this->unresolvablePrimitive($parameter); | |
} | |
/** | |
* Resolve a class based dependency from the container. | |
* | |
* @param \ReflectionParameter $parameter | |
* @return mixed | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
protected function resolveClass(ReflectionParameter $parameter) | |
{ | |
try { | |
return $parameter->isVariadic() | |
? $this->resolveVariadicClass($parameter) | |
: $this->make(Util::getParameterClassName($parameter)); | |
} | |
// If we can not resolve the class instance, we will check to see if the value | |
// is optional, and if it is we will return the optional parameter value as | |
// the value of the dependency, similarly to how we do this with scalars. | |
catch (BindingResolutionException $e) { | |
if ($parameter->isDefaultValueAvailable()) { | |
array_pop($this->with); | |
return $parameter->getDefaultValue(); | |
} | |
if ($parameter->isVariadic()) { | |
array_pop($this->with); | |
return []; | |
} | |
throw $e; | |
} | |
} | |
/** | |
* Resolve a class based variadic dependency from the container. | |
* | |
* @param \ReflectionParameter $parameter | |
* @return mixed | |
*/ | |
protected function resolveVariadicClass(ReflectionParameter $parameter) | |
{ | |
$className = Util::getParameterClassName($parameter); | |
$abstract = $this->getAlias($className); | |
if (! is_array($concrete = $this->getContextualConcrete($abstract))) { | |
return $this->make($className); | |
} | |
return array_map(function ($abstract) { | |
return $this->resolve($abstract); | |
}, $concrete); | |
} | |
/** | |
* Throw an exception that the concrete is not instantiable. | |
* | |
* @param string $concrete | |
* @return void | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
protected function notInstantiable($concrete) | |
{ | |
if (! empty($this->buildStack)) { | |
$previous = implode(', ', $this->buildStack); | |
$message = "Target [$concrete] is not instantiable while building [$previous]."; | |
} else { | |
$message = "Target [$concrete] is not instantiable."; | |
} | |
throw new BindingResolutionException($message); | |
} | |
/** | |
* Throw an exception for an unresolvable primitive. | |
* | |
* @param \ReflectionParameter $parameter | |
* @return void | |
* | |
* @throws \Illuminate\Contracts\Container\BindingResolutionException | |
*/ | |
protected function unresolvablePrimitive(ReflectionParameter $parameter) | |
{ | |
$message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}"; | |
throw new BindingResolutionException($message); | |
} | |
/** | |
* Register a new before resolving callback for all types. | |
* | |
* @param \Closure|string $abstract | |
* @param \Closure|null $callback | |
* @return void | |
*/ | |
public function beforeResolving($abstract, Closure $callback = null) | |
{ | |
if (is_string($abstract)) { | |
$abstract = $this->getAlias($abstract); | |
} | |
if ($abstract instanceof Closure && is_null($callback)) { | |
$this->globalBeforeResolvingCallbacks[] = $abstract; | |
} else { | |
$this->beforeResolvingCallbacks[$abstract][] = $callback; | |
} | |
} | |
/** | |
* Register a new resolving callback. | |
* | |
* @param \Closure|string $abstract | |
* @param \Closure|null $callback | |
* @return void | |
*/ | |
public function resolving($abstract, Closure $callback = null) | |
{ | |
if (is_string($abstract)) { | |
$abstract = $this->getAlias($abstract); | |
} | |
if (is_null($callback) && $abstract instanceof Closure) { | |
$this->globalResolvingCallbacks[] = $abstract; | |
} else { | |
$this->resolvingCallbacks[$abstract][] = $callback; | |
} | |
} | |
/** | |
* Register a new after resolving callback for all types. | |
* | |
* @param \Closure|string $abstract | |
* @param \Closure|null $callback | |
* @return void | |
*/ | |
public function afterResolving($abstract, Closure $callback = null) | |
{ | |
if (is_string($abstract)) { | |
$abstract = $this->getAlias($abstract); | |
} | |
if ($abstract instanceof Closure && is_null($callback)) { | |
$this->globalAfterResolvingCallbacks[] = $abstract; | |
} else { | |
$this->afterResolvingCallbacks[$abstract][] = $callback; | |
} | |
} | |
/** | |
* Fire all of the before resolving callbacks. | |
* | |
* @param string $abstract | |
* @param array $parameters | |
* @return void | |
*/ | |
protected function fireBeforeResolvingCallbacks($abstract, $parameters = []) | |
{ | |
$this->fireBeforeCallbackArray($abstract, $parameters, $this->globalBeforeResolvingCallbacks); | |
foreach ($this->beforeResolvingCallbacks as $type => $callbacks) { | |
if ($type === $abstract || is_subclass_of($abstract, $type)) { | |
$this->fireBeforeCallbackArray($abstract, $parameters, $callbacks); | |
} | |
} | |
} | |
/** | |
* Fire an array of callbacks with an object. | |
* | |
* @param string $abstract | |
* @param array $parameters | |
* @param array $callbacks | |
* @return void | |
*/ | |
protected function fireBeforeCallbackArray($abstract, $parameters, array $callbacks) | |
{ | |
foreach ($callbacks as $callback) { | |
$callback($abstract, $parameters, $this); | |
} | |
} | |
/** | |
* Fire all of the resolving callbacks. | |
* | |
* @param string $abstract | |
* @param mixed $object | |
* @return void | |
*/ | |
protected function fireResolvingCallbacks($abstract, $object) | |
{ | |
$this->fireCallbackArray($object, $this->globalResolvingCallbacks); | |
$this->fireCallbackArray( | |
$object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) | |
); | |
$this->fireAfterResolvingCallbacks($abstract, $object); | |
} | |
/** | |
* Fire all of the after resolving callbacks. | |
* | |
* @param string $abstract | |
* @param mixed $object | |
* @return void | |
*/ | |
protected function fireAfterResolvingCallbacks($abstract, $object) | |
{ | |
$this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks); | |
$this->fireCallbackArray( | |
$object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks) | |
); | |
} | |
/** | |
* Get all callbacks for a given type. | |
* | |
* @param string $abstract | |
* @param object $object | |
* @param array $callbacksPerType | |
* | |
* @return array | |
*/ | |
protected function getCallbacksForType($abstract, $object, array $callbacksPerType) | |
{ | |
$results = []; | |
foreach ($callbacksPerType as $type => $callbacks) { | |
if ($type === $abstract || $object instanceof $type) { | |
$results = array_merge($results, $callbacks); | |
} | |
} | |
return $results; | |
} | |
/** | |
* Fire an array of callbacks with an object. | |
* | |
* @param mixed $object | |
* @param array $callbacks | |
* @return void | |
*/ | |
protected function fireCallbackArray($object, array $callbacks) | |
{ | |
foreach ($callbacks as $callback) { | |
$callback($object, $this); | |
} | |
} | |
/** | |
* Get the container's bindings. | |
* | |
* @return array | |
*/ | |
public function getBindings() | |
{ | |
return $this->bindings; | |
} | |
/** | |
* Get the alias for an abstract if available. | |
* | |
* @param string $abstract | |
* @return string | |
*/ | |
public function getAlias($abstract) | |
{ | |
return isset($this->aliases[$abstract]) | |
? $this->getAlias($this->aliases[$abstract]) | |
: $abstract; | |
} | |
/** | |
* Get the extender callbacks for a given type. | |
* | |
* @param string $abstract | |
* @return array | |
*/ | |
protected function getExtenders($abstract) | |
{ | |
return $this->extenders[$this->getAlias($abstract)] ?? []; | |
} | |
/** | |
* Remove all of the extender callbacks for a given type. | |
* | |
* @param string $abstract | |
* @return void | |
*/ | |
public function forgetExtenders($abstract) | |
{ | |
unset($this->extenders[$this->getAlias($abstract)]); | |
} | |
/** | |
* Drop all of the stale instances and aliases. | |
* | |
* @param string $abstract | |
* @return void | |
*/ | |
protected function dropStaleInstances($abstract) | |
{ | |
unset($this->instances[$abstract], $this->aliases[$abstract]); | |
} | |
/** | |
* Remove a resolved instance from the instance cache. | |
* | |
* @param string $abstract | |
* @return void | |
*/ | |
public function forgetInstance($abstract) | |
{ | |
unset($this->instances[$abstract]); | |
} | |
/** | |
* Clear all of the instances from the container. | |
* | |
* @return void | |
*/ | |
public function forgetInstances() | |
{ | |
$this->instances = []; | |
} | |
/** | |
* Flush the container of all bindings and resolved instances. | |
* | |
* @return void | |
*/ | |
public function flush() | |
{ | |
$this->aliases = []; | |
$this->resolved = []; | |
$this->bindings = []; | |
$this->instances = []; | |
$this->abstractAliases = []; | |
} | |
/** | |
* Get the globally available instance of the container. | |
* | |
* @return static | |
*/ | |
public static function getInstance() | |
{ | |
if (is_null(static::$instance)) { | |
static::$instance = new static; | |
} | |
return static::$instance; | |
} | |
/** | |
* Set the shared instance of the container. | |
* | |
* @param \Illuminate\Contracts\Container\Container|null $container | |
* @return \Illuminate\Contracts\Container\Container|static | |
*/ | |
public static function setInstance(ContainerContract $container = null) | |
{ | |
return static::$instance = $container; | |
} | |
/** | |
* Determine if a given offset exists. | |
* | |
* @param string $key | |
* @return bool | |
*/ | |
public function offsetExists($key) | |
{ | |
return $this->bound($key); | |
} | |
/** | |
* Get the value at a given offset. | |
* | |
* @param string $key | |
* @return mixed | |
*/ | |
public function offsetGet($key) | |
{ | |
return $this->make($key); | |
} | |
/** | |
* Set the value at a given offset. | |
* | |
* @param string $key | |
* @param mixed $value | |
* @return void | |
*/ | |
public function offsetSet($key, $value) | |
{ | |
$this->bind($key, $value instanceof Closure ? $value : function () use ($value) { | |
return $value; | |
}); | |
} | |
/** | |
* Unset the value at a given offset. | |
* | |
* @param string $key | |
* @return void | |
*/ | |
public function offsetUnset($key) | |
{ | |
unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]); | |
} | |
/** | |
* Dynamically access container services. | |
* | |
* @param string $key | |
* @return mixed | |
*/ | |
public function __get($key) | |
{ | |
return $this[$key]; | |
} | |
/** | |
* Dynamically set container services. | |
* | |
* @param string $key | |
* @param mixed $value | |
* @return void | |
*/ | |
public function __set($key, $value) | |
{ | |
$this[$key] = $value; | |
} | |
} |