##简介

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

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

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

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

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

简单一下:

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

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

###图解

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

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

###代码演示

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

<?php
class B {
public function func() {
print_r(__CLASS__);
}
} class A {
private $b; public function A() {
$this->b = new B();
} public function func() {
$this->b->func();
}
}

解耦方案:

class A {
private $b; public function A(B $bObj) {
$this->b = $bObj;
} public function func() {
$this->b->func();
}
}

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

IoC容器详细解释

以超人打怪兽的故事

###1.第一阶段

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

class Power {
/**
* 能力值
*/
protected $ability; /**
* 能力范围或距离
*/
protected $range; public function __construct($ability, $range)
{
$this->ability = $ability;
$this->range = $range;
}
}
class Superman
{
protected $power; public function __construct()
{
$this->power = new Power(999, 100);
}
}

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

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

class Flight
{
protected $speed;
protected $holdtime;
public function __construct($speed, $holdtime) {}
} class Force
{
protected $force;
public function __construct($force) {}
} class Shot
{
protected $atk;
protected $range;
protected $limit;
public function __construct($atk, $range, $limit) {}
}

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

class Superman
{
protected $power; public function __construct()
{ $this->power = new Fight(9, 100);
// $this->power = new Force(45);
// $this->power = new Shot(99, 50, 2);
/*
$this->power = array(
new Force(45),
new Shot(99, 50, 2)
);
*/
}
}

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

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

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

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

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

class SuperModuleFactory
{
public function makeModule($moduleName, $options)
{
switch ($moduleName) {
case 'Fight': return new Fight($options[0], $options[1]);
case 'Force': return new Force($options[0]);
case 'Shot': return new Shot($options[0], $options[1], $options[2]);
}
}
}
class Superman
{
protected $power; public function __construct()
{
// 初始化工厂
$factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块
$this->power = $factory->makeModule('Fight', [9, 100]);
// $this->power = $factory->makeModule('Force', [45]);
// $this->power = $factory->makeModule('Shot', [99, 50, 2]);
/*
$this->power = array(
$factory->makeModule('Force', [45]),
$factory->makeModule('Shot', [99, 50, 2])
);
*/
}
}

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

class Superman
{
protected $power; public function __construct(array $modules)
{
// 初始化工厂
$factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块
foreach ($modules as $moduleName => $moduleOptions) {
$this->power[] = $factory->makeModule($moduleName, $moduleOptions);
}
}
}
// 创建超人
$superman = new Superman([
'Fight' => [9, 100],
'Shot' => [99, 50, 2]
]);

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

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

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

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

class SuperModuleFactory
{
public function makeModule($moduleName, $options)
{
switch ($moduleName) {
case 'Fight': return new Fight($options[0], $options[1]);
case 'Force': return new Force($options[0]);
case 'Shot': return new Shot($options[0], $options[1], $options[2]);
// case 'more': .......
// case 'and more': .......
// case 'and more': .......
// case 'oh no! its too many!': .......
}
}
}

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

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

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

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

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

/**
* X-超能量
*/
class XPower implements SuperModuleInterface
{
public function activate(array $target)
{
// 这只是个例子。。具体自行脑补
}
} /**
* 终极炸弹
*/
class UltraBomb implements SuperModuleInterface
{
public function activate(array $target)
{
// 这只是个例子。。具体自行脑补
}
} class Superman
{
protected $module; public function __construct(SuperModuleInterface $module)
{
$this->module = $module
}
}

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

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

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

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

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

$superModule = new XPower;
$superMan = new Superman($superModule);

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

class Container
{
protected $binds; protected $instances;
//服务绑定
public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
//服务解析
public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
} array_unshift($parameters, $this); return call_user_func_array($this->binds[$abstract], $parameters);
}
}

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

// 创建一个容器(后面称作超级工厂)
$container = new Container; // 向该 超级工厂 添加 超人 的生产脚本
$container->bind('superman', function($container, $moduleName) {
return new Superman($container->make($moduleName));
}); // 向该 超级工厂 添加 超能力模组 的生产脚本
$container->bind('xpower', function($container) {
return new XPower;
}); // 同上
$container->bind('ultrabomb', function($container) {
return new UltraBomb;
}); // ****************** 华丽的分割线 **********************
// 开始启动生产
$superman_1 = $container->make('superman', 'xpower');
$superman_2 = $container->make('superman', 'ultrabomb');
$superman_3 = $container->make('superman', 'xpower');
// ...随意添加

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

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

##参考文章

laravel 学习笔记 —— 神奇的服务容器
https://www.insp.top/learn-laravel-container Laravel初级教程之服务容器
https://www.jellythink.com/archives/383 laravel中文文档
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. 【译】PEP 318--函数和方法的装饰器

    PEP原文 : https://www.python.org/dev/peps/pep-0318 PEP标题: Decorators for Functions and Methods PEP作者: ...

  2. window10 自带虚拟机输入ip addr 不显示ip,显示字母加数字

    \(\color{Black}{文/魂皓轩}\) 1.在界面输入ip addr 2.通过ls 查看当前文件 我的虚拟机网络配置文件为ifcfg-eth0(不同主机文件名不一样) 3.通过 vi ifc ...

  3. .NET ORM 开源项目 FreeSql 1.0 正式版发布

    一.简介 FreeSql 是 .NET 平台下的对象关系映射技术(O/RM),支持 .NetCore 2.1+ 或 .NetFramework 4.0+ 或 Xamarin. 从 0.0.1 发布,历 ...

  4. linux大盘格式化分区

    Linux 实例的磁盘管理 对于 Linux 系统上的大磁盘,也要采用 GPT 分区格式, 也可以不分区, 把磁盘当成一个整体设备使用. 在 Linux 上一般采用 XFS 或者 EXT4 来做大盘的 ...

  5. ES6学习之二

    本文的学习来自技术胖大神的教程:https://jspang.com/ 1扩展运算符和rest运算符 扩展运算符和rest运算符,它们都是…(三个点). 它们有很多相似之处,甚至很多时候不用特意去区分 ...

  6. $CF1063B\ Labyrinth$ $01$最短路/$01BFS$

    \(Des\) 有一个网格图,上面的格子分为空地和障碍,障碍是不可以走的.现在从给定的起点出发开始到处乱走,最多可以往左走\(l\)次,往右走\(r\)次.求可能到达的点数. \(Sol\) 如果只限 ...

  7. Web漏洞总结: OWASP Top 10

    本文原创,更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 开发安全 - OWASP Top 10 在学习安全需要总体了解安全趋势和常见的Web漏洞,首推了解OWASP,因为它代表着业 ...

  8. 讲真,这两个IDE插件,可以让你写出质量杠杠的代码

    昨晚躺在床上看<拯救大兵瑞恩>的时候,不由得感叹道:"斯皮尔伯格的电影质量真高,片头真实地还原了二战的残酷性."看完后,我的精神异常的亢奋,就想写篇文章来帮助大家提高一 ...

  9. MySQL数据库保存emoji表情

    何为emoji表情?这里的全是. MySQL数据库为什么不能存储emoji?不是MySQL不能,而是MySQL的utf8编码不能!原来MySQL下的utf8编码每个字符占3个字节,而emoji占4个字 ...

  10. 推荐一款国产优秀的基于 AI 的 Web 自动化测试工具——kylinTOP 测试与监控平台

    对于于一般的传统的自动化测试工具,如:Selenium,robotFramework,QTP等.QTP可以通过操作录制生成自动化用例脚本.生成的脚本与Selenium.robotFramework类似 ...