Laravel容器绑定函数instance、bind、singleton、以及make解析方法源码解析


在阅读该文章前建议先阅读Laravel学习笔记之IOC容器源码分析文章了解下Laravel的IOC容器是如何实现的
在Laravel的文档中提到了如何在服务提供者ServerProvider中绑定对象到容器。
我们可以通过bind方法注册一个绑定

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

singleton 方法绑定一个只需要解析一次的类或接口到容器,然后接下来对容器的调用将会返回同一个实例:

$this->app->singleton('FooBar', function ($app) {
    return new FooBar($app->make('HttpClient'));
});

还可以用instance方法将已存在的对象绑定到容器中

$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);

从容器中解析对象

$fooBar = $this->app->make('HelpSpot\API');

我们具体分析一下他们的源码,看看有什么区别
instance()

public function instance($abstract, $instance)
    {
        $abstract = $this->normalize($abstract);//normalize函数对传进来的$abstract判断是否为字符串,如果是字符串,开头有\就去掉,不是字符串就直接返回
        //这里判断传递进来是否为数组,比如['app'=>'Illuminate\Container\Container']
        if (is_array($abstract)) {
            list($abstract, $alias) = $this->extractAlias($abstract);

            $this->alias($abstract, $alias);//存放到aliases[]数组中
        }

        unset($this->aliases[$abstract]);

        $bound = $this->bound($abstract);//判断bindings、instances、aliases数组中是否已经存在当前对象

        $this->instances[$abstract] = $instance;//将对象丢进instances数组中

        if ($bound) {
        //如果已经注册过了,执行reboundCallbacks的回调函数
            $this->rebound($abstract); 
        }
    }

instance的作用是将已有的实例注册到容器中

bind()方法

public function bind($abstract, $concrete = null, $shared = false)
    {
        $abstract = $this->normalize($abstract);//normalize函数对传进来的$abstract判断是否为字符串,如果是字符串,开头有\就去掉,不是字符串就直接返回

        $concrete = $this->normalize($concrete);//同上

        //这里判断传递进来是否为数组

        if (is_array($abstract)) {
            list($abstract, $alias) = $this->extractAlias($abstract);

            $this->alias($abstract, $alias);//存放到aliases[]数组中
        }

        //删除instances、aliases已经注册过的
        $this->dropStaleInstances($abstract);
    //这里如果没有传递$concrete变量$concrete变量默认为别名
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

    //如果$concrete不是一个闭包,就封装成闭包返回,该闭包会判断$abstract和$concrete是否相等,相等就调用容器中的build方法,否则调用容器的make方法解析
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
    //保存在bindings当中
        $this->bindings[$abstract] = compact('concrete', 'shared');

        //如果已经注册过了,执行reboundCallbacks的回调函数
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

我们来看build方法,make方法放到最后分析

public function build($concrete, array $parameters = [])
    {
        //如果参数$concrete是一个闭包,那么执行这个闭包并且返回
        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);
        }
    //将类存放到buildStack数组
        $this->buildStack[] = $concrete;
    //得到构造函数
        $constructor = $reflector->getConstructor();

        //如果构造函数为空,说明没有依赖,可以直接实例化返回
        if (is_null($constructor)) {
        //从buildStack中删除这个类
            array_pop($this->buildStack);

            return new $concrete;
        }
    //利用php反射获取构造函数的参数
        $dependencies = $constructor->getParameters();

        //keyParametersByArgument函数将已经存在的参数整合到$parameters变量
        $parameters = $this->keyParametersByArgument(
            $dependencies, $parameters
        );
    //这里getDependencies函数会去遍历参数,调用make函数实例化依赖的类并返回,不是类就抛出错误
        $instances = $this->getDependencies(
            $dependencies, $parameters
        );
    //从buildStack中删除这个类
        array_pop($this->buildStack);
    //返回实例化后的类
        return $reflector->newInstanceArgs($instances);
    }

可以看出build的主要作用是用来执行闭包函数,或者实例化类名后返回

所以最终bind方法会在bindings[]中存放如下内容:

bindings['events']=>[
    'concrete'=>$this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make('Illuminate\Contracts\Queue\Factory');
            });
        }),
    'shared'=>false
]
singleton函数其实就是调用了bind方法,不过$shared参数为true;
也就是说bindings[]中存放内容如下:
bindings['events']=>[
    'concrete'=>$this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make('Illuminate\Contracts\Queue\Factory');
            });
        }),
    'shared'=>true
]

最后我们来看make()函数

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

        //如果该实例已经存在,直接返回,也就是instances注册的实例
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }
    //通过别名获取之前bind方法保存在bindings[]数组中存放的闭包函数
        $concrete = $this->getConcrete($abstract);

        //判断$concrete是否是闭包函数,或者$concrete==$abstract 满足的话执行build函数,否则执行make函数(这里其实在判断传的是闭包还是类名还是绑定接口到实现的方式)
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete, $parameters);//build函数上面已经分析过了,这里$object已经被build函数生成了实例了
        } else {
        //如果是绑定接口到实现的方式,那么再执行make函数,把实现类的类名传入,这次调用build生成的就是实现类的实例
            $object = $this->make($concrete, $parameters);
        }

        //这里应该是执行一些额外设置的回调函数
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        //如果是否是单例注册,放到instances,下次执行make直接返回已经实例化好的对象
        if ($this->isShared($abstract)) {
            $this->instances[$abstract] = $object;
        }
    //运行回调函数
        $this->fireResolvingCallbacks($abstract, $object);
    //标记已经实例化
        $this->resolved[$abstract] = true;
    //返回实例
        return $object;
    }

完。

本文地址:https://www.blear.cn/article/Laravel-instance-bind-singleton-make-source

转载时请以链接形式注明出处

评论