Laravel Facades
简介
Facade 为应用程序的服务容器中可用的类提供“静态”接口。Laravel自带了许多Facade,可以说几乎覆盖了Laravel的所有功能。Laravel Facade作为服务容器中基础类的“静态代理”,具有简洁,表达性强的语法的优点,同时比传统静态方法具有更高的可测试性和灵活性。
Laravel的所有Facade都在Illuminate\Support\Facades
命名空间中定义。因此,我们可以像这样轻松访问 Facade:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
在整个Laravel文档中,许多示例都将使用Facades来演示框架的功能。
何时使用 Facade
Facade 有很多好处。它们提供了一种简洁且容易记的语法,使我们可以使用Laravel的功能而无需记住必须手动注入或配置的那么长的类名。此外,由于它们使用了PHP动态方法,因此也易于测试。
但是,在使用 Facade 时必须格外小心。Facade 的主要危险是类范围蠕变。由于 Facade 是如此易于使用且不需要注入,因此可以很容易地让我们的类变得过大,并且很容易在一个类中使用许多Fcacade。因此,在使用Facade时,应特别注意类的大小,尽量使类的职责单一。
构建与Laravel交互的第三方程序包时,最好注入Laravel Contract,而不要使用Facade。由于软件包是在Laravel本身之外构建的,因此我们将无法使用Laravel的Facade测试函数。
Facade 与 依赖注入
依赖项注入的主要好处之一是可以改变所注入类的实现。这在测试期间很有用,因为我们可以注入一个 mock 或 stub 并对stub上的各种方法进行断言。
通常,不可能mock或stub真正的静态类方法。但是,由于Facade使用动态方法将代理调用服务容器中解析的对象的方法,因此实际上我们可以像测试注入的类实例一样测试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 与 辅助功能
除了 Facade 外,Laravel还包括各种“辅助”功能,可以执行常见的任务,例如生成视图,触发事件,调度作业或发送HTTP响应。这些帮助程序中的许多功能与相应的 Facade 具有相同的功能。例如,下面Facade调用和函数调用是等效的:
return View::make('profile');
return view('profile');
Facade 和 辅助功能之间没有实际区别。使用辅助函数时,我们仍然可以像对相应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是提供从容器中访问对象的类。Laravel自带的Facade 以及我们创建的任何自定义 Facade 都将继承Illuminate\Support\Facades\Facade
基类。
Facade基类利用__callStatic()
魔术方法来延迟Facade对从容器解析出来的对象的调用。在下面的示例中,调用了Laravel缓存系统。通过看一下这段代码,可以假定在调用Cache类上的静态方法get:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*
* @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的:
class Cache extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
相反,Cache facade继承了Facade基类并定义了getFacadeAccessor()
方法。该方法的工作是返回服务容器绑定的实例名称。当用户引用Cache Facade上的任何静态方法时,Laravel都会从服务容器解析绑定的实例cache,并执行请求该对象的 get 方法。
实时 Facade
使用实时 Facade ,我们可以将应用程序中的任何类都视为Facade。为了说明如何使用它,让我们研究一个替代方法。例如,假设我们的Podcast模型有一个publish方法。但是,为了发布播客,我们需要注入一个Publisher实例:
<?php
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
将publisher
实现注入到方法中,因为我们可以模拟注入的发布者,因此我们可以轻松地单独测试该方法。但是,它要求我们每次调用该publish方法时始终传递一个 publisher 实例。使用实时Facade,我们可以保持相同的可测试性,而无需显式传递Publisher实例。要生成实时Facade,需要在导入的类的命名空间添加前缀Facades:
<?php
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
使用实时 Facade 时,将使用出现在Facades前缀之后的接口或类名称的一部分,将 Publisher 实现从服务容器中解析出来。在测试时,我们可以使用Laravel的内置 Facade 测试方法来模拟此方法调用:
<?php
namespace Tests\Feature;
use App\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* A test example.
*
* @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 | 对应的类 | 服务容器中绑定的名称 |
---|---|---|
App | Illuminate\Foundation\Application | app |
Artisan | Illuminate\Contracts\Console\Kernel | artisan |
Auth | Illuminate\Auth\AuthManager | auth |
Auth (Instance) | Illuminate\Contracts\Auth\Guard | auth.driver |
Blade | Illuminate\View\Compilers\BladeCompiler | blade.compiler |
Broadcast | Illuminate\Contracts\Broadcasting\Factory | |
Broadcast (Instance) | Illuminate\Contracts\Broadcasting\Broadcaster | |
Bus | Illuminate\Contracts\Bus\Dispatcher | |
Cache | Illuminate\Cache\CacheManager | cache |
Cache (Instance) | Illuminate\Cache\Repository | cache.store |
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 | db.connection |
Event | Illuminate\Events\Dispatcher | events |
File | Illuminate\Filesystem\Filesystem | files |
Gate | Illuminate\Contracts\Auth\Access\Gate | |
Hash | Illuminate\Contracts\Hashing\Hasher | hash |
Http | Illuminate\Http\Client\Factory | |
Lang | Illuminate\Translation\Translator | translator |
Log | Illuminate\Log\LogManager | log |
Illuminate\Mail\Mailer | mailer | |
Notification | Illuminate\Notifications\ChannelManager | |
Password | Illuminate\Auth\Passwords\PasswordBrokerManager | auth.password |
Password (Instance) | Illuminate\Auth\Passwords\PasswordBroker | auth.password.broker |
Queue | Illuminate\Queue\QueueManager | queue |
Queue (Instance) | Illuminate\Contracts\Queue\Queue | queue.connection |
Queue (Base Class) | Illuminate\Queue\Queue | |
Redirect | Illuminate\Routing\Redirector | redirect |
Redis | Illuminate\Redis\RedisManager | redis |
Redis (Instance) | Illuminate\Redis\Connections\Connection | redis.connection |
Request | Illuminate\Http\Request | request |
Response | Illuminate\Contracts\Routing\ResponseFactory | |
Response (Instance) | Illuminate\Http\Response | |
Route | Illuminate\Routing\Router | router |
Schema | Illuminate\Database\Schema\Builder | |
Session | Illuminate\Session\SessionManager | session |
Session (Instance) | Illuminate\Session\Store | session.store |
Storage | Illuminate\Filesystem\FilesystemManager | filesystem |
Storage (Instance) | Illuminate\Contracts\Filesystem\Filesystem | filesystem.disk |
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 |