1480 lines
40 KiB
PHP
Executable File
1480 lines
40 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Illuminate\Container;
|
|
|
|
use ArrayAccess;
|
|
use Closure;
|
|
use Exception;
|
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
|
use Illuminate\Contracts\Container\CircularDependencyException;
|
|
use Illuminate\Contracts\Container\Container as ContainerContract;
|
|
use LogicException;
|
|
use ReflectionClass;
|
|
use ReflectionException;
|
|
use ReflectionParameter;
|
|
use TypeError;
|
|
|
|
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 container's scoped instances.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $scopedInstances = [];
|
|
|
|
/**
|
|
* 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}
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function has(string $id): bool
|
|
{
|
|
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
|
|
*
|
|
* @throws \TypeError
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register a scoped binding in the container.
|
|
*
|
|
* @param string $abstract
|
|
* @param \Closure|string|null $concrete
|
|
* @return void
|
|
*/
|
|
public function scoped($abstract, $concrete = null)
|
|
{
|
|
$this->scopedInstances[] = $abstract;
|
|
|
|
$this->singleton($abstract, $concrete);
|
|
}
|
|
|
|
/**
|
|
* Register a scoped binding if it hasn't already been registered.
|
|
*
|
|
* @param string $abstract
|
|
* @param \Closure|string|null $concrete
|
|
* @return void
|
|
*/
|
|
public function scopedIf($abstract, $concrete = null)
|
|
{
|
|
if (! $this->bound($abstract)) {
|
|
$this->scoped($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) {
|
|
$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 fn () => $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)
|
|
{
|
|
$pushedToBuildStack = false;
|
|
|
|
if (is_array($callback) && ! in_array(
|
|
$className = (is_string($callback[0]) ? $callback[0] : get_class($callback[0])),
|
|
$this->buildStack,
|
|
true
|
|
)) {
|
|
$this->buildStack[] = $className;
|
|
|
|
$pushedToBuildStack = true;
|
|
}
|
|
|
|
$result = BoundMethod::call($this, $callback, $parameters, $defaultMethod);
|
|
|
|
if ($pushedToBuildStack) {
|
|
array_pop($this->buildStack);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get a closure to resolve the given type from the container.
|
|
*
|
|
* @param string $abstract
|
|
* @return \Closure
|
|
*/
|
|
public function factory($abstract)
|
|
{
|
|
return fn () => $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}
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function get(string $id)
|
|
{
|
|
try {
|
|
return $this->resolve($id);
|
|
} catch (Exception $e) {
|
|
if ($this->has($id) || $e instanceof CircularDependencyException) {
|
|
throw $e;
|
|
}
|
|
|
|
throw new EntryNotFoundException($id, is_int($e->getCode()) ? $e->getCode() : 0, $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
|
|
* @throws \Illuminate\Contracts\Container\CircularDependencyException
|
|
*/
|
|
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
|
|
* @throws \Illuminate\Contracts\Container\CircularDependencyException
|
|
*/
|
|
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 Util::unwrapIfClosure($concrete, $this);
|
|
}
|
|
|
|
if ($parameter->isDefaultValueAvailable()) {
|
|
return $parameter->getDefaultValue();
|
|
}
|
|
|
|
if ($parameter->isVariadic()) {
|
|
return [];
|
|
}
|
|
|
|
$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(fn ($abstract) => $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 = [];
|
|
}
|
|
|
|
/**
|
|
* Clear all of the scoped instances from the container.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function forgetScopedInstances()
|
|
{
|
|
foreach ($this->scopedInstances as $scoped) {
|
|
unset($this->instances[$scoped]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flush the container of all bindings and resolved instances.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function flush()
|
|
{
|
|
$this->aliases = [];
|
|
$this->resolved = [];
|
|
$this->bindings = [];
|
|
$this->instances = [];
|
|
$this->abstractAliases = [];
|
|
$this->scopedInstances = [];
|
|
}
|
|
|
|
/**
|
|
* 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): bool
|
|
{
|
|
return $this->bound($key);
|
|
}
|
|
|
|
/**
|
|
* Get the value at a given offset.
|
|
*
|
|
* @param string $key
|
|
* @return mixed
|
|
*/
|
|
public function offsetGet($key): mixed
|
|
{
|
|
return $this->make($key);
|
|
}
|
|
|
|
/**
|
|
* Set the value at a given offset.
|
|
*
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
public function offsetSet($key, $value): void
|
|
{
|
|
$this->bind($key, $value instanceof Closure ? $value : fn () => $value);
|
|
}
|
|
|
|
/**
|
|
* Unset the value at a given offset.
|
|
*
|
|
* @param string $key
|
|
* @return void
|
|
*/
|
|
public function offsetUnset($key): void
|
|
{
|
|
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;
|
|
}
|
|
}
|