Laravel 核心--Facades 门面

介绍

Facades 为应用的 IoC 服务容器 的类提供了一个静态的接口。Laravel 里面自带了一些 Facades,如Cache等。Laravel 的门面作为服务容器中底层类的“静态代理”,相比于传统静态方法,在维护时能够提供更加易于测试、更加灵活、简明优雅的语法。

解释

在 Laravel 应用这个上下文里面,一个 Facade 就是一个类,使用这个类可以访问到来自容器里的一个对象,这个功能就是在 Facade 类里面定义的。Laravel 的 Facades 还有任何你自己定义的 Facades,都会去继承 Facade 这个类。

你的 Facade 类只需要实施一个的方法:getFacadeAccessor。要在容器里 resolve 什么出来,都是在这个方法里去做的。Facade 这个基类里面使用了__callStatic() 魔术方法,可以延迟到 resolved 对象上的,来自 Facade 的调用。

所以,当你使用 Facade 调用的时候,比如像这样:Cache:get,laravel 会从 Ioc 服务容器 里面 resolves 缓存管理类,然后再去调用这个类上面的 get 方法。Laravel 的 Facades 可以去定位服务,它是一种使用 Laravel 的 Ioc 服务容器 的更方便的语法。

优点

Facade 有诸多优点,其提供了简单、易记的语法,让我们无需记住长长的类名即可使用 Laravel 提供的功能特性,此外,由于他们对 PHP 动态方法的独到用法,使得它们很容易测试。

实际使用

下面的例子,去调用了一下 Laravel 的缓存系统。先看一下下面这行代码,你可能会觉得,这是直接去调用 Cache 这个类上面的一个叫 get 的静态的方法。

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

不过,如果你查看 Illuminate\Support\Facades\Cache 这个类,你会发现这里根本就没有 get 这个静态方法:

class Cache extends Facade {

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'cache'; }

}

Cache 这个类继承了 Facade 这个基类,它里面定义了一个叫 getFacadeAccessor() 的方法。注意,这个方法的干的事就是去返回一个 Ioc 绑定的名字,这里就是 cache。

当用户在引用任何在 Cache 这个 Facade 上的静态方法的时候,Laravel 就会从 Ioc 服务容器 里面去 resolves cache 这个绑定,并且会去执行在对象上的这个所请求的方法(这里就是 get 这个方法)。

所以,我们在调用 Cache::get 的时候,它的真正的意思是这样的:

$value = $app->make('cache')->get('key');

导入 Facades

注意,在使用 facade 的时候,如果控制器里面用到了命名空间,你需要把 Facade 类导入到这个命名空间里。所有的 Facades 都是在全局命名空间下:

<?php namespace App\Http\Controllers;

use Cache;

class PhotosController extends Controller {

    /**
     * Get all of the application photos.
     *
     * @return Response
     */
    public function index()
    {
        $photos = Cache::get('photos');

        //
    }

}

创建 Facades

创建 Facade 只需要三个东西:

  • 一个 IoC 绑定。
  • 一个 Facade 类。
  • 一个 Facade 别名的配置。

在下面我们定义了一个类:PaymentGateway\Payment

namespace PaymentGateway;

class Payment {

    public function process()
    {
        //
    }

}

我们需要能在 Ioc 服务容器 里面去 resolve 这个类。所以,先要去添加一个 Service Provider 绑定:

App::bind('payment', function()
{
    return new \PaymentGateway\Payment;
});

去注册这个绑定最好的方法就是去创建一个新的 Service Provider ,把它命名为 PaymentServiceProvider ,然后把它绑定到 register 方法上。再去配置 laravel 在 config/app.php 这个配置文件里加载你的 Service Provider

下一步就是去创建自己的 Facade 类:

use Illuminate\Support\Facades\Facade;

class Payment extends Facade {

    protected static function getFacadeAccessor() {
             return 'payment'; 
    }

}

最后,如果你愿意,可以去给 Facade 添加一个别名,放到 config/app.php 配置文件里的 aliases 数组里。

可以去调用 Payment 类的一个实例上的 process 这个方法了。像这样:

Payment::process();

何时使用 Facade

注意

在使用 Facade 也有需要注意的地方,一个最主要的危险就是类范围蠕变。由于Facade 如此好用并且不需要注入,在单个类中使用过多Facade,会让类很容易变得越来越大。使用依赖注入则会让此类问题缓解,因为一个巨大的构造函数会让我们很容易判断出类在变大。因此,使用Facade的时候要尤其注意类的大小,以便控制其有限职责。

注:构建与 Laravel 交互的第三方扩展包时,最好注入 Laravel 契约而不是使用门面,因为扩展包在 Laravel 之外构建,你将不能访问 Laravel 的门面测试辅助函数。

Facade vs. 依赖注入

依赖注入的最大优点是可以替换注入类的实现,这在测试时很有用,因为你可以注入一个模拟或存根并且在存根上断言不同的方法。

但是在静态类方法上进行模拟或存根却行不通,不过,由于Facade 使用了动态方法对服务容器中解析出来的对象方法调用进行了代理,我们也可以像测试注入类实例那样测试门面。例如,给定以下路由:

use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});

我们可以这样编写测试来验证 Cache::get 方法以我们期望的方式被调用:

use Illuminate\Support\Facades\Cache;

/**
 * A basic functional test example.
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $this->visit('/cache')
        ->see('value');
}

Facade vs. 辅助函数

除了Facade之外,Laravel 还内置了许多辅助函数用于执行通用任务,比如生成视图、触发事件、分配任务,以及发送 HTTP 响应等。很多辅助函数提供了和相应 Facade 一样的功能,例如,下面这个Facade调用和辅助函数调用是等价的:

return View::make('profile');
return view('profile');

Facade和辅助函数之间并不存在实质性差别,使用辅助函数的时候,可以像测试相应门面那样测试它们。例如,给定以下路由:

Route::get('/cache', function () {
    return cache('key');
});

在调用底层, cache 方法会去调用 Cache Facade上的 get方法,因此,尽管我们使用这个辅助函数,我们还是可以编写如下测试来验证这个方法以我们期望的方式和参数被调用:

use Illuminate\Support\Facades\Cache;

/**
 * A basic functional test example.
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $this->visit('/cache')
        ->see('value');
}

Facade 工作原理

在 Laravel 应用中,Facade就是一个为容器中对象提供访问方式的类。该机制原理由 Facade 类实现。Laravel 自带的 Facade,以及我们创建的自定义门面,都会继承自 Illuminate\Support\Facades\Facade 基类。可以参考 Facade 实现原理

Facade 类只需要实现一个方法:getFacadeAccessor。正是 getFacadeAccessor方法定义了从容器中解析什么,然后 Facade 基类使用魔术方法 __callStatic() 从你的门面中调用解析对象。

下面的例子中,我们将会调用 Laravel 的缓存系统,浏览代码后,也许你会觉得我们调用了 Cache 的静态方法 get

<?php

namespace App\Http\Controllers;

use Cache;
use App\Http\Controllers\Controller;

class UserController extends Controller{
    /**
     * 为指定用户显示属性
     *
     * @param  int  $id
     * @return Response
     */
    public function showProfile($id)
    {
        $user = Cache::get('user:'.$id);

        return view('profile', ['user' => $user]);
    }
}

注意我们在顶部位置引入了 Cache Facade。该门面作为代理访问底层 Illuminate\Contracts\Cache\Factory 接口的实现。我们对门面的所有调用都会被传递给 Laravel 缓存服务的底层实例。

如果我们查看 Illuminate\Support\Facades\Cache 类的源码,将会发现其中并没有静态方法 get

class Cache extends Facade
{
    /**
     * 获取组件注册名称
     *
     * @return string
     */
    protected static function getFacadeAccessor() { 
        return 'cache'; 
    }
}

Cache Facade 继承 Facade 基类并定了 getFacadeAccessor方法,该方法的工作就是返回服务容器绑定类的别名,当用户引用 Cache
类的任何静态方法时,Laravel 从服务容器中解析 cache
绑定,然后在解析出的对象上调用所有请求方法(本例中是 get

门面类列表

下面列出了每个门面及其对应的底层类,这对深入给定根门面的 API 文档而言是个很有用的工具。服务容器绑定键也被包含进来:

门面 Facade class 服务容器绑定
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\Repository cache
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB(Instance) Illuminate\Database\Connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate
Hash Illuminate\Contracts\Hashing\Hasher hash
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\Writer log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Queue Illuminate\Queue\QueueManager queue
Queue(Instance) Illuminate\Contracts\Queue\Queue queue
Queue(Base Class) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\Database redis
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Blueprint
Session Illuminate\Session\SessionManager session
Session(Instance) Illuminate\Session\Store
Storage Illuminate\Contracts\Filesystem\Factory filesystem
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator(Instance) Illuminate\Validation\Validator
View Illuminate\View\Factory view
View(Instance) Illuminate\View\View
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容