老司机带你深入理解 Laravel 之 Facade

前言

老司机带你深入理解Laravel之Facade
时间真的过的很快啊,今天都2019年12月2号了,准确的说,写这篇博客的时间是晚上21点40分,刚从公司加班回来,洗完澡就坐下来写这篇文章了,不知不觉除这篇博客外,我已经写了11篇了,要讲的东西实在是太多了,来日方长。今天要讲的东西,我感觉是兄弟们一直没搞懂的,那就是Laravel中非常重要的Facade,我相信凡是使用Laravel的兄弟,肯定使用过Facade了,很明显,他就是我们今天的主角了。

阅读建议

在阅读这篇文章之前,我希望您对Laravel的容器具有一定的使用和了解,如果不熟悉的话,请阅读Laravel容器,这方面的知识对于理解我今天要讲的东西非常有必要,再次提醒一下各位,这篇博文容量很大,仔细体会消化,希望能有所收获。

注册Facade

如果你使用过第三方的composer包,它会提醒你,把它的ServiceProvider和Facade写入到config/app.php文件中,如下:

老司机带你深入理解Laravel之Facade
当然了,注册Facade和ServiceProvider不止这么一种方式,你感兴趣的话,可以看看官方的文档有很清楚的描述,开发第三方Laravel包

这些东西都没啥可说的,如果我就说这些,也许兄弟们会说,我都知道,你还说个啥?也确实,如果就说这个,我也不好意思了,可是,后面的内容就不那么容易了。

为了给兄弟们讲Facade,我还是大致的给大家讲解一下Laravel的引导过程(只关注与Facade有关的部分),大家都知道Laravel的入口文件为public/index.php,下面的这行代码很关键。

老司机带你深入理解Laravel之Facade

那么这个文件干啥了呢?现在我们只关心下面这两行:

老司机带你深入理解Laravel之Facade

上面创建了一个Application类的对象,这个类代表了我们当前的应用程序,也是整个Laravel最为核心的类,注意了Application类继承自Container类(这个类就是Laravel容器的核心),所以我们可以在Application类的对象上操作容器的方法,这就是为啥我在这篇博文的开头,提醒大家需要一定的容器的知识。

老司机带你深入理解Laravel之Facade

上面的这段代码,向容器中添加了一个单例,所以当我们创建Illuminate\Contracts\Http\Kernel::class对象的时候,实际上是创建的App\Http\Kernel::class类的对象,这一点极其重要,希望大家一定要记住,这在后面会使用到。

回到index.php文件中,继续看下面这行代码:

老司机带你深入理解Laravel之Facade

Illuminate\Contracts\Http\Kernel::class指向的是谁啊?不就是我们上面说的App\Http\Kernel::class,这个文件的位置如下:

老司机带你深入理解Laravel之Facade

这个Kernel就是我们的目标了,我们打开看一下这个文件:

老司机带你深入理解Laravel之Facade

这个Kernel类继承自了Illuminate\Foundation\Http\Kernel类,兄弟们记住这一点,后面会使用到,下面我们回到index.php文件中,laravel调用:

老司机带你深入理解Laravel之Facade

$kernel的handle方法被调用,通过上面的分析,我们知道这个handle方法属于Illuminate\Foundation\Http\Kernel类的,对于我们分析Facade来说,只有一行代码是关键的,就是下面:

老司机带你深入理解Laravel之Facade

我们看一下这个方法sendRequestThroughRouter,我们不必关心它的参数$request是啥,它对我们分析当前的目标一点儿关系都没有,这个函数中也只有一行是我们关心的,如下:

老司机带你深入理解Laravel之Facade

bootstrap方法很简单,如下:

老司机带你深入理解Laravel之Facade

这里首先调用bootstrappers方法,这个方法的返回值是一个数组,它的内容如下:

老司机带你深入理解Laravel之Facade

上面这个我们只需要关心RegisterFacades类,回到bootstrap方法中,它调用app->bootstrapWith方法,这里的app是谁呢?他就是Application类的对象,这个对象在整个Laravel的生命周期中是唯一的,因为他是单例的,既然知道这个了,我们看Application对象的bootstrapWith方法。

老司机带你深入理解Laravel之Facade

还记得我们的Application类继承自Container类么?所以它可以使用make方法,上面说了,我们当前只关心RegisterFacades这个bootstrapper,所以,我们进入到这个类的bootstrap方法:

老司机带你深入理解Laravel之Facade

老司机带你深入理解Laravel之Facade

因为这篇博文主要给大家讲解Facade的整个实现的,所以我会忽略掉一些细节,关于这些细节,以后我会给大家讲解,但是在这里我会先说明他们的作用。上面这张图,我已经标注了序号,序号1这行代码返回了config/app.php文件的aliases字段值,我们自己的Facade就注册在了这个地方,在这篇博文的开头,我已经给大家说过了。序号2的作用是干啥呢?还记得我开始说的么?当我们开发laravel包的时候,可以让laravel自动加载我们的ServiceProvider和Facade,我们所要做的就是在我们的composer.json中,加入下面的这段:

老司机带你深入理解Laravel之Facade

上面这个截图,大家应该可以看的很清楚,我就不再详述了,PackageManifest类的作用就是负责自动加载我们在composer.json文件中的ServiceProvider和Facade。这么说大家应该明白了吧。

回到RegisterFacades类的bootstrap方法中,array_merge方法合并1和2的Facade,并把它传递给AliasLoader的getInstance静态方法。
这个getInstance方法返回了一个AliasLoader类的对象,下面我们看它的register方法:

老司机带你深入理解Laravel之Facade

这个方法很简单,直接调用方法prependToLoaderStack,如下:

老司机带你深入理解Laravel之Facade

spl_autoload_register这个方法可能很多人不知道,因为现在都是使用成熟的框架了,简单来说,它的作用就是负责加载我们的类文件的,你有没有好奇过,php是如何找到并加载我们的php类文件的,这当中的功臣就是spl_autoload_register了,如果你不知道它,请参考php的官方文档spl_autoload_register,它的第一个参数是一个回调方法,作用就是负责加载类文件的,我们的程序中可以多次调用spl_autoload_register方法,也就是说可以注册多个加载函数,关于spl_autoload_register的介绍就这么多了,回到当前的代码中,laravel注册的自动加载函数为AliasLoader对象的load方法,我们看哈:

老司机带你深入理解Laravel之Facade

这个方法我们只需要看标注出来的部分,aliases属性存储着之前解析的所有的Facade,部分截图如下:

老司机带你深入理解Laravel之Facade

之所以我会把部分标出来,是因为我后面会用到,上面的代码中使用到了class_alias方法,这个方法是给一个类取个别名,比如说对于Illuminate\Support\Facades\Route::class这个类,它的别名为Route,为了证实这一点,我们来测试一下,在我们的路由文件中,我们经常这么做:

老司机带你深入理解Laravel之Facade

注意了我们并没有引入Route这么一个东西,但是为啥php没报错呢?这就是我们上面给Illuminate\Support\Facades\Route::class取了Route这个别名的原因,你可以把class_alias这段代码删除掉,肯定会报错的:

老司机带你深入理解Laravel之Facade

你再刷新一下页面,页面报错了,哈哈,就是这么刺激:

老司机带你深入理解Laravel之Facade

实例分析

上面分析了Laravel的整个Facade注册的过程,是不是有点儿懵?不要慌,后面还有,任重而道远啊。

老司机带你深入理解Laravel之Facade

在Facade注册一节中,我们标注了Route这个Facade,所以这一节,就以它为例来进行讲解,Route类如下:

老司机带你深入理解Laravel之Facade

所有的Facade都继承自Illuminate\Support\Facades\Facade类,并且都必须实现getFacadeAccessor这个方法,不然会抛出异常的,我们看Route的getFacadeAccessor方法如下,他返回字符串”router”,至于它的作用,我们后面会讲到:

老司机带你深入理解Laravel之Facade

为了给大家讲解后面的问题,我写了一个很简单的例子,如下:

老司机带你深入理解Laravel之Facade

在路由文件中,我用Route注册了一个路由,这里调用了get方法,但是我们打开Illuminate\Support\Facades\Facade\Route类,这个方法是不存在的,它的父类也没有,然而我们注意到了Illuminate\Support\Facades\Facade类实现了__callStatic方法,如下:

老司机带你深入理解Laravel之Facade

callStatic简单来说就是如果你调用某个类的静态方法,但是这个静态方法不存在的话,就会调用这个类的callStatic方法,如果你还是不清楚,可以网上查阅相关资料,这里不再阐述。好了,废话不多说了,我们回到Facade的__callStatic方法中,这个方法首先调用 getFacadeRoot 方法,如下:

老司机带你深入理解Laravel之Facade

看到没,这里就是我上面说的Facade为啥必须实现getFacadeAccessor方法,在当前的实例中,它返回的是 “router“.。
resolveFacadeInstance方法是啥呢?很简单,但是我还是准备贴出来:

老司机带你深入理解Laravel之Facade

因为Illuminate\Support\Facades\Facade类是所有的Facade的父类,所以任何的Facade调用静态方法,都会进入到这个方法中,静态属性$resolvedInstance存储着当前所有被解析的Facade对应的实例对象,你要记住任何的Facade后面都有一个对象的,而且这个对象在整个Laravel程序的生命周期中是唯一的,只有这么一个实例,上面标注的1首先检查之前是否已经解析过这么一个对象,如果解析过了,直接返回就是了,这是单例的常见手法。如果之前没有解析过的话,那么代码就会走到2整个地方了,我们知道$app就是全局唯一的Application类对象,它继承了Illuminate\Container\Container,而Illuminate\Container\Container又实现了接口ArrayAccess,对于ArrayAccess接口不熟悉的同学,可以查阅相关的资料,简单来说,如果你的类实现了ArrayAccess接口,那么你就可以像获取数组元素一样,获取对象的内容而不会出错。
老司机带你深入理解Laravel之Facade

这个接口有几个方法必须实现,offsetGet方法是其中之一,当你采用数组的写法作用在对象上时,offsetGet会被调用,我们看Illuminate\Container\Container类的offsetGet方法,如下

老司机带你深入理解Laravel之Facade

在当前情况下我们获取的是$app[‘router’],所以这里的参数$key就是”router”,关于容器的make方法,请大家参考文档,非常简单:

老司机带你深入理解Laravel之Facade

make是容器暴露给我们获取容器注册内容的少有几个方法,好了,现在我们的疑问是我们什么时候注册了一个”router”这么一个东西,大家如果使用的是phpstorm的话,可以这么做:

老司机带你深入理解Laravel之Facade

搜索内容为”router”,如下:

老司机带你深入理解Laravel之Facade

通过搜索我们知道,在Illuminate\Routing\RoutingServiceProvider这个类中,注册了router的单例,你可能会问,这段代码是啥时候调用的,也就是registerRouter方法是啥时候被调用的,当前的RoutingServiceProvider中的register方法如下:

老司机带你深入理解Laravel之Facade

那么register方法是怎么被调用的呢?不知道没关系,我细细道来,在之前的laravel框架引导过程中,创建Application类实例的时候,它的构造函数如下:

老司机带你深入理解Laravel之Facade
registerBaseServiceProviders方法如下:

老司机带你深入理解Laravel之Facade

看见没,这个地方出现了RoutingServiceProvider类对象,我们再进入到Application类的register方法中,如下:

老司机带你深入理解Laravel之Facade

啊哈,register方法被调用了,这个时候名为router的单例就被注册了,分析了这些,我们回到RoutingServiceProvider类的registerRouter方法中。

老司机带你深入理解Laravel之Facade

这里直接返回了Illuminate\Routing\Router类的实例,这个实际上就是Laravel全局唯一的路由器对象,路由就是靠它来实现的,分析了这些,我们再一次回到Illuminate\Support\Facades\Facade类的resolveFacadeInstance方法中。

老司机带你深入理解Laravel之Facade

这里把解析的实例存储到$resolvedInstance属性中,这样下次就不需要解析了,resolveFacadeInstance方法调用完毕之后,返回到Facade的getFacadeRoot方法中。

老司机带你深入理解Laravel之Facade

上面也是直接返回刚才获取到的对象Router实例对象,getFacadeRoot方法调用完毕之后,继续返回到__callStatic方法中。

老司机带你深入理解Laravel之Facade

红色的代码就是翻译一下就是:

 $router->get('/',function () {
    echo "Hello World";
})

令人欣喜的是奇迹出现了,Illuminate\Routing\Router类有如下的代码:

老司机带你深入理解Laravel之Facade

总结

Laravel的源代码错综复杂,理解起来不是那么容易,上面给大家标注出了主要的脉络,希望大家仔细体会和理解。不知不觉写完这篇博客已经凌晨40分了,整整三个小时,这效率还可以的哈。如果大家有什么不懂的可以联系我,我有一个qq群,感兴趣的可以加一下,如下:

本作品采用《CC 协议》,转载必须注明作者和本文链接
微信:okayGoHome
本帖由系统于 4年前 自动加精
Dennis_Ritchie
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 49

社区新星正在诞生

4年前 评论

社区新星正在诞生

4年前 评论
Tomo11111

感谢大神的辛苦输出。

4年前 评论

hello,有一点没明白:在 Illuminate\Foundation\Http\Kernel 类中,在给属性 $app 赋值的过程中。

这不是一个抽象类吗? 为什么可以进行实例化呢?
file

Laravel

4年前 评论
hiword 4年前
zpers 4年前
Senkorl 4年前
zpers 4年前
Senkorl 4年前
zpers 4年前
zpers 4年前
zpers 4年前
Senkorl 4年前
保安 2年前

我是谁??我在哪儿??? :sweat_smile:

4年前 评论
Dennis_Ritchie (楼主) 4年前

兄台如若能将此做成流程图就更好了

4年前 评论

老司机可以出书了 :smiley:

4年前 评论

真的讲的不错

4年前 评论

老司机 :+1: :+1: :+1:;
您什么时候可以讲讲Eloquent ORM

4年前 评论

循循善诱 跟着读都能读懂 老司机发车就是稳

4年前 评论

厉害,讲解的非常简单易懂

4年前 评论

必须支持,干货,目前水平还看不太懂,但是先收藏.

4年前 评论

干货,先收藏,以后再慢慢消化。

4年前 评论

所谓Facade就是门面模式,即将多个对象组合起来完成一个功能,对外提供统一接口。以laravel facade为例,使用魔术方法__callStatic+约束接口getFacadeAccessor来统一,另外的使用composer.json的extra信息指定接入laravel,以provider信息作为服务提供者注册信息,以alias作为Facade注册信息,完成一个大统一以及对外的良好接入。

4年前 评论

第二遍,收益很大,非常感谢! :+1: :+1:

4年前 评论
Dennis_Ritchie (楼主) 4年前

感谢作者,讲解的非常简单易懂。
指出一处笔误:

好了,废话不多说了,我们回到 Facade 的__callStatic 方法中,这个方法首先调用 getFacadeRoute 方法,如下:

这里应该是getFacadeRoot吧。

4年前 评论

看的有点懵逼,打算多看几次。

4年前 评论

大概能看懂50%,剩下的50%慢慢消化,感谢

3年前 评论
taowendi

厉害 厉害

3年前 评论

厉害厉害,写的很好,思路清晰

3年前 评论

2020年12月2日再来打卡一次

3年前 评论
Dennis_Ritchie (楼主) 3年前

能不能也说下扩展包中的Facade是在哪里注册的。。。找了半天没找到

3年前 评论
zpers 2年前
kakaxi (作者) 2年前
zpers 2年前

@zpers 比如这个扩展purifier

下面是它的Facade类

<?php

namespace Mews\Purifier\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * @method static mixed clean($dirty, $config = null, \Closure $postCreateConfigHook = null)
 * @see \Mews\Purifier
 */
class Purifier extends Facade
{

    protected static function getFacadeAccessor()
    {
        return 'purifier';
    }
}

注册'purifier'的provider是下面这个

PurifierServiceProvider.php

    public function register()
    {
        $this->mergeConfigFrom($this->getConfigSource(), 'purifier');

        $this->app->singleton('purifier', function (Container $app) {
            return new Purifier($app['files'], $app['config']);
        });

        $this->app->alias('purifier', Purifier::class);
    }

我不明白的是这个PurifierServiceProvider是在哪里被加载的

2年前 评论
zpers 2年前

厉害,写的真的很好的文章,谢谢博主分享。

2年前 评论
gutao123

老司机,就是稳!

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!