Facades

简介

Facades /fəˈsäd/ 为应用程序的服务容器中可用的类提供了一个「静态」接口。Laravel 自带了许多的 facades,可以用来访问其几乎所有的服务。Laravel facades 就是服务容器里那些基类的「静态代理」,相比于传统的静态方法调用,facades 在提供更简洁且丰富的语法的同时,还有更好的可测试性和扩展性。

所有的 Laravel facades 都在 Illuminate\Support\Facades 这个命名空间下。所以我们可以用类似下面的代码轻松调用:

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

在 Laravel 的文档中,很多代码示例都使用了 facades 来演示框架的各种特性。

何时使用 Facades

Facades 有很多的好处,它们提供了简单易记的语法让你使用 Laravel 的各种功能,你再也不需要记住长长的类名来实现注入或者手动去配置。还有,因为它们对于 PHP 动态方法的独特用法,测试起来就非常容易。

但是,在使用 facades 时,有些地方还是需要特别注意。使用 facades 最主要的风险就是会引起类的体积的膨胀。由于 facades 使用起来非常简单而且不需要注入,我们会不经意间在单个类中大量使用。不像是使用依赖注入,用得越多,构造方法会越长,在视觉上就会引起注意,提醒你这个类有点太庞大了。所以,在使用 facades 时,要特别注意把类的体积控制在一个合理的范围。

{tip} 在开发第三方扩展包时,如果需要使用和 Laravel 交互的扩展包,最好是注入 Laravel contracts 而不是使用 facades,因为扩展包不是在 Laravel 内部使用的,无法使用 Laravel 的 facades 的测试辅助函数。

Facades Vs. 依赖注入

依赖注入一个主要的好处就是可以切换注入的类的具体实现。这在测试时很有用,因为你可以注入一个 mock 或者 stub 方法并且断言一些方法在 stub 中被调用。

在以前,静态方法是不可能被 mock 或者 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. * A basic functional test example.
  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 一样。举例来说,这个 facede 和辅助函数是一样的:

  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 方法。因此,即使我们是在使用辅助函数,我们依然可以用下面的代码来测试方法是不是被正确的调用了:

  1. use Illuminate\Support\Facades\Cache;
  2. /**
  3. * A basic functional test example.
  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,都是继承了 Illuminate\Support\Facades\Facade 这个类。

Facade 类利用了 __callStatic() 这个魔术方法来延迟调用容器中的对象的方法。在下面的例子里,调用了 Laravel 缓存系统。在代码里,我们可能以为 Cache 这个类的静态方法 get 被调用了:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Cache;
  4. use App\Http\Controllers\Controller;
  5. class UserController extends Controller
  6. {
  7. /**
  8. * Show the profile for the given user.
  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. * Get the registered name of the component.
  5. *
  6. * @return string
  7. */
  8. protected static function getFacadeAccessor()
  9. {
  10. return 'cache';
  11. }
  12. }

其实,Cache facade 是继承了基类 Facade 并且定义了 getFacadeAccessor() 方法。这个方法的功能是返回服务容器中绑定的名字。当用户调用 Cache facade 的静态方法时,Laravel 会解析到 cache 在服务容积里绑定的具体的那个实例对象,并且调用这个对象的相应方法(在这个例子里就是 get 方法)。

Facade 类参考

在下方你可以找到每个 facade 及其底层的类。这个工具对于通过指定 facade 的来源快速寻找 API 文档相当有用。可应用的服务容器绑定关键字也包含在里面。

Facade Class Service Container Binding
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
Password Illuminate\Auth\Passwords\PasswordBroker 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

{note} 欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。

转载请注明:本文档由 Laravel China 社区 [laravel-china.org] 组织翻译,详见 翻译召集帖

文档永久地址: http://d.laravel-china.org