##简介

服务容器是Laravel的核心。见名知意,服务容器就是一个存放服务的地方,当我们需要某个服务的时候,我们就可以从这个容器中取出我们需要的服务。用更专业一点的术语来说,官网定义服务容器是这样的:

  1. Laravel服务容器是一个用于管理类依赖和执行依赖注入的强大工具。

说到管理类依赖和执行依赖注入,搞过Spring开发的朋友,应该都熟悉,这些概念在这些年特别火。因为好用,所以火。现在这把火烧到了PHP这里。而说到这里,我们又不得不说两个特别重要的概念:IoC(控制反转)与DI(依赖注入)。

IoC(控制反转)与DI(依赖注入)

###概念
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。通过控制反转,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

简单一下:

  1. 控制反转:容器(Container)控制应用程序(Application),由容器反向的向应用程序注入应用程序所需要的外部资源(组件类);
  2. 依赖注入:应用程序(Application)依赖容器(Container)创建并注入它所需要的外部资源(组件类)。

控制反转是用来进行对象解耦。通过借助第三方,将具有依赖关系的对象进行解耦,而这个第三方就是IoC容器。

###图解

比如全体齿轮的转动由一个对象来控制,如类B。如下图所示:

引入了IoC容器后,对象A、B、C、D之间没有了依赖关系,全体齿轮转动的控制权交给IoC容器。这时候齿轮转动控制权不属于任何对象,而属于IoC容器,所以控制权反转了,从某个对象转到了IoC容器。

###代码演示

对于依赖注入,首先依赖就是指一种关系,如果在类A中创建了类B的实例,我们就可以说类A依赖类B。例如:

  1. <?php
  2. class B {
  3. public function func() {
  4. print_r(__CLASS__);
  5. }
  6. }
  7. class A {
  8. private $b;
  9. public function A() {
  10. $this->b = new B();
  11. }
  12. public function func() {
  13. $this->b->func();
  14. }
  15. }

解耦方案:

  1. class A {
  2. private $b;
  3. public function A(B $bObj) {
  4. $this->b = $bObj;
  5. }
  6. public function func() {
  7. $this->b->func();
  8. }
  9. }

将B对象实例作为类A的构造器参数进行传入,在调用类A构造器之前,类B实例已经被初始化好了。像这种非自己主动初始化依赖,而通过外部传入依赖对象的方式,就很完美的解决了上述的问题,这就是依赖注入。

IoC容器详细解释

以超人打怪兽的故事

###1.第一阶段

我们可以想象,一个超人诞生的时候肯定拥有至少一个超能力,这个超能力也可以抽象为一个对象,为这个对象定义一个描述他的类吧。一个超能力肯定有多种属性、(操作)方法,但是目前我们先大致定义一个只有属性的“超能力”:

  1. class Power {
  2. /**
  3. * 能力值
  4. */
  5. protected $ability;
  6. /**
  7. * 能力范围或距离
  8. */
  9. protected $range;
  10. public function __construct($ability, $range)
  11. {
  12. $this->ability = $ability;
  13. $this->range = $range;
  14. }
  15. }
  16. class Superman
  17. {
  18. protected $power;
  19. public function __construct()
  20. {
  21. $this->power = new Power(999, 100);
  22. }
  23. }

这样的话,当我们创建一个“超人”实例的时候,同时也创建了一个“超能力”的实例,“超人”和“超能力”之间不可避免的产生了一个依赖。

我们知道,超人的超能力是多元化的。我们现在进行修改,我们假设超人可以有以下多种超能力:
飞行,属性有:飞行速度、持续飞行时间
蛮力,属性有:力量值
能量弹,属性有:伤害值、射击距离、同时射击个数
我们创建了如下类:

  1. class Flight
  2. {
  3. protected $speed;
  4. protected $holdtime;
  5. public function __construct($speed, $holdtime) {}
  6. }
  7. class Force
  8. {
  9. protected $force;
  10. public function __construct($force) {}
  11. }
  12. class Shot
  13. {
  14. protected $atk;
  15. protected $range;
  16. protected $limit;
  17. public function __construct($atk, $range, $limit) {}
  18. }

在超人初始化的时候,我们会根据需要来实例化其拥有的超能力吗,大致如下:

  1. class Superman
  2. {
  3. protected $power;
  4. public function __construct()
  5. {
  6. $this->power = new Fight(9, 100);
  7. // $this->power = new Force(45);
  8. // $this->power = new Shot(99, 50, 2);
  9. /*
  10. $this->power = array(
  11. new Force(45),
  12. new Shot(99, 50, 2)
  13. );
  14. */
  15. }
  16. }

我们需要自己手动的在构造函数内实例化一系列需要的类,这样并不好。可以想象,假如需求变更(不同的怪物横行地球),需要更多的有针对性的 新的 超能力或者需要 变更 超能力的方法,我们必须 重新改造 超人。

###2.第二阶段(工厂模式)

我们不应该手动在 “超人” 类中固化了他的 “超能力” 初始化的行为,而转由外部负责,由外部创造超能力模组、装置或者芯片等(我们后面统一称为 “模组”),植入超人体内的某一个接口,只要这个 “模组” 满足这个接口的装置都可以被超人所利用。这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”。

工厂模式,就是一个类所以依赖的外部事物的实例,都可以被一个或多个 “工厂” 创建的这样一种开发模式。

我们为了给超人制造超能力模组,我们创建了一个工厂,它可以制造各种各样的模组,且仅需要通过一个方法:

  1. class SuperModuleFactory
  2. {
  3. public function makeModule($moduleName, $options)
  4. {
  5. switch ($moduleName) {
  6. case 'Fight': return new Fight($options[0], $options[1]);
  7. case 'Force': return new Force($options[0]);
  8. case 'Shot': return new Shot($options[0], $options[1], $options[2]);
  9. }
  10. }
  11. }
  12. class Superman
  13. {
  14. protected $power;
  15. public function __construct()
  16. {
  17. // 初始化工厂
  18. $factory = new SuperModuleFactory;
  19. // 通过工厂提供的方法制造需要的模块
  20. $this->power = $factory->makeModule('Fight', [9, 100]);
  21. // $this->power = $factory->makeModule('Force', [45]);
  22. // $this->power = $factory->makeModule('Shot', [99, 50, 2]);
  23. /*
  24. $this->power = array(
  25. $factory->makeModule('Force', [45]),
  26. $factory->makeModule('Shot', [99, 50, 2])
  27. );
  28. */
  29. }
  30. }

可以看得出,我们不再需要在超人初始化之初,去初始化许多第三方类,只需初始化一个工厂类,即可满足需求。但这样似乎和以前区别不大,只是没有那么多 new 关键字。其实我们稍微改造一下这个类,你就明白,工厂类的真正意义和价值了。

  1. class Superman
  2. {
  3. protected $power;
  4. public function __construct(array $modules)
  5. {
  6. // 初始化工厂
  7. $factory = new SuperModuleFactory;
  8. // 通过工厂提供的方法制造需要的模块
  9. foreach ($modules as $moduleName => $moduleOptions) {
  10. $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
  11. }
  12. }
  13. }
  14. // 创建超人
  15. $superman = new Superman([
  16. 'Fight' => [9, 100],
  17. 'Shot' => [99, 50, 2]
  18. ]);

现在修改的结果令人满意。现在,“超人” 的创建不再依赖任何一个 “超能力” 的类,我们如若修改了或者增加了新的超能力,只需要针对修改 SuperModuleFactory 即可。但是,这才刚刚开始。

###第三阶段(依赖注入)
由 “超人” 对 “超能力” 的依赖变成 “超人” 对 “超能力模组工厂” 的依赖后,对付小怪兽们变得更加得心应手。但这也正如你所看到的,依赖并未解除,只是由原来对多个外部的依赖变成了对一个 “工厂” 的依赖。假如工厂出了点麻烦,问题变得就很棘手。

其实大多数情况下,工厂模式已经足够了。工厂模式的缺点就是:接口未知(即没有一个很好的契约模型)、产生对象类型单一。总之就是,还是不够灵活。虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。

我们知道,超人依赖的模组,我们要求有统一的接口,这样才能和超人身上的注入接口对接。事实上,不仅仅只有一堆小怪兽,还有更多的大怪兽。这时候似乎工厂的生产能力显得有些不足 —— 由于工厂模式下,所有的模组都已经在工厂类中安排好了,如果有新的、高级的模组加入,我们必须修改工厂类(好比增加新的生产线):

  1. class SuperModuleFactory
  2. {
  3. public function makeModule($moduleName, $options)
  4. {
  5. switch ($moduleName) {
  6. case 'Fight': return new Fight($options[0], $options[1]);
  7. case 'Force': return new Force($options[0]);
  8. case 'Shot': return new Shot($options[0], $options[1], $options[2]);
  9. // case 'more': .......
  10. // case 'and more': .......
  11. // case 'and more': .......
  12. // case 'oh no! its too many!': .......
  13. }
  14. }
  15. }

看到没。。。噩梦般的感受!

下一步就是我们今天的主要配角 —— DI (依赖注入)

由于对超能力模组的需求不断增大,我们需要集合整个世界的高智商人才一起解决问题,不应该仅仅只有几个工厂垄断负责。不过高智商人才们创造出的超能力模组没有统一的接口,自然而然无法被正常使用。这时我们需要提出一种契约,这样无论是谁创造出的模组,都符合这样的接口,自然就可被正常使用。

  1. interface SuperModuleInterface
  2. {
  3. /**
  4. * 超能力激活方法
  5. *
  6. * 任何一个超能力都得有该方法,并拥有一个参数
  7. *@param array $target 针对目标,可以是一个或多个,自己或他人
  8. */
  9. public function activate(array $target);
  10. }

上文中,我们定下了一个接口 (超能力模组的规范、契约),所有被创造的模组必须遵守该规范,才能被生产。
这时候,那些提出更好的超能力模组的高智商人才,遵循这个接口,创建了下述(模组)类:

  1. /**
  2. * X-超能量
  3. */
  4. class XPower implements SuperModuleInterface
  5. {
  6. public function activate(array $target)
  7. {
  8. // 这只是个例子。。具体自行脑补
  9. }
  10. }
  11. /**
  12. * 终极炸弹
  13. */
  14. class UltraBomb implements SuperModuleInterface
  15. {
  16. public function activate(array $target)
  17. {
  18. // 这只是个例子。。具体自行脑补
  19. }
  20. }
  21. class Superman
  22. {
  23. protected $module;
  24. public function __construct(SuperModuleInterface $module)
  25. {
  26. $this->module = $module
  27. }
  28. }

改造完毕!现在,当我们初始化 “超人” 类的时候,提供的模组实例必须是一个 SuperModuleInterface 接口的实现。
正是由于超人的创造变得容易,一个超人也就不需要太多的超能力,我们可以创造多个超人,并分别注入需要的超能力模组即可。这样的话,虽然一个超人只有一个超能力,但超人更容易变多,我们也不怕怪兽啦!

现在有人疑惑了,你要讲的 依赖注入 呢?其实,上面讲的内容,正是依赖注入。

什么叫做 依赖注入?
本文从开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于 依赖注入(DI) 。下面就是一个典型的依赖注入:

  1. // 超能力模组
  2. $superModule = new XPower;
  3. // 初始化一个超人,并注入一个超能力模组依赖
  4. $superMan = new Superman($superModule);

关于依赖注入这个本文的主要配角,也就这么多需要讲的。理解了依赖注入,我们就可以继续深入问题。慢慢走近今天的主角……
###第四阶段 (IoC容器)
刚刚列了一段代码:

  1. $superModule = new XPower;
  2. $superMan = new Superman($superModule);

手动的创建了一个超能力模组、手动的创建超人并注入了刚刚创建超能力模组。
一群怪兽来了,如此低效率产出超人是不现实,我们需要自动化。我们需要一种高级的生产车间,我们只需要向生产车间提交一个脚本,工厂便能够通过指令自动化生产。这种更为高级的工厂,就是工厂模式的升华 —— IoC 容器。

  1. class Container
  2. {
  3. protected $binds;
  4. protected $instances;
  5. //服务绑定
  6. public function bind($abstract, $concrete)
  7. {
  8. if ($concrete instanceof Closure) {
  9. $this->binds[$abstract] = $concrete;
  10. } else {
  11. $this->instances[$abstract] = $concrete;
  12. }
  13. }
  14. //服务解析
  15. public function make($abstract, $parameters = [])
  16. {
  17. if (isset($this->instances[$abstract])) {
  18. return $this->instances[$abstract];
  19. }
  20. array_unshift($parameters, $this);
  21. return call_user_func_array($this->binds[$abstract], $parameters);
  22. }
  23. }

这时候,一个十分粗糙的容器就诞生了。现在的确很简陋,但不妨碍我们进一步提升他。先着眼现在,看看这个容器如何使用吧!

  1. // 创建一个容器(后面称作超级工厂)
  2. $container = new Container;
  3. // 向该 超级工厂 添加 超人 的生产脚本
  4. $container->bind('superman', function($container, $moduleName) {
  5. return new Superman($container->make($moduleName));
  6. });
  7. // 向该 超级工厂 添加 超能力模组 的生产脚本
  8. $container->bind('xpower', function($container) {
  9. return new XPower;
  10. });
  11. // 同上
  12. $container->bind('ultrabomb', function($container) {
  13. return new UltraBomb;
  14. });
  15. // ****************** 华丽的分割线 **********************
  16. // 开始启动生产
  17. $superman_1 = $container->make('superman', 'xpower');
  18. $superman_2 = $container->make('superman', 'ultrabomb');
  19. $superman_3 = $container->make('superman', 'xpower');
  20. // ...随意添加

通过最初的 绑定(bind) 操作,我们向 超级工厂 注册了一些生产脚本,这些生产脚本在生产指令下达之时便会执行。我们彻底的解除了 超人 与 超能力模组 的依赖关系,更重要的是,容器类也丝毫没有和他们产生任何依赖!我们通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为生产一个类的实例的 脚本 ,只有在真正的 生产(make) 操作被调用执行时,才会触发。

实际上,真正的 IoC 容器更为高级。我们现在的例子中,还是需要手动提供超人所需要的模组参数,但真正的 IoC 容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,

##参考文章

  1. laravel 学习笔记 —— 神奇的服务容器
  2. https://www.insp.top/learn-laravel-container
  3. Laravel初级教程之服务容器
  4. https://www.jellythink.com/archives/383
  5. laravel中文文档
  6. https://learnku.com/docs/laravel/5.7/lifecycle/2248

laravel5.5的服务容器分析的更多相关文章

  1. laravel5.2总结--服务容器(依赖注入,控制反转)

    1.依赖 我们定义两个类:class Supperman 和 class Power,现在我们要使用Supperman ,而Supperman 依赖了Power class Supperman { p ...

  2. Laravel5.1学习笔记12 系统架构4 服务容器

    Service Container 介绍 绑定的用法  绑定实例到接口 上下文绑定 标签 解析 容器事件 #介绍 The Laravel service container is a powerful ...

  3. Laravel5 快速认证逻辑流程分析

    Laravel5本身自带一套用户认证功能,只需在新项目下,使用命令行php artisan make:auth 和 php artisan migrate就可以使用自带的快速认证功能. 以下为分析登录 ...

  4. laravel框架总结(四) -- 服务容器

    1.依赖 我们定义两个类:class Supperman 和 class Power,现在我们要使用Supperman ,而Supperman 依赖了Power class Supperman { p ...

  5. 零配置Socket TCP消息通讯服务容器EC

    EC全称是elastic communication,是基于c#实现的Socket网络通讯服务容器,支持windows .Net和mono.通过EC容器可以让开发人员在不了解Socket网络通讯知识和 ...

  6. Laravel 服务容器、服务提供器、契约实例讲解

        前言 刚开始看laravel服务容器.契约.服务提供器的确生涩难懂,不单单是概念繁多,而且实际的demo很难找(找是找到了,但难用啊),最后就隔一段时间看一遍,大概个十来遍,还真给看出个门道, ...

  7. Docker+Kubernetes(k8s)微服务容器化实践

    第1章 初识微服务微服务的入门,我们从传统的单体架构入手,看看在什么样的环境和需求下一步步走到微服务的,然后再具体了解一下什么才是微服务,让大家对微服务的概念有深入的理解.然后我们一起画一个微服务的架 ...

  8. Laravel修炼:服务容器绑定与解析

    前言   老实说,第一次老大让我看laravel框架手册的那天早上,我是很绝望的,因为真的没接触过,对我这种渣渣来说,laravel的入门门槛确实有点高了,但还是得硬着头皮看下去(虽然到现在我还有很多 ...

  9. Laravel服务容器的绑定与解析

    本篇文章给大家带来的内容是关于Laravel服务容器的绑定与解析,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 前言   老实说,第一次老大让我看laravel框架手册的那天早上,我 ...

随机推荐

  1. git authentication failed for 或 fatal:not a git repository

    第一种解决 (我的是第一种解决) github上更改密码之后,我在本地操作git发现出错,错误代码如上,在网上搜了一圈,没有解决问题,后发现需要进行如下操作: 进入控制面板>用户账号>凭据 ...

  2. Spring Boot 嵌入式Web容器

    目录 前言 1.起源 2.容器启动流程解析 2.1.获取应用类型 2.2.容器启动流程 3.加载 Web 容器工厂 4.总结 前言         最近在学习Spring Boot相关的课程,过程中以 ...

  3. PyTorch深度学习:60分钟入门(Translation)

    这是https://zhuanlan.zhihu.com/p/25572330的学习笔记. Tensors Tensors和numpy中的ndarrays较为相似, 因此Tensor也能够使用GPU来 ...

  4. 将 Sidecar 容器带入新的阶段

    作者 | 徐迪.张晓宇 导读:本文根据徐迪和张晓宇在 KubeCon NA 2019 大会分享整理.分享将会从以下几个方面进行切入:首先会简单介绍一下什么是 Sidecar 容器:其次,会分享几个阿里 ...

  5. 日期格式化跨年bug,是否与你不期而遇?

    2020年来临之前,日期格式化操作也为程序员准备了一个跨年级别的bug,不知你的系统是否遇到? 临近2020年元旦的几天,不少网站出现了类似2020/12/29,2020/12/30,2020/12/ ...

  6. 大量SQL的解决方案——sdmap

    大量SQL的解决方案--sdmap 最近看到群里面经常讨论大型应用中SQL的管理办法,有人说用EF/EF Core,但很多人不信任它生成SQL的语句:有人说用Dapper,但将SQL写到代码中有些人觉 ...

  7. react根据传参的不同动态注册不同的子组件

    上一篇文章介绍了关于Vue如何根据传参的不同动态注册不同的子组件,实现过程请查阅Vue.extend动态注册子组件,由Vue的这个功能我就自然联想到了使用react该如何实现同样的功能呢.其实,用re ...

  8. 【转】Twitter算法面试题详解(Java实现)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://androidguy.blog.51cto.com/974126/1319659 ...

  9. 越来越清晰的TFRecord处理图片的步骤

    # 首先是模块的导入 """ os模块是处理文件夹用的 PIL模块是用来处理图片的 """ import tensorflow as tf ...

  10. 斯坦福算法分析和设计_2. 排序算法MergeSort

      Motivate MergeSort是个相对古老的算法了,为什么现在我们还要讨论这么古老的东西呢?有几个原因: 它虽然年龄很大了,但是在实践中一直被沿用,仍然是很多程序库中的标准算法之一. 实现它 ...