介绍
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 |
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 |