diff --git a/laravel/documentation/changes.md b/laravel/documentation/changes.md index 04bfebf4..24610420 100644 --- a/laravel/documentation/changes.md +++ b/laravel/documentation/changes.md @@ -71,6 +71,7 @@ ## Laravel 3.2 - Added Response::json method for creating JSON responses. - Added Response::eloquent method for creating Eloquent responses. - Fixed bug when using many-to-many relationships on non-default database connection. +- Added true reflection based IoC to container. ## Upgrading From 3.1 diff --git a/laravel/ioc.php b/laravel/ioc.php index cfdafaf0..a90dda58 100644 --- a/laravel/ioc.php +++ b/laravel/ioc.php @@ -20,11 +20,11 @@ class IoC { * Register an object and its resolver. * * @param string $name - * @param Closure $resolver + * @param mixed $resolver * @param bool $singleton * @return void */ - public static function register($name, Closure $resolver, $singleton = false) + public static function register($name, $resolver, $singleton = false) { static::$registry[$name] = compact('resolver', 'singleton'); } @@ -72,39 +72,7 @@ public static function instance($name, $instance) } /** - * Register a controller with the IoC container. - * - * @param string $name - * @param Closure $resolver - * @return void - */ - public static function controller($name, $resolver) - { - static::register("controller: {$name}", $resolver); - } - - /** - * Resolve a core Laravel class from the container. - * - * - * // Resolve the "laravel.router" class from the container - * $input = IoC::core('router'); - * - * // Equivalent resolution of the router using the "resolve" method - * $input = IoC::resolve('laravel.router'); - * - * - * @param string $name - * @param array $parameters - * @return mixed - */ - public static function core($name, $parameters = array()) - { - return static::resolve("laravel.{$name}", $parameters); - } - - /** - * Resolve an object instance from the container. + * Resolve a given type to an instance. * * * // Get an instance of the "mailer" object registered in the container @@ -114,28 +82,112 @@ public static function core($name, $parameters = array()) * $mailer = IoC::resolve('mailer', array('test')); * * - * @param string $name - * @param array $parameters + * @param string $type * @return mixed */ - public static function resolve($name, $parameters = array()) + public static function resolve($type, $parameters = array()) { - if (array_key_exists($name, static::$singletons)) + // If an instance of the type is currently being managed as a singleton, we will + // just return the existing instance instead of instantiating a fresh instance + // so the developer can keep re-using the exact same object instance from us. + if (isset(static::$singletons[$type])) { - return static::$singletons[$name]; + return static::$singletons[$type]; } - $object = call_user_func_array(static::$registry[$name]['resolver'], $parameters); + $concrete = array_get(static::$registry, "{$type}.resolver", $type); - // If the resolver is registering as a singleton resolver, we will cache - // the instance of the object in the container so we can resolve it next - // time without having to instantiate a brand new instance. - if (static::$registry[$name]['singleton']) + // We're ready to instantiate an instance of the concrete type registered for + // the binding. This will instantiate the type, as well as resolve any of + // its nested dependencies recursively until they are each resolved. + if ($concrete == $type or $concrete instanceof Closure) { - return static::$singletons[$name] = $object; + $object = static::build($concrete); + } + else + { + $object = static::resolve($concrete); + } + + // If the requested type is registered as a singleton, we want to cache off + // the instance in memory so we can return it later without creating an + // entirely new instances of the object on each subsequent request. + if (isset(static::$registry[$type]['singleton'])) + { + static::$singletons[$type] = $object; } return $object; } + /** + * Instantiate an instance of the given type. + * + * @param string $type + * @param array $parameters + * @return mixed + */ + protected static function build($type, $parameters = array()) + { + // If the concrete type is actually a Closure, we will just execute it and + // hand back the results of the function, which allows functions to be + // used as resolvers for more fine-tuned resolution of the objects. + if ($type instanceof Closure) + { + return call_user_func_array($type, $parameters); + } + + $reflector = new \ReflectionClass($type); + + // If the type is not instantiable, the developer is attempting to resolve + // an abstract type such as an Interface of Abstract Class and there is + // no binding registered for the abstraction so we need to bail out. + if ( ! $reflector->isInstantiable()) + { + throw new Exception("Resolution target [$type] is not instantiable."); + } + + $constructor = $reflector->getConstructor(); + + // If there is no constructor, that means there are no dependencies and + // we can just resolve an instance of the object right away without + // resolving any other types or dependencies from the container. + if (is_null($constructor)) + { + return new $type; + } + + $dependencies = static::dependencies($constructor->getParameters()); + + return $reflector->newInstanceArgs($dependencies); + } + + /** + * Resolve all of the dependencies from the ReflectionParameters. + * + * @param array $parameterrs + * @return array + */ + protected static function dependencies($parameters) + { + $dependencies = array(); + + foreach ($parameters as $parameter) + { + $dependency = $parameter->getClass(); + + // If the class is null, it means the dependency is a string or some other + // primitive type, which we can not esolve since it is not a class and + // we'll just bomb out with an error since we have nowhere to go. + if (is_null($dependency)) + { + throw new Exception("Unresolvable dependency resolving [$parameter]."); + } + + $dependencies[] = static::resolve($dependency->name); + } + + return (array) $dependencies; + } + } \ No newline at end of file