一、laravel 的依赖注入

在controller中,经常会使用

public function index(Requet $request, $id, User $user)
{
    $type = $request->get('type');
    $u = $user->where('id', $id)->first();
}

其中Request $request (Request type-hint 依赖约束), 以及User $user都是依赖注入 $id是普通参数,这三个参数的顺序交换了也可以正常运行,但是如果是增加一个普通的参数,(Requet $request, $id, User $user, $type),如果route中没有$type就会报错。对于构造函数同样也可以, laravel中的这种依赖注入是怎么实现的呢?

二、laravel 的 Container

container是laravel中的核心组件,所有service等都会在初始化的时候bind到container中,需要使用的时候从container中make,包括我们常用到的Route,Cache以及Controller。在Illuminate\Container\Container.php中有bind方法,负责初始化时将各种service中bind,还有就是make方法,负责使用时make,依赖注入也是在make中实现的

 public function make($abstract, array $parameters = [])
 {
     $abstract = $this->getAlias($this->normalize($abstract));

     if (isset($this->instances[$abstract])) {
         return $this->instances[$abstract];
     }

     $concrete = $this->getConcrete($abstract);

     if ($this->isBuildable($concrete, $abstract)) {
         $object = $this->build($concrete, $parameters);
     } else {
         $object = $this->make($concrete, $parameters);
     }

     foreach ($this->getExtenders($abstract) as $extender) {
         $object = $extender($object, $this);
     }

     if ($this->isShared($abstract)) {
         $this->instances[$abstract] = $object;
     }

     $this->fireResolvingCallbacks($abstract, $object);

     $this->resolved[$abstract] = true;

     return $object;
 }

在isBuildable之前都是为了把传入的$abstract(string)转换成可以build的类名字符串

否则迭代自己重复进行format,而isBuildable之后则是对创建成功的实例做后续处理,比如如果这个类是共享的,则要保存起来以后创建时直接取这个实例,所以具体new instance是在build()函数中实现的

 public function build($concrete, array $parameters = [])
 {
     if ($concrete instanceof Closure) {
         return $concrete($this, $parameters);
     }

     $reflector = new ReflectionClass($concrete);

     if (! $reflector->isInstantiable()) {
         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);
     }

     $this->buildStack[] = $concrete;

     $constructor = $reflector->getConstructor();

     if (is_null($constructor)) {
         array_pop($this->buildStack);

         return new $concrete;
     }
     $dependencies = $constructor->getParameters();

     $parameters = $this->keyParametersByArgument(
         $dependencies, $parameters
     );

     $instances = $this->getDependencies(
         $dependencies, $parameters
      );

     array_pop($this->buildStack);

     return $reflector->newInstanceArgs($instances);
 }

build中通过反射ReflectionClass得到$reflector,如果$reflector不可实例化报错,检查这个类的构造函数,如果构造函数不包含参数,则直接新建实例返回结束;如果有参数,则通过getDependencies,将有依赖约束的全部new instance注入,放在数组instances中,通过$reflector->newInstanceArgs新建一个实例 其中getDependencies内容

protected function getDependencies(array $parameters, array $primitives = [])
{
    $dependencies = [];

    foreach ($parameters as $parameter) {
        $dependency = $parameter->getClass();

        if (array_key_exists($parameter->name, $primitives)) {
            $dependencies[] = $primitives[$parameter->name];
        } elseif (is_null($dependency)) {
            $dependencies[] = $this->resolveNonClass($parameter);
        } else {
            $dependencies[] = $this->resolveClass($parameter);
        }
    }

    return (array) $dependencies;
}

getDependencies会对每一个参数处理,在resolveClass中继续调用了自己的make,之后的步骤就与之前相同了

protected function resolveClass(ReflectionParameter $parameter)
{
    try {
        return $this->make($parameter->getClass()->name);
    }

    catch (BindingResolutionContractException $e) {
        if ($parameter->isOptional()) {
            return $parameter->getDefaultValue();
        }

        throw $e;
    }
}

总的从container中new一个instance的步骤是,先通过make format 传入的字符串是一个可以new的类名字符串,然后传入build,在build中判断此类是否有构造函数,没有直接new这个类就可以返回结束了。否则,对这个构造函数中的每一个依赖注入逐次make,每一个依赖注入的类如果构造函数中也有依赖注入的类,则递归处理,直到所有参数全部new成功后返回。