当前位置: 首页 > 后端技术 > PHP

深入理解Laravel的Facade

时间:2023-03-29 21:55:06 PHP

阅读建议在阅读本文之前,希望你对Laravel的容器有一定的使用和了解。如果你不熟悉,请阅读Laravel的容器。我说的很有必要。再次提醒大家,本博文容量较大。请大家认真理解消化,希望有所收获。注册Facade如果你使用过第三方的composer包,它会提醒你将它的ServiceProvider和Facade写入config/app.php文件中,如下:当然,注册Facade和ServiceProvider的方式不止一种,如果你有兴趣,可以查看官方文档,对于开发第三方Laravel包有清晰的描述。这些事无话可说。如果我就这么说,可能兄弟们会说,我都知道了,你还扯什么?诚然,只说这些我会很尴尬,但背后的内容却没那么简单。为了给兄弟们讲讲Facade,我就Laravel的启动流程给大家做一个大概的讲解(只关注Facade相关的部分)。大家都知道Laravel的入口文件是public/index.php,下面这行代码很重要。那么这个文件是做什么的呢?现在我们只关心下面两行:上面创建了一个Application类的对象,它代表了我们当前的应用,也是整个Laravel的核心类。注意,Application类继承自Container类(该类是Laravel容器Core),所以我们可以在Application类的对象上操作container方法,这也是我提醒大家需要一定的容器知识的原因在这篇博文的开头。上面的代码在容器中添加了一个单例,所以当我们创建一个Illuminate\Contracts\Http\Kernel::class对象的时候,实际上是创建了一个App\Http\Kernel::class类的对象,这个非常重要,我希望大家一定要记住,后面会用到。回到index.php文件,继续看下面这行代码:Illuminate\Contracts\Http\Kernel::class指向了谁?不就是我们上面说的App\Http\Kernel::class吗,这个文件的位置如下:这个Kernel就是我们的目标,我们打开看看这个文件:这个Kernel类继承自Illuminate\Foundation\Http\Kernelclass,兄弟们记住这个,后面会用到,我们回到index.php文件,laravel调用:调用了$kernel的handle方法,通过上面的分析我们知道这个handle方法是属于Illuminate\Foundation\Http\Kernel类的,对于我们分析Facade来说,只有一行代码是关键,就是下面的:我们来看看sendRequestThroughRouter这个方法,我们不需要关心它的参数$request,跟我们分析当前目标None有点关系,这个函数里面只有一行我们关心,如下:bootstrap方法很简单,如下:这里先调用bootstrappers方法,该方法的返回值是一个数组,其内容为如下:我们只需要关心Class上面的RegisterFacades,回到bootstrap方法,它调用了app->bootstrapWith方法,这里的app是谁?他是Application类的对象。这个对象在整个Laravel生命周期中是唯一的,因为它是一个单例。现在我们知道了这一点,让我们看看Application对象的bootstrapWith方法。还记得我们的Application类继承自Container类吗?所以它可以使用make方法。上面说了,我们目前只关心bootstrapperRegisterFacades,所以我们进入这个类的bootstrap方法:因为这篇博文主要讲解Facade的整个实现,所以我会忽略一些Details,关于这些细节,我会解释到你稍后再说,这里我先解释一下它们的作用。在上图中,我已经标记了序列号。第1行代码返回config/app.php文件中别名字段的值。我们自己的Facade就是在这个地方注册的。在这篇博文的开头,我已经给大家说过了。数字2的作用是什么?还记得我的开头吗?我们在开发laravel包的时候,可以让laravel自动加载我们的ServiceProvider和Facade。我们要做的就是在我们的composer.json中加入如下一段:上面的截图,大家应该能看的清楚,我就不细说了,PackageManifest类负责自动加载我们的ServiceProvider和Facade在composer.json文件。所以大家应该明白。回到RegisterFacades类的bootstrap方法,array_merge方法将1和2的门面合并,传递给AliasLoader的getInstance静态方法。此getInstance方法返回AliasLoader类的一个对象。再来看看它的注册方法:这个方法很简单,直接调用方法prependToLoaderStack,如下:spl_autoload_register这个方法可能很多人不知道,因为现在用的是成熟的框架。简单的说,它的作用就是加载我们的类文件。你有没有想过php是如何找到并加载我们的php类文件的?贡献者是spl_autoload_register。不知道的请参考php的官方文档spl_autoload_register,它的第一个参数是一个回调方法,负责加载类文件。在我们的程序中,可以多次调用spl_autoload_register方法,也就是说可以注册多个加载函数。关于spl_autoload_register的介绍就这些了。回到现在的代码,laravel注册的自动加载功能是AliasLoader对象的load方法。看一下:我们只需要看这个方法中标记的部分。aliases属性存放的是之前解析过的所有Facade,部分截图如下:之所以会标注部分是因为后面会用到。上面代码中使用了class_alias方法。这个方法是给一个类起一个别名,比如对于Illuminate\Support\Facades\Route::class这个类,它的别名是Route,为了证实这一点,我们来测试一下,在我们的路由文件中,我们经常做this:注意我们没有引入Route这种东西,但是为什么php不报错呢?这就是为什么我们在上面给Illuminate\Support\Facades\Route::class取了Route的别名,你可以删掉class_alias代码,肯定会报错:你再刷新页面,页面报错,哈哈,太精彩了:实例分析以上分析了Laravel的整个Facade注册流程,是不是有点懵?不要惊慌,还有更多的事情要做,还有很长的路要走。在FacadeRegistration部分,我们标注了RouteFacade,所以在本节中,我们将以此为例进行说明。Route类如下:所有的Facades都继承自Illuminate\Support\Facades\Facade类,必须实现getFacadeAccessor方法,否则会抛出异常。下面看Route的getFacadeAccessor方法。它返回字符串“router”。至于它的作用,后面再说:为了给大家解释下面的问题,我写了一个很简单的例子如下:在路由文件中,我用Route注册了一个路由,调用了get方法这里,但是当我们打开Illuminate\Support\Facades\Facade\Route类时,这个方法是不存在的,它的父类也不存在,但是我们注意到Illuminate\Support\Facades\Facade类实现了__callStatic方法,如如下:callStatic简单来说就是如果你调用了一个类的静态方法,但是这个静态方法不存在,就会调用这个类的callStatic方法。如果还有不明白的,可以上网查相关资料,这里就不细说了。好了,废话不多说,我们回到Facade的__callStatic方法,这个方法首先调用了getFacadeRoot方法,如下:看到了吗,这里就是为什么我上面说的Facade必须实现getFacadeAccessor方法,在当前instance,它返回“路由器”。什么是resolveFacadeInstance方法?很简单,但是还是要贴出来:因为Illuminate\Support\Facades\Facade类是所有Facades的父类,任何调用静态方法的Facade都会进入这个方法,静态属性$resolvedInstance存储所有当前解析出来的Facade对应的实例对象,你要记住任何一个Facade后面都有一个对象,而且这个对象在整个Laravel程序的生命周期中是唯一的,只有这么一个实例,而标示的1上面首先检查了这样一个对象是否解析过,如果解析过,直接返回即可,这是单例常用的方法。如果之前没有解析过,那么代码会转到2的整个地方,我们知道$app是世界上唯一的Application类对象,它继承了Illuminate\Container\Container,而Illuminate\Container\Container实现了ArrayAccess接口,不熟悉ArrayAccess接口的同学可以参考相关资料。简单的说,如果你的类实现了ArrayAccess接口,那么你就可以像获取数组元素一样,无误的获取对象的内容。这个接口有几个必须实现的方法,offsetGet方法就是其中之一。当你使用数组写法作用于对象时,会调用offsetGet。我们看一下Illuminate\Container\Container类的offsetGet方法,如下在当前情况下得到的是$app\['router'\],所以这里的参数$key是“router”。容器的make方法请参考文档。很简单:make是容器暴露给我们获取容器注册内容的方法很少,好了,现在我们的问题是我们什么时候注册了一个像“router”这样的东西,如果你用的是phpstorm,你可以做this:搜索内容为“router”,如下:通过搜索我们知道,在Illuminate\Routing\RoutingServiceProvider类中,注册了一个router的实例。你可能会问,这段代码是什么时候调用的,也就是什么时候调用了registerRouter方法?当前RoutingServiceProvider中的register方法如下:那么register方法是怎么调用的呢?不知道,没关系,我详细解释一下,在之前的laravel框架启动过程中,在创建Application类实例时,其构造函数如下:registerBaseServiceProviders方法如下:看,有这里有个RoutingServiceProvider类对象,我们再进入到Application类的register方法中,如下:啊哈,调用了register方法,此时注册了名为router的单例。分析完这些,我们回到RoutingServiceProvider类的registerRouter方法。这里直接返回了Illuminate\Routing\Router类的实例。这实际上是Laravel全局唯一的路由器对象。路由就是由它实现的。分析完这些,我们再次回到Illuminate\Support\Facades\Facade类。resolveFacadeInstance方法。这里将解析后的实例保存在$resolvedInstance属性中,这样下次就不需要再解析了。resolveFacadeInstance方法调用后,返回到Facade的getFacadeRoot方法。上面也是直接返回获取到的对象Router实例对象。调用getFacadeRoot方法后,继续返回__callStatic方法。红色的代码是翻译过来的:$router->get('/',function(){echo"HelloWorld";})幸运的是,奇迹发生了,Illuminate\Routing\Router类有如下代码:总结,Laravel的源代码错综复杂,不是那么容易理解。上面标注了主要脉络,希望大家仔细体会和理解。更多学习内容,可访问【与各大厂商对比】优质PHP架构师教程目录。只要能读懂,就能保证你的薪水更上一层楼(持续更新中)。以上内容希望对大家有所帮助。很多PHPer总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道从哪里开始改进,我整理了一些这方面的资料,包括但不限于:分布式架构,高可扩展性、高性能、高并发、服务器性能调优、TP6、laravel、YII2、Redis、Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等知识点免费分享给你如果你需要高级干货。有需要的可以点击下方链接领取高级PHP月薪30k>>>架构师成长之路【免费获取视频和面试资料】