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

如果对服务容器是什么没有清晰概念的话推荐一篇博文来了解一下服务容器的来龙去脉:laravel神奇的服务容器

服务容器中有两个概念控制反转(IOC)和依赖注入(DI):

依赖注入和控制反转是对同一件事情的不同描述,它们描述的角度不同。依赖注入是从应用程序的角度在描述,应用程序依赖容器创建并注入它所需要的外部资源。而控制反转是从容器的角度在描述,容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

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

本篇就来探讨一下Laravel中的服务绑定和解析是如何实现的

服务绑定

常用的绑定服务到容器的方法有instance, bind, singleton, alias。下面我们分别来看一下。

instance

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

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

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

['HelpSpot\Api' => $api//$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)
{$abstract = $this->normalize($abstract);$concrete = $this->normalize($concrete);//如果$abstract为数组类似['Illuminate/ServiceName' => 'service_alias']//抽取别名"service_alias"并且注册到$aliases[]中//注意:数组绑定别名的方式在5.4中被移除,别名绑定请使用下面的alias方法if (is_array($abstract)) {list($abstract, $alias) = $this->extractAlias($abstract);$this->alias($abstract, $alias);}$this->dropStaleInstances($abstract);if (is_null($concrete)) {$concrete = $abstract;}//如果只提供$abstract,则在这里为其生成concrete闭包if (! $concrete instanceof Closure) {$concrete = $this->getClosure($abstract, $concrete);}$this->bindings[$abstract] = compact('concrete', 'shared');if ($this->resolved($abstract)) {$this->rebound($abstract);}
}protected function getClosure($abstract, $concrete)
{// $c 就是$container,即服务容器,会在回调时传递给这个变量return function ($c, $parameters = []) use ($abstract, $concrete) {$method = ($abstract == $concrete) ? 'build' : 'make';return $c->$method($concrete, $parameters);};
}

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 方法在上面讲bind方法里有用到过,它会把把服务别名和服务类的对应关系注册到服务容器的$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()方式绑定的,就直接解析返回绑定的serviceif (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类的反射来实现服务的依赖注入)

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(引导、初始化)由于篇幅问题关于Laravel服务提供器的内容请看另一篇Laravel核心解读--服务提供器(ServiceProvider)。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

Laravel核心解读--服务容器(IocContainer)相关推荐

  1. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器绑定)

    服务容器的绑定 bind 绑定 bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种: 绑定自身 绑定闭包 绑定接口 今天,我们这篇文章主要从源码上讲解 Ioc ...

  2. php ico容器,Laravel 核心——IoC 服务容器

    服务容器 Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具. 在理解这句话之前,我们需要先了解一下服务容器的来龙去脉: laravel神奇的服务容器.这篇博客告诉我们,服务容器就是 ...

  3. Laravel核心解读--服务提供器(ServiceProvider)

    服务提供器是所有 Laravel 应用程序引导中心.你的应用程序自定义的服务.第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注册(register)和引导(boot) ...

  4. laravel —— 神奇的服务容器

    容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器.一个容器能够装什么,全部取决于你对该容器的定义.当然,有这样一种容器,它存放的不是文本.数值,而是对象.对象的描述(类.接口)或 ...

  5. Laravel神奇的服务容器

    Laravel的核心是IocContainer, 文档中称其为"服务容器",服务容器是一个用于管理类依赖和执行依赖注入的强大工具,Laravel中的功能模块比如 Route.Elo ...

  6. Laravel核心解读--控制器

    控制器 控制器能够将相关的请求处理逻辑组成一个单独的类, 通过前面的路由和中间件两个章节我们多次强调Laravel应用的请求在进入应用后首现会通过Http Kernel里定义的基本中间件 protec ...

  7. Laravel核心解读--Console内核

    Console内核 上一篇文章我们介绍了Laravel的HTTP内核,详细概述了网络请求从进入应用到应用处理完请求返回HTTP响应整个生命周期中HTTP内核是如何调动Laravel各个核心组件来完成任 ...

  8. Laravel核心解读--Contracts契约

    Contracts Laravel 的契约是一组定义框架提供的核心服务的接口, 例如我们在介绍用户认证的章节中到的用户看守器契约IllumninateContractsAuthGuard 和用户提供器 ...

  9. Laravel核心解读--控制器 1

    控制器 控制器能够将相关的请求处理逻辑组成一个单独的类, 通过前面的路由和中间件两个章节我们多次强调Laravel应用的请求在进入应用后首现会通过Http Kernel里定义的基本中间件 protec ...

最新文章

  1. oral_quiz-#求链表中的倒数第K个结点#
  2. sony service tel
  3. spring 学习—spring的相关概念(01)
  4. 写了个Python小工具,再也不怕孩子偷偷玩电脑游戏啦
  5. 大学物理光学思维导图_头晕/眩晕问诊最全思维导图,赶紧收藏!
  6. 基于SpringBoot的在线音乐播放系统
  7. Linux下运行JAVA程序——JRE安装和配置
  8. (Matlab实现)基于蒙特卡洛模拟的大规模电动车充电模型
  9. 一款好用的绘制组织结构图的软件-亿图组织结构图
  10. cad编辑节点快捷键是什么_CAD所有快捷键
  11. 浩辰3D设计软件中如何进行弹簧设计?
  12. 有向图的深度优先遍历
  13. 超自动化如何提升保险业?
  14. 电脑小米手机,小米手机怎么连接电脑?
  15. 【h5py 报错】ImportError: DLL load failed while importing defs: 找不到指定的程序。
  16. test 4:假币问题
  17. html多张图片合在一块,多张照片怎么拼在一起?10张以上多图拼图方法 超简单! (全文)...
  18. STM32MP157(实验四)——串口通信(串口打印)
  19. RoboMaster视觉教程(6)目标位置解算(PnP求解目标与摄像头间的相对位置)
  20. Hexo 搭建个人博客 基础配置及实操

热门文章

  1. 《大型网站技术架构》1.大型网站架构演练
  2. vue练习之vue+cnode api
  3. sccm终于把用户装软件的问题解决了
  4. 国外博客透露JavaFX 2.0路线图
  5. CDN行业“三足鼎立”格局已定,谁能代表未来?
  6. Android实现按钮点击效果(第一次点击变色,第二次恢复)
  7. 如何获得线程对象,获得Thread对象,得到线程对象,根据线程名获得线程对象,多线程取得线程对象...
  8. 初志科技发布云策略 安全云平台如稳固建筑
  9. IT永远也不可能做到整体外包,这句话是我说的。。。
  10. 黑客攻防技术宝典Web实战篇第2版—第4章 解析应用程序