# Facades
## 简介
>[success]Facades 为应用的[服务容器]提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。`Laravel Facades`实际是服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法
所有的 Laravel Facades 都定义在`Illuminate\Support\Facades`命名空间下。所以,我们可以轻松的使用 Facade :
~~~php
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
~~~
在 Laravel 文档中,有很多示例代码都会使用 Facades 来演示框架的各种功能
## 何时使用 Facades
>[success]Facades 有很多优点,它提供了简单,易记的语法,从而无需手动注入或配置长长的类名。此外,由于他们对`PHP`静态方法的独特调用,使得测试起来非常容易。
然而,在使用 Facades 时,有些地方需要特别注意。使用 Facades 时最主要的危险就是会引起类作用范围的膨胀。由于 Facades 使用起来非常简单并且不需要注入,就会使得我们不经意间在单个类中使用许多 Facades ,从而导致类变得越来越大。然而使用依赖注入的时候,使用的类越多,构造方法就会越长,在视觉上注意到这个类有些庞大了。因此在使用 Facades 的时候,要特别注意控制类的大小,让类的作用范围保持短小。
> {tip} 在开发与 Laravel 进行交互的第三方扩展包时,最好选择注入[Laravel 契约](https://laravel-china.org/docs/laravel/5.7/contracts)而不使用 Facades 。因为扩展包是在 Laravel 之外构建,你无法使用 Laravel Facades 测试辅助函数
### Facades Vs. 依赖注入
>[success]依赖注入的主要优点之一是切换注入类的实现。这在测试的时候很有用,因为你可以注入一个 模拟或`stub`,并断言`stub`上调出各种方法。
通常,真正的静态方法是不可能模拟或`stub`的。但是 Facades 使用动态方法对服务容器中解析出来的对象方法的调用进行了代理,我们也可以像测试注入类实例一样测试 Facades。比如,像下面的路由:
~~~php
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
~~~
我们可以带上我们期望的参数编写下面的测试代码来验证`Cache::get`方法:
~~~php
use Illuminate\Support\Facades\Cache;
/**
* 一个基础功能的测试
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
~~~
### Facades Vs. 辅助函数
>[success]除了 Facades,Laravel 还包含各种 『辅助函数』 来实现这些常用功能,比如生成视图、触发事件、任务调度或者发送 HTTP 响应。许多辅助函数都有与之对应的 Facades 。例如,下面这个 Facades 和辅助函数的作用是一样的:
~~~php
return View::make('profile');
return view('profile');
~~~
Facade 和辅助函数之间没有实际的区别。当你使用辅助函数时,你可以像测试相应的 Facade 那样进行测试。例如,下面的路由:
~~~php
Route::get('/cache', function () {
return cache('key');
});
~~~
在底层实现,辅助函数`cache`实际是调用`Cache`这个 Facade 的`get`方法。因此,尽管我们使用的是辅助函数,我们依然可以带上我们期望的参数编写下面的测试代码来验证`Cache::get`方法:
~~~php
use Illuminate\Support\Facades\Cache;
/**
* 一个基础功能的测试用例
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
~~~
## Facades 工作原理
>[success]在 Laravel 应用中,Facade 就是一个可以从容器访问对象的类。其中核心的部件就是`Facade`类。不管是 Laravel 自带的 Facades,还是自定义的 Facades,都继承自`Illuminate\Support\Facades\Facade`类。
`Facade`基类使用了`__callStatic()`魔术方法,直到对象从容器中被解析出来后,才会进行调用。在下面的例子中,调用了 Laravel 的缓存系统。通过浏览这段代码,可以假定在`Cache`类中调用了静态方法`get`:
~~~php
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
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。这个 Facade 作为访问`Illuminate\Contracts\Cache\Factory`接口底层实现的代理。我们使用 Facade 进行的任何调用都将传递给 Laravel 缓存服务的底层实例。
如果我们看一下`Illuminate\Support\Facades\Cache`这个类,你会发现类中根本没有`get`这个静态方法:
~~~php
class Cache extends Facade
{
/**
* 获取组件的注册名称。
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
~~~
`Cache`Facade 继承了`Facade`类,并且定义了`getFacadeAccessor()`方法。这个方法的作用是返回服务容器绑定的名称。当用户调用`Cache`Facade 中的任何静态方法时,Laravel 会从[服务容器](https://laravel-china.org/docs/laravel/5.7/container)中解析`cache`绑定以及该对象运行所请求的方法(在这个例子中就是`get`方法)。
## 实时 Facades
>[success]使用实时 Facades,你可以将应用程序中的任何类视为 Facade。为了说明这是如何使用的,我们来看看另一种方法。例如,假设我们的`Podcast`模型有一个`publish`方法。然而,为了发布 Podcast,我们需要注入一个`Publisher`实例:
~~~php
<?php
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布 Podcast。
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
~~~
>[info]将发布者的实现注入到该方法中,我们可以轻松地测试这种方法,因为我们可以模拟注入的发布者。但是,它要求我们每次调用`publish`方法时都要传递一个发布者实例。使用实时的 Facades,我们可以保持同样的可测试性,而不需要显式地通过`Publisher`实例。要生成实时Facade,请在导入类的名称空间中加上`Facades`:
~~~php
<?php
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布 Podcast。
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
~~~
当使用实时 Facade 时,发布者实现将通过使用`Facades`前缀后出现的接口或类名的部分来解决服务容器的问题。在测试时,我们可以使用 Laravel 的内置 facade 测试辅助函数来模拟这种方法调用:
~~~php
<?php
namespace Tests\Feature;
use App\Podcast;
use Tests\TestCase;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* 一个测试演示。
*
* @return void
*/
public function test_podcast_can_be_published()
{
$podcast = factory(Podcast::class)->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
~~~
## Facade 类参考
在下面你可以找到每个 Facade 类及其对应的底层类。这是一个查找给定 Facade 类 API 文档的工具。[服务容器绑定](https://laravel-china.org/docs/laravel/5.7/container)的关键信息也包含在内。
| Facade | Class | Service Container Binding |
| --- | --- | --- |
| App | [Illuminate\\Foundation\\Application](https://laravel.com/api/laravel/5.7/Illuminate/Foundation/Application.html) | `app` |
| Artisan | [Illuminate\\Contracts\\Console\\Kernel](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Console/Kernel.html) | `artisan` |
| Auth | [Illuminate\\Auth\\AuthManager](https://laravel.com/api/laravel/5.7/Illuminate/Auth/AuthManager.html) | `auth` |
| Auth (Instance) | [Illuminate\\Contracts\\Auth\\Guard](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Auth/Guard.html) | `auth.driver` |
| Blade | [Illuminate\\View\\Compilers\\BladeCompiler](https://laravel.com/api/laravel/5.7/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler` |
| Broadcast | [Illuminate\\Contracts\\Broadcasting\\Factory](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Broadcasting/Factory.html) | |
| Broadcast (Instance) | [Illuminate\\Contracts\\Broadcasting\\Broadcaster](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Broadcasting/Broadcaster.html) | |
| Bus | [Illuminate\\Contracts\\Bus\\Dispatcher](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Bus/Dispatcher.html) | |
| Cache | [Illuminate\\Cache\\CacheManager](https://laravel.com/api/laravel/5.7/Illuminate/Cache/CacheManager.html) | `cache` |
| Cache (Instance) | [Illuminate\\Cache\\Repository](https://laravel.com/api/laravel/5.7/Illuminate/Cache/Repository.html) | `cache.store` |
| Config | [Illuminate\\Config\\Repository](https://laravel.com/api/laravel/5.7/Illuminate/Config/Repository.html) | `config` |
| Cookie | [Illuminate\\Cookie\\CookieJar](https://laravel.com/api/laravel/5.7/Illuminate/Cookie/CookieJar.html) | `cookie` |
| Crypt | [Illuminate\\Encryption\\Encrypter](https://laravel.com/api/laravel/5.7/Illuminate/Encryption/Encrypter.html) | `encrypter` |
| DB | [Illuminate\\Database\\DatabaseManager](https://laravel.com/api/laravel/5.7/Illuminate/Database/DatabaseManager.html) | `db` |
| DB (Instance) | [Illuminate\\Database\\Connection](https://laravel.com/api/laravel/5.7/Illuminate/Database/Connection.html) | `db.connection` |
| Event | [Illuminate\\Events\\Dispatcher](https://laravel.com/api/laravel/5.7/Illuminate/Events/Dispatcher.html) | `events` |
| File | [Illuminate\\Filesystem\\Filesystem](https://laravel.com/api/laravel/5.7/Illuminate/Filesystem/Filesystem.html) | `files` |
| Gate | [Illuminate\\Contracts\\Auth\\Access\\Gate](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Auth/Access/Gate.html) | |
| Hash | [Illuminate\\Contracts\\Hashing\\Hasher](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Hashing/Hasher.html) | `hash` |
| Lang | [Illuminate\\Translation\\Translator](https://laravel.com/api/laravel/5.7/Illuminate/Translation/Translator.html) | `translator` |
| Log | [Illuminate\\Log\\Logger](https://laravel.com/api/laravel/5.7/Illuminate/Log/Logger.html) | `log` |
| Mail | [Illuminate\\Mail\\Mailer](https://laravel.com/api/laravel/5.7/Illuminate/Mail/Mailer.html) | `mailer` |
| Notification | [Illuminate\\Notifications\\ChannelManager](https://laravel.com/api/laravel/5.7/Illuminate/Notifications/ChannelManager.html) | |
| Password | [Illuminate\\Auth\\Passwords\\PasswordBrokerManager](https://laravel.com/api/laravel/5.7/Illuminate/Auth/Passwords/PasswordBrokerManager.html) | `auth.password` |
| Password (Instance) | [Illuminate\\Auth\\Passwords\\PasswordBroker](https://laravel.com/api/laravel/5.7/Illuminate/Auth/Passwords/PasswordBroker.html) | `auth.password.broker` |
| Queue | [Illuminate\\Queue\\QueueManager](https://laravel.com/api/laravel/5.7/Illuminate/Queue/QueueManager.html) | `queue` |
| Queue (Instance) | [Illuminate\\Contracts\\Queue\\Queue](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Queue/Queue.html) | `queue.connection` |
| Queue (Base Class) | [Illuminate\\Queue\\Queue](https://laravel.com/api/laravel/5.7/Illuminate/Queue/Queue.html) | |
| Redirect | [Illuminate\\Routing\\Redirector](https://laravel.com/api/laravel/5.7/Illuminate/Routing/Redirector.html) | `redirect` |
| Redis | [Illuminate\\Redis\\RedisManager](https://laravel.com/api/laravel/5.7/Illuminate/Redis/RedisManager.html) | `redis` |
| Redis (Instance) | [Illuminate\\Redis\\Connections\\Connection](https://laravel.com/api/laravel/5.7/Illuminate/Redis/Connections/Connection.html) | `redis.connection` |
| Request | [Illuminate\\Http\\Request](https://laravel.com/api/laravel/5.7/Illuminate/Http/Request.html) | `request` |
| Response | [Illuminate\\Contracts\\Routing\\ResponseFactory](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Routing/ResponseFactory.html) | |
| Response (Instance) | [Illuminate\\Http\\Response](https://laravel.com/api/laravel/5.7/Illuminate/Http/Response.html) | |
| Route | [Illuminate\\Routing\\Router](https://laravel.com/api/laravel/5.7/Illuminate/Routing/Router.html) | `router` |
| Schema | [Illuminate\\Database\\Schema\\Builder](https://laravel.com/api/laravel/5.7/Illuminate/Database/Schema/Builder.html) | |
| Session | [Illuminate\\Session\\SessionManager](https://laravel.com/api/laravel/5.7/Illuminate/Session/SessionManager.html) | `session` |
| Session (Instance) | [Illuminate\\Session\\Store](https://laravel.com/api/laravel/5.7/Illuminate/Session/Store.html) | `session.store` |
| Storage | [Illuminate\\Filesystem\\FilesystemManager](https://laravel.com/api/laravel/5.7/Illuminate/Filesystem/FilesystemManager.html) | `filesystem` |
| Storage (Instance) | [Illuminate\\Contracts\\Filesystem\\Filesystem](https://laravel.com/api/laravel/5.7/Illuminate/Contracts/Filesystem/Filesystem.html) | `filesystem.disk` |
| URL | [Illuminate\\Routing\\UrlGenerator](https://laravel.com/api/laravel/5.7/Illuminate/Routing/UrlGenerator.html) | `url` |
| Validator | [Illuminate\\Validation\\Factory](https://laravel.com/api/laravel/5.7/Illuminate/Validation/Factory.html) | `validator` |
| Validator (Instance) | [Illuminate\\Validation\\Validator](https://laravel.com/api/laravel/5.7/Illuminate/Validation/Validator.html) | |
| View | [Illuminate\\View\\Factory](https://laravel.com/api/laravel/5.7/Illuminate/View/Factory.html) | `view` |
| View (Instance) | [Illuminate\\View\\View](https://laravel.com/api/laravel/5.7/Illuminate/View/View.html) |