这两个概念对于 Laravel
的使用者来说应该并不陌生,尤其是当你希望扩展或者替换 Laravel
核心库的时候,理解和合理使用它们可以极大提升 Laravel
的战斗力。这里以创建一个自己的 ServiceProvider 为例理解 Inversion of
Control 和 Facade 在 Laravel 中的应用。

本书的 GitHub 地址:

简述

控制反转(Inversion of Control)

Laravel 作为现在最流行的 PHP
框架,其中的知识较多,所以单独拿出来写一篇。

当你接触一段时间Laravel的Service Container, Service
Provider,Contracts和Facade后,也许已经知道它们是什么了,但是对于如何使用,在什么时候使用,以及它们之间的关系是什么,还不是非常清楚。
而关键是如果你反复看文档,你会被它坑死,因为文档有些部分不但没有解释清楚,反而有误导的内容;
现在我们就来一次性把它们搞定;

什么是 IoC

控制反转(Inversion of
Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency
Injection,简称DI),还有一种方式叫“依赖查找”(Dependency
Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。
— 维基百科

简单说来,就是一个类把自己的的控制权交给另外一个对象,类间的依赖由这个对象去解决。依赖注入属于依赖的显示申明,而依赖查找则是通过查找来解决依赖。

  • 简述 Laravel 的生命周期

基本概念

Laravel 中的使用

注入一个类:

App::bind('foo', function($app)
{
    return new FooBar;
});

这个例子的意思是创建一个别名为 foo 的类,使用时实际实例化的是 FooBar

使用这个类的方法是:

$value = App::make('foo');

$value 实际上是 FooBar 对象。

如果希望使用单例模式来实例化类,那么使用:

App::singleton('foo', function()
{
    return new FooBar;
});

这样的话每次实例化后的都是同一个对象。

注入类的更多例子可以看 Laravel 官网

你可能会疑问上面的代码应该写在哪儿呢?答案是你希望他们在哪儿运行就写在哪儿。0
—— 0 知道写哪儿还用来看这种基础文章么!

Laravel 采用了单一入口模式,应用的所有请求入口都是 public/index.php
文件。

  1. 注册类文件自动加载器 : Laravel通过 composer
    进行依赖管理,无需开发者手动导入各种类文件,而由自动加载器自行导入。

  2. 创建服务容器:从 bootstrap/app.php 文件中取得 Laravel 应用实例
    $app

  3. 创建 HTTP / Console 内核:传入的请求会被发送给 HTTP 内核或者
    console 内核进行处理

  4. 载入服务提供者至容器:在内核引导启动的过程中最重要的动作之一就是载入服务提供者到你的应用,服务提供者负责引导启动框架的全部各种组件,例如数据库、队列、验证器以及路由组件。

  5. 分发请求:一旦应用完成引导和所有服务提供者都注册完成,Request
    将会移交给路由进行分发。路由将分发请求给一个路由或控制器,同时运行路由指定的中间件

  • 服务提供者是什么?

服务提供者是所有 Laravel 应用程序引导启动的中心, Laravel
的核心服务器、注册服务容器绑定、事件监听、中间件、路由注册以及我们的应用程序都是由服务提供者引导启动的。

在继续本教程之前,你需要先对以上概念有基本了解,知道它们是什么;

服务提供器 (Service Providers)

为了让依赖注入的代码不至于写乱,Laravel 搞了一个 服务提供器(Service
Provider)的东西,它将这些依赖聚集在了一块,统一申明和管理,让依赖变得更加容易维护。

  • IoC 容器是什么?

Service Container和 Service Provider

Laravel 中的使用

定义一个服务提供器:

use Illuminate\Support\ServiceProvider;

class FooServiceProvider extends ServiceProvider {

    public function register()
    {
        $this->app->bind('foo', function()
        {
            return new Foo;
        });
    }

}

这个代码也不难理解,就是申明一个服务提供器,这个服务提供器有一个 register的方法。这个方法实现了我们上面讲到的依赖注入。

当我们执行下面代码:

App::register('FooServiceProvider');

我们就完成一个注入了。但是这个还是得手动写,所以怎么让 Laravel
自己来做这事儿呢?

我们只要在 app/config/app.php 中的 providers 数组里面增加一行:

'providers' => [
    …
       ‘FooServiceProvider’,
],

这样我们就可以使用 App::make(‘foo’) 来实例化一个类了。

你不禁要问了,这么写也太难看了吧?莫慌,有办法。

IoC(Inversion of Control)译为
「控制反转」,也被叫做「依赖注入」。什么是「控制反转」?对象 A
功能依赖于对象 B,但是控制权由对象 A
来控制,控制权被颠倒,所以叫做「控制反转」,而「依赖注入」是实现 IoC
的方法,就是由 IoC
容器在运行期间,动态地将某种依赖关系注入到对象之中。

其作用简单来讲就是利用依赖关系注入的方式,把复杂的应用程序分解为互相合作的对象,从而降低解决问题的复杂度,实现应用程序代码的低耦合、高扩展。

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

Service Container,也就是IOC容器的使用并不依赖Service Provider,例如:

门面模式(Facade)

为了让 Laravel 中的核心类使用起来更加方便,Laravel实现了门面模式。

外觀模式(Facade
pattern),是軟件工程中常用的一種軟件設計模式,它為子系統中的一組接口提供一個統一的高層接口,使得子系統更容易使用。
— 维基百科

  • Facades 是什么?

$app->make(‘App\Models\Post’);

Laravel 中的使用

我们使用的大部分核心类都是基于门面模式实现的。例如:

$value = Cache::get('key');

这些静态调用实际上调用的并不是静态方法,而是通过 PHP 的魔术方法__callStatic() 讲请求转到了相应的方法上。

那么如何讲我们前面写的服务提供器也这样使用呢?方法很简单,只要这么写:

use Illuminate\Support\Facades\Facade;

class Foo extends Facade {

    protected static function getFacadeAccessor() { return ‘foo’; }

}

这样我们就可以通过 Foo::test() 来调用我们之前真正的 FooBar 类的方法了。

Facades(一种设计模式,通常翻译为外观模式)提供了一个”static”接口去访问注册到
IoC
容器中的类。提供了简单、易记的语法,而无需记住必须手动注入或配置的长长的类名。此外,由于对
PHP 动态方法的独特用法,也使测试起来非常容易。

这句话和 new App\Models\Post; 的结果完全一样;
另外你在控制器里使用构造函数,type-hint进行依赖注入,也完全和Service
Provider没有半毛钱关系。

别名(Alias)

有时候我们可能将 Facade 放在我们扩展库中,它有比较深的命名空间,如:\Library\MyClass\Foo。这样导致使用起来并不方便。Laravel
可以用别名来替换掉这么长的名字。

我们只要在 app/config/app.php 中 aliases 下增加一行即可:

'aliases' => [
    …
    'Foo' => ‘Library\MyClass\Foo’,
],

这样它的使用就由 \Library\MyClass\Foo::test() 变成 Foo::test() 了。

  • Contract 是什么?

总之,你可以完全不使用Service Provider;

总结

所以有了控制反转(Inversion of
Control)和门面模式(Facade),实际还有服务提供器(Service
Providers)和别名(Alias),我们创建自己的类库和扩展 Laravel
都会方便很多。

这里总结一下创建自己类库的方法:

  1. 在 app/library/MyFoo 下创建类 MyFoo.php
  2. 在 app/library/MyFoo/providers 下创建 MyFooServiceProvider.php
  3. 在 app/library/MyFoo/facades 下创建 MyFooFacade.php
  4. 在 app/config/app.php 中添加 providers 和 aliases

Contract是 laravel 定义框架提供的核心服务的接口。Contract 和 Facades
并没有本质意义上的区别,其作用就是使接口低耦合、更简单。

Service Provider 和Contracts

  • 依赖注入的原理?

如果说IOC容器的使用并不依赖Service
Provider,那么为什么我们用composer下载扩展包的时候总是要在config/app.php里绑定一下Service
Provider呢,有时候还需要绑定一下Facade;

这个不解释,这是理解 IoC 容器的前提。

理解的思路是这样的,Laravel核心类(Services)都是用接口(contracts)+实现来构成的,
如果不理解这个概念,仔细看文档接口那一章。而你在使用的时候,如果要拿到某个接口实现的实例的话,需要用到Service
Container,而要用Service
Container去解析一个接口,而不是直接解析一个类,这时就要用到Service
Provider了,可以说,Service Provider的主要功能,就是来绑定接口的。

  • 谈谈 Laravel 和 YII 框架的区别

下我准备要讲坑爹的事情了,在讲接口绑定前,先了解一些基本的事实:

  1. 在 YII 框架中的路由是通过书写 Controller、Action 间接定义路由,而
    Laravel 中是在 route 路由文件中直接定义路由入口
  2. Laravel 提供 ORM 对象关系映射,使读写数据库的操作更加简单
  3. Laravel 提供更多的 Artisan 命令和脚手架开发
  4. Laravel 的 Composer 扩展包比 Yii 框架更多,开发更加高效

一些事实

$app->make(‘App\Models\Post’);

你可以这样写,

$app->make(‘post’);

也可以这样写,这里的post是一个别名,这个别名是造成混淆的主要地方;
这个时候你肯定在想,这样写有啥用,我去哪里关联这个别名到App\Models\Post呢?

Service Provider 的 bind方法

对,就是在Service Provider里用bind方法来绑定别名:

$this->app->bind(‘post’, function ($app) {    return new
App\Models\Post;});

这样绑定后你就可以$app->make(‘post’);这样写了;然而搞个别名到目前为止也没什么卵用。没关系,稍后会讲到,它和Facade有关系;我们先来解释文档坑爹的地方:

文档是这样写这个bind方法的:

$this->app->bind(‘HelpSpot\API’,

function ($app) {  

 return new HelpSpot\API($app[‘HttpClient’]);}
);

哇擦,您的这第一个参数到底填的啥啊,事实上,第一个参数可以填类的全称,但是如果不是填简称,我这样绑定有任何意义么?
后面再返回一个一样的类实例? 咦?$app[‘HttpClient’]这个是什么??
其实它是告诉你可以在解析类的时候可以再接着注入一个其他类的实例;文档大哥,拜托你解释一下好不好,能不能举个靠谱点的例子…

如果你到其他的扩展包中去看别人的bind的写法,你会发现千奇百怪的绑定写法,先不管他们,现在我们来看Service
Provider对接口的使用方法,最最基本的原理是这样的:

//给一个接口起个别名$this->app->bind(‘event_pusher’, function
($app) {    return new
App\Contracts\EventPusher;});//指定这个接口应该解析的实例$this->app->bind(‘App\Contracts\EventPusher’,
‘App\Services\RedisEventPusher’);
通过这两步,我们让这个接口有了别名,也有了解析时对应的实现;

这样,我们就可以:

$app->make(‘event_pusher’);

得到App\Services\RedisEventPusher;

Service Provider 和 Facades

我们来看Facade的写法,比如说Illuminate\Support\Facades\Cache:

class Cache extends Facade{   

protected static function getFacadeAccessor() { return ‘cache’; }}

这个cache就是上面提到过的别名;

下面我们来看Facade的对应关系图:

Facade Name Facade Class Resolved Class Service Provider Binding Alias
Cache Illuminate\Support\Facades\Cache Illuminate\Cache\Repository cache
所以你调用Cache::get(‘user_id’)的时候,实际上是调用了Illuminate\Support\Facades\Cache
这个类,get并不是这个类的静态方法,事实上,get这个方法在Facade这个类里根本不存在,这正是它设计的本意,当get这个方法不存在的时候,它就会调用Facade基类里的__callStatic魔术方法(需要提前了解这个魔术方法),这个方法中就会把Service
Provider中绑定的类(或接口)解析并返回出来,本例中也就是Illuminate\Cache\Repository
这个类,所以get其实是Illuminate\Cache\Repository这个类的方法;

Author

发表评论

电子邮件地址不会被公开。 必填项已用*标注