Laravel底层原理(一) —— 门面(Facades)

Royal
2021-12-25 / 0 评论 / 843 阅读 / 正在检测是否收录...

什么是门面

门面为应用服务容器中的绑定类提供了一个「静态」接口。Laravel 内置了很多门面,你可能在不知道的情况下正在使用它们。Laravel 的门面作为服务容器中底层类的「静态代理」,相比于传统静态方法,在维护时能够提供更加易于测试、更加灵活、简明优雅的语法。
Laravel 的所有门面都定义在 Illuminate\Support\Facades 命名空间下,所以我们可以轻松访问到门面。
在config/app.php文件中找到别名这一栏可以看到很多门面:
kxlywxdc.png
所有的 Laravel Facades 都定义在 vendor/laravel/framework/src/Illuminate/Support/Facades 目录下
kxlyzuas.png

何时使用门面

门面有诸多优点,其提供了简单、易记的语法,让我们无需记住长长的类名即可使用 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)
kxppccsf.png

步骤2 - 通过执行以下命令创建一个名为TestProvider的服务提供者。

php artisan make:provider TestProvider

成功执行后,您将收到以下输出 -
kxppesa7.png

以及在app/Providers目录下生成TestProvider.php服务文件
kxppklsn.png

步骤3 - 注册服务提供者 config/app.php中的 providers。
kxppgm6l.png
步骤4 - 创建一个门面类 在 App\Facade下
kxpphct8.png
步骤5 - 在config/app.php中的aliases
kxppi0ha.png
步骤6 - 开始调用
kxppiqmo.png
kxppiz4d.png

源码分析

在 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 数组中,有门面类的别名配置
kxpputvr.png
在 vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php 文件中解析到容器的
kxppv7r3.png

0

评论

博主关闭了当前页面的评论