Facades

简介

Facades 为应用的 服务容器 提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。Laravel Facades 实际是服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法

所有的 Laravel Facades 都定义在 Illuminate\Support\Facades 命名空间下。所以,我们可以轻松的使用 Facade :

  1. use Illuminate\Support\Facades\Cache;
  2. Route::get('/cache', function () {
  3. return Cache::get('key');
  4. });

在 Laravel 文档中,有很多示例代码都会使用 Facades 来演示框架的各种功能

何时使用 Facades

Facades 有很多优点,它提供了简单,易记的语法,从而无需手动注入或配置长长的类名。此外,由于他们对 PHP 静态方法的独特调用,使得测试起来非常容易。

然而,在使用 Facades 时,有些地方需要特别注意。使用 Facades 时最主要的危险就是会引起类作用范围的膨胀。由于 Facades 使用起来非常简单并且不需要注入,就会使得我们不经意间在单个类中使用许多 Facades ,从而导致类变得越来越大。然而使用依赖注入的时候,使用的类越多,构造方法就会越长,在视觉上注意到这个类有些庞大了。因此在使用 Facades 的时候,要特别注意控制类的大小,让类的作用范围保持短小。

{tip} 在开发与 Laravel 进行交互的第三方扩展包时,最好选择注入 Laravel 契约 而不使用 Facades 。因为扩展包是在 Laravel 之外构建,你无法使用 Laravel Facades 测试辅助函数

Facades Vs. 依赖注入

依赖注入的主要优点之一是切换注入类的实现。这在测试的时候很有用,因为你可以注入一个 模拟或 stub ,并断言 stub 上调出各种方法。

通常,真正的静态方法是不可能模拟或 stub 的。但是 Facades 使用动态方法对服务容器中解析出来的对象方法的调用进行了代理,我们也可以像测试注入类实例一样测试 Facades。比如,像下面的路由:

  1. use Illuminate\Support\Facades\Cache;
  2. Route::get('/cache', function () {
  3. return Cache::get('key');
  4. });

我们可以带上我们期望的参数编写下面的测试代码来验证Cache::get 方法:

  1. use Illuminate\Support\Facades\Cache;
  2. /**
  3. * 一个基础功能的测试
  4. *
  5. * @return void
  6. */
  7. public function testBasicExample()
  8. {
  9. Cache::shouldReceive('get')
  10. ->with('key')
  11. ->andReturn('value');
  12. $this->visit('/cache')
  13. ->see('value');
  14. }

Facades Vs. 辅助函数

除了 Facades,Laravel 还包含各种 『辅助函数』 来实现这些常用功能,比如生成视图、触发事件、任务调度或者发送 HTTP 响应。许多辅助函数都有与之对应的 Facades 。例如,下面这个 Facades 和辅助函数的作用是一样的:

  1. return View::make('profile');
  2. return view('profile');

Facade 和辅助函数之间没有实际的区别。当你使用辅助函数时,你可以像测试相应的 Facade 那样进行测试。例如,下面的路由:

  1. Route::get('/cache', function () {
  2. return cache('key');
  3. });

在底层实现,辅助函数 cache 实际是调用 Cache 这个 Facade 的 get 方法。因此,尽管我们使用的是辅助函数,我们依然可以带上我们期望的参数编写下面的测试代码来验证Cache::get 方法:

  1. use Illuminate\Support\Facades\Cache;
  2. /**
  3. * 一个基础功能的测试用例
  4. *
  5. * @return void
  6. */
  7. public function testBasicExample()
  8. {
  9. Cache::shouldReceive('get')
  10. ->with('key')
  11. ->andReturn('value');
  12. $this->visit('/cache')
  13. ->see('value');
  14. }

Facades 工作原理

在 Laravel 应用中,Facade 就是一个可以从容器访问对象的类。其中核心的部件就是Facade类。不管是 Laravel 自带的 Facades,还是自定义的 Facades,都继承自Illuminate\Support\Facades\Facade类。

Facade基类使用了__callStatic()魔术方法,直到对象从容器中被解析出来后,才会进行调用。在下面的例子中,调用了 Laravel 的缓存系统。通过浏览这段代码,可以假定在Cache类中调用了静态方法get

  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Support\Facades\Cache;
  5. class UserController extends Controller
  6. {
  7. /**
  8. * 显示给定用户的信息。
  9. *
  10. * @param int $id
  11. * @return Response
  12. */
  13. public function showProfile($id)
  14. {
  15. $user = Cache::get('user:'.$id);
  16. return view('profile', ['user' => $user]);
  17. }
  18. }

注意在上面这段代码中,我们『导入』了 Cache Facade。这个 Facade 作为访问Illuminate\Contracts\Cache\Factory接口底层实现的代理。我们使用 Facade 进行的任何调用都将传递给 Laravel 缓存服务的底层实例。

如果我们看一下Illuminate\Support\Facades\Cache这个类,你会发现类中根本没有get这个静态方法:

  1. class Cache extends Facade
  2. {
  3. /**
  4. * 获取组件的注册名称。
  5. *
  6. * @return string
  7. */
  8. protected static function getFacadeAccessor() { return 'cache'; }
  9. }

CacheFacade 继承了Facade类,并且定义了getFacadeAccessor()方法。这个方法的作用是返回服务容器绑定的名称。当用户调用CacheFacade 中的任何静态方法时,Laravel 会从 服务容器 中解析cache绑定以及该对象运行所请求的方法(在这个例子中就是get方法)。

实时 Facades

使用实时 Facades,你可以将应用程序中的任何类视为 Facade。为了说明这是如何使用的,我们来看看另一种方法。例如,假设我们的Podcast模型有一个publish方法。然而,为了发布 Podcast,我们需要注入一个Publisher实例:

  1. <?php
  2. namespace App;
  3. use App\Contracts\Publisher;
  4. use Illuminate\Database\Eloquent\Model;
  5. class Podcast extends Model
  6. {
  7. /**
  8. * 发布 Podcast。
  9. *
  10. * @param Publisher $publisher
  11. * @return void
  12. */
  13. public function publish(Publisher $publisher)
  14. {
  15. $this->update(['publishing' => now()]);
  16. $publisher->publish($this);
  17. }
  18. }

将发布者的实现注入到该方法中,我们可以轻松地测试这种方法,因为我们可以模拟注入的发布者。但是,它要求我们每次调用publish方法时都要传递一个发布者实例。使用实时的 Facades,我们可以保持同样的可测试性,而不需要显式地通过Publisher实例。要生成实时Facade,请在导入类的名称空间中加上Facades

  1. <?php
  2. namespace App;
  3. use Facades\App\Contracts\Publisher;
  4. use Illuminate\Database\Eloquent\Model;
  5. class Podcast extends Model
  6. {
  7. /**
  8. * 发布 Podcast。
  9. *
  10. * @return void
  11. */
  12. public function publish()
  13. {
  14. $this->update(['publishing' => now()]);
  15. Publisher::publish($this);
  16. }
  17. }

当使用实时 Facade 时,发布者实现将通过使用Facades前缀后出现的接口或类名的部分来解决服务容器的问题。在测试时,我们可以使用 Laravel 的内置 facade 测试辅助函数来模拟这种方法调用:

  1. <?php
  2. namespace Tests\Feature;
  3. use App\Podcast;
  4. use Tests\TestCase;
  5. use Facades\App\Contracts\Publisher;
  6. use Illuminate\Foundation\Testing\RefreshDatabase;
  7. class PodcastTest extends TestCase
  8. {
  9. use RefreshDatabase;
  10. /**
  11. * 一个测试演示。
  12. *
  13. * @return void
  14. */
  15. public function test_podcast_can_be_published()
  16. {
  17. $podcast = factory(Podcast::class)->create();
  18. Publisher::shouldReceive('publish')->once()->with($podcast);
  19. $podcast->publish();
  20. }
  21. }

Facade 类参考

在下面你可以找到每个 Facade 类及其对应的底层类。这是一个查找给定 Facade 类 API 文档的工具。服务容器绑定 的关键信息也包含在内。

FacadeClassService Container Binding
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
AuthIlluminate\Auth\AuthManagerauth
Auth (Instance)Illuminate\Contracts\Auth\Guardauth.driver
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
BroadcastIlluminate\Contracts\Broadcasting\Factory
Broadcast (Instance)Illuminate\Contracts\Broadcasting\Broadcaster
BusIlluminate\Contracts\Bus\Dispatcher
CacheIlluminate\Cache\CacheManagercache
Cache (Instance)Illuminate\Cache\Repositorycache.store
ConfigIlluminate\Config\Repositoryconfig
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DBIlluminate\Database\DatabaseManagerdb
DB (Instance)Illuminate\Database\Connectiondb.connection
EventIlluminate\Events\Dispatcherevents
FileIlluminate\Filesystem\Filesystemfiles
GateIlluminate\Contracts\Auth\Access\Gate
HashIlluminate\Contracts\Hashing\Hasherhash
LangIlluminate\Translation\Translatortranslator
LogIlluminate\Log\Loggerlog
MailIlluminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager
PasswordIlluminate\Auth\Passwords\PasswordBrokerManagerauth.password
Password (Instance)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
QueueIlluminate\Queue\QueueManagerqueue
Queue (Instance)Illuminate\Contracts\Queue\Queuequeue.connection
Queue (Base Class)Illuminate\Queue\Queue
RedirectIlluminate\Routing\Redirectorredirect
RedisIlluminate\Redis\RedisManagerredis
Redis (Instance)Illuminate\Redis\Connections\Connectionredis.connection
RequestIlluminate\Http\Requestrequest
ResponseIlluminate\Contracts\Routing\ResponseFactory
Response (Instance)Illuminate\Http\Response
RouteIlluminate\Routing\Routerrouter
SchemaIlluminate\Database\Schema\Builder
SessionIlluminate\Session\SessionManagersession
Session (Instance)Illuminate\Session\Storesession.store
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
Storage (Instance)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
URLIlluminate\Routing\UrlGeneratorurl
ValidatorIlluminate\Validation\Factoryvalidator
Validator (Instance)Illuminate\Validation\Validator
ViewIlluminate\View\Factoryview
View (Instance)Illuminate\View\View

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接 我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。