HOME


Mini Shell 1.0
DIR: /home/dhnidqcz/africaprag.org/wp-content/themes/yuki/lotta-framework/src/Container/
Upload File :
Current File : //home/dhnidqcz/africaprag.org/wp-content/themes/yuki/lotta-framework/src/Container/Container.php
<?php

namespace LottaFramework\Container;

use ArrayAccess;
use Closure;
use Exception;
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;

class Container implements ArrayAccess {
	/**
	 * The current globally available container (if any).
	 *
	 * @var static
	 */
	protected static $instance;
	/**
	 * The contextual binding map.
	 *
	 * @var array[]
	 */
	public $contextual = [];
	/**
	 * 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 = [];
	/**
	 * All of the registered rebound callbacks.
	 *
	 * @var array[]
	 */
	protected $reboundCallbacks = [];

	/**
	 * All of the global resolving callbacks.
	 *
	 * @var \Closure[]
	 */
	protected $globalResolvingCallbacks = [];

	/**
	 * All of the global after resolving callbacks.
	 *
	 * @var \Closure[]
	 */
	protected $globalAfterResolvingCallbacks = [];

	/**
	 * 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 = [];

	/**
	 * 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 Container|null $container
	 *
	 * @return \Illuminate\Contracts\Container\Container|static
	 */
	public static function setInstance( Container $container = null ) {
		return static::$instance = $container;
	}

	/**
	 * Define a contextual binding.
	 *
	 * @param array|string $concrete
	 *
	 * @return ContextualBindingBuilder
	 */
	public function when( $concrete ) {
		$aliases = [];

		foreach ( Util::arrayWrap( $concrete ) as $c ) {
			$aliases[] = $this->getAlias( $c );
		}

		return new ContextualBindingBuilder( $this, $aliases );
	}

	/**
	 * Get the alias for an abstract if available.
	 *
	 * @param string $abstract
	 *
	 * @return string
	 */
	public function getAlias( $abstract ) {
		if ( ! isset( $this->aliases[ $abstract ] ) ) {
			return $abstract;
		}

		return $this->getAlias( $this->aliases[ $abstract ] );
	}

	/**
	 * 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 );
		}
	}

	/**
	 * 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 );
	}

	/**
	 * 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 ) {
			$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 );
		}
	}

	/**
	 * Drop all of the stale instances and aliases.
	 *
	 * @param string $abstract
	 *
	 * @return void
	 */
	protected function dropStaleInstances( $abstract ) {
		unset( $this->instances[ $abstract ], $this->aliases[ $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
			);
		};
	}

	/**
	 * Instantiate a concrete instance of the given type.
	 *
	 * @param string $concrete
	 *
	 * @return mixed
	 *
	 * @throws 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 );
	}

	/**
	 * Get the last parameter override.
	 *
	 * @return array
	 */
	protected function getLastParameterOverride() {
		return count( $this->with ) ? end( $this->with ) : [];
	}

	/**
	 * 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 );
	}

	/**
	 * 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 this dependency has a 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.
			$results[] = is_null( Util::getParameterClassName( $dependency ) )
				? $this->resolvePrimitive( $dependency )
				: $this->resolveClass( $dependency );
		}

		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 ];
	}

	/**
	 * 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 );
	}

	/**
	 * Get the contextual concrete binding for the given abstract.
	 *
	 * @param string $abstract
	 *
	 * @return \Closure|string|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 $abstract
	 *
	 * @return \Closure|string|null
	 */
	protected function findInContextualBindings( $abstract ) {
		return $this->contextual[ end( $this->buildStack ) ][ $abstract ] ?? null;
	}

	/**
	 * 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 );
	}

	/**
	 * 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 $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->isOptional() ) {
				return $parameter->getDefaultValue();
			}

			throw $e;
		}
	}

	/**
	 * Resolve the given type from the container.
	 *
	 * @param string $abstract
	 * @param array $parameters
	 *
	 * @return mixed
	 *
	 * @throws \Illuminate\Contracts\Container\BindingResolutionException
	 */
	public function make( $abstract, array $parameters = [] ) {
		return $this->resolve( $abstract, $parameters );
	}

	/**
	 * Resolve the given type from the container.
	 *
	 * @param string $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 );

		$needsContextualBuild = ! empty( $parameters ) || ! is_null(
				$this->getContextualConcrete( $abstract )
			);

		// 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;

		$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 $abstract
	 *
	 * @return mixed
	 */
	protected function getConcrete( $abstract ) {
		if ( ! is_null( $concrete = $this->getContextualConcrete( $abstract ) ) ) {
			return $concrete;
		}

		// 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;
	}

	/**
	 * 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;
	}

	/**
	 * Get the extender callbacks for a given type.
	 *
	 * @param string $abstract
	 *
	 * @return array
	 */
	protected function getExtenders( $abstract ) {
		$abstract = $this->getAlias( $abstract );

		return $this->extenders[ $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 );
	}

	/**
	 * 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 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 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 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 )
		);
	}

	/**
	 * 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 ] );
	}

	/**
	 * 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 ] ?? [];
	}

	/**
	 * 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 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 );
	}

	/**
	 * "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;
	}

	/**
	 * 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 );
		} );
	}

	/**
	 * 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 );
		}
	}

	/**
	 * 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 $parameters
	 * @param string|null $defaultMethod
	 *
	 * @return mixed
	 */
	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 $abstract
	 * @param array $parameters
	 *
	 * @return mixed
	 */
	public function makeWith( $abstract, array $parameters = [] ) {
		return $this->make( $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 );
		}
	}

	/**
	 *  {@inheritdoc}
	 */
	public function has( $id ) {
		return $this->bound( $id );
	}

	/**
	 * 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;
		}
	}

	/**
	 * Get the container's bindings.
	 *
	 * @return array
	 */
	public function getBindings() {
		return $this->bindings;
	}

	/**
	 * 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 ) ] );
	}

	/**
	 * 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 = [];
	}

	/**
	 * Determine if a given offset exists.
	 *
	 * @param string $key
	 *
	 * @return bool
	 */
	#[\ReturnTypeWillChange]
	public function offsetExists( $key ) {
		return $this->bound( $key );
	}

	/**
	 * Get the value at a given offset.
	 *
	 * @param string $key
	 *
	 * @return mixed
	 */
	#[\ReturnTypeWillChange]
	public function offsetGet( $key ) {
		return $this->make( $key );
	}

	/**
	 * Set the value at a given offset.
	 *
	 * @param string $key
	 * @param mixed $value
	 *
	 * @return void
	 */
	#[\ReturnTypeWillChange]
	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
	 */
	#[\ReturnTypeWillChange]
	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;
	}
}