设计模式中的 Facade pattern (外观模式),指的是将整组的介面包装起来,提供统一的介面方便取用各个介面的功能。
在 Laravel 中已经定义好的 Facade 类别都定义在 Illuminate\Support\Facades 命名空间底下,资料夹目录的话是在 vendor/laravel/framework/src/Illuminate/Support/Facades/ 中。
我们可以引用这些类别并直接使用该类别的方法,即使是静态(static)的方法,先看一下使用范例。
/app/Http/Controllers/TodoController.php
use Illuminate\Support\Facades\Auth;
public function store(Request $request)
{
$data = $request->all();
$user = Auth::user(); //藉由 Auth 的 Facade 直接使用 user 函式
$this->todoService->create([
'name' => $data['name']
]);
}
前面我们就已经藉由 Auth 的 Facade 类别取用到登入的 user 资讯,简单的一行程序其实底下也一路连结到 Laravel 的 Service Container 产生的实例,接着就来抽丝剥茧看看 Facade 怎麽运作的。
首先我们找到 Facades/Auth ,可以看到是继承了 Facade 类别,所有的 Facade 都是继承这个类别而来
vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.php
<?php
namespace Illuminate\Support\Facades;
//...
class Auth extends Facade
{
protected static function getFacadeAccessor()
{
return 'auth';
}
//...
}
Auth 里的内容相当少,主要就是定义了 getFacadeAccessor 方法,功能只有回传了 auth 字串,不知道干嘛用。
再来找到 Facade 类别,跟 Auth 在同一个目录
vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
<?php
namespace Illuminate\Support\Facades;
//...
abstract class Facade
{
//...
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
protected static function getFacadeAccessor()
{
throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
if (static::$app) {
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
}
Facade 里面定义了许多方法,不过要介绍 Facade 魔法般功能的话只要看上面几个函式,我调换一下顺序方便解说。
首先是 __callStatic() ,这个是 PHP 原生定义的魔法函式,当一个类别有定义 __callStatic() 的时候,如果试图直接从类别呼叫静态(static)方法或属性,就会变成呼叫 __callStatic()。
static 属性跟方法正常只能在类别被建成实例後才能经由实例取用,直接从类别取用会报错
也就是当我们呼叫 Auth::user() 时,其实我们是在呼叫 Facade 的 __callStatic() ,并解 user 以字串作为 $method 传入,如果有参数的话经由 $args 传入。
接着,__callStatic() 试图藉由 getFacadeRoot 取得实例好执行该实例的 $method 方法。
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
那怎麽知道要创建哪个类别的实例呢? 首先看到这两个方法
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
protected static function getFacadeAccessor()
{
throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}
有没有发现 getFacadeAccessor 很眼熟? 在 Auth 继承 Facade 之後已经覆写了这个函式,所以在 Auth 中这边的功能变成了回传 'auth' 字串。
接着将这个 auth 字串作为 $name 传入 resolveFacadeInstance 方法。
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
if (static::$app) {
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
放大看最後一行
return static::$resolvedInstance[$name] = static::$app[$name];
$app 指的就是 Laravel App 实例,也就是我们好朋友 Service Container,所以这边就是回传了在 Service Container 中注册为 auth 的实例,接着在 __callStatic 中才能藉由这个实例使用静态属性。
至於 auth 在哪边注册的,自然是 Service Provider 罗。
vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php
<?php
namespace Illuminate\Auth;
//...
class AuthServiceProvider extends ServiceProvider
{
//...
protected function registerAuthenticator()
{
$this->app->singleton('auth', function ($app) {
return new AuthManager($app);
});
$this->app->singleton('auth.driver', function ($app) {
return $app['auth']->guard();
});
}
//...
}
看完上面 Facade 的运作流程,要如何自订 Facade 也很明显了。
首先要在 Service Container 注册好类别或介面。
接着建一个继承 Facade 的类别,并覆盖 getFacadeAccessor 方法,让他回传你注册的类别或介面名称。
class Response extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return ResponseFactoryContract::class;
}
}
这样就能直接从新建立的 Facade 类别取用静态功能了。
上面说了自制 Facade 的方法,不过一个个建也是很麻烦,於是贴心的 Laravel 设想了能够动态建立 Facade 的方法。
先看一般的依赖注入方法
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
再来看看 Facade 版
<?php
namespace App\Models;
use Facades\App\Contracts\Publisher; //原本的类别命名域前面加上了 Facades
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
可以看到引用的 Publisher 其命名域前面被加上了 Facades ,这样宣告的话 Laravel 就会以 Facades\ 之後的字串作为 name 来产生 Facade 类别。
因为 Facade 不用像依赖注入一样额外用 __construct 方法来定义,当一个类别依赖的 Facade 越来越多时,是不容易发现的。
反过来说用依赖注入的话 __construct 就会越依赖越大包,看到 __construct 过大就要知道该拆分类别的功能了,而用 Facade 就比较不容易发现这种问题。
<<: Day23 - 在 XState 中的平行式状态 Parallel States
>>: Day23:终於要进去新手村了-Javascript-物件建立
TableView:Storyboard + Table View + Table View Cel...
哈罗,今天是一周的第一天 我们来试试 Forensics 吧 放心,一定从简单的题开始 又是拚手速的...
生活中的每个细节,有些人习惯使用图像的方式做纪录;有些人更喜欢使用文字去做纪录。 那在资讯领域中呢?...
本文同步刊登於个人技术部落格,有兴趣关注更多 Kubernetes、DevOps 相关资源的读者,请...
昨天了解了 Composite 是什麽後,一如我们本来的安排,今天要来介绍的是 Composites...