Laravel内核分析-服务容器-继续深入

在看这篇之前,先看这里 Laravel内核分析-神奇的服务容器

再简单看一下这张图,看不懂没事,看完后再回来看一下,就明白了

Laravel 的核心是 IocContainer, 文档中称其为 “服务容器”,服务容器是一个用于管理类依赖和执行依赖注入的强大工具,Laravel 中的功能模块比如 Route、Eloquent ORM、Request、Response 等等等等,实际上都是与核心无关的类模块提供的,这些类从注册到实例化,最终被我们所使用,其实都是 laravel 的服务容器负责的。

在 Laravel 中框架把自带的各种服务绑定到服务容器,我们也可以绑定自定义服务到容器。当应用程序需要使用某一个服务时,服务容器会将服务解析出来同时自动解决服务之间的依赖然后交给应用程序使用。

 

在 Laravel 中框架把自带的各种服务绑定到服务容器,我们也可以绑定自定义服务到容器。当应用程序需要使用某一个服务时,服务容器会将服务解析出来同时自动解决服务之间的依赖然后交给应用程序使用。

服务绑定

常用的绑定服务到容器的方法有 instance, bind, singleton, alias

instance

将一个已存在的对象绑定到服务容器里,随后通过名称解析该服务时,容器将总返回这个绑定的实例

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

会把对象注册到服务容器的 $instnces 属性里

[
     'HelpSpot\Api' => new HelpSpot\API(new HttpClient);//$api是API类的对象,这里简写了
 ]

bind

绑定服务到服务容器

有三种绑定方式:

1.绑定自身
$this->app->bind('HelpSpot\API', null);

2.绑定闭包
$this->app->bind('HelpSpot\API', function () {
    return new HelpSpot\API();
});//闭包直接提供类实现方式
$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});//闭包返回需要依赖注入的类
3. 绑定接口和实现
$this->app->bind('Illuminate\Tests\Container\IContainerContractStub', 'Illuminate\Tests\Container\ContainerImplementationStub');

 

针对第一种情况,其实在 bind 方法内部会在绑定服务之前通过 getClosure() 为服务生成闭包,我们来看一下 bind 方法源码。

public function bind($abstract, $concrete = null, $shared = false)
    {
        //在绑定一个新的实现类之前,先清除旧的实例,以确保每次获取对象都是获取最新的实例。
        $this->dropStaleInstances($abstract);

        //如果未指定具体实现类,则将具体实现类设置为抽象类型,
        //即使得抽象类型与具体类型相同。这样在调用 make 方法时,会自动使用抽象类型来创建实例
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        //如果具体实现不是匿名函数,即不是 Closure 类型,
        //则将其包装在一个匿名函数中,以便在扩展时更加方便。
        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);
        }

        //将抽象类型和具体实现绑定存储在 $bindings 数组中,以便后续可以使用 make 方法来创建对象。
        $this->bindings[$abstract] = compact('concrete', 'shared');

        //如果该抽象类型已经在容器中解析过,则触发 rebound 事件,以便更新已经解析的对象实例
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
/**
 * 用于创建一个匿名函数,以便在注册绑定时将具体实现类包装在闭包中
 *
 * @param string $abstract
 * @param string $concrete
 * @return \Closure
 */
protected function getClosure($abstract, $concrete)
{
    //创建一个匿名函数,并使用 use 关键字引入 $abstract  $concrete 变量,使其在匿名函数中可用。
    return function ($container, $parameters = []) use ($abstract, $concrete) {
        //如果抽象类型与具体类型相同,则直接通过容器的 build 方法创建一个新的实例。
        if ($abstract == $concrete) {
            return $container->build($concrete);
        }

        //如果抽象类型与具体类型不同,则通过容器的 resolve 方法解析出具体类型的实例。
        return $container->resolve(
            //$parameters 参数是一个数组,用于传递额外的构造函数参数;
            //$raiseEvents 参数为 false,表示不触发事件
            $concrete, $parameters, $raiseEvents = false
        );
    };
}

 

bind 把服务注册到服务容器的 $bindings 属性里类似这样:

$bindings = [
    'HelpSpot\API' =>  [//闭包绑定
        'concrete' => function ($app, $paramters = []) {
            return $app->build('HelpSpot\API');
        },
        'shared' => false//如果是singleton绑定,这个值为true
    ]        
    'Illuminate\Tests\Container\IContainerContractStub' => [//接口实现绑定
        'concrete' => 'Illuminate\Tests\Container\ContainerImplementationStub',
        'shared' => false
    ]
]

 

singleton

public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}

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

 

alias

把服务和服务别名注册到容器:

public function alias($abstract, $alias)
{
    $this->aliases[$alias] = $this->normalize($abstract);
}

alias 方法会把把服务别名和服务类的对应关系注册到服务容器的 $aliases 属性里。

例如:

$this->app->alias('\Illuminate\ServiceName', 'service_alias');

绑定完服务后在使用时就可以通过

$this->app->make('service_alias');

服务对象解析出来,这样 make 的时候就不用写那些比较长的类名称了,对 make 方法的使用体验上有很大提升。

 

服务解析

make: 从服务容器中解析出服务对象,该方法接收你想要解析的类名或接口名作为参数

/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array   $parameters
 * @return mixed
 */
public function make($abstract, array $parameters = [])
{
    //getAlias方法会假定$abstract是绑定的别名,从$aliases找到映射的真实类型名
    //如果没有映射则$abstract即为真实类型名,将$abstract原样返回
    $abstract = $this->getAlias($this->normalize($abstract));

    // 如果服务是通过instance()方式绑定的,就直接解析返回绑定的service
    if (isset($this->instances[$abstract])) {
        return $this->instances[$abstract];
    }

    // 获取$abstract接口对应的$concrete(接口的实现)
    $concrete = $this->getConcrete($abstract);

    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete, $parameters);
    } else {
        //如果时接口实现这种绑定方式,通过接口拿到实现后需要再make一次才能
        //满足isBuildable的条件 ($abstract === $concrete)
        $object = $this->make($concrete, $parameters);
    }

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

    //如果服务是以singleton方式注册进来的则,把构建好的服务对象放到$instances里,
    //避免下次使用时重新构建
    if ($this->isShared($abstract)) {
        $this->instances[$abstract] = $object;
    }

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

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

    return $object;
}

protected function getConcrete($abstract)
{
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
        return $concrete;
    }

    // 如果是$abstract之前没有注册类实现到服务容器里,则服务容器会认为$abstract本身就是接口的类实现
    if (! isset($this->bindings[$abstract])) {
        return $abstract;
    }

    return $this->bindings[$abstract]['concrete'];
}

protected function isBuildable($concrete, $abstract)
{        
    return $concrete === $abstract || $concrete instanceof Closure;
}

通过对 make 方法的梳理我们发现,build 方法的职能是构建解析出来的服务的对象的

下面看一下构建对象的具体流程。(构建过程中用到了 [PHP 类的反射][1] 来实现服务的依赖注入)

PHP 类的反射如果不了解,请点看这里#

public function build($concrete, array $parameters = [])
{
    // 如果是闭包直接执行闭包并返回(对应闭包绑定)
    if ($concrete instanceof Closure) {
        return $concrete($this, $parameters);
    }

    // 使用反射ReflectionClass来对实现类进行反向工程
    $reflector = new ReflectionClass($concrete);

    // 如果不能实例化,这应该是接口或抽象类,再或者就是构造函数是private的
    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();

    // 如果构造函数是空,说明没有任何依赖,直接new返回
    if (is_null($constructor)) {
        array_pop($this->buildStack);

        return new $concrete;
    }

    // 获取构造函数的依赖(形参),返回一组ReflectionParameter对象组成的数组表示每一个参数
    $dependencies = $constructor->getParameters();

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

    // 构建构造函数需要的依赖
    $instances = $this->getDependencies(
        $dependencies, $parameters
    );

    array_pop($this->buildStack);

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

//获取依赖
protected function getDependencies(array $parameters, array $primitives = [])
{
    $dependencies = [];

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

        // 某一依赖值在$primitives中(即build方法的$parameters参数)已提供
        // $parameter->name返回参数名
        if (array_key_exists($parameter->name, $primitives)) {
            $dependencies[] = $primitives[$parameter->name];
        } 
        elseif (is_null($dependency)) {
             // 参数的ReflectionClass为null,说明是基本类型,如'int','string'
            $dependencies[] = $this->resolveNonClass($parameter);
        } else {
             // 参数是一个类的对象, 则用resolveClass去把对象解析出来
            $dependencies[] = $this->resolveClass($parameter);
        }
    }

    return $dependencies;
}

//解析出依赖类的对象
protected function resolveClass(ReflectionParameter $parameter)
{
    try {
        // $parameter->getClass()->name返回的是类名(参数在typehint里声明的类型)
        // 然后递归继续make(在make时发现依赖类还有其他依赖,那么会继续make依赖的依赖
        // 直到所有依赖都被解决了build才结束)
        return $this->make($parameter->getClass()->name);
    } catch (BindingResolutionException $e) {
        if ($parameter->isOptional()) {
            return $parameter->getDefaultValue();
        }

        throw $e;
    }
}

服务容器就是 laravel 的核心, 它通过依赖注入很好的替我们解决对象之间的相互依赖关系,

而又通过控制反转让外部来来定义具体的行为(Route, Eloquent 这些都是外部模块,

它们自己定义了行为规范,这些类从注册到实例化给你使用才是服务容器负责的)。

一个类要被容器所能够提取,必须要先注册至这个容器

既然 laravel 称这个容器叫做服务容器,那么我们需要某个服务,就得先注册、绑定这个服务到容器

那么提供服务并绑定服务至容器的东西就是服务提供器(ServiceProvider)

服务提供者主要分为两个部分:register(注册) 和 boot(引导、初始化)

版权声明:
作者:linrux
链接:https://www.tot7.cn/technology/laravel/443.html
来源:阿信博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录