Laravel 服务容器源码讲解

心随所遇 / 2023-07-05 / 原文

Laravel 服务容器是一个用于管理类依赖以及实现依赖注入的强有力工具。通过服务容器对象的 bind 方法将服务类的名称与创建方式,即闭包函数关联在一起,挂载到服务容器中。

容器对象通过反射,解析到有需要该类型对象时,自动创建并注入。bind() 方法是基础方法,其他,单例的绑定 singleton, 带作用域单例绑定 scoped, 绑定实例 instance 都是调用 bind 方法实现的。

绑定完成之后,生成实例时,需要调用 $app->make() 方法,而 app 的 make() 方法调用父级容器的 make() 方法,接着用调的 resolve()方法。

服务提供者,是指引导批量注册服务的一种实现方式。服务提供者有引导方法 boot() 和注册方法 register(), 所有服务提供者的 register() 先执行完,然后再执行 所有的 boot() 方法,以保证  boot() 方法执行时,所需要的服务未注册的情况发生。

 

class 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 (is_null($concrete)) {
            $concrete = $abstract;
        }

        // 如果 $concrete 不是闭包函数,则抛出异常
        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 数组,结构类似于:
        // $this->bindings["App\Models\User"] = [ 'concrete'=> Closure, 'shared' => false ]
        // 后面会在 \Illuminate\Container\Container::getConcrete 方法中使用到
        $this->bindings[$abstract] = compact('concrete', 'shared');


        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }



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


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

        if ($raiseEvents) {
            $this->fireBeforeResolvingCallbacks($abstract, $parameters);
        }

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

        $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

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

        // 将参数 放入 $this->with 栈
        $this->with[] = $parameters;

        if (is_null($concrete)) {

            // 获取 $this->bindings[$abstract]['concrete'] 闭包函数
            $concrete = $this->getConcrete($abstract);
        }

        //如果可以绑定: $concrete === $abstract 或  $concrete 是闭包函数
        if ($this->isBuildable($concrete, $abstract)) {
            // 通过闭包创建对象
            $object = $this->build($concrete);
        } else {
            // 递归去创建
            $object = $this->make($concrete);
        }


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

        // 是单例 且 不需要上下文构建
        if ($this->isShared($abstract) && ! $needsContextualBuild) {

            // 创建完成,放入 $this->instances 数组
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        // 标记为已经创建
        $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 (isset($this->bindings[$abstract])) {
            // 从绑定数组中拿闭包函数
            return $this->bindings[$abstract]['concrete'];
        }

        return $abstract;
    }


}