资讯专栏INFORMATION COLUMN

深入浅出 Laravel 的 Facade 外观系统

KavenFan / 2351人阅读

摘要:外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。将使用者与子系统从直接耦合,转变成由外观类提供统一的接口给使用者使用,以降低客户端与子系统之间的耦合度。接下来将深入分析外观服务的加载过程。引导程序将在处理请求是完成引导启动。

本文首发于 深入浅出 Laravel 的 Facade 外观系统,转载请注明出处。

今天我们将学习 Laravel 核心架构中的另一个主题「Facade(外观)」。

本文将从以下几个方面出发,全面讲解 Laravel 中 Facade 的运行原理,为了便于理解后续中所有 Facade 译作「外观」:

简单介绍「外观」设计模式;

Laravel「外观」的加载原理;

Laravel「外观」基本使用。

什么是「外观」设计模式 外观模式定义

为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,
为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。 - 设计模式 Java 版

核心 就是在 客户端(使用者)子系统(接口或服务) 之间引入一个「外观」角色。

将使用者与子系统从直接耦合,转变成由「外观」类提供统一的接口给使用者使用,以降低客户端与子系统之间的耦合度。

结构示意图:

关于「外观模式」可以阅读 设计模式 Java 版 - 外观模式

Laravel 外观组件

Laravel 中的「外观」组件实际上是服务容器中底层类的「静态代理」,它将 Laravel 内核中定义的「Contracts(在 Laravel 中又
称为服务、契约或者通常我们所说的接口)」,以静态可调用的方式封装到各个「外观」服务中供我们使用。

外观加载原理

在讲解如何使用外观组件之前,我们依旧先去深入分析「外观」组件是如何被 Laravel 加载到项目中的。这一步是
用好「外观」组件的前提。

外观组件配置

所有内置的外观组件的配置数据,同 Laravel 其它服务一样被定义在 config/app.php 文件中。让我们来浏览一下 aliases 节点的配置数据吧:

    ...
    "aliases" => [

        "App" => IlluminateSupportFacadesApp::class,
        "Artisan" => IlluminateSupportFacadesArtisan::class,
        ...
    ],
    ...

外观配置定义格式遵循 「别名」:「外观类」 的数据格式。当一个 HTTP 请求被接收时,将在处理请求阶段将这些「外观」组件加载到服务中。

接下来将深入分析外观服务的加载过程。

加载外观服务

「外观」服务的加载工作由定义在 IlluminateFoundationHttpKernel 内核中的 IlluminateFoundationBootstrapRegisterFacades::class 启动程序完成。

引导启动外观服务

如果你已经阅读我的另一篇文章 深入剖析 Laravel 服务提供者实现原理,你应该对引导程序不会太陌生。

引导程序将在处理 HTTP 请求是完成引导启动 bootstrap()。所以这里我们需要深入到 RegisterFacades 类的内部去了解更多细节上的处理。

make("config")->get("app.aliases", []),
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }
}

加载外观服务有 AliasLoader 组件完成:

首先,会从配置文件 config/app.php 中读取所有的「外观」服务配置 aliases

再从清单文件中读取别名服务 $app->make(PackageManifest::class)->aliases()

将两个配置数组合并后注入到 AliasLoader 完成 注册(register)

注册外观服务

最后我们来瞧瞧 AliasLoader 加载器是如何将所有的「外观」服务加载到系统中的。

getAliases(), $aliases);

        static::$instance->setAliases($aliases);

        return static::$instance;
    }

    /**
     * Set the registered aliases. 设置需注册别名数据。
     */
    public function setAliases(array $aliases)
    {
        $this->aliases = $aliases;
    }

    /**
     * Register the loader on the auto-loader stack. 将加载器注册到自动加载中。
     */
    public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }

    /**
     * Prepend the load method to the auto-loader stack. 设置自动加载方法。
     */
    protected function prependToLoaderStack()
    {
        // 将 AliasLoader 的 load 方法作为 __autoload 的实现
        spl_autoload_register([$this, "load"], true, true);
    }

    /**
     * Load a class alias if it is registered.从注册过的服务中加载这个「外观」服务。
     */
    public function load($alias)
    {
        if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
            $this->loadFacade($alias);

            return true;
        }

        if (isset($this->aliases[$alias])) {
            return class_alias($this->aliases[$alias], $alias);
        }
    }
}

注意 这里是知识点,在 AliasLoader->register() 完成「外服服务注册」涉及 PHP 两个知识的应用:

PHP 内置魔术方法 __autoload 的使用;

PHP 如何给类创建别名。

➤ 1. 外观服务的动态引入

我们知道 __autoload 魔术方法的作用是尝试加载未经定义的类,这样当我们使用一个未经引入的类时,则会自动的给我们引入这个类。

更优的解决方案是通过 spl_autoload_register 函数,将自定义的类加载程序作为 __autoload 的实现,以替代默认 __autoload() 模式函数或方法的行为。

所有 prependToLoaderStack() 方法:

    /**
     * Prepend the load method to the auto-loader stack. 设置自动加载方法。
     */
    protected function prependToLoaderStack()
    {
        // 将 AliasLoader 的 load 方法作为 __autoload 的实现
        spl_autoload_register([$this, "load"], true, true);
    }

就是去完成这样的作用,将 AliasLoader->load() 方法作为自动加载程序的实现,在使用「外观」服务时动态引入这个类。

➤ 2. 支持外观服务别名

我们已经了解到当「外观」服务被使用时,由 AliasLoader->load() 去自动加载这个类。

与此同时,load 方法通过 class_alias($original, $alias) 函数完成别名注册。

这样,当我们使用 App 类时实际上就是在使用 IlluminateSupportFacadesApp 类。

很完美么,我们的「狗蛋」终于与「世界上最好的语言」画上了等号。你就是我,我就是你。

到这里其实已经完成了「外观」服务工作原理分析工作的 70%

探秘 Facade

最后我们将揭开 Facade 的神秘面纱,研究一下 Laravel 是如何实现 Facade 设计模式的。

我们拿 IlluminateSupportFacadesApp 外观服务开刀,去解开类似 App::make() 静态方法使用的奥秘。

深入 FacadesApp


我们看到它的实现内部仅仅定义了一个 getFacadeAccessor 方法,该方法的功能是获取已注册组件的名称 app;除此之外,一无所有。

看来在这里我们得不到什么有用的信息了。继续调查基类 IlluminateSupportFacadesFacade。如果你有去通便浏览全部的源码。

$method(...$args);
    }
}

你会发现这个 Facade 基类并没有定义类似 make 的方法,那么这里能够静态调用 App::make() 看来是需要从 __callStatic 着手才行。

不过在这里我们需要再次厘清一个事实:「外观」模式的功能是什么?

将使用者与子系统从直接耦合,转变成由「外观」类提供统一的接口给使用者使用,以降低客户端与子系统之间的耦合度。

这句话的意思就是我「外观」啥也不提供,就是一层对服务(或者说组件或接口)的封装,然后以统一的方式提供给你们外部调用。

好了现在我们来看看 Facade::__callStatic 是如何获取实际的服务并调用响应的方法的吧。

首先,通过 getFacadeRoot 静态方法获取实际服务的实例对象;

然后,调用实例对象的相关方法并返回处理结果。


getFacadeRoot 解析对象的功能中我们可以看到:它会调用实现「外观」的 getFacadeAccessor 方法获取到组件(服务或者说接口)的名称;然后从 Laravel 服务容器 static::$app[$name](app 是在 RegisterFacades 中注册到「外观」中) 中解析出相关服务。

到这里,我们就将「外观」服务的基本工作原理给分析透彻了。

另外有关「外观」组件的一些细枝末节,如:

在文档「Facades Vs. 辅助函数」一节提到的测试验证是如何实现的 Cache::shouldReceive("get")

什么是「实时 Facades」。

还是需要你自行深入到 Facade 基类去一探究竟。

扫盲 ArrayAccess 接口

另外补充一个知识点就是关于 static::$app[$name] 这一句代码。你不经要问,这有啥好补充的呢,不就是一个简单获取数据么。

获取数据不假,简单也不假。

不过你仔细看一下,你会发现 static::$app 静态成员变量难道不是一个 IlluminateContractsFoundationApplication 实现实例么,怎么可以从对象中以数组的方式获取值呢?

这是因为我们的服务容器 IlluminateContainerContainer 实现了 ArrayAccess 接口。

该接口的功能是提供像访问数组一样访问对象的能力的接口,这样就可以像数组一样访问对象访问成员。

/**
 *@link https://github.com/laravel/framework/blob/5.6/src/Illuminate/Container/Container.php
 */
class Container implements ArrayAccess, ContainerContract
{
    /**
     * Get the value at a given offset. 获取一个偏移位置的值,实际上从容器中解析出服务。
     */
    public function offsetGet($key)
    {
        return $this->make($key);
    }
}
Laravel「外观」基本使用

外观服务的一个典型使用场景是在定义路由时使用 Route::get("/", ...)。这样一看似乎「Laravel 别名服务」也就不这么神秘了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28846.html

相关文章

  • PHP中facade pattern(外观模式)

    摘要:本文来自原文链接欢迎作客我们的学习群该篇属于底层核心技术实战揭秘这一课程底层核心概念解析这一章的扩展阅读。考虑到学员们的基础差异,为了避免视频当中过于详细而连篇累牍,故将一些底层实现相关的知识点以文章形式呈现,供大家预习和随时查阅。 本文来自pilishen.com----原文链接; 欢迎作客我们的php&Laravel学习群:109256050该篇属于《Laravel底层核心技术实战...

    jaysun 评论0 收藏0
  • Laravel核心解读 -- 外观模式

    摘要:外观模式的目的在于降低系统的复杂程度。在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了开闭原则。 外观模式 外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模...

    zoomdong 评论0 收藏0
  • Laravel 创建自己 Facade

    摘要:使用现在,在任何一个控制器,或者路由的回调函数中,使用你会发现,已经可以好好工作了,参考文章设计模式九外观模式结构型服务容器实例教程深入理解控制反转和依赖注入服务提供者实例教程创建测试实例 我的博客原文: http://www.qinblog.net/Articl... 前言 laravel 提供了一个灵活的模式,那就是 facade 。框架内部的 DB、Auth、File 等功能也...

    K_B_Z 评论0 收藏0
  • 设计模式系列·Facade模式之MVC烦恼

    摘要:没有任何意外,王小二的公司用来开发公司的主打产品。臃肿的着手开干吧小二打开熟悉的,找到提交订单模块的。要不再去请教下哥的烦恼小二找到哥,详细的描述了他的问题。 流行的MVC架构模式 如今的Web开发,各种框架风起云涌,势如破竹。 从国民第一的ThinkPhp到称霸全球的Laravel,这些框架有一个共同特征,都采用了MVC的架构模式。 showImg(https://segmentfa...

    zhichangterry 评论0 收藏0
  • Laravel核心概念

    摘要:可以为服务提供者的方法设置类型提示。方法将在所有其他服务提供者均已注册之后调用。所有服务提供者都在配置文件中注册。可以选择推迟服务提供者的注册,直到真正需要注册绑定时,这样可以提供应用程序的性能。 本文最早发布于 Rootrl的Blog 导言 Laravel是一款先进的现代化框架,里面有一些概念非常重要。在上手Laravel之前,我认为先弄懂这些概念是很有必要的。你甚至需要重温下PHP...

    ddongjian0000 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<