bail out. if ( ! $reflector->isInstantiable() ) { $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 array $dependencies * * @return array * @throws BindingResolutionException * @throws ReflectionException */ 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. $result = is_null( $dependency->getClass() ) ? $this->resolvePrimitive( $dependency ) : $this->resolveClass( $dependency ); if ( $dependency->isVariadic() ) { array_push( $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 ReflectionException */ protected function resolvePrimitive( ReflectionParameter $parameter ) { if ( $parameter->isDefaultValueAvailable() ) { return $parameter->getDefaultValue(); } $this->unresolvablePrimitive( $parameter ); return null; } /** * Resolve a class based dependency from the container. * * @param ReflectionParameter $parameter * * @return mixed * @throws BindingResolutionException * @throws ReflectionException */ protected function resolveClass( ReflectionParameter $parameter ) { try { return $this->make( $parameter->getClass()->name ); } // 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() ) { return $parameter->getDefaultValue(); } if ( $parameter->isVariadic() ) { return []; } throw $e; } } /** * Throw an exception that the concrete is not instantiable. * * @param string $concrete * * @return void * @throws 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 BindingResolutionException */ protected function unresolvablePrimitive( ReflectionParameter $parameter ) { $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}"; throw new BindingResolutionException( $message ); } /** * 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 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 ) { array_push( $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 ) { if ( ! isset( $this->aliases[ $abstract ] ) ) { return $abstract; } return $this->getAlias( $this->aliases[ $abstract ] ); } /** * Get the extender callbacks for a given type. * * @param string $abstract * * @return array */ protected function getExtenders( $abstract ) { $abstract = $this->getAlias( $abstract ); return isset( $this->extenders[ $abstract ] ) ? $this->extenders[ $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 = []; } /** * 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; } /** * Checks to see if the key exists. * * @param $key * * @return bool */ public function __isset( $key ) { return isset( $this[ $key ] ); } }