门面为应用服务容器中的绑定类提供了一个「静态」接口。Laravel 内置了很多门面,你可能在不知道的情况下正在使用它们。Laravel 的门面作为服务容器中底层类的「静态代理」,相比于传统静态方法,在维护时能够提供更加易于测试、更加灵活、简明优雅的语法。
Laravel 的所有门面都定义在 Illuminate\Support\Facades 命名空间下,所以我们可以轻松访问到门面。
在config/app.php文件中找到别名这一栏可以看到很多门面:
所有的 Laravel Facades 都定义在 vendor/laravel/framework/src/Illuminate/Support/Facades 目录下
门面有诸多优点,其提供了简单、易记的语法,让我们无需记住长长的类名即可使用 Laravel 提供的功能特性,此外,由于他们对 PHP 动态方法的独到用法,使得它们很容易测试。
但是,使用门面也有需要注意的地方,一个最主要的危险就是类范围蠕变。由于门面如此好用并且不需要注入,在单个类中使用过多门面,会让类很容易变得越来越大。使用依赖注入则会让此类问题缓解,因为一个巨大的构造函数会让我们很容易判断出类在变大。因此,使用门面的时候要尤其注意类的大小,以便控制其有限职责。
注:构建与 Laravel 交互的第三方扩展包时,最好注入 Laravel契约(这个后面单独讲)而不是使用门面,因为扩展包在 Laravel 之外构建,你将不能访问 Laravel 的门面测试辅助函数。
以下是在Laravel中创建Facade的步骤。
步骤1 - 创建自定义PHP类文件。
步骤2 - 创建服务提供者并将该类绑定到服务提供商。
步骤3 - 将ServiceProvider注册到Config app.php作为提供者。
步骤4 - 创建类,这个类扩展到luminate Support Facades Facade。
步骤5 - 将点4配置为Config app.php作为别名。
步骤1 - 创建文件及目录(\App\Units\Test\Test)
步骤2 - 通过执行以下命令创建一个名为TestProvider的服务提供者。
php artisan make:provider TestProvider
成功执行后,您将收到以下输出 -
以及在app/Providers目录下生成TestProvider.php服务文件
步骤3 - 注册服务提供者 config/app.php中的 providers。
步骤4 - 创建一个门面类 在 App\Facade下
步骤5 - 在config/app.php中的aliases
步骤6 - 开始调用
在 Laravel 应用中,门面就是一个为容器中对象提供访问方式的类。该机制原理由 Facade 类实现。Laravel 自带的门面,以及我们创建的自定义门面,都会继承自 Illuminate\Support\Facades\Facade 基类。
门面类只需要实现一个方法: getFacadeAccessor 。正是 getFacadeAccessor 方法定义了从容器中解析什么,然后 Facade 基类使用魔术方法 __callStatic() 从你的门面中调用解析对象。
\App\Facade\Test::test(); 实际上是调用了父类 Illuminate\Support\Facades\Facade 中的 __callStatic方法;
//就是这个魔术方法
public static function __callStatic($method, $args)
{
//最后是根据子类的 getFacadeAccessor 方法中返回的字符串,从服务容器中解析出对应的服务组件类
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
getFacadeRoot()方法源码
public static function getFacadeRoot()
{
//根据getFacadeAccessor方法返回的字符串,解析出服务容器中对应的类
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
protected static function resolveFacadeInstance($name)
{
//如果getFacadeAccessor方法中的是服务组件类,直接返回,不用解析了
if (is_object($name)) {
return $name;
}
//判断是否解析过,要是解析过,就直接返回,否则从下面的服务容器中取
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
//从服务容器中取,
//static::$app就是vendor/laravel/framework/src/Illuminate/Foundation/Application.php
//为什么可以像从数组中取东西一样?因为Application类继承的vendor/laravel/framework/src/Illuminate/Container/Container.php 容器基类,实现了ArrayAccess接口,这个接口可以去百度查
if (static::$app) {
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
这个$app在setFacadeApplication被赋值
public static function setFacadeApplication($app)
{
static::$app = $app;
}
setFacadeApplication方法在 vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php被调用
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();
//在这里被调用
Facade::setFacadeApplication($app);
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []),
$app->make(PackageManifest::class)->aliases()
))->register();
}
bootstrap方法又在 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 中调用
//为HTTP请求引导应用程序。
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
/**
* $this->bootstrappers() 返回的是这个数组,此数组就在此类中
*
* protected $bootstrappers = [
* \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, //env文件的加载引导类
* \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, //配置文件的加载引导类
* \Illuminate\Foundation\Bootstrap\HandleExceptions::class, //异常处理类引导类
* \Illuminate\Foundation\Bootstrap\RegisterFacades::class, //注册门面引导类
* \Illuminate\Foundation\Bootstrap\RegisterProviders::class, //注册服务提供者引导类,路由服务就是在这里加载的
* \Illuminate\Foundation\Bootstrap\BootProviders::class, //启动服务提供者引导类
* ];
*
*/
//最终完成配置文件加载,环境配置,门面,服务提供者的注册及启动
$this->app->bootstrapWith($this->bootstrappers());
}
}
这个Kernel.php,被 app/Http/Kernel.php 类继承
最终app/Http/Kernel.php类,在bootstrap/app.php中注册,在public/index.php中被调用
======================================================================
在 config/app.php 文件的 aliases 数组中,有门面类的别名配置
在 vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php 文件中解析到容器的
评论