深入理解 Laravel 之 Facade

blank

深入理解 Laravel 之 Facade

阅读建议

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

注册 Facade

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

blankblank

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

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

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

blankblank

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

blankblank

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

blankblank

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

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

blankblank

IlluminateContractsHttpKernel::class 指向的是谁啊?不就是我们上面说的 AppHttpKernel::class,这个文件的位置如下:

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

bootstrap 方法很简单,如下:

blankblank

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

blankblank

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

blankblank

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

blankblank
blankblank

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

blankblank

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

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

实例分析

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

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

registerBaseServiceProviders 方法如下:

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

blankblank

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

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

令人欣喜的是奇迹出现了,IlluminateRoutingRouter 类有如下的代码:

blankblank

总结

Laravel 的源代码错综复杂,理解起来不是那么容易,上面给大家标注出了主要的脉络,希望大家仔细体会和理解。


更多学习内容可以访问

以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的PHP技术交流群953224940

What do you think?

Written by marketer

blank

用了这么久的Laravel框架,你分析过核心架构了没

blank

Laravel吐槽系列之(一)