博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Laravel 服务容器
阅读量:5734 次
发布时间:2019-06-18

本文共 11073 字,大约阅读时间需要 36 分钟。

Laravel 服务容器

服务容器绑定形式

1. bind 简单绑定    $this->app->bind('HelpSpot\API', function ($app) {        return new HelpSpot\API($app->make('HttpClient'));    });2. singleton 绑定一个单例    $this->app->singleton('HelpSpot\API', function ($app) {        return new HelpSpot\API($app->make('HttpClient'));    });3. instance 绑定实例    $api = new HelpSpot\API(new HttpClient);    $this->app->instance('HelpSpot\Api', $api);

服务容器绑定剖析

1. singleton 和 bind 的解析public function singleton($abstract, $concrete = null){    // 实际是通过bind来实现的,区别在于最后的一个默认参数    $this->bind($abstract, $concrete, true);}public function bind($abstract, $concrete = null, $shared = false){    // 移除以前的实例和别名    $this->dropStaleInstances($abstract);    if (is_null($concrete)) {        $concrete = $abstract;    }    // 统一成匿名函数的形式,进行统一的调用。**注意:这是laravel里面的按需加载的一种实现方式**    if (! $concrete instanceof Closure) {        $concrete = $this->getClosure($abstract, $concrete);        }    $this->bindings[$abstract] = compact('concrete', 'shared');    // 若已经实例化过了,则重新进行构建    if ($this->resolved($abstract)) {        $this->rebound($abstract);    }}protected function dropStaleInstances($abstract){    unset($this->instances[$abstract], $this->aliases[$abstract]);}protected function getClosure($abstract, $concrete){    // 参数仅用来区分调用容器里面的方法,build和make后面进行讲解    return function ($container, $parameters = []) use ($abstract, $concrete) {        $method = ($abstract == $concrete) ? 'build' : 'make';            return $container->$method($concrete, $parameters);    };}public function resolved($abstract){    if ($this->isAlias($abstract)) {        $abstract = $this->getAlias($abstract);    }    return isset($this->resolved[$abstract]) ||           isset($this->instances[$abstract]);}

小结:singleton和bind绑定之后的结果是填充一个容器属性$this->bindings,为后期的服务解析提供数据,数组如下

$this->bindings[$abstract] = [     'concrete' => $concrete,    // 匿名函数,用来构建实例     'shared' => $shared,        // 此参数则是用来实现单例的关键,也是singleton和bind的差别所在。后期服务解析时会通过此参数和上下文参数来确定是否放入$this->instances数组里,这个就是单例的本质。]
2. instance 的解析public function instance($abstract, $instance){    $this->removeAbstractAlias($abstract);    unset($this->aliases[$abstract]);    // instance方法的本质,将实例$instance注入到容器的instances属性里    $this->instances[$abstract] = $instance;        if ($this->bound($abstract)) {        $this->rebound($abstract);    }}protected function removeAbstractAlias($searched){    if (! isset($this->aliases[$searched])) {        return;    }    foreach ($this->abstractAliases as $abstract => $aliases) {        foreach ($aliases as $index => $alias) {            if ($alias == $searched) {                unset($this->abstractAliases[$abstract][$index]);            }        }    }}public function bound($abstract){    return isset($this->bindings[$abstract]) ||           isset($this->instances[$abstract]) ||           $this->isAlias($abstract);}

小结:instance绑定之后的结果是填充一个容器属性的数组$this->instances,为后期的服务解析提供数据,数组如下

$this->instances[$abstract] = Object(xxx)  // instances对应的是具体的实现
3. 总结  本质上,服务容器的绑定就是将相应的代码(实例、类名、匿名函数等)注入到服务容器相应的属性里。这样,就可以通过容器的服务解析(make)来进行相应的操作。当然,一般情况都是通过服务容器来自动解决类之间的依赖关系的(类的反射)。

服务解析

  • make | makeWith 方法(makeWith可以指定参数)

    $api = $this->app->make('HelpSpot\API');
  • resolve 全局函数(当不能使用$this->app实例时,本质上是还是调用容器的make或makeWith)

    $api = resolve('HelpSpot\API');
  • 自动注入(最重要)

    可以在类的构造函数或方法中对依赖使用「类型提示」,依赖的类将会被容器自动进行解析,包括在控制器,事件监听器,队列任务,中间件等地方。事实上,这也是大部分类被容器解析的方式。

服务解析剖析

1. 代码解析public function make($abstract){    return $this->resolve($abstract);}public function makeWith($abstract, array $parameters){    return $this->resolve($abstract, $parameters);}protected function resolve($abstract, $parameters = []){    // 参数数组入栈    $this->with[] = $parameters;    // 是否有参数或为上下文形式绑定(when绑定),优先从全局别名 $this->aliases 里面取出最终值,没有则返回原值    $needsContextualBuild = ! empty($parameters) || ! is_null(        $this->getContextualConcrete($abstract = $this->getAlias($abstract))        );    // 若存在且没有上下文关系的实例,则直接返回。    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {        return $this->instances[$abstract];            }    $concrete = $this->getConcrete($abstract);    // 此方法是通过反射循环处理依赖关系的核心    if ($this->isBuildable($concrete, $abstract)) {        $object = $this->build($concrete);                    } else {        $object = $this->make($concrete);    }    // $this->app->extend($abstract, Closure $closure)方法可以指定$abstract在实例化后需要执行的额外操作,将在此时进行调用    foreach ($this->getExtenders($abstract) as $extender) {        $object = $extender($object, $this);                }    // singleton只实例化一次的本质    if ($this->isShared($abstract) && ! $needsContextualBuild) {        $this->instances[$abstract] = $object;                }    // 对对象执行额外的函数,类似全局函数等后续操作,被定义在$this->globalResolvingCallbacks、$this->resolvingCallbacks、$this->globalAfterResolvingCallbacks、$this->afterResolvingCallbacks数组中,可以通过$this->app->resolving()或$this->app->afterResolving()方法进行绑定    $this->fireResolvingCallbacks($abstract, $object);        // 处理完之后标记为已解决    $this->resolved[$abstract] = true;    array_pop($this->with);    return $object;}

注意:上面操作的$abstract键很多都是通过$this->getAlias($abstract)处理过的。$this->aliases数组类似

$this->aliases['Illuminate\Foundation\Application'] = 'app';    $this->aliases['Illuminate\Contracts\Container\Container'] = 'app';    $this->aliases['Illuminate\Contracts\Foundation\Application'] = 'app';

所以通过$this->getAlias($abstract)获取到的是类似'app'的别名;

从resolve方法中也可以看出,make 时先尝试将 abstract 转换为 alias,再从 instances 取,最后才取 bindings。

public function getAlias($abstract){    // 不存在则原值返回    if (! isset($this->aliases[$abstract])) {        return $abstract;    }    if ($this->aliases[$abstract] === $abstract) {        throw new LogicException("[{$abstract}] is aliased to itself.");    }    // 否则取别名的最终值,可能会出现链式别名。a=>b=>c=>d,a的别名是b,b的别名是c,c的别名是d,最终取d    return $this->getAlias($this->aliases[$abstract]);}protected function getContextualConcrete($abstract){    // 若存在上下文的concrete,则直接返回    if (! is_null($binding = $this->findInContextualBindings($abstract))) {        return $binding;    }    if (empty($this->abstractAliases[$abstract])) {        return;    }    foreach ($this->abstractAliases[$abstract] as $alias) {        if (! is_null($binding = $this->findInContextualBindings($alias))) {            return $binding;        }    }}protected function findInContextualBindings($abstract){    // 此数组的构建方式是由$this->app->when(x)->needs(y)->give(z)方式来构建的,可以理解为:当x需要y时给z    if (isset($this->contextual[end($this->buildStack)][$abstract])) {        return $this->contextual[end($this->buildStack)][$abstract];    }}protected function getConcrete($abstract){    // 优先取对应的上下文的concrete    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {        return $concrete;    }    // 再从之前的$this->bindings里面取    if (isset($this->bindings[$abstract])) {        return $this->bindings[$abstract]['concrete'];        }    return $abstract;}protected function isBuildable($concrete, $abstract){    return $concrete === $abstract || $concrete instanceof Closure;}// 通过反射处理依赖关系并构建相应的实例的方法 public function build($concrete){    // 如果是匿名函数则直接调用并返回    if ($concrete instanceof Closure) {        return $concrete($this, end($this->with));    }    // 否则获取concrete的反射类    $reflector = new ReflectionClass($concrete);    // 若不能实例化,抛异常    if (! $reflector->isInstantiable()) {        return $this->notInstantiable($concrete);    }    // 将所有依赖的要处理的$concrete入栈,待一个一个处理完之后出栈,处理完所有的concrete之后应该是个空数组    $this->buildStack[] = $concrete;    // 获取构造器    $constructor = $reflector->getConstructor();    // 没有构造器(即没有依赖),直接创建实例并返回    if (is_null($constructor)) {        array_pop($this->buildStack);        return new $concrete;    }    // 获取构造器的依赖关系,即参数类型    $dependencies = $constructor->getParameters();    // 处理依赖关系,返回的是一个依次含有所有依赖的实例    $instances = $this->resolveDependencies(        $dependencies    );    // 处理完就出栈    array_pop($this->buildStack);    // 返回相应的实例    return $reflector->newInstanceArgs($instances);}protected function resolveDependencies(array $dependencies){    $results = [];    // 依次对每个参数进行操作    foreach ($dependencies as $dependency) {        // 查看依赖的类型是否有覆盖,有则取最新的数据类型        if ($this->hasParameterOverride($dependency)) {            $results[] = $this->getParameterOverride($dependency);            continue;        }        $results[] = is_null($class = $dependency->getClass())                        ? $this->resolvePrimitive($dependency)    //resolvePrimitive基本类型处理                        : $this->resolveClass($dependency);        //resolveClass类类型处理    }    return $results;}protected function resolvePrimitive(ReflectionParameter $parameter){    if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {        return $concrete instanceof Closure ? $concrete($this) : $concrete;    }    if ($parameter->isDefaultValueAvailable()) {        return $parameter->getDefaultValue();    }    $this->unresolvablePrimitive($parameter);}protected function resolveClass(ReflectionParameter $parameter){    try {        return $this->make($parameter->getClass()->name);    // 递归解析各类之间的依赖关系    }    catch (BindingResolutionException $e) {        if ($parameter->isOptional()) {            return $parameter->getDefaultValue();        }        throw $e;    }}
2. 总结服务解析,实际上就是委托服务容器通过反射来处理类之间的依赖关系,从而得到相应的实例。但前提是容器里服务的注册,所以,需要再服务解析之前,将所有需要的服务注入到服务容器里。而laravel里面的服务注入方式是通过服务提供者来进行注入的,然后在将服务提供者注册到容器里,相应的服务便会注入到容器里。

容器事件

每当服务容器解析一个对象时就会触发一个事件。你可以使用 resolving 方法监听这个事件

$this->app->resolving(function ($object, $app) {    // 解析任何类型的对象时都会调用该方法...});    $this->app->resolving(HelpSpot\API::class, function ($api, $app) {    // 解析「HelpSpot\API」类型的对象时调用...});    对应于$this->app->make()调用的resolve()方法:$this->fireResolvingCallbacks($abstract, $object);注意:如果是单例,则只在解析时会触发一次

额外补充[参考]()

  1. 绑定初始数据

    有时,你的类不仅需要注入类,还需要注入一些原始数据,如一个整数。此时,你可以容易地通过情景绑定注入需要的任何值:

    $this->app->when('AppHttpControllersUserController')

    ->needs('$variableName')
    ->give($value);

  2. 绑定接口至实现

    服务容器有一个强大的功能,就是将一个指定接口的实现绑定到接口上。例如,如果我们有一个 EventPusher 接口和一个它的实现类 RedisEventPusher 。编写完接口的 RedisEventPusher 实现类后,我们就可以在服务容器中像下面例子一样注册它:

    $this->app->bind(

    'App\Contracts\EventPusher','App\Services\RedisEventPusher'

    );

  3. 情境绑定

    有时候,你可能有两个类使用到相同的接口,但你希望每个类都能注入不同的实现。例如,两个控制器可能需要依赖不同的 IlluminateContractsFilesystemFilesystem 契约 的实现类。 Laravel 为此定义了一种简单、平滑的接口:

    use IlluminateSupportFacadesStorage;

    use AppHttpControllersPhotoController;
    use AppHttpControllersVideoController;
    use IlluminateContractsFilesystemFilesystem;

    $this->app->when(PhotoController::class)

    ->needs(Filesystem::class)->give(function () {      return Storage::disk('local');

    });

    $this->app->when(VideoController::class)

    ->needs(Filesystem::class)->give(function () {      return Storage::disk('s3');

    });

  4. 标记

    有时候,你可能需要解析某个「分类」下的所有绑定。例如,你正在构建一个报表的聚合器,它需要接受不同 Report 接口的实例。分别注册了 Report 实例后,你可以使用 tag 方法为他们赋予一个标签:

$this->app->bind('SpeedReport', function () {        //    });    $this->app->bind('MemoryReport', function () {        //    });    $this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');    一旦服务被标记后,你可以通过 tagged 方法轻松地将它们全部解析:        $this->app->bind('ReportAggregator', function ($app) {        return new ReportAggregator($app->tagged('reports'));    });

转载地址:http://zwwzx.baihongyu.com/

你可能感兴趣的文章
siki学习之观察者模式笔记
查看>>
PYQT窗口可视化编程
查看>>
单元测试
查看>>
spring.net 继承
查看>>
关于Vector中存放指针的问题
查看>>
Java线程角度的内存模型和volatile型变量
查看>>
linux Ubuntu14.04 python(c++版本) tesorflow(python版本)c++
查看>>
Nginx 浏览器缓存
查看>>
html5-css的引入
查看>>
Spring AOP
查看>>
<基础> PHP 进阶之 函数(Function)
查看>>
强极则辱
查看>>
eslasticsearch操作集锦
查看>>
git stuff
查看>>
前端 -- margin的用法
查看>>
Ext Gridpanel的用法
查看>>
SQL结构化查询语言
查看>>
ES6:模块简单解释
查看>>
JavaScript indexOf() 方法
查看>>
Java 8 新特性:2-消费者(Consumer)接口
查看>>