├── images ├── pths.png ├── laravel.png ├── test_00.png ├── test_01.png ├── test_02.png ├── test_03.png ├── test_04.png ├── test_05.png ├── test_06.png ├── test_07.png ├── test_08.png ├── test_09.png ├── test_10.png ├── test_11.png ├── test_12.png ├── test_13.png ├── test_14.png ├── test_15.png ├── test_16.png ├── test_17.png ├── test_18.png ├── test_19.png ├── test_20.png ├── test_21.png ├── error_01.png └── pttl_cover.png ├── .idea ├── vcs.xml ├── .gitignore ├── pierce_through_the_laravel.iml └── modules.xml ├── README.md ├── 03第三章:实现自动加载 └── README.md ├── 附录一:断点测试和中断测试 └── README.md ├── 09第九章:渲染页面 └── README.md ├── 02第二章:核心代码结构分析 └── README.md ├── 11结束语 └── README.md ├── 10第十章:终止程序 └── README.md ├── 附录二:Container之instance方法 └── README.md ├── 附录四:Container之bind方法 └── README.md ├── 01第一章:基础环境搭建 └── README.md ├── 附录十:Route之run方法 └── README.md ├── 附录八:扩展自动注册(Package auto discovery) └── README.md ├── 附录六:Application之register方法 └── README.md ├── 附录五:Container之resolveDependencies方法 └── README.md ├── LICENSE ├── 04第四章:艰难的开始 └── README.md ├── 07第七章:解析HTTP内核 └── README.md ├── 附录十一:ServiceProvider类的注册和引导 └── README.md ├── 06第六章:继续前行 └── README.md ├── 05第五章:陷入困境 └── README.md ├── 附录三:Container之resolve方法 └── README.md └── 附录七:Dispatcher之dispatch方法 └── README.md /images/pths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/pths.png -------------------------------------------------------------------------------- /images/laravel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/laravel.png -------------------------------------------------------------------------------- /images/test_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_00.png -------------------------------------------------------------------------------- /images/test_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_01.png -------------------------------------------------------------------------------- /images/test_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_02.png -------------------------------------------------------------------------------- /images/test_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_03.png -------------------------------------------------------------------------------- /images/test_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_04.png -------------------------------------------------------------------------------- /images/test_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_05.png -------------------------------------------------------------------------------- /images/test_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_06.png -------------------------------------------------------------------------------- /images/test_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_07.png -------------------------------------------------------------------------------- /images/test_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_08.png -------------------------------------------------------------------------------- /images/test_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_09.png -------------------------------------------------------------------------------- /images/test_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_10.png -------------------------------------------------------------------------------- /images/test_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_11.png -------------------------------------------------------------------------------- /images/test_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_12.png -------------------------------------------------------------------------------- /images/test_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_13.png -------------------------------------------------------------------------------- /images/test_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_14.png -------------------------------------------------------------------------------- /images/test_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_15.png -------------------------------------------------------------------------------- /images/test_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_16.png -------------------------------------------------------------------------------- /images/test_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_17.png -------------------------------------------------------------------------------- /images/test_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_18.png -------------------------------------------------------------------------------- /images/test_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_19.png -------------------------------------------------------------------------------- /images/test_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_20.png -------------------------------------------------------------------------------- /images/test_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/test_21.png -------------------------------------------------------------------------------- /images/error_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/error_01.png -------------------------------------------------------------------------------- /images/pttl_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youngtrix/pierce_through_the_laravel/HEAD/images/pttl_cover.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/pierce_through_the_laravel.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 穿透Laravel 2 | 在线阅读链接:http://www.idocloud.net/pttl/index.html 3 | 4 | ![](./images/pttl_cover.png) 5 | ## 引言 6 | 近几年来Laravel在PHP领域大放异彩,逐渐成为PHP开发框架中的中流砥柱。 7 | 这个系列的文章, 会带你一起探知Laravel框架底层的实现细节。与其他框架相比,Laravel的设计理念确实更为先进(服务、容器、依赖注入、facade。。。),初读代码时会感觉代码晦涩难懂,而一旦弄清了整套框架的基础结构,你就会惊叹于作者精巧的构思和对面向对象编程娴熟的驾驭能力。 8 | 9 | 下面是【穿透Laravel】这个系列文章对应的思维导图: 10 | 11 | ![](./images/laravel.png) 12 | 13 | 【图0.1】 14 | 15 | 以下为全书目录结构: 16 | 17 | ### 引言 18 | ### 第一章:基础环境搭建 19 | #### 关于Nginx重定向 20 | #### 思考 21 | ### 第二章:核心代码结构分析 22 | ### 第三章:实现自动加载 23 | ### 第四章:艰难的开始 24 | #### new static 25 | #### make方法 26 | ### 第五章:陷入困境 27 | #### 闭包 28 | #### 反射 29 | #### 题外话 30 | ### 第六章:继续前行 31 | ### 第七章:解析HTTP内核 32 | ### 第八章:处理请求 33 | ### 第九章:渲染页面 34 | ### 第十章:终止程序 35 | ### 结束语 36 | ### 附录一:断点测试和中断测试 37 | ### 附录二:Container之instance方法 38 | ### 附录三:Container之resolve方法 39 | ### 附录四:Container之bind方法 40 | ### 附录五:Container之resolveDependencies方法 41 | ### 附录六:Application之register方法 42 | ### 附录七:Dispatcher之dispatcher方法 43 | ### 附录八:扩展自动注册(Package auto discovery) 44 | ### 附录九:Route之findRoute方法 45 | ### 附录十:Route之run方法 46 | ### 附录十一:ServiceProvider类的注册和引导 -------------------------------------------------------------------------------- /03第三章:实现自动加载/README.md: -------------------------------------------------------------------------------- 1 | # 第三章:实现自动加载 2 | ```php 3 | 10 | */ 11 | 12 | define('LARAVEL_START', microtime(true)); 13 | ... 14 | ... ... 15 | ``` 16 | 17 | 第一行代码,只是简单定义了一个全局常量,并将其值设置为当前时间的时间戳 + 微秒数(小数点后4位)。这是方便在合适的地方计算代码运行时间的一个常量。值得注意的是,我们在框架内全局搜索"LARAVEL_START"关键词时,并没有任何发现,说明这行代码是框架预留的,在5.8.38版本中并没有实际的用处。 18 | 19 | 20 | >mircotime函数如果不加任何参数,返回的是一个空格隔开的字符串,时间戳 + 微秒数) 21 | 22 | 接下来这行代码: 23 | 24 | ```php 25 | require __DIR__.'/../vendor/autoload.php'; 26 | ``` 27 | 28 | vendor目录实际上是第三方库的目录,使用composer来进行包管理。 29 | 因为包含了这个autoload.php文件,在之后的所有php代码中任何使用vendor目录中的类语句(这些包都是按照文件夹独立开来的),直接引用类名即可,无需再显示包含类文件本身。 30 | 31 | 比如,我们很快会阅读的app.php(bootstrap/app.php)中,第14行 ~ 16行代码: 32 | 33 | ```php 34 | $app = new Illuminate\Foundation\Application( 35 | $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) 36 | ); 37 | ``` 38 | 39 | 我们看到,这里直接实例化了一个Application对象,如果没有之前的包含autoload.php文件语句,这里肯定会报错。一个简单的验证方法是,注释`require __DIR__.'/../vendor/autoload.php`语句,直接刷新页面,报错: 40 | ``` 41 | Fatal error: Uncaught Error: Class 'Illuminate\Foundation\Application' not found in /home/vagrant/code/blog5/bootstrap/app.php:14 Stack trace: #0 /home/vagrant/code/blog5/public/index.php(38): require_once() #1 {main} thrown in /home/vagrant/code/blog5/bootstrap/app.php on line 14 42 | ``` 43 | 44 | 继续追踪autoload.php中的代码,最终可以发现,composer使用了`spl_autoload_register`这个核心函数,来完成对PHP类的自动加载。 45 | 46 | 这里,我们不再对composer的包自动加载机制做过多的讲解,感兴趣的读者可以阅读下面这两篇文章。 47 | 48 | **参考链接:** 49 | 50 | - https://segmentfault.com/a/1190000014948542 51 | - https://www.cnblogs.com/a609251438/p/12659934.html -------------------------------------------------------------------------------- /附录一:断点测试和中断测试/README.md: -------------------------------------------------------------------------------- 1 | # 附录一:断点测试和中断测试 2 | 3 | ## 断点测试 4 | 5 | 断点这个概念来源于单步调试,在一次单步调试中,如果设置了一个断点,那么程序在debug的过程中会首先在断点处停下来。什么意思呢,就是说程序会先执行完断点之前的所有代码,在碰到断点所在行时,等待用户执行单步调试。单步调试时,可以跟踪代码中各个变量的值。 6 | 7 | 接下来,我们以Dev-C++为例(笔者本机上版本为5.6.3),给大家详细演示一下一次单步调试的详细步骤。 8 | 9 | 首先我们编辑一个test.c文件,源码如下: 10 | 11 | ```c 12 | #include 13 | #include 14 | 15 | main() 16 | { 17 | int dividend = 20; 18 | int divisor = 13; 19 | int quotient; 20 | int i = 0; 21 | 22 | while (i<=10) { 23 | printf("%d\n", i); 24 | i++; 25 | } 26 | 27 | if( divisor == 0){ 28 | fprintf(stderr, "除数为 0 退出运行...\n"); 29 | exit(-1); 30 | } 31 | 32 | quotient = dividend / divisor; 33 | fprintf(stderr, "quotient 变量的值为 : %d\n", quotient ); 34 | 35 | exit(0); 36 | } 37 | ``` 38 | 39 | 接下来,在编辑器中,将鼠标定位到下面这一行: 40 | 41 | ```c 42 | printf("%d\n", i); 43 | ``` 44 | 45 | 按F4键,或者鼠标移到这一行的行号位置(行号是编辑器自动标注出来的)点击鼠标,断点即设置成功。此时,该行会呈现为红色背景,且行首会出现一个红色的勾,如下: 46 | 47 | ![](../images/test_12.png) 48 | 49 | 【图11.1】 50 | 51 | 接下来,我们点击下面的"Add watch"按钮弹出如下对话框: 52 | 53 | ![](../images/test_13.png) 54 | 55 | 【图11.2】 56 | 57 | 这个对话框,是让我们输入需要监测的变量,在这个代码实例中,我们输入i即可。 58 | 59 | 接下来,就可以开始Debug了,单击底部的"Debug"按钮,代码在断点所在行停了下来并且背景色变成了蓝色。同时还弹出了一个命令行的结果窗口。笔者建议您使用一个外接显示器来调试代码,否则在单步调试时,你无法"实时"查看到结果窗中的变化。 60 | 61 | 当你给电脑连接上一个外接显示器后,你就可以直接将结果窗拖动到外接显示器上。之后我们点击底部的"Next line"按钮,就开启单步调试代码之旅了。每点击一次按钮,程序就多运行一步(这里的一步对应代码编辑器中的一行),同时你能清楚地看到代码在循环、分支和顺序结构中的执行次序。并且,在设置了监测变量之后,编辑器左侧的Debug窗口中还会实时显示出变量在每一步代码运行后的值: 62 | 63 | ![](../images/test_14.png) 64 | 65 | 【图11.3】 66 | 67 | 68 | 69 | 在PHP中,同样有类似的断点测试工具,但是需要我们先安装php的xdebug扩展,这个过程稍显复杂。 70 | 71 | ## var_dump中断测试 72 | 73 | 通常php不会被用来书写包含复杂算法的大块代码,更多的是处理业务逻辑。因此大部分情况下,我们不需要安装xdebug扩展来做单步调试。使用var_dump输出函数,就可以有效完成代码的调试。 74 | 75 | 当然使用var_dump函数输出变量内容的同时,必须及时让代码停下来,否则我们可能需要等待程序运行很长时间才能看到输出的内容,而这是完全不必要的。一般我们在var_dump之后,紧跟着使用die或者exit函数就可以了。 76 | 77 | > 注意:这里我们不提倡使用print_r函数,因为print_r输出信息时,不会包含变量的类型信息。比如一个整型的1和一个字符串的1,使用print_r输出时,命令行窗口都是显示1,这在很多情况下是不符合我们的调试需求的。 -------------------------------------------------------------------------------- /09第九章:渲染页面/README.md: -------------------------------------------------------------------------------- 1 | # 第九章:渲染页面 2 | 3 | 这一章,我们实际上只需要分析一条语句: 4 | 5 | ```php 6 | $response->send(); 7 | ``` 8 | 9 | > public/index.php 10 | 11 | 我们知道,response实际上是\symfony\http-foundation\Response类的一个实例,因此要找到send方法很容易: 12 | 13 | ```php 14 | /** 15 | * Sends HTTP headers and content. 16 | * 17 | * @return $this 18 | */ 19 | public function send() 20 | { 21 | $this->sendHeaders(); 22 | $this->sendContent(); 23 | 24 | if (\function_exists('fastcgi_finish_request')) { 25 | fastcgi_finish_request(); 26 | } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { 27 | static::closeOutputBuffers(0, true); 28 | flush(); 29 | } 30 | 31 | return $this; 32 | } 33 | ``` 34 | 35 | > vendor/symfony/http-foundation/Response.php 36 | 37 | 接下来,就是继续追踪sendHeaders方法和sendContents方法: 38 | 39 | sendHeaders: 40 | 41 | ```php 42 | /** 43 | * Sends HTTP headers. 44 | * 45 | * @return $this 46 | */ 47 | public function sendHeaders() 48 | { 49 | // headers have already been sent by the developer 50 | if (headers_sent()) { 51 | return $this; 52 | } 53 | 54 | // headers 55 | foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { 56 | $replace = 0 === strcasecmp($name, 'Content-Type'); 57 | foreach ($values as $value) { 58 | header($name.': '.$value, $replace, $this->statusCode); 59 | } 60 | } 61 | 62 | // cookies 63 | foreach ($this->headers->getCookies() as $cookie) { 64 | header('Set-Cookie: '.$cookie, false, $this->statusCode); 65 | } 66 | 67 | // status 68 | header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); 69 | 70 | return $this; 71 | } 72 | ``` 73 | 74 | > vendor/symfony/http-foundation/Response.php 75 | 76 | 这个方法里面的代码容易理解,就是设置HTTP的请求头,至于请求头中的信息来源,框架在前面程序的处理中已经"装填"好了。 77 | 78 | sendContents: 79 | 80 | ```php 81 | /** 82 | * Sends content for the current web response. 83 | * 84 | * @return $this 85 | */ 86 | public function sendContent() 87 | { 88 | echo $this->content; 89 | 90 | return $this; 91 | } 92 | ``` 93 | 94 | > vendor/symfony/http-foundation/Response.php 95 | 96 | 这个方法中的代码简单解释就是:输出当前对象的content成员变量值并返回当前对象。至于content变量的内容是什么时候赋值的,这和上一章我们讲的route类中的run方法相关。 97 | 98 | -------------------------------------------------------------------------------- /02第二章:核心代码结构分析/README.md: -------------------------------------------------------------------------------- 1 | # 第二章:核心代码结构分析 2 | 3 | 入口文件index.php的源码: 4 | 5 | ```php 6 | 13 | */ 14 | 15 | define('LARAVEL_START', microtime(true)); 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Register The Auto Loader 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Composer provides a convenient, automatically generated class loader for 23 | | our application. We just need to utilize it! We'll simply require it 24 | | into the script here so that we don't have to worry about manual 25 | | loading any of our classes later on. It feels great to relax. 26 | | 27 | */ 28 | 29 | require __DIR__.'/../vendor/autoload.php'; // 第一阶段 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Turn On The Lights 34 | |-------------------------------------------------------------------------- 35 | | 36 | | We need to illuminate PHP development, so let us turn on the lights. 37 | | This bootstraps the framework and gets it ready for use, then it 38 | | will load up this application so that we can run it and send 39 | | the responses back to the browser and delight our users. 40 | | 41 | */ 42 | 43 | $app = require_once __DIR__.'/../bootstrap/app.php'; // 第二阶段 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Run The Application 48 | |-------------------------------------------------------------------------- 49 | | 50 | | Once we have the application, we can handle the incoming request 51 | | through the kernel, and send the associated response back to 52 | | the client's browser allowing them to enjoy the creative 53 | | and wonderful application we have prepared for them. 54 | | 55 | */ 56 | 57 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); // 第三阶段 58 | 59 | $response = $kernel->handle( // 第四阶段 60 | $request = Illuminate\Http\Request::capture() 61 | ); 62 | 63 | $response->send(); // 第五阶段 64 | 65 | $kernel->terminate($request, $response); // 第六阶段 66 | 67 | ``` 68 | 69 | > public/index.php 70 | 71 | 按照上面代码中的注释,我们简要说明一下6个阶段,每个阶段大致都做了什么: 72 | 73 | 1、实现项目第三方库文件的自动加载 74 | 75 | 2、引入框架app启动文件 76 | 77 | 这个阶段框架做了大量的准备工作,包括: 78 | 79 | - 2.1、实例化容器类 80 | - 2.1.1 设置基础目录路径 81 | - 2.1.2 注册基础绑定 82 | - 2.1.3 注册服务提供者 83 | - 2.1.4 注册核心别名类 84 | - 2.2、向容器中"装填"处理HTTP请求的核心类HttpKernel 85 | - 2.3、向容器中"装填"处理命令行的核心类ConsoleKernel 86 | - 2.4、向容器中"装填"处理异常的核心类 87 | 88 | 3、从app中得到容器中装填好的处理HTTP请求的类赋值给变量kernel 89 | 90 | 4、运行HttpKernel类的handle方法,处理接收到的HTTP请求,得到需要返回的对象 91 | 92 | 5、运行返回对象的send方法渲染页面 93 | 94 | 6、继续运行HttpKernel类的terminate方法,执行绑定在容器中可能要执行的其他动作 95 | 96 | 从下一章开始,我们将按照上面的次序从上到下为大家详细阐述每个阶段框架所做的工作。 -------------------------------------------------------------------------------- /11结束语/README.md: -------------------------------------------------------------------------------- 1 | # 结束语 2 | 到这里,我们分析完了在php-fpm进程接收到nginx发起一个内部http请求后,Laravel框架是如何完整处理这个请求的。 3 | 4 | 在nginx阶段,nginx会把所有某个域名下的的http请求都转发给配置文件中定义目录(框架public目录)下的index.php文件,同时指定端口号。这里之所以要指定端口号,是因为在操作系统中,进程是以端口号来区分不同的服务。我们常见的9000端口,实际就是对应的php-fpm进程。 5 | 6 | 通过完整地分析一个php请求的生命周期,我们能清晰得看到Laravel框架的大致思路和结构。 7 | 8 | 现在回过头来看,大家有没有觉得,Laravel框架就像是构造了一套完整的结构精巧的组件。通过组合这些不同的组件,我们得到了一个能有效处理http请求的工具。 9 | 10 | 这个过程很像是搭乐高积木,乐高提前生产了一套可自由拼装的积木(组件),用户使用这些积木(组件)进行自由拼装,就能得到一个自己要想要的玩具。 11 | 12 | 或者我们更进一步得说,Container就像是Laravel提供的一个结构精巧的组件,用于处理类的依赖注入、类的自动构建以及类的缓存等等功能。而Application使用了这个组件的同时(Application继承了Container),又定义了很多其他不同的方法,使用这些方法来搭建一个完整的处理http请求的框架。它确实是起到了"胶水"(glue)的作用。 13 | 14 | 在Application类中,包含下面这些起粘贴组件作用的属性和方法: 15 | 16 | 属性: 17 | 18 | - 当前框架版本号 19 | 20 | - 框架所属基础路径 21 | 22 | - 是否已经执行过bootstrap方法 23 | 24 | - 是否已经执行过boot方法 25 | 26 | - 启动中回调事件 27 | 28 | - 启动后回调 29 | 30 | - 结束回调 31 | 32 | - 服务提供者数组 33 | 34 | - 已加载服务提供者 35 | 36 | - 延迟加载服务提供者 37 | 38 | - app路径 39 | 40 | - database路径 41 | 42 | - 自定义存储文件路径 43 | 44 | - 配置文件路径 45 | 46 | - 配置文件名 47 | 48 | - app命名空间 49 | 50 | 方法: 51 | 52 | - 获取当前框架版本号 53 | - 注册基础绑定 54 | - 注册基础服务提供者 55 | - 引导执行bootstrapper数组中事件 56 | - 注册加载配置文件后事件 57 | - 注册"引导执行bootstrapper事件前"事件 58 | - 注册"引导执行bootstrapper事件后"事件 59 | - 判断是否执行过bootstrap事件 60 | - 设置基础路径 61 | - 绑定路径到容器 62 | - 获取应用app目录路径 63 | - 设置appPath 64 | - 获取Laravel应用安装路径 65 | - 获取bootstrap目录路径 66 | - 获取config目录路径 67 | - 获取database目录路径 68 | - 设置database目录路径 69 | - 获取lang目录路径 70 | - 获取public目录路径 71 | - 获取storage目录路径 72 | - 设置storage目录路径 73 | - 获取resouces目录路径 74 | - 获取配置文件路径 75 | - 设置配置文件路径 76 | - 设置配置文件并返回对象application实例 77 | - 获取配置文件名 78 | - 获取配置文件完整路径(包含文件名) 79 | - 获取或者判断当前应用环境 80 | - 判断当前是否为local环境 81 | - 判断当前是否为production环境 82 | - 检测当前环境 83 | - 判断当前是否处于命令行 84 | - 判断当前是否处于单元测试 85 | - 注册已配置服务提供者 86 | - 注册服务提供者 87 | - 获取对应的服务提供者(存在于已注册服务者成员数组中) 88 | - 获取所有已注册服务提供者 89 | - 解析一个服务提供者 90 | - 标记服务提供者类为已注册 91 | - 加载所有延迟服务提供者 92 | - 加载指定延迟服务提供者 93 | - 注册延迟服务提供者 94 | - 解析一个字符串对应的类 95 | - 判断给定的字符串是否已绑定 96 | - 判断应用是否已启动 97 | - 启动应用 98 | - 启动服务提供者 99 | - 注册启动中监听事件 100 | - 注册启动后监听事件 101 | - 触发应用回调事件 102 | - 判断是否应跳过中间件 103 | - 获取services.php缓存文件完整路径 104 | - 获取packages.php缓存文件完整路径 105 | - 判断应用配置文件是否已缓存 106 | - 获取缓存配置文件完整路径 107 | - 判断应用路由是否已缓存 108 | - 获取缓存路由配置文件完整路径 109 | - 判断应用事件是否已缓存 110 | - 获取应用缓存事件文件完整路径 111 | - 判断当前应用是否处于维护中 112 | - 抛出一个http异常 113 | - 注册应用结束回调事件 114 | - 触发结束回调事件执行 115 | - 获取已加载服务提供者 116 | - 获取延迟加载服务提供者 117 | - 设置延迟加载服务提供者 118 | - 添加延迟加载服务提供者 119 | - 判断是否是延迟服务提供者 120 | - 配置实时facade命名空间 121 | - 获取当前应用的locale配置值 122 | - 设置当前应用的locale配置值 123 | - 判断当前应用配置的locale值是否是给定值 124 | - 注册核心别名类 125 | - 清空应用中所有的绑定和事件 126 | - 获取应用命名空间 127 | 128 | 列出上面这个列表,是为了让大家能更直观地感受到,为什么说Application类是Laravel框架中起调度作用的一个核心"胶水"类。 129 | 130 | 到这里,这段探寻Laravel生命周期的旅途已到终点。在分析源码的过程中,我们能看到Laravel的底层是使用面向对象的方式经过了复杂封装的,因此很多源码的实现细节我们并不能完全覆盖到,笔者相信阅读过这个系列的文章后你也可以用类似的方式自己去弄懂它们。 131 | 132 | 接下来,enjoy coding! 133 | 134 | **全文完** -------------------------------------------------------------------------------- /10第十章:终止程序/README.md: -------------------------------------------------------------------------------- 1 | # 第十章:终止程序 2 | 3 | 现在,我们来到框架执行过程中的最后一个阶段(第六阶段),这个阶段对应下面这一行语句: 4 | 5 | ```php 6 | $kernel->terminate($request, $response); 7 | ``` 8 | 9 | > public/index.php 10 | 11 | 显然terminate方法是属于类\Illuminate\Foundation\Http\Kernel的: 12 | 13 | ```php 14 | /** 15 | * Call the terminate method on any terminable middleware. 16 | * 17 | * @param \Illuminate\Http\Request $request 18 | * @param \Illuminate\Http\Response $response 19 | * @return void 20 | */ 21 | public function terminate($request, $response) 22 | { 23 | $this->terminateMiddleware($request, $response); 24 | 25 | $this->app->terminate(); 26 | } 27 | ``` 28 | 29 | > vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 30 | 31 | 继续追踪terminateMiddleware方法: 32 | 33 | ```php 34 | /** 35 | * Call the terminate method on any terminable middleware. 36 | * 37 | * @param \Illuminate\Http\Request $request 38 | * @param \Illuminate\Http\Response $response 39 | * @return void 40 | */ 41 | protected function terminateMiddleware($request, $response) 42 | { 43 | $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge( 44 | $this->gatherRouteMiddleware($request), 45 | $this->middleware 46 | ); 47 | 48 | foreach ($middlewares as $middleware) { 49 | if (! is_string($middleware)) { 50 | continue; 51 | } 52 | 53 | [$name] = $this->parseMiddleware($middleware); 54 | 55 | $instance = $this->app->make($name); 56 | 57 | if (method_exists($instance, 'terminate')) { 58 | $instance->terminate($request, $response); 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | > vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 65 | 66 | 这个方法体,是在处理所有中间件中的terminate方法,框架定义了规则:中间件中定义的terminate方法,会在这个阶段执行。 67 | 68 | 接下来继续看最后这行语句: 69 | 70 | ```php 71 | $this->app->terminate(); 72 | ``` 73 | 74 | > vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 75 | 76 | 我们知道$this->app是指向应用唯一的容器实例,对应的类文件是:\Illuminate\Foundation\Application.php,因此很容易就能找到terminate方法: 77 | 78 | ```php 79 | /** 80 | * Terminate the application. 81 | * 82 | * @return void 83 | */ 84 | public function terminate() 85 | { 86 | foreach ($this->terminatingCallbacks as $terminating) { 87 | $this->call($terminating); 88 | } 89 | } 90 | ``` 91 | 92 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 93 | 94 | 这里是执行app容器身上绑定的terminatingCallback事件,而terminatingCallbacks中的内容是通过调用Application类中的terminating方法进行"装填"的。那什么时候框架会调用terminating方法呢,通过在phpstorm IDE中对">terminating("字符串进行全局查找,我们并没有发现哪里有主动执行这个方法,我们猜测这里是需要用户自己去主动调用的。 95 | 96 | 由于我们访问的默认主页这个URL对应的controller和相关的其他所有代码中都没有包含这部分处理,因此即使在terminate方法中主动加exit,也不会影响程序的执行: 97 | 98 | ```php 99 | public function terminate() 100 | { 101 | exit('ccc'); 102 | foreach ($this->terminatingCallbacks as $terminating) { 103 | $this->call($terminating); 104 | } 105 | } 106 | ``` 107 | 108 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 109 | 110 | 程序并不会输出ccc,而是正常显示首页: 111 | 112 | ![](../images/test_11.png) -------------------------------------------------------------------------------- /附录二:Container之instance方法/README.md: -------------------------------------------------------------------------------- 1 | # 附录二:Container之instance方法 2 | 3 | 源文件路径:vendor/laravel/framework/src/Illuminate/Container/Container.php 4 | 5 | 方法名:instance 6 | 7 | ````php 8 | /** 9 | * Register an existing instance as shared in the container. 10 | * 11 | * @param string $abstract 12 | * @param mixed $instance 13 | * @return mixed 14 | */ 15 | public function instance($abstract, $instance) 16 | { 17 | $this->removeAbstractAlias($abstract); 18 | 19 | $isBound = $this->bound($abstract); 20 | 21 | unset($this->aliases[$abstract]); 22 | 23 | // We'll check to determine if this type has been bound before, and if it has 24 | // we will fire the rebound callbacks registered with the container and it 25 | // can be updated with consuming classes that have gotten resolved here. 26 | $this->instances[$abstract] = $instance; 27 | 28 | if ($isBound) { 29 | $this->rebound($abstract); 30 | } 31 | 32 | return $instance; 33 | } 34 | ```` 35 | 36 | 我们进入removeAbstractAlias方法内部,进行var_dump中断测试,如下: 37 | 38 | ```php 39 | /** 40 | * Remove an alias from the contextual binding alias cache. 41 | * 42 | * @param string $searched 43 | * @return void 44 | */ 45 | protected function removeAbstractAlias($searched) 46 | { 47 | if (! isset($this->aliases[$searched])) { 48 | var_dump($searched);exit; 49 | return; 50 | } 51 | 52 | foreach ($this->abstractAliases as $abstract => $aliases) { 53 | foreach ($aliases as $index => $alias) { 54 | if ($alias == $searched) { 55 | unset($this->abstractAliases[$abstract][$index]); 56 | } 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | 测试结果: 63 | 64 | ```php 65 | string(4)"path" 66 | ``` 67 | 68 | 继续做如下的var_dump中断测试: 69 | 70 | ```php 71 | /** 72 | * Remove an alias from the contextual binding alias cache. 73 | * 74 | * @param string $searched 75 | * @return void 76 | */ 77 | protected function removeAbstractAlias($searched) 78 | { 79 | if (! isset($this->aliases[$searched])) { 80 | return; 81 | } 82 | 83 | foreach ($this->abstractAliases as $abstract => $aliases) { 84 | var_dump($searched);exit; 85 | foreach ($aliases as $index => $alias) { 86 | if ($alias == $searched) { 87 | unset($this->abstractAliases[$abstract][$index]); 88 | } 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | 测试发现,首页仍然能正常访问。 95 | 96 | 上面两次中断测试,说明默认情况下,foreach循环体中的语句并不会被触发运行。这里我们能看到,这个方法的主要目的,就是检测abstractAliases成员变量中是否包含$searched这个键,如果包含则主动删除。 97 | 98 | instance方法中剩余的代码,做的工作可以简述如下: 99 | 100 | 将$instance装填到容器的instances成员变量上,注意这个变量是一个数组,键值就是传递进来的$abstract。 101 | 102 | 之后判断$abstract是否被绑定过,如果是,则执行绑定回调事件。 103 | 104 | 最后返回$instance变量 105 | 106 | ```php 107 | $this->instances[$abstract] = $instance; 108 | 109 | if ($isBound) { 110 | $this->rebound($abstract); 111 | } 112 | 113 | return $instance; 114 | ``` 115 | 116 | 最后我们简单总结一下: 117 | 118 | 1) instance方法执行的主要动作就是往容器对象的成员变量instances身上绑定新的键值对 119 | 120 | 2) 绑定之前会移除成员变量abstractAliases中的相应键值(如果有) 121 | 122 | 3) 如果成员变量aliases中有相应键值对也移除 123 | 124 | 4) 如果当前要绑定的这个键已经被绑定过,主动运行当前键上保存的绑定回调事件 -------------------------------------------------------------------------------- /附录四:Container之bind方法/README.md: -------------------------------------------------------------------------------- 1 | # 附录四:Container之bind方法 2 | 3 | 源文件路径:vendor\laravel\framework\src\Illuminate\Container\Container.php 4 | 5 | 方法名:bind 6 | 7 | ````php 8 | /** 9 | * Register a binding with the container. 10 | * 11 | * @param string $abstract 12 | * @param \Closure|string|null $concrete 13 | * @param bool $shared 14 | * @return void 15 | */ 16 | public function bind($abstract, $concrete = null, $shared = false) 17 | { 18 | $this->dropStaleInstances($abstract); 19 | 20 | // If no concrete type was given, we will simply set the concrete type to the 21 | // abstract type. After that, the concrete type to be registered as shared 22 | // without being forced to state their classes in both of the parameters. 23 | if (is_null($concrete)) { 24 | $concrete = $abstract; 25 | } 26 | 27 | // If the factory is not a Closure, it means it is just a class name which is 28 | // bound into this container to the abstract type and we will just wrap it 29 | // up inside its own Closure to give us more convenience when extending. 30 | if (! $concrete instanceof Closure) { 31 | $concrete = $this->getClosure($abstract, $concrete); 32 | } 33 | 34 | $this->bindings[$abstract] = compact('concrete', 'shared'); 35 | 36 | // If the abstract type was already resolved in this container we'll fire the 37 | // rebound listener so that any objects which have already gotten resolved 38 | // can have their copy of the object updated via the listener callbacks. 39 | if ($this->resolved($abstract)) { 40 | $this->rebound($abstract); 41 | } 42 | } 43 | ```` 44 | 45 | bind方法是Laravel实现绑定机制的核心方法之一,另外还有很多其他的方法: 46 | 47 | 1) bind把接口和实现类绑定,当make解析接口的时候创建其实现类的实例对象 48 | 49 | 2) singleton把接口和其实现类绑定,当第一次make解析的时候创建实例,后面都返回该实例不再创建 50 | 51 | 3) instance把接口和其实现类的实例绑定,直接绑定实例对象 52 | 53 | 4) 上下文绑定 54 | 55 | 5) 自动绑定 56 | 57 | 6) tag绑定 58 | 59 | 7) extends扩展绑定 60 | 61 | 本节我们仅聚焦bind方法的实现。 62 | 63 | **参数** 64 | 65 | 1) 首先明确第一个参数$abstract,简单说就是id,可以当作是存入容器中的名字,它可以使一个字符串,一个类甚至是一个接口。 66 | 67 | 2) 第二个参数$concrete简单说就是真实的值,可以当作是一个真正存入容器的实体。他可以是一个实现类,实例或者一个闭包。 68 | 69 | 3) 第三个参数控制shared的值。 70 | 71 | 方法体: 72 | 1、绑定前先清空instances和aliases中存在的同名字的服务: 73 | 74 | ```php 75 | protected function dropStaleInstances($abstract) 76 | { 77 | unset($this->instances[$abstract], $this->aliases[$abstract]); 78 | } 79 | ``` 80 | 81 | 2、然后判断第二个参数$concrete是不是空,如果是空,则将abstract变量值直接赋值给$concrete 82 | 83 | 3、判断$concrete是否是一个闭包,不是则调用getClosure,返回一个闭包便于后面操作 84 | 85 | 我们看一下getClosure方法: 86 | 87 | ```php 88 | protected function getClosure($abstract, $concrete) 89 | { 90 | return function ($container, $parameters = []) use ($abstract, $concrete) { 91 | if ($abstract == $concrete) { 92 | return $container->build($concrete); 93 | } 94 | 95 | return $container->resolve( 96 | $concrete, $parameters, $raiseEvents = false 97 | ); 98 | }; 99 | } 100 | ``` 101 | 102 | 大家可以很清楚地看到,这里再次调用了build和resolve方法,并且将解析出来的对象当作闭包函数的返回值。 103 | 104 | 回到bind方法,上面 $concrete 得到一个闭包函数后,调用 compact 把 $concrete 和 $shard (第三个参数判断是否 shared)组成一个 key 分别为 concrete 和 shared 的数组,存入 binding 数组中,而 binding 数组的 key 是当前的抽象类: 105 | 106 | ```php 107 | $this->bindings[$abstract] = compact('concrete', 'shared'); 108 | ``` 109 | 110 | 处理后的结构是这样的: 111 | 112 | ```php 113 | $binding[$abstract] => [ 114 | 'concrete' => function($container,$parameters=[]),//getClosure()得到的 115 | 'shared' => true/false,//shared的值是bind的第三个参数 116 | ] 117 | ``` 118 | 119 | 最后这里,如果当前的抽象类曾经被解析过。那再次绑定的时候,我们要使用 rebound 函数触发 执行reboundCallbacks 数组中的回调函数: 120 | 121 | ```php 122 | if ($this->resolved($abstract)) 123 | { 124 | $this->rebound($abstract); 125 | } 126 | ``` 127 | 128 | 如何判断当前的$abstract曾经被解析过,我们看下resolved函数,两个条件: 129 | 130 | 1、当前的resolved数组中是否存在$abstract 131 | 132 | 2、instances数组中是否存在对应的值,注意:在bind方法的第一句`$this->dropStaleInstances($abstract);`,那时我们已经清空了instances对应的abstract值,因此这里$abstract实际是找到的$abstract的别名。对应resolved方法开始的这几条语句。 133 | 134 | ```php 135 | public function resolved($abstract) 136 | { 137 | if ($this->isAlias($abstract)) { 138 | $abstract = $this->getAlias($abstract); 139 | } 140 | 141 | return isset($this->resolved[$abstract]) || 142 | isset($this->instances[$abstract]); 143 | } 144 | ``` 145 | 146 | 147 | 148 | > 本节内容部分参考自LearnKu社区,以下为转载详情: 149 | > 作者:HarveyNorman 150 | > 链接:https://learnku.com/articles/41504 151 | 152 | -------------------------------------------------------------------------------- /01第一章:基础环境搭建/README.md: -------------------------------------------------------------------------------- 1 | # 第一章:基础环境搭建 2 | 3 | 分析代码前,有必要主动告知大家本文分析的Laravel框架是哪个版本。以防有读者在核对代码时,发现作者引用的代码和其本地的代码版本不一致,由此引发不必要的误会。 4 | 5 | 对于框架版本不一致的情况,强烈建议您在本地安装一个和作者分析的版本一致的Laravel应用。作者相信通读这个系列的文章后,对于新版本框架的代码,您可以依葫芦画瓢,依靠自己的力量去理解新旧框架之间的不同部分。 6 | 7 | 下面是本文安装的Laravel框架版本和php版本: 8 | ## Laravel 9 | ``` 10 | $ php artisan 11 | Laravel Framework 5.8.38 12 | 13 | Usage: 14 | command [options] [arguments] 15 | ... 16 | ... ... 17 | ``` 18 | 19 | > 如果读者本地的Laravel版本不是5.8.38,建议以git方式下载本书,同时切换到相应的分支(除main分支外,其他分支名称均包含Laravel版本号),目前主分支main对应的Laravel版本号为:5.8.38。 20 | 21 | ## PHP 22 | ``` 23 | vagrant@homestead:~$ php7.2 --version 24 | PHP 7.2.34-24+ubuntu20.04.1+deb.sury.org+1 (cli) (built: Aug 26 2021 15:55:49) ( NTS ) 25 | Copyright (c) 1997-2018 The PHP Group 26 | Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies 27 | with Zend OPcache v7.2.34-24+ubuntu20.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies 28 | ``` 29 | > 笔者本机上采用的运行环境为Homestead, Homestead依赖Virtualbox虚拟机需要前置安装Vagrant、Virtualbox等软件。 30 | 31 | ## composer安装 32 | composer安装方法:`composer create-project --prefer-dist laravel/laravel blog "5.8.*"` 33 | 34 | > 上述命令中的blog,实际上是我们安装完laravel框架后项目的文件夹名称 35 | 36 | ## .env配置 37 | 通常,当我们使用composer执行完上述命令后,blog应用并不是立即可用的,你还需要做一些必要的配置: 38 | 39 | 源文件路径:blog/.env 40 | 41 | ``` 42 | APP_NAME=Blog 43 | APP_ENV=local 44 | APP_KEY=base64:fvgDEFOa3vK7Hk8lvUwN8Pat0w/u7o16IJ4j+lj3g1k= 45 | APP_DEBUG=true 46 | APP_URL=http://dev.blog.z 47 | 48 | LOG_CHANNEL=stack 49 | 50 | DB_CONNECTION=mysql 51 | DB_HOST=192.168.10.1 52 | DB_PORT=3306 53 | DB_DATABASE=laravel 54 | DB_USERNAME=root 55 | DB_PASSWORD=123456 56 | 57 | BROADCAST_DRIVER=log 58 | CACHE_DRIVER=file 59 | QUEUE_CONNECTION=database 60 | QUEUE_DRIVER=database 61 | SESSION_DRIVER=file 62 | #SESSION_DRIVER=database 63 | #SESSION_DRIVER=redis//使用redis 64 | #SESSION_CONNECTION=session//使用redis 65 | SESSION_LIFETIME=120 66 | 67 | REDIS_HOST=127.0.0.1 68 | REDIS_PASSWORD=null 69 | REDIS_PORT=6379 70 | REDIS_DB=0 71 | 72 | MAIL_DRIVER=smtp 73 | MAIL_HOST=smtp.mailtrap.io 74 | MAIL_PORT=2525 75 | MAIL_USERNAME=null 76 | MAIL_PASSWORD=null 77 | MAIL_ENCRYPTION=null 78 | 79 | AWS_ACCESS_KEY_ID= 80 | AWS_SECRET_ACCESS_KEY= 81 | AWS_DEFAULT_REGION=us-east-1 82 | AWS_BUCKET= 83 | 84 | PUSHER_APP_ID= 85 | PUSHER_APP_KEY= 86 | PUSHER_APP_SECRET= 87 | PUSHER_APP_CLUSTER=mt1 88 | 89 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 90 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 91 | 92 | ``` 93 | 94 | > 注意:默认composer安装laravel框架完成后,根目录下只有一个".env.example"文件,你需要自己生成一个.env文件,并且修改其中的配置值。上面这个示例,是作者本机应用的一个配置。关于Laravel如何配置,您可以阅读Laravel5.8手册获取相关知识,此处不再赘述。 95 | > 96 | > 由于我们分析的代码不涉及读取数据库的任何操作,上述配置仍可继续简化(将"DB_"为前缀的配置项全部删除或注释) 97 | 98 | ## nginx站点配置 99 | 100 | ```nginx 101 | server { 102 | listen 80; 103 | server_name dev.blog.z; 104 | root "/home/www/blog/public/"; 105 | index index.htm index.html index.php; 106 | 107 | location / { 108 | try_files $uri $uri/ /index.php?$query_string; 109 | } 110 | 111 | location ~ \.php(.*)$ { 112 | fastcgi_pass 127.0.0.1:9000; 113 | fastcgi_index index.php; 114 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 115 | include fastcgi_params; 116 | } 117 | } 118 | ``` 119 | 120 | > 注意:在这个配置实例中,我们给web网站定义的域名是"dev.blog.z"(和之前.env文件中的APP_URL保持一致),这个域名您当然可以随意更改。同时您需要注意的是root对应的配置值:/home/www/blog/public/,这个路径一定要对应您本地安装blog应用后文件夹所处的实际目录。 121 | 122 | 123 | 124 | 默认情况下完成上述配置后,访问的主页应该像下面这样: 125 | 126 | ![](../images/test_11.png) 127 | 128 | 【图1.2】 129 | 130 | ## 关于Nginx重定向 131 | 132 | 通常,基于MVC模式的框架都是走统一的单一入口文件模式,而在web服务器中, 要实现这一点也很简单,利用web服务器的URL重定向功能,比如nginx提供的try files指令就能实现。我们来看一个Laravel站点的配置文件(nginx.conf): 133 | 134 | ```nginx 135 | server { 136 | listen 80; 137 | server_name dev.blog.z; 138 | root "/home/www/www/blog/public/"; 139 | index index.htm index.html index.php; 140 | 141 | location / { 142 | try_files $uri $uri/ /index.php?$query_string; 143 | } 144 | 145 | location ~ \.php(.*)$ { 146 | fastcgi_pass 127.0.0.1:9000; 147 | fastcgi_index index.php; 148 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 149 | include fastcgi_params; 150 | } 151 | } 152 | ``` 153 | 154 | 现在假设用户在浏览器中输入:http://dev.blog.z/test/index 155 | 根据上面这个配置, nginx会进行怎样的处理呢? 156 | 157 | - 首先nginx读取路径中的uri部分:/test/index,try files指令的意思是优先读取相对应目录下的文件,读取不到则丢弃该请求,并重新发起一个子请求。 158 | - 由于/home/www/www/blog/public/下没有test/index文件夹,因此服务器会发起一个内部子请求到/home/www/www/blog/public/index.php 159 | - 由于try files指令中index.php后面携带了?$query_string,因此请求发送到index.php后,在服务端PHP通过`$_SERVER['REQUEST_URI']`语句获取到的值为/test/index 160 | 161 | 由此, 我们可以猜想Laravel框架在路由解析这一块使用的是分析`$_SERVER['REQUEST_URI']`结果, 和ThinkPHP3中分析path_info或者Phalcon中直接将uri放在_url=后作为重定向地址不同。 162 | 163 | ## 思考 164 | 165 | 1) 如果public下面有test/index/index.htm文件存在, 浏览器直接访问http://dev.blog.z/test/index 会发生什么? 166 | 2) 关于本文末尾提到的路由解析猜想,如何验证? -------------------------------------------------------------------------------- /附录十:Route之run方法/README.md: -------------------------------------------------------------------------------- 1 | # 附录十:Route之run方法 2 | 3 | 源文件路径:vendor\laravel\framework\src\Illuminate\Routing\Route.php 4 | 5 | 方法名:run 6 | 7 | ```php 8 | /** 9 | * Run the route action and return the response. 10 | * 11 | * @return mixed 12 | */ 13 | public function run() 14 | { 15 | $this->container = $this->container ?: new Container; 16 | 17 | try { 18 | if ($this->isControllerAction()) { 19 | return $this->runController(); 20 | } 21 | 22 | return $this->runCallable(); 23 | } catch (HttpResponseException $e) { 24 | return $e->getResponse(); 25 | } 26 | } 27 | ``` 28 | 29 | 回到`Handle`类的`dispatchToRoute`方法: 30 | 31 | ```php 32 | public function dispatchToRoute(Request $request) 33 | { 34 | return $this->runRoute($request, $this->findRoute($request)); 35 | } 36 | ``` 37 | 38 | `findRoute`方法找到一条匹配的路由之后,接着就运行`Router`类的`runRoute`方法了: 39 | 40 | ```php 41 | /** 42 | * Return the response for the given route. 43 | * 44 | * @param \Illuminate\Http\Request $request 45 | * @param \Illuminate\Routing\Route $route 46 | * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse 47 | */ 48 | protected function runRoute(Request $request, Route $route) 49 | { 50 | $request->setRouteResolver(function () use ($route) { 51 | return $route; 52 | }); 53 | 54 | $this->events->dispatch(new Events\RouteMatched($route, $request)); 55 | 56 | return $this->prepareResponse($request, 57 | $this->runRouteWithinStack($route, $request) 58 | ); 59 | } 60 | ``` 61 | 62 | Route类的`run`方法,就是`runRoute`中`$this->runRouteWithinStack()`语句调用的: 63 | 64 | ```php 65 | protected function runRouteWithinStack(Route $route, Request $request) 66 | { 67 | $shouldSkipMiddleware = $this->container->bound('middleware.disable') && 68 | $this->container->make('middleware.disable') === true; 69 | 70 | $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); 71 | 72 | return (new Pipeline($this->container)) 73 | ->send($request) 74 | ->through($middleware) 75 | ->then(function ($request) use ($route) { 76 | return $this->prepareResponse( 77 | $request, $route->run() 78 | ); 79 | }); 80 | } 81 | ``` 82 | 83 | 现在我们来逐行分析这个方法中的语句: 84 | 85 | 第一行:`$this->container = $this->container ?: new Container;`很简单,这里就是对本类中的成员变量container进行赋值操作,这里之所以需要赋值,是因为在Route类的其他方法中使用到了这个`$this->container`对象的方法。 86 | 87 | 紧接着,程序就进入一个try catch结构中: 88 | 89 | ```php 90 | try { 91 | if ($this->isControllerAction()) { 92 | return $this->runController(); 93 | } 94 | 95 | return $this->runCallable(); 96 | } catch (HttpResponseException $e) { 97 | return $e->getResponse(); 98 | } 99 | ``` 100 | 101 | 首先代码调用`isControllerAction`方法判断当前是否是Controller上的action: 102 | 103 | ```php 104 | protected function isControllerAction() 105 | { 106 | return is_string($this->action['uses']); 107 | } 108 | ``` 109 | 110 | 这个方法中的逻辑虽然简洁,但是这里涉及到`RouteCollection`类是怎样创建一个`Route`的,关于这一点读者可以回到【附录九】中,查看关于"路由注册"的部分。 111 | 112 | 接着如果`isControllerAction()`返回值为true,则执行`runController()`,然后返回其结果。否则执行`runCallable`并返回其结果。 113 | 114 | runController: 115 | 116 | ```php 117 | protected function runController() 118 | { 119 | return $this->controllerDispatcher()->dispatch( 120 | $this, $this->getController(), $this->getControllerMethod() 121 | ); 122 | } 123 | ``` 124 | 125 | 继续追踪`controllerDispatcher()`: 126 | 127 | ```php 128 | public function controllerDispatcher() 129 | { 130 | if ($this->container->bound(ControllerDispatcherContract::class)) { 131 | return $this->container->make(ControllerDispatcherContract::class); 132 | } 133 | 134 | return new ControllerDispatcher($this->container); 135 | } 136 | ``` 137 | 138 | 这个方法很明显返回的是一个`ControllerDispatcher`类,继续追踪`dispatch`方法: 139 | 140 | ```php 141 | public function dispatch(Route $route, $controller, $method) 142 | { 143 | $parameters = $this->resolveClassMethodDependencies( 144 | $route->parametersWithoutNulls(), $controller, $method 145 | ); 146 | 147 | if (method_exists($controller, 'callAction')) { 148 | return $controller->callAction($method, $parameters); 149 | } 150 | 151 | return $controller->{$method}(...array_values($parameters)); 152 | } 153 | ``` 154 | 155 | 到这里,我们最终发现,程序会去检测对应的controller类中是否包含`callAction`方法,是则直接执行controller类上的`callAction`方法,否则执行controller类上的`method`方法。回到调用`dispatch()`的语句中,我们会发现参数值$controller和$method是通过调用类中的`getController()`和`getControllerMethod()`方法得到的。 156 | 157 | 接下来我们来看另一个方法`runCallable`: 158 | 159 | ```php 160 | /** 161 | * Run the route action and return the response. 162 | * 163 | * @return mixed 164 | */ 165 | protected function runCallable() 166 | { 167 | $callable = $this->action['uses']; 168 | 169 | return $callable(...array_values($this->resolveMethodDependencies( 170 | $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses']) 171 | ))); 172 | } 173 | ``` 174 | 175 | 可以看到,这里直接取出当前对象中的成员变量`action`的uses键值赋值给$callable,然后直接执行$callable方法。 176 | 177 | 这种情况正是因为在路由文件中,支持下面这种定义: 178 | 179 | ```php 180 | Route::get('/', function () { 181 | return view('welcome'); 182 | }); 183 | ``` 184 | 185 | 即某个路由直接对应到一个闭包函数上去。读者可以自行做vard_dump中断测试,在这种情况下,`route`对象中的各个成员变量究竟是什么。 -------------------------------------------------------------------------------- /附录八:扩展自动注册(Package auto discovery)/README.md: -------------------------------------------------------------------------------- 1 | # 附录八:扩展自动注册(Package auto discovery) 2 | 3 | 在进入探究 Laravel 包提供者与门面如何自动发现之前,让我们先粗浅剖析一下 PHP 中包的概念。 4 | 5 | 一个包就是一个在多个项目内可复用的代码片段,例如 包 spatie/laravel-analytics 可以让你在 laravel 项目内,用一种简易方式从谷歌统计(Google Analytics)中取回数据,该包被托管 在 GitHub 上,由 Spatie 进行维护,它们会持续发布,更新和修复该包 bug,如果你在项目当中使用该包,希望获取这些一旦发布的更新和修复,无须担心使用 Composer 从 Github 上 拷贝一份新代码即可。 6 | 7 | > Composer 是一个 PHP 依赖管理工具。它允许你声明项目库依赖且管理(安装 / 更新)它们。 -- 详见官网 getcomposer.org 8 | 9 | Laravel 自带 composer.json 文件,文件内的 require 或 require-dev 条目下,给出了你扩展应用功能需用到的包,执行 `composer update`: 10 | 11 | ``` 12 | { 13 | "require": { 14 | "spatie/laravel-analytics": "3.*", 15 | }, 16 | } 17 | ``` 18 | 19 | 你也可以使用下面命令,达到同样的效果: 20 | 21 | ``` 22 | composer require spatie/laravel-analytics 23 | ``` 24 | 25 | Composer 所做的工作在于,拉取你所需版本包,下载到 vendor 目录,上述命令执行完毕, 包内所有类和文件被加载进项目,你就可以马上使用它们了,每次当你再次执行`composer update`,Composer将会重新获取(译者注 通常从 composer 仓库拉取)更新该包,并且自动更新位于你项目 vendor 目录下的文件。 26 | 27 | 在 Laravel 项目中使用某些 Laravel 包 需要以下额外几个步骤: 28 | 29 | - 注册服务提供者 30 | - 注册别名 / 门面 31 | - 发布资源 32 | 33 | 如果你看过"Spatie 包安装说明"你会发现,在继续下一步这前,项目配置必须注册服务提供者和一个门面,是一个很好的习惯,这个步骤由 Taylor Otwell 定义,只是一个非必要条件, Dries Vints,且达到无论何时你决定引入一个新包或移除包,服务提供者和门面皆可被自动发现。 34 | 重温 Taylor 的新特性声明:在媒体上(https://medium.com/@taylorotwell/package-auto-discovery-in-laravel-5-5-ea9e3ab20518). 35 | 36 | ## 什么是服务提供者和门面? 37 | 38 | > 服务提供者负责将事物绑定到 Laravel 的服务容器中,并通知 Laravel 在哪里加载包资源,例如视图,配置和本地化文件。-- laravel.com 文档 39 | 40 | 你可以在上面阅读有关服务提供者的更多信息 官方文档(https://learnku.com/docs/laravel/5.4/providers). 41 | 42 | > 门面为应用程序服务容器中可用的类提供 "static" 接口 -- laravel.com 文档 43 | 44 | 你可以在上面阅读更多有关门面的信息 官方文档(https://learnku.com/docs/laravel/5.4/facades). 45 | 46 | 在查找和安装 / 更新不同的扩展包时,Composer 会触发你可以订阅的多个事件并运行你自己的一段代码甚至是命令行可执行文件,其中一个有趣的事件称为`post-autoload-dump`。 在 composer 生成项目中自动加载的最终类列表之后直接触发,此时 Laravel 已经可以访问所有类,并且应用程序已准备好使用加载到其中的所有包类。 47 | 48 | Laravel在主composer.json文件中订阅此事件: 49 | 50 | ``` 51 | "scripts": { 52 | "post-autoload-dump": [ 53 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 54 | "@php artisan package:discover" 55 | ] 56 | } 57 | ``` 58 | 59 | 首先它调用`postAutoloadDump()`静态方法,此方法会清理缓存的服务或之前发现的包,另一个是它运行`package:discover`的artisan命令,这就是 Laravel 可以自动发现的秘密。 60 | 61 | 62 | ## 包自动发现 63 | 64 | `Illuminate\Foundation\Console\PackageDiscoverCommand` 在 `Illuminate\Foundation\PackageManifest` 类中调用 `build()` 方法,该类是 Laravel 发现已安装包的地方。 65 | 66 | PackageManifest 在应用程序引导程序的早期注册到容器中,完全来自 `Illuminate\Foundation\Application::registerBaseServiceProviders()`,此方法在创建 Laravel 应用程序的新实例后直接运行。 67 | 68 | 在`build()`方法中,Laravel 查找 `vendor/composer/installed.json` 文件,它由 composer 生成并保存一个完整的映射,其中包含 composer 安装的所有扩展包的 composer.json 文件内容, Laravel 映射该文件的内容并搜索包含`extra.laravel`部分的包: 69 | 70 | ``` 71 | "extra": { 72 | "laravel": { 73 | "providers": [ 74 | "Barryvdh\\Debugbar\\ServiceProvider" 75 | ], 76 | "aliases": { 77 | "Debugbar": "Barryvdh\\Debugbar\\Facade" 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | 它首先收集该部分的内容,然后查看主composer.json文件下的`extra.laravel.dont-discover`的内容,看看你是否决定不自动发现某些包或所有包: 84 | 85 | ``` 86 | "extra": { 87 | "laravel": { 88 | "dont-discover": [ 89 | "barryvdh/laravel-debugbar" 90 | ] 91 | } 92 | } 93 | ``` 94 | 95 | 你可以在数组中添加 * 以指示 laravel 完全停止自动注册。 96 | 97 | ## 现在 Laravel 收集了有关扩展包的信息 98 | 99 | 是的,一旦获得所需要的信息,它将在 `bootstrap/cache/packages.php` 中编写一个 PHP 文件: 100 | 101 | ``` 102 | 104 | array ( 105 | 'providers' => 106 | array ( 107 | 0 => 'Barryvdh\\Debugbar\\ServiceProvider', 108 | ), 109 | 'aliases' => 110 | array ( 111 | 'Debugbar' => 'Barryvdh\\Debugbar\\Facade', 112 | ), 113 | ), 114 | ); 115 | ``` 116 | 117 | ## 包注册 118 | 119 | Laravel 有两个 bootstrappers,在 HTTP 或控制台内核启动时使用: 120 | 121 | - `\Illuminate\Foundation\Bootstrap\RegisterFacades` 122 | 123 | - `\Illuminate\Foundation\Bootstrap\RegisterProviders` 124 | 125 | 第一个使用`Illuminate\Foundation\AliasLoader`将所有门面加载到应用程序中,现在 Laravel 将查看 packages.php 生成的文件并提取包中希望 Laravel 自动注册的所有别名并注册这些别名。 它使用`PackageManifest::aliases()`方法来收集这些信息。 126 | 127 | ``` 128 | // 在 RegisterFacades::bootstrap() 129 | 130 | AliasLoader::getInstance(array_merge( 131 | $app->make('config')->get('app.aliases', []), 132 | $app->make(PackageManifest::class)->aliases() 133 | ))->register(); 134 | ``` 135 | 136 | 如你所见,从`config/app.php`文件加载的别名与从 PackageManifest 类加载的别名合并。 137 | 类似地,Laravel 在启动时注册服务提供者,`RegisterProviders`中的`bootstrapper 调用`Foundation\Application` 的`registerConfiguredProviders()` 方法,并且 Laravel 在这会收集所有应该自动注册的包提供者并注册它们。 138 | 139 | ``` 140 | $providers = Collection::make($this->config['app.providers']) 141 | ->partition(function ($provider) { 142 | return Str::startsWith($provider, 'Illuminate\\'); 143 | }); 144 | 145 | $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); 146 | (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) 147 | ->load($providers->collapse()->toArray()); 148 | ``` 149 | 150 | 在这里,我们在 Illuminate 服务提供者和可能在你的配置文件中的任何其他服务提供者之间注入自动发现的服务提供者,这样我们确保你可以通过在配置文件中重新注册它们来覆盖扩展包服务提供者,并且 Illuminate 组件将会在尝试加载任何其他服务提供者之前加载。 151 | 152 | >原文作者:Summer 153 | >转自链接:https://learnku.com/laravel/t/33459 -------------------------------------------------------------------------------- /附录六:Application之register方法/README.md: -------------------------------------------------------------------------------- 1 | # 附录六:Application之register方法 2 | 3 | 源文件路径:vendor\laravel\framework\src\Illuminate\Foundation\Application.php 4 | 5 | 方法名:register 6 | 7 | ```php 8 | /** 9 | * Register a service provider with the application. 10 | * 11 | * @param \Illuminate\Support\ServiceProvider|string $provider 12 | * @param bool $force 13 | * @return \Illuminate\Support\ServiceProvider 14 | */ 15 | public function register($provider, $force = false) 16 | { 17 | if (($registered = $this->getProvider($provider)) && ! $force) { 18 | return $registered; 19 | } 20 | 21 | // If the given "provider" is a string, we will resolve it, passing in the 22 | // application instance automatically for the developer. This is simply 23 | // a more convenient way of specifying your service provider classes. 24 | if (is_string($provider)) { 25 | $provider = $this->resolveProvider($provider); 26 | } 27 | 28 | $provider->register(); 29 | 30 | // If there are bindings / singletons set as properties on the provider we 31 | // will spin through them and register them with the application, which 32 | // serves as a convenience layer while registering a lot of bindings. 33 | if (property_exists($provider, 'bindings')) { 34 | foreach ($provider->bindings as $key => $value) { 35 | $this->bind($key, $value); 36 | } 37 | } 38 | 39 | if (property_exists($provider, 'singletons')) { 40 | foreach ($provider->singletons as $key => $value) { 41 | $this->singleton($key, $value); 42 | } 43 | } 44 | 45 | $this->markAsRegistered($provider); 46 | 47 | // If the application has already booted, we will call this boot method on 48 | // the provider class so it has an opportunity to do its boot logic and 49 | // will be ready for any usage by this developer's application logic. 50 | if ($this->isBooted()) { 51 | $this->bootProvider($provider); 52 | } 53 | 54 | return $provider; 55 | } 56 | ``` 57 | 58 | 第一步: 59 | 60 | ```php 61 | if (($registered = $this->getProvider($provider)) && ! $force) { 62 | return $registered; 63 | } 64 | ``` 65 | 66 | 这里主要就是调用`getProvider($provider)`,作用就是检查前面是否已经加载了我们需要的provider,如果是,则直接返回这个provider,不用再加载。如果不是,返回null。 67 | 68 | getProvider: 69 | 70 | ```php 71 | public function getProvider($provider) 72 | { 73 | return array_values($this->getProviders($provider))[0] ?? null; 74 | } 75 | ``` 76 | 77 | > array_values函数:返回数组的value值不包含key 78 | 79 | getProviders: 80 | 81 | ```php 82 | public function getProviders($provider) 83 | { 84 | $name = is_string($provider) ? $provider : get_class($provider); 85 | 86 | return Arr::where($this->serviceProviders, function ($value) use ($name) { 87 | return $value instanceof $name; 88 | }); 89 | } 90 | ``` 91 | 92 | 这里分两步: 93 | 94 | - a) 如果参数$provider是字符串,直接赋值给变量$name,如果不是,使用get_class获取该参数$provider所属类的路径,返回 95 | 96 | - b) 调用工具类Arr的where方法 97 | 98 | ```php 99 | public static function where($array, callable $callback) 100 | { 101 | return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); 102 | } 103 | ``` 104 | 105 | 这里简单解释就是去$this->serviceProviders中找匹配$name值的项,以数组形式返回。 106 | 107 | 继续看后面的代码: 108 | 109 | ```php 110 | if (is_string($provider)) { 111 | $provider = $this->resolveProvider($provider); 112 | } 113 | ``` 114 | 115 | 如果$provider是字符串,则执行if中的代码,我们继续看resolveProvider方法: 116 | 117 | ```php 118 | public function resolveProvider($provider) 119 | { 120 | return new $provider($this); 121 | } 122 | ``` 123 | 124 | 很简单,直接new一个provider对象并返回。 125 | 126 | 这里我们需要注意new对象同时,传入了$this变量值作为参数。我们猜测,这个$this一定会在类的构造函数中使用到,于是我们去找一个具体的provider类型的类,比如RoutingServiceProvider,看看情况是不是这样。 127 | 128 | 然而通过查看RoutingServiceProvider类的源码,我们并没有找到这个类的构造行数__contruct(),既然这样我们只能"向上"继续查找,注意到RoutingServiceProvider类继承了父类ServiceProvider,我们直接跳转到ServiceProvider,终于在这个类中看到了它的构造函数: 129 | 130 | ```php 131 | public function __construct($app) 132 | { 133 | $this->app = $app; 134 | } 135 | ``` 136 | 137 | 这样就能解释通了,在执行register方法实例化所有"ServiceProvider类型类"的时候,把当前所有对象共享的容器也""挂载"进去。因为容器需要知道哪些ServiceProvider类是已经加载过的,哪些是没有加载的。而容器是联结所有对象的纽带,因此操作共享容器app,就是最好的方式。 138 | 139 | > ServiceProvider类型类:是指所有继承了ServiceProvider基类的类,实际上在Laravel框架中要编写一个新的Provider类,默认都需要继承ServiceProvider基类,因为只有这样Laravel框架在执行过程中才能正确解析出这个Provider类。 140 | 141 | 接下来这一句,是非常关键的一句: 142 | 143 | ```php 144 | $provider->register(); 145 | ``` 146 | 147 | 执行ServiceProvider类型类上的`register`方法,仍以RouteServiceProvider为例: 148 | 149 | ```php 150 | public function register() 151 | { 152 | $this->registerRouter(); 153 | $this->registerUrlGenerator(); 154 | $this->registerRedirector(); 155 | $this->registerPsrRequest(); 156 | $this->registerPsrResponse(); 157 | $this->registerResponseFactory(); 158 | $this->registerControllerDispatcher(); 159 | } 160 | ``` 161 | 162 | 我们看到,这里就是RouteServiceProvider类的核心逻辑了。 163 | 164 | 由于本节我们重点关注的是Application类中的register方法,这里我们不再对RouteServiceProvider这个类中的register方法中的所有子方法展开讲解。 165 | 166 | 继续往下看后面的代码: 167 | 168 | ```php 169 | if (property_exists($provider, 'bindings')) { 170 | foreach ($provider->bindings as $key => $value) { 171 | $this->bind($key, $value); 172 | } 173 | } 174 | 175 | if (property_exists($provider, 'singletons')) { 176 | foreach ($provider->singletons as $key => $value) { 177 | $this->singleton($key, $value); 178 | } 179 | } 180 | ``` 181 | 182 | 这里,我们能分析出来,代码是在检测上一步实例化出来的类(赋值给$provider)中是否包含bindings和singletons成员,如果有包含,就将bindings和singletons成员中的包含的内容解析出来,调用Application类的bind和singleton方法。 183 | 184 | 然而Application类本身并没有bind和singleton方法,那这两个方法在哪呢? 185 | 186 | Application类继承了Container类,所以这两个方法实际就是容器类中的方法。在【附录四】中,我们已经给大家讲解过,bind和singleton正是容器执行绑定的两个常用方式。 187 | 188 | 继续看后面的代码: 189 | 190 | ```php 191 | $this->markAsRegistered($provider); 192 | 193 | if ($this->isBooted()) { 194 | $this->bootProvider($provider); 195 | } 196 | 197 | return $provider; 198 | ``` 199 | 200 | `markAsRegistered`方法,是把这个已经注册完成的provider对象进行保存(存入serviceProvider数组中)。后面再有需求的时候不需要再次进行注册,这里正好和前面我们讲的第一步相"呼应"。除了这个动作之外,还把对应的类名存储到loadedProviders数组中,以备后用。 201 | 202 | ```php 203 | protected function markAsRegistered($provider) 204 | { 205 | $this->serviceProviders[] = $provider; 206 | 207 | $this->loadedProviders[get_class($provider)] = true; 208 | } 209 | ``` 210 | 211 | 之后,判断当前application这个容器对象是都已经启动过了,简单说,就是application的boot方法是否已经执行过了。如果执行过了(即容器已经启动),则继续调用`bootProvider`方法。 212 | 213 | 这里要怎么理解呢? 214 | 215 | 我们可以这么分析:既然程序执行`register`方法时,已经走到了这里,那就是说当前这个provider是肯定没有注册过的。那么首次注册这个provider,就需要去触发这个provider对象上的boot方法执行。 216 | 217 | bootProvider: 218 | 219 | ```php 220 | protected function bootProvider(ServiceProvider $provider) 221 | { 222 | if (method_exists($provider, 'boot')) { 223 | return $this->call([$provider, 'boot']); 224 | } 225 | } 226 | ``` 227 | 228 | 这个强制执行provider对象身上boot方法的规则,大家可以理解为它就是Laravel处理ServiceProvider类的方式。实际上框架提供的文档中,也会对此有明确的说明。 229 | 230 | 除了在容器主动执行`register`方法时会触发执行$provider身上定义的boot方法外,在应用启动阶段也会遍历容器定义的所有serviceProviders,然后触发执行这些类身上的bootProvider方法,关于这一点,大家可以在Application类中的boot方法中,找到相同功能的代码: 231 | 232 | ```php 233 | public function boot() 234 | { 235 | ... 236 | array_walk($this->serviceProviders, function ($p) { 237 | $this->bootProvider($p); 238 | }); 239 | ... 240 | } 241 | ``` 242 | 243 | 最后一句很好理解,直接返回$provider对象。 244 | 245 | -------------------------------------------------------------------------------- /附录五:Container之resolveDependencies方法/README.md: -------------------------------------------------------------------------------- 1 | # 附录五:Container之resolveDependencies方法 2 | 3 | 源文件路径:vendor\laravel\framework\src\Illuminate\Container\Container.php 4 | 5 | 方法名:resolveDependencies 6 | 7 | ````php 8 | /** 9 | * Resolve all of the dependencies from the ReflectionParameters. 10 | * 11 | * @param array $dependencies 12 | * @return array 13 | * 14 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 15 | */ 16 | protected function resolveDependencies(array $dependencies) 17 | { 18 | $results = []; 19 | 20 | foreach ($dependencies as $dependency) { 21 | // If this dependency has a override for this particular build we will use 22 | // that instead as the value. Otherwise, we will continue with this run 23 | // of resolutions and let reflection attempt to determine the result. 24 | if ($this->hasParameterOverride($dependency)) { 25 | $results[] = $this->getParameterOverride($dependency); 26 | 27 | continue; 28 | } 29 | 30 | // If the class is null, it means the dependency is a string or some other 31 | // primitive type which we can not resolve since it is not a class and 32 | // we will just bomb out with an error since we have no-where to go. 33 | $results[] = is_null($dependency->getClass()) 34 | ? $this->resolvePrimitive($dependency) 35 | : $this->resolveClass($dependency); 36 | } 37 | 38 | return $results; 39 | } 40 | ```` 41 | 42 | 首先我们需要清楚的是,$dependencies是通过反射的方式获取到的对象构造函数需要的参数值。由于参数值可能很多,因此这里是传递的数组。但是这个数组大家需要注意的是,每个数组中的元素,并不是简单的字符串类型的值,而是对象类型的值。那这个对象的类型是什么呢? 43 | 44 | 首先需要追溯$dependencies是怎么产生的: 45 | 46 | ```php 47 | $reflector = new ReflectionClass($concrete); 48 | 49 | if (! $reflector->isInstantiable()) { 50 | return $this->notInstantiable($concrete); 51 | } 52 | 53 | $this->buildStack[] = $concrete; 54 | 55 | $constructor = $reflector->getConstructor(); 56 | 57 | if (is_null($constructor)) { 58 | array_pop($this->buildStack); 59 | return new $concrete; 60 | } 61 | 62 | $dependencies = $constructor->getParameters(); 63 | try { 64 | $instances = $this->resolveDependencies($dependencies); 65 | } catch (BindingResolutionException $e) { 66 | ... 67 | ... ... 68 | ``` 69 | 70 | 上面这段代码正是调用`resolveDependencies`方法前,`build`方法中的核心代码。可以看到,$dependencies是反射类的构造器通过调用`getParameters`方法获取到的值,继续追溯getConstructor方法: 71 | 72 | 大家可以查看php的在线手册,在这个页面:https://www.php.net/manual/zh/class.reflectionclass.php,包含了ReflectionClass能调用的所有方法: 73 | 74 | ![](../images/test_17.png) 75 | 76 | 可以看到,`getConstructor`方法返回的是ReflectionMethod类型的值。 77 | 78 | 于是我们继续找到ReflectionMethod的手册页面(https://www.php.net/manual/zh/class.reflectionmethod.php),找到`getParameters`方法: 79 | 80 | ![](../images/test_18.png) 81 | 82 | 点进去仔细查看这个页面的说明: 83 | 84 | ![](../images/test_19.png) 85 | 86 | 至此,我们可以确定,$dependencies数组中的元素类型,是ReflectionParameter类。这一点,和后面我们看到的`resolvePreimitive`方法中给出的形参类型一致。 87 | 88 | 弄清楚这一点,我们才能知道,当后面的代码出现`$dependency->name`,`$dependency->getClass()`,以及`$parameter->isDefaultValueAvailable()`和`$parameter->getDefaultValue()`、`$parameter->isOptional()`、`$parameter->getClass()->name`时,究竟发生了什么。 89 | 90 | 接下来我们继续看源码: 91 | 92 | ```php 93 | foreach ($dependencies as $dependency) { 94 | // If this dependency has a override for this particular build we will use 95 | // that instead as the value. Otherwise, we will continue with this run 96 | // of resolutions and let reflection attempt to determine the result. 97 | if ($this->hasParameterOverride($dependency)) { 98 | $results[] = $this->getParameterOverride($dependency); 99 | 100 | continue; 101 | } 102 | 103 | // If the class is null, it means the dependency is a string or some other 104 | // primitive type which we can not resolve since it is not a class and 105 | // we will just bomb out with an error since we have no-where to go. 106 | $results[] = is_null($dependency->getClass()) 107 | ? $this->resolvePrimitive($dependency) 108 | : $this->resolveClass($dependency); 109 | ``` 110 | 111 | 这里是直接遍历整个数组,然后使用方法`$this->hasParameterOverride($dependency)`判断是否存在参数覆盖,我们看一下`hasParameterOverride`方法的源码: 112 | 113 | ```php 114 | protected function hasParameterOverride($dependency) 115 | { 116 | return array_key_exists( 117 | $dependency->name, $this->getLastParameterOverride() 118 | ); 119 | } 120 | ``` 121 | 122 | `getLastParameterOverride`是获取make时候存入with数组的参数。如果有覆盖,就将`$this->getParameterOverride($dependency)`中的值存入result数组,中断本次循环继续执行下一次循环。我们继续看一下`getParameterOverride`方法的源码: 123 | 124 | ```php 125 | protected function getParameterOverride($dependency) 126 | { 127 | return $this->getLastParameterOverride()[$dependency->name]; 128 | } 129 | ``` 130 | 131 | 还是通过 `getLastParameterOverride `获取 with 数组,然后在数组中通过 name获取。 132 | 133 | 如果`hasParameterOverride`方法返回值为false,判断这个类是不是存在于当前代码中,如果不存在,则使用`resolvePrimitive`方法,看看上下文绑定中有没有对应的值,再看看依赖自己有没有默认值。如果存在则使用`resolveClass`方法,就是使用make函数解析这个依赖。 134 | 135 | resolvePrimitive: 136 | 137 | ```php 138 | protected function resolvePrimitive(ReflectionParameter $parameter) 139 | { 140 | if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) { 141 | return $concrete instanceof Closure ? $concrete($this) : $concrete; 142 | } 143 | 144 | if ($parameter->isDefaultValueAvailable()) { 145 | return $parameter->getDefaultValue(); 146 | } 147 | 148 | $this->unresolvablePrimitive($parameter); 149 | } 150 | ``` 151 | 152 | 注意这个方法中参数$parameter的类型是:**ReflectionParameter**,我们在本节的前面已经给大家分析过怎样确定$parameter变量的的类型。 153 | 154 | resolveClass: 155 | 156 | ```php 157 | protected function resolveClass(ReflectionParameter $parameter) 158 | { 159 | try { 160 | return $this->make($parameter->getClass()->name); 161 | } 162 | 163 | // If we can not resolve the class instance, we will check to see if the value 164 | // is optional, and if it is we will return the optional parameter value as 165 | // the value of the dependency, similarly to how we do this with scalars. 166 | catch (BindingResolutionException $e) { 167 | if ($parameter->isOptional()) { 168 | return $parameter->getDefaultValue(); 169 | } 170 | 171 | throw $e; 172 | } 173 | } 174 | ``` 175 | 176 | `resolveDependencies`方法源自Container类的`build`方法,而`build`又是核心方法`resolve`中调用的一个子方法。这个方法之所以命名为resolveDependencies,正是因为Laravel框架底层实现"依赖注入"的方式就是使用PHP5之后出现的反射。实际上,使用反射来构建类,必然绕不过循环。因为通过反射提供的相关方法,只能获取到构造函数的参数列表,由于参数列表中的参数数目和参数类型是不确定的,这里只能通过循环去做处理。 177 | 178 | 下面是另一个处理"依赖注入"的典型方式: 179 | 180 | ```php 181 | dep1 = $dependence1; 206 | $this->dep2 = $dependence2; 207 | } 208 | 209 | } 210 | 211 | $constructor = new ReflectionMethod(myClass::class, '__construct'); 212 | $parameters = $constructor->getParameters(); 213 | 214 | $dependences = []; 215 | foreach ($parameters as $parameter) { 216 | $dependenceClass = (string) $parameter->getType(); 217 | $dependences[] = new $dependenceClass(); 218 | } 219 | 220 | $instance = new myClass(...$dependences); 221 | var_dump($instance); 222 | ``` 223 | 224 | 这段代码的运行结果如下: 225 | 226 | ```php 227 | object(myClass)#6 (2) { 228 | ["dep1":"myClass":private]=> 229 | object(Dependence1)#4 (0) { 230 | } 231 | ["dep2":"myClass":private]=> 232 | object(Dependence2)#5 (0) { 233 | } 234 | } 235 | ``` 236 | 237 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License 2 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 3 | 4 | Section 1 – Definitions. 5 | 6 | Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 7 | Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 8 | Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 9 | Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 10 | Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 11 | Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 12 | Licensor means the individual(s) or entity(ies) granting rights under this Public License. 13 | NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. 14 | Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 15 | Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 16 | You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 17 | Section 2 – Scope. 18 | 19 | License grant. 20 | Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 21 | reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and 22 | produce and reproduce, but not Share, Adapted Material for NonCommercial purposes only. 23 | Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 24 | Term. The term of this Public License is specified in Section 6(a). 25 | Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 26 | Downstream recipients. 27 | Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 28 | No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 29 | No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 30 | Other rights. 31 | 32 | Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 33 | Patent and trademark rights are not licensed under this Public License. 34 | To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. 35 | Section 3 – License Conditions. 36 | 37 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 38 | 39 | Attribution. 40 | 41 | If You Share the Licensed Material, You must: 42 | 43 | retain the following if it is supplied by the Licensor with the Licensed Material: 44 | identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 45 | a copyright notice; 46 | a notice that refers to this Public License; 47 | a notice that refers to the disclaimer of warranties; 48 | a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 49 | indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 50 | indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 51 | For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. 52 | You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 53 | If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 54 | Section 4 – Sui Generis Database Rights. 55 | 56 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 57 | 58 | for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only and provided You do not Share Adapted Material; 59 | if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 60 | You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 61 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 62 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 63 | 64 | Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 65 | To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 66 | The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 67 | Section 6 – Term and Termination. 68 | 69 | This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 70 | Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 71 | 72 | automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 73 | upon express reinstatement by the Licensor. 74 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 75 | For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 76 | Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 77 | Section 7 – Other Terms and Conditions. 78 | 79 | The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 80 | Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 81 | Section 8 – Interpretation. 82 | 83 | For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 84 | To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 85 | No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 86 | Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. -------------------------------------------------------------------------------- /04第四章:艰难的开始/README.md: -------------------------------------------------------------------------------- 1 | # 第四章:艰难的开始 2 | 3 | 继续看index.php后面的代码, 4 | 5 | ```php 6 | $app = require_once __DIR__.'/../bootstrap/app.php'; 7 | ... 8 | ... ... 9 | ``` 10 | 11 | 追踪app.php源码: 12 | 13 | ```php 14 | singleton( 43 | Illuminate\Contracts\Http\Kernel::class, 44 | App\Http\Kernel::class 45 | ); 46 | 47 | $app->singleton( 48 | Illuminate\Contracts\Console\Kernel::class, 49 | App\Console\Kernel::class 50 | ); 51 | 52 | $app->singleton( 53 | Illuminate\Contracts\Debug\ExceptionHandler::class, 54 | App\Exceptions\Handler::class 55 | ); 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Return The Application 60 | |-------------------------------------------------------------------------- 61 | | 62 | | This script returns the application instance. The instance is given to 63 | | the calling script so we can separate the building of the instances 64 | | from the actual running of the application and sending responses. 65 | | 66 | */ 67 | 68 | return $app; 69 | 70 | ``` 71 | 72 | >bootstrap/app.php 73 | 74 | 我们看到$app其实是类Application的一个实例,那么Application类又是怎样定义的呢?通过查看`vendor/laravel/framework/src/Illuminate/Foundation/Application.php`文件的源码,我们能了解到,Application类其实是负责整个应用调度的类,它起到了一个胶水的作用(将Laravel的各个组件粘贴在一起)。这一点,Laravel作者给出了明确的提示: 75 | 76 | ```php 77 | bootstrap/app.php 94 | 95 | serves as the "glue"就是"扮演胶水这个角色"的意思。 96 | 97 | 我们知道,`$app = new Illuminate\Foundation\Application($_ENV['APP_BASE_PATH'] ?? dirname(__DIR__));` 这行代码,表面上是创建一个Application的实例然后赋值给变量$app,实际上创建Application实例的同时,必然会调用Application类的构造函数。我们猜测,在构造函数中可能会做一些执行MVC之前必须做的基础工作,比如解析URL,初始化参数值等。 98 | 99 | 因此,我们继续追踪Application类的构造函数: 100 | 101 | ```php 102 | public function __construct($basePath = null) 103 | { 104 | if ($basePath) { 105 | $this->setBasePath($basePath); 106 | } 107 | 108 | $this->registerBaseBindings(); 109 | $this->registerBaseServiceProviders(); 110 | $this->registerCoreContainerAliases(); 111 | } 112 | ``` 113 | 114 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 115 | 116 | 从上到下依次执行了四个主要动作: 117 | 118 | - 设置基础目录路径 119 | 120 | - 注册基础绑定 121 | 122 | - 注册服务提供者 123 | 124 | - 注册核心别名类 125 | 126 | 127 | 接下来是就是setBasePath方法: 128 | 129 | ````php 130 | /** 131 | * Set the base path for the application. 132 | * 133 | * @param string $basePath 134 | * @return $this 135 | */ 136 | public function setBasePath($basePath) 137 | { 138 | $this->basePath = rtrim($basePath, '\/'); 139 | 140 | $this->bindPathsInContainer(); 141 | 142 | return $this; 143 | } 144 | ```` 145 | 146 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 147 | 148 | 程序赋值了basePath这个类的成员变量后,执行了另一个方法bindPathsInContainer: 149 | 150 | ````php 151 | /** 152 | * Bind all of the application paths in the container. 153 | * 154 | * @return void 155 | */ 156 | protected function bindPathsInContainer() 157 | { 158 | $this->instance('path', $this->path()); 159 | $this->instance('path.base', $this->basePath()); 160 | $this->instance('path.lang', $this->langPath()); 161 | $this->instance('path.config', $this->configPath()); 162 | $this->instance('path.public', $this->publicPath()); 163 | $this->instance('path.storage', $this->storagePath()); 164 | $this->instance('path.database', $this->databasePath()); 165 | $this->instance('path.resources', $this->resourcePath()); 166 | $this->instance('path.bootstrap', $this->bootstrapPath()); 167 | } 168 | ```` 169 | 170 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 171 | 172 | 我们看到,这里调用了另一个核心的方法instance,继续追踪: 173 | 174 | ````php 175 | /** 176 | * Register an existing instance as shared in the container. 177 | * 178 | * @param string $abstract 179 | * @param mixed $instance 180 | * @return mixed 181 | */ 182 | public function instance($abstract, $instance) 183 | { 184 | $this->removeAbstractAlias($abstract); 185 | 186 | $isBound = $this->bound($abstract); 187 | 188 | unset($this->aliases[$abstract]); 189 | 190 | // We'll check to determine if this type has been bound before, and if it has 191 | // we will fire the rebound callbacks registered with the container and it 192 | // can be updated with consuming classes that have gotten resolved here. 193 | $this->instances[$abstract] = $instance; 194 | 195 | if ($isBound) { 196 | $this->rebound($abstract); 197 | } 198 | 199 | return $instance; 200 | } 201 | ```` 202 | 203 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 204 | 205 | 在这一段代码中,我们把注意力集中到`this->instances[$abstract] = $instance;`这一行上,可以很清楚地看到,代码做的事情,就是简单地给类的成员变量instances(通过后面的操作可以看到该变量实际上是一个数组类型的变量),添加一个健值对。其中键名是传入的参数$abstract的参数值,键值是另一个参数$instance的参数值。由此,我们可以很清楚地看到,bindPathsInContainer方法,完成了多个路径的"键值对绑定"工作。 206 | 207 | > instance方法全部代码的详细解读,请参考【附录二】 208 | 209 | 那么,在框架需要用到路径的地方,必然会引用到这些键值对。如何验证呢?很简单,使用phpstorm编辑器的快捷键:ctrl + shift + f,全局搜索即可。我们以"path.storage"为例,在我们的blog项目中,全局搜索"path.storage",会发现搜索到的结果如下: 210 | 211 | ![](../images/pths.png) 212 | 213 | 【图4.1】 214 | 215 | 点开helpers.php文件中的第846行代码,追踪到下面这个方法: 216 | 217 | ````php 218 | /** 219 | * Get the path to the storage folder. 220 | * 221 | * @param string $path 222 | * @return string 223 | */ 224 | function storage_path($path = '') 225 | { 226 | return app('path.storage').($path ? DIRECTORY_SEPARATOR.$path : $path); 227 | } 228 | ```` 229 | 230 | > vendor\laravel\framework\src\Illuminate\Foundation\helpers.php 231 | 232 | 这里,让人感到疑惑的是:为什么获取"path.storage"这个"键值"的时候是直接将"path.storage"作为参数传入app这个函数的呢?为了弄清楚这个问题,我们继续去追踪app这个函数的具体实现: 233 | 234 | ````php 235 | /** 236 | * Get the available container instance. 237 | * 238 | * @param string|null $abstract 239 | * @param array $parameters 240 | * @return mixed|\Illuminate\Contracts\Foundation\Application 241 | */ 242 | function app($abstract = null, array $parameters = []) 243 | { 244 | if (is_null($abstract)) { 245 | return Container::getInstance(); 246 | } 247 | 248 | return Container::getInstance()->make($abstract, $parameters); 249 | } 250 | ```` 251 | 252 | > vendor\laravel\framework\src\Illuminate\Foundation\helpers.php 253 | 254 | 显然,我们发现在`app('path.storage')`这种方式的调用情况下,代码执行的是if之外的那个return语句。这样一来,我们必然会去查看Container这个类的`getInstance`方法,和`Container::getInstance()->make($abstract, $parameters)`这条语句做了什么: 255 | 256 | 首先是getInstance方法: 257 | 258 | ````php 259 | /** 260 | * Get the globally available instance of the container. 261 | * 262 | * @return static 263 | */ 264 | public static function getInstance() 265 | { 266 | if (is_null(static::$instance)) { 267 | static::$instance = new static; 268 | } 269 | 270 | return static::$instance; 271 | } 272 | ```` 273 | 274 | >vendor/laravel/framework/src/Illuminate/Container/Container.php 275 | 276 | 这里,我们看到一条比较生僻的php语句:`static::$instance = new static;`。其次,我们发现Container类的$instance变量设置成了静态变量,因此这是一个典型的单例实现(所有引用Container::getInstance()得到的实例,都指向同一个静态成员变量$instance)。 277 | 278 | ## new static 279 | 280 | 在一个类中,常见的是new self()操作,代表返回自身类的实例。 281 | 282 | 当父类中存在方法,然后每个子类继承于父类,调用这个方法会返回自身的实例化对象,如下: 283 | 284 | ````php 285 | class A { 286 | function create() { 287 | return new self(); 288 | } 289 | } 290 | 291 | class B extends A { 292 | 293 | } 294 | 295 | new B()->create(); // 返回类A的实例 296 | ```` 297 | 298 | 上例中,调用create方法,总是返回类A的实例。如果要返回调用者类B的实例呢?很简单: 299 | 300 | ````php 301 | class A { 302 | function create() { 303 | return new static(); 304 | } 305 | } 306 | 307 | class B extends A { 308 | 309 | } 310 | 311 | new B()->create(); // 返回类B的实例 312 | ```` 313 | 314 | 也就是说,只需要将`new self()`改成`new static()`就行了。上面这种使用static关键字的语法,就是PHP5.3新加入的特性:**延迟静态绑定**,我们可以简单理解为:由调用者来决定static后的对象该指向谁(谁调用就指向谁),和传统的static属性和方法不同,这里static指向的对象不能在编译阶段就确定下来,必须要等到执行阶段才能确定下来,所以叫"**延迟静态绑定**"。 315 | 316 | ## make方法 317 | 318 | 既然getInstance方法返回的是Container自身,那么找到make方法就很简单了,直接在Container.php中查找"function make"即可: 319 | 320 | ````php 321 | /** 322 | * Resolve the given type from the container. 323 | * 324 | * @param string $abstract 325 | * @param array $parameters 326 | * @return mixed 327 | * 328 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 329 | */ 330 | public function make($abstract, array $parameters = []) 331 | { 332 | return $this->resolve($abstract, $parameters); 333 | } 334 | ```` 335 | 336 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 337 | 338 | 继续追踪resolve方法: 339 | 340 | ````php 341 | /** 342 | * Resolve the given type from the container. 343 | * 344 | * @param string $abstract 345 | * @param array $parameters 346 | * @param bool $raiseEvents 347 | * @return mixed 348 | * 349 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 350 | */ 351 | protected function resolve($abstract, $parameters = [], $raiseEvents = true) 352 | { 353 | $abstract = $this->getAlias($abstract); 354 | 355 | $needsContextualBuild = ! empty($parameters) || ! is_null( 356 | $this->getContextualConcrete($abstract) 357 | ); 358 | 359 | // If an instance of the type is currently being managed as a singleton we'll 360 | // just return an existing instance instead of instantiating new instances 361 | // so the developer can keep using the same objects instance every time. 362 | if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { 363 | return $this->instances[$abstract]; 364 | } 365 | 366 | $this->with[] = $parameters; 367 | 368 | $concrete = $this->getConcrete($abstract); 369 | 370 | // We're ready to instantiate an instance of the concrete type registered for 371 | // the binding. This will instantiate the types, as well as resolve any of 372 | // its "nested" dependencies recursively until all have gotten resolved. 373 | if ($this->isBuildable($concrete, $abstract)) { 374 | $object = $this->build($concrete); 375 | } else { 376 | $object = $this->make($concrete); 377 | } 378 | 379 | // If we defined any extenders for this type, we'll need to spin through them 380 | // and apply them to the object being built. This allows for the extension 381 | // of services, such as changing configuration or decorating the object. 382 | foreach ($this->getExtenders($abstract) as $extender) { 383 | $object = $extender($object, $this); 384 | } 385 | 386 | // If the requested type is registered as a singleton we'll want to cache off 387 | // the instances in "memory" so we can return it later without creating an 388 | // entirely new instance of an object on each subsequent request for it. 389 | if ($this->isShared($abstract) && ! $needsContextualBuild) { 390 | $this->instances[$abstract] = $object; 391 | } 392 | 393 | if ($raiseEvents) { 394 | $this->fireResolvingCallbacks($abstract, $object); 395 | } 396 | 397 | // Before returning, we will also set the resolved flag to "true" and pop off 398 | // the parameter overrides for this build. After those two things are done 399 | // we will be ready to return back the fully constructed class instance. 400 | $this->resolved[$abstract] = true; 401 | 402 | array_pop($this->with); 403 | 404 | return $object; 405 | } 406 | ```` 407 | 408 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 409 | 410 | 要完全理解这段代码,我们必须对Container类本身有深入的分析和了解(参考【附录三】)。在本节中,我们只需要关注到"获取基础路径"的代码实现部分即可。通过阅读,我们很容易发现,下面这一小段代码,正是我们要找的部分: 411 | 412 | ````php 413 | if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { 414 | return $this->instances[$abstract]; 415 | } 416 | ```` 417 | 418 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 419 | 420 | 在前面基础路径的设置部分,代码正是将路径信息保存在了容器对象的成员变量instances中。 421 | 422 | 现在我们就来回答前面提到的问题:为什么获取path.storage这个"键值"的时候是直接将path.storage作为参数传入app这个函数的? 423 | 424 | 大家可以回到本章引用的【图4.1】,从这个图中,可以很明显得看到有php语句将"path.storage"传递给了instance方法。而instance方法会把这个传入的字符串作为键名存储到容器对象的成员变量instances中。当再次使用make方法获取这个键名对应的值时,是优先从容器对象的成员变量instances数组中获取,检测到有这个键名并且当前无需上下文构建时直接返回这个保存的键值。 425 | 426 | 至此,我们终于理解了四个动作中的第一个动作:设置基础目录路径。这一步完成之后,以后框架需要使用到基础路径的地方都会和这里产生联系。我们终于迈出了第一步。 -------------------------------------------------------------------------------- /07第七章:解析HTTP内核/README.md: -------------------------------------------------------------------------------- 1 | # 第七章:解析HTTP内核 2 | 3 | 现在我们来到了"第三阶段":从app中得到容器中装填好的内核实例(上一步我们已经把内核绑定到了容器中)。 4 | 5 | > 注意这里的"装填",实际是执行容器类中的bind方法,进行一次键值对绑定的意思。其中键值对中的值,并不是真实存在的对象实体,而是实现对象的闭包。 6 | 7 | 对应的是下面这行代码: 8 | 9 | ````php 10 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 11 | ```` 12 | 13 | 我们来重点看一下make方法: 14 | 15 | ````php 16 | /** 17 | * Resolve the given type from the container. 18 | * 19 | * @param string $abstract 20 | * @param array $parameters 21 | * @return mixed 22 | * 23 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 24 | */ 25 | public function make($abstract, array $parameters = []) 26 | { 27 | return $this->resolve($abstract, $parameters); 28 | } 29 | ```` 30 | 31 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 32 | 33 | 我们又重新回到了resolve方法,在第四章中,我们已经讲解过了resolve方法的代码。因此这里,我们就简单理解为,得到之前"装填"在容器上的内核实例即可。 34 | 35 | 我们需要重点关注一下Kernel类的结构,下面是App\Http\Kernel类的源代码: 36 | 37 | ````php 38 | [ 68 | \App\Http\Middleware\EncryptCookies::class, 69 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 70 | \Illuminate\Session\Middleware\StartSession::class, 71 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 72 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 73 | \App\Http\Middleware\VerifyCsrfToken::class, 74 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 75 | ], 76 | 77 | 'api' => [ 78 | 'throttle:60,1', 79 | 'bindings', 80 | ], 81 | ]; 82 | 83 | /** 84 | * The application's route middleware. 85 | * 86 | * These middleware may be assigned to groups or used individually. 87 | * 88 | * @var array 89 | */ 90 | protected $routeMiddleware = [ 91 | 'auth' => \App\Http\Middleware\Authenticate::class, 92 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 93 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 94 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 95 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 96 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 97 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 98 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 99 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 100 | ]; 101 | 102 | /** 103 | * The priority-sorted list of middleware. 104 | * 105 | * This forces non-global middleware to always be in the given order. 106 | * 107 | * @var array 108 | */ 109 | protected $middlewarePriority = [ 110 | \Illuminate\Session\Middleware\StartSession::class, 111 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 112 | \App\Http\Middleware\Authenticate::class, 113 | \Illuminate\Session\Middleware\AuthenticateSession::class, 114 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 115 | \Illuminate\Auth\Middleware\Authorize::class, 116 | ]; 117 | } 118 | ```` 119 | 120 | > app/Http/Kernel.php 121 | 122 | 可以看到Kernel类只是在继承HttpKernel类的基础上,自定义了一些成员变量。我们继续看这个HttpKernel类: 123 | 124 | ````php 125 | app = $app; 216 | $this->router = $router; 217 | 218 | $router->middlewarePriority = $this->middlewarePriority; 219 | 220 | foreach ($this->middlewareGroups as $key => $middleware) { 221 | $router->middlewareGroup($key, $middleware); 222 | } 223 | 224 | foreach ($this->routeMiddleware as $key => $middleware) { 225 | $router->aliasMiddleware($key, $middleware); 226 | } 227 | } 228 | 229 | /** 230 | * Handle an incoming HTTP request. 231 | * 232 | * @param \Illuminate\Http\Request $request 233 | * @return \Illuminate\Http\Response 234 | */ 235 | public function handle($request) 236 | { 237 | try { 238 | $request->enableHttpMethodParameterOverride(); 239 | 240 | $response = $this->sendRequestThroughRouter($request); 241 | } catch (Exception $e) { 242 | $this->reportException($e); 243 | 244 | $response = $this->renderException($request, $e); 245 | } catch (Throwable $e) { 246 | $this->reportException($e = new FatalThrowableError($e)); 247 | 248 | $response = $this->renderException($request, $e); 249 | } 250 | 251 | $this->app['events']->dispatch( 252 | new Events\RequestHandled($request, $response) 253 | ); 254 | 255 | return $response; 256 | } 257 | 258 | /** 259 | * Send the given request through the middleware / router. 260 | * 261 | * @param \Illuminate\Http\Request $request 262 | * @return \Illuminate\Http\Response 263 | */ 264 | protected function sendRequestThroughRouter($request) 265 | { 266 | $this->app->instance('request', $request); 267 | 268 | Facade::clearResolvedInstance('request'); 269 | 270 | $this->bootstrap(); 271 | 272 | return (new Pipeline($this->app)) 273 | ->send($request) 274 | ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 275 | ->then($this->dispatchToRouter()); 276 | } 277 | 278 | /** 279 | * Bootstrap the application for HTTP requests. 280 | * 281 | * @return void 282 | */ 283 | public function bootstrap() 284 | { 285 | if (! $this->app->hasBeenBootstrapped()) { 286 | $this->app->bootstrapWith($this->bootstrappers()); 287 | } 288 | } 289 | 290 | /** 291 | * Get the route dispatcher callback. 292 | * 293 | * @return \Closure 294 | */ 295 | protected function dispatchToRouter() 296 | { 297 | return function ($request) { 298 | $this->app->instance('request', $request); 299 | 300 | return $this->router->dispatch($request); 301 | }; 302 | } 303 | 304 | /** 305 | * Call the terminate method on any terminable middleware. 306 | * 307 | * @param \Illuminate\Http\Request $request 308 | * @param \Illuminate\Http\Response $response 309 | * @return void 310 | */ 311 | public function terminate($request, $response) 312 | { 313 | $this->terminateMiddleware($request, $response); 314 | 315 | $this->app->terminate(); 316 | } 317 | 318 | /** 319 | * Call the terminate method on any terminable middleware. 320 | * 321 | * @param \Illuminate\Http\Request $request 322 | * @param \Illuminate\Http\Response $response 323 | * @return void 324 | */ 325 | protected function terminateMiddleware($request, $response) 326 | { 327 | $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge( 328 | $this->gatherRouteMiddleware($request), 329 | $this->middleware 330 | ); 331 | 332 | foreach ($middlewares as $middleware) { 333 | if (! is_string($middleware)) { 334 | continue; 335 | } 336 | 337 | [$name] = $this->parseMiddleware($middleware); 338 | 339 | $instance = $this->app->make($name); 340 | 341 | if (method_exists($instance, 'terminate')) { 342 | $instance->terminate($request, $response); 343 | } 344 | } 345 | } 346 | 347 | /** 348 | * Gather the route middleware for the given request. 349 | * 350 | * @param \Illuminate\Http\Request $request 351 | * @return array 352 | */ 353 | protected function gatherRouteMiddleware($request) 354 | { 355 | if ($route = $request->route()) { 356 | return $this->router->gatherRouteMiddleware($route); 357 | } 358 | 359 | return []; 360 | } 361 | 362 | /** 363 | * Parse a middleware string to get the name and parameters. 364 | * 365 | * @param string $middleware 366 | * @return array 367 | */ 368 | protected function parseMiddleware($middleware) 369 | { 370 | [$name, $parameters] = array_pad(explode(':', $middleware, 2), 2, []); 371 | 372 | if (is_string($parameters)) { 373 | $parameters = explode(',', $parameters); 374 | } 375 | 376 | return [$name, $parameters]; 377 | } 378 | 379 | /** 380 | * Determine if the kernel has a given middleware. 381 | * 382 | * @param string $middleware 383 | * @return bool 384 | */ 385 | public function hasMiddleware($middleware) 386 | { 387 | return in_array($middleware, $this->middleware); 388 | } 389 | 390 | /** 391 | * Add a new middleware to beginning of the stack if it does not already exist. 392 | * 393 | * @param string $middleware 394 | * @return $this 395 | */ 396 | public function prependMiddleware($middleware) 397 | { 398 | if (array_search($middleware, $this->middleware) === false) { 399 | array_unshift($this->middleware, $middleware); 400 | } 401 | 402 | return $this; 403 | } 404 | 405 | /** 406 | * Add a new middleware to end of the stack if it does not already exist. 407 | * 408 | * @param string $middleware 409 | * @return $this 410 | */ 411 | public function pushMiddleware($middleware) 412 | { 413 | if (array_search($middleware, $this->middleware) === false) { 414 | $this->middleware[] = $middleware; 415 | } 416 | 417 | return $this; 418 | } 419 | 420 | /** 421 | * Get the bootstrap classes for the application. 422 | * 423 | * @return array 424 | */ 425 | protected function bootstrappers() 426 | { 427 | return $this->bootstrappers; 428 | } 429 | 430 | /** 431 | * Report the exception to the exception handler. 432 | * 433 | * @param \Exception $e 434 | * @return void 435 | */ 436 | protected function reportException(Exception $e) 437 | { 438 | $this->app[ExceptionHandler::class]->report($e); 439 | } 440 | 441 | /** 442 | * Render the exception to a response. 443 | * 444 | * @param \Illuminate\Http\Request $request 445 | * @param \Exception $e 446 | * @return \Symfony\Component\HttpFoundation\Response 447 | */ 448 | protected function renderException($request, Exception $e) 449 | { 450 | return $this->app[ExceptionHandler::class]->render($request, $e); 451 | } 452 | 453 | /** 454 | * Get the application's route middleware groups. 455 | * 456 | * @return array 457 | */ 458 | public function getMiddlewareGroups() 459 | { 460 | return $this->middlewareGroups; 461 | } 462 | 463 | /** 464 | * Get the application's route middleware. 465 | * 466 | * @return array 467 | */ 468 | public function getRouteMiddleware() 469 | { 470 | return $this->routeMiddleware; 471 | } 472 | 473 | /** 474 | * Get the Laravel application instance. 475 | * 476 | * @return \Illuminate\Contracts\Foundation\Application 477 | */ 478 | public function getApplication() 479 | { 480 | return $this->app; 481 | } 482 | } 483 | ```` 484 | 485 | > vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 486 | 487 | 这里,通过查看这个类的构造函数和handle方法,我们大致能看出来。Kernel类就是负责解析HTTP请求的核心类。关于这个类的详细分析,我们很快会在下一章为大家讲解。 -------------------------------------------------------------------------------- /附录十一:ServiceProvider类的注册和引导/README.md: -------------------------------------------------------------------------------- 1 | # 附录十一:ServiceProvider类的注册和引导 2 | 服务提供器是所有 Laravel 应用程序引导中心。你的应用程序自定义的服务、第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注册(register)和引导(boot)的。 3 | 4 | 拿一个Laravel框架自带的服务提供器来举例子 5 | 6 | ``` 7 | class BroadcastServiceProvider extends ServiceProvider 8 | { 9 | protected $defer = true; 10 | public function register() 11 | { 12 | $this->app->singleton(BroadcastManager::class, function ($app) { 13 | return new BroadcastManager($app); 14 | }); 15 | $this->app->singleton(BroadcasterContract::class, function ($app) { 16 | return $app->make(BroadcastManager::class)->connection(); 17 | }); 18 | //将BroadcastingFactory::class设置为BroadcastManager::class的别名 19 | $this->app->alias( 20 | BroadcastManager::class, BroadcastingFactory::class 21 | ); 22 | } 23 | public function provides() 24 | { 25 | return [ 26 | BroadcastManager::class, 27 | BroadcastingFactory::class, 28 | BroadcasterContract::class, 29 | ]; 30 | } 31 | } 32 | ``` 33 | 在服务提供器`BroadcastServiceProvider`的`register`中, 为`BroadcastingFactory`的类名绑定了类实现BroadcastManager,这样就能通过服务容器来make出通过`BroadcastingFactory::class`绑定的服务`BroadcastManger`对象供应用程序使用了。 34 | 35 | 本文主要时来梳理一下laravel是如何注册、和初始化这些服务的,关于如何编写自己的服务提供器,可以参考[官方文档](https://d.laravel-china.org/docs/5.5/providers#deferred-providers) 36 | 37 | ## BootStrap 38 | 39 | 首先laravel注册和引导应用需要的服务是发生在寻找路由处理客户端请求之前的Bootstrap阶段的,在框架的入口文件里我们可以看到,框架在实例化了`Application`对象后从服务容器中解析出了`HTTP Kernel`对象 40 | 41 | ``` 42 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 43 | 44 | $response = $kernel->handle( 45 | $request = Illuminate\Http\Request::capture() 46 | ); 47 | ``` 48 | 在Kernel处理请求时会先让请求通过中间件然后在发送请求给路由对应的控制器方法, 在这之前有一个BootStrap阶段来引导启动Laravel应用程序,如下面代码所示。 49 | 50 | ``` 51 | public function handle($request) 52 | { 53 | ...... 54 | $response = $this->sendRequestThroughRouter($request); 55 | ...... 56 | 57 | return $response; 58 | } 59 | ``` 60 | 61 | ``` 62 | protected function sendRequestThroughRouter($request) 63 | { 64 | $this->app->instance('request', $request); 65 | 66 | Facade::clearResolvedInstance('request'); 67 | 68 | $this->bootstrap(); 69 | 70 | return (new Pipeline($this->app)) 71 | ->send($request) 72 | ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 73 | ->then($this->dispatchToRouter()); 74 | } 75 | 76 | //引导启动Laravel应用程序 77 | public function bootstrap() 78 | { 79 | if (! $this->app->hasBeenBootstrapped()) { 80 | /**依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数 81 | $bootstrappers = [ 82 | 'Illuminate\Foundation\Bootstrap\DetectEnvironment', 83 | 'Illuminate\Foundation\Bootstrap\LoadConfiguration', 84 | 'Illuminate\Foundation\Bootstrap\ConfigureLogging', 85 | 'Illuminate\Foundation\Bootstrap\HandleExceptions', 86 | 'Illuminate\Foundation\Bootstrap\RegisterFacades', 87 | 'Illuminate\Foundation\Bootstrap\RegisterProviders', 88 | 'Illuminate\Foundation\Bootstrap\BootProviders', 89 | ];*/ 90 | $this->app->bootstrapWith($this->bootstrappers()); 91 | } 92 | } 93 | ``` 94 | 上面bootstrap中会分别执行每一个bootstrapper的bootstrap方法来引导启动应用程序的各个部分 95 | 96 | 1. DetectEnvironment 检查环境 97 | 2. LoadConfiguration 加载应用配置 98 | 3. ConfigureLogging 配置日至 99 | 4. HandleException 注册异常处理的Handler 100 | 5. RegisterFacades 注册Facades 101 | 6. RegisterProviders 注册Providers 102 | 7. BootProviders 启动Providers 103 | 104 | ``` 105 | namespace Illuminate\Foundation; 106 | 107 | class Application extends Container implements ... 108 | { 109 | public function bootstrapWith(array $bootstrappers) 110 | { 111 | $this->hasBeenBootstrapped = true; 112 | 113 | foreach ($bootstrappers as $bootstrapper) { 114 | $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); 115 | 116 | $this->make($bootstrapper)->bootstrap($this); 117 | 118 | $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | 启动应用程序的最后两部就是注册服务提供这和启动提供者,如果对前面几个阶段具体时怎么实现的可以参考[这篇文章](https://segmentfault.com/a/1190000006946685#articleHeader5)。在这里我们主要关注服务提供器的注册和启动。 125 | 126 | 127 | 先来看注册服务提供器,服务提供器的注册由类 `\Illuminate\Foundation\Bootstrap\RegisterProviders::class` 负责,该类用于加载所有服务提供器的 register 函数,并保存延迟加载的服务的信息,以便实现延迟加载。 128 | 129 | ``` 130 | class RegisterProviders 131 | { 132 | public function bootstrap(Application $app) 133 | { 134 | //调用了Application的registerConfiguredProviders() 135 | $app->registerConfiguredProviders(); 136 | } 137 | } 138 | 139 | class Application extends Container implements ApplicationContract, HttpKernelInterface 140 | { 141 | public function registerConfiguredProviders() 142 | { 143 | (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) 144 | ->load($this->config['app.providers']); 145 | } 146 | 147 | public function getCachedServicesPath() 148 | { 149 | return $this->bootstrapPath().'/cache/services.php'; 150 | } 151 | } 152 | ``` 153 | 可以看出,所有服务提供器都在配置文件 app.php 文件的 providers 数组中。类 ProviderRepository 负责所有的服务加载功能: 154 | ``` 155 | class ProviderRepository 156 | { 157 | public function load(array $providers) 158 | { 159 | $manifest = $this->loadManifest(); 160 | if ($this->shouldRecompile($manifest, $providers)) { 161 | $manifest = $this->compileManifest($providers); 162 | } 163 | foreach ($manifest['when'] as $provider => $events) { 164 | $this->registerLoadEvents($provider, $events); 165 | } 166 | foreach ($manifest['eager'] as $provider) { 167 | $this->app->register($provider); 168 | } 169 | $this->app->addDeferredServices($manifest['deferred']); 170 | } 171 | } 172 | ``` 173 | `loadManifest()`会加载服务提供器缓存文件`services.php`,如果框架是第一次启动时没有这个文件的,或者是缓存文件中的providers数组项与`config/app.php`里的providers数组项不一致都会编译生成`services.php`。 174 | ``` 175 | //判断是否需要编译生成services文件 176 | public function shouldRecompile($manifest, $providers) 177 | { 178 | return is_null($manifest) || $manifest['providers'] != $providers; 179 | } 180 | 181 | //编译生成文件的具体过程 182 | protected function compileManifest($providers) 183 | { 184 | $manifest = $this->freshManifest($providers); 185 | foreach ($providers as $provider) { 186 | $instance = $this->createProvider($provider); 187 | if ($instance->isDeferred()) { 188 | foreach ($instance->provides() as $service) { 189 | $manifest['deferred'][$service] = $provider; 190 | } 191 | $manifest['when'][$provider] = $instance->when(); 192 | } 193 | else { 194 | $manifest['eager'][] = $provider; 195 | } 196 | } 197 | return $this->writeManifest($manifest); 198 | } 199 | 200 | 201 | protected function freshManifest(array $providers) 202 | { 203 | return ['providers' => $providers, 'eager' => [], 'deferred' => []]; 204 | } 205 | ``` 206 | - 缓存文件中 providers 放入了所有自定义和框架核心的服务。 207 | - 如果服务提供器是需要立即注册的,那么将会放入缓存文件中 eager 数组中。 208 | - 如果服务提供器是延迟加载的,那么其函数 provides() 通常会提供服务别名,这个服务别名通常是向服务容器中注册的别名,别名将会放入缓存文件的 deferred 数组中,与真正要注册的服务提供器组成一个键值对。 209 | - 延迟加载如果由 event 事件激活,那么可以在 when 函数中写入事件类,并写入缓存文件的 when 数组中。 210 | 211 | 生成的缓存文件内容如下: 212 | ``` 213 | array ( 214 | 'providers' => 215 | array ( 216 | 0 => 'Illuminate\\Auth\\AuthServiceProvider', 217 | 1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 218 | ... 219 | ), 220 | 221 | 'eager' => 222 | array ( 223 | 0 => 'Illuminate\\Auth\\AuthServiceProvider', 224 | 1 => 'Illuminate\\Cookie\\CookieServiceProvider', 225 | ... 226 | ), 227 | 228 | 'deferred' => 229 | array ( 230 | 'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 231 | 'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 232 | ... 233 | ), 234 | 235 | 'when' => 236 | array ( 237 | 'Illuminate\\Broadcasting\\BroadcastServiceProvider' => 238 | array ( 239 | ), 240 | ... 241 | ) 242 | ``` 243 | ## 事件触发时注册延迟服务提供器 244 | 245 | 延迟服务提供器除了利用 IOC 容器解析服务方式激活,还可以利用 Event 事件来激活: 246 | ``` 247 | protected function registerLoadEvents($provider, array $events) 248 | { 249 | if (count($events) < 1) { 250 | return; 251 | } 252 | $this->app->make('events')->listen($events, function () use ($provider) { 253 | $this->app->register($provider); 254 | }); 255 | } 256 | ``` 257 | ## 即时注册服务提供器 258 | 需要即时注册的服务提供器的register方法由Application的register方法里来调用: 259 | ``` 260 | class Application extends Container implements ApplicationContract, HttpKernelInterface 261 | { 262 | public function register($provider, $options = [], $force = false) 263 | { 264 | if (($registered = $this->getProvider($provider)) && ! $force) { 265 | return $registered; 266 | } 267 | if (is_string($provider)) { 268 | $provider = $this->resolveProvider($provider); 269 | } 270 | if (method_exists($provider, 'register')) { 271 | $provider->register(); 272 | } 273 | $this->markAsRegistered($provider); 274 | if ($this->booted) { 275 | $this->bootProvider($provider); 276 | } 277 | return $provider; 278 | } 279 | 280 | public function getProvider($provider) 281 | { 282 | $name = is_string($provider) ? $provider : get_class($provider); 283 | return Arr::first($this->serviceProviders, function ($value) use ($name) { 284 | return $value instanceof $name; 285 | }); 286 | } 287 | 288 | public function resolveProvider($provider) 289 | { 290 | eturn new $provider($this); 291 | } 292 | 293 | protected function markAsRegistered($provider) 294 | { 295 | //这个属性在稍后booting服务时会用到 296 | $this->serviceProviders[] = $provider; 297 | $this->loadedProviders[get_class($provider)] = true; 298 | } 299 | 300 | protected function bootProvider(ServiceProvider $provider) 301 | { 302 | if (method_exists($provider, 'boot')) { 303 | return $this->call([$provider, 'boot']); 304 | } 305 | } 306 | } 307 | ``` 308 | 可以看出,服务提供器的注册过程: 309 | 310 | - 判断当前服务提供器是否被注册过,如注册过直接返回对象 311 | - 解析服务提供器 312 | - 调用服务提供器的 register 函数 313 | - 标记当前服务提供器已经注册完毕 314 | - 若框架已经加载注册完毕所有的服务容器,那么就启动服务提供器的 boot 函数,该函数由于是 call 调用,所以支持依赖注入。 315 | 316 | 317 | ## 服务解析时注册延迟服务提供器 318 | 延迟服务提供器首先需要添加到 Application 中 319 | ``` 320 | public function addDeferredServices(array $services) 321 | { 322 | $this->deferredServices = array_merge($this->deferredServices, $services); 323 | } 324 | ``` 325 | 我们之前说过,延迟服务提供器的激活注册有两种方法:事件与服务解析。 326 | 327 | 当特定的事件被激发后,就会调用 Application 的 register 函数,进而调用服务提供器的 register 函数,实现服务的注册。 328 | 329 | 当利用 Ioc 容器解析服务名时,例如解析服务名 BroadcastingFactory: 330 | 331 | ``` 332 | class BroadcastServiceProvider extends ServiceProvider 333 | { 334 | protected $defer = true; 335 | 336 | public function provides() 337 | { 338 | return [ 339 | BroadcastManager::class, 340 | BroadcastingFactory::class, 341 | BroadcasterContract::class, 342 | ]; 343 | } 344 | } 345 | ``` 346 | 347 | 在Application的make方法里会通过别名BroadcastingFactory查找是否有对应的延迟注册的服务提供器,如果有的话那么 348 | 就先通过registerDeferredProvider方法注册服务提供器。 349 | 350 | ``` 351 | class Application extends Container implements ApplicationContract, HttpKernelInterface 352 | { 353 | public function make($abstract) 354 | { 355 | $abstract = $this->getAlias($abstract); 356 | if (isset($this->deferredServices[$abstract])) { 357 | $this->loadDeferredProvider($abstract); 358 | } 359 | return parent::make($abstract); 360 | } 361 | 362 | public function loadDeferredProvider($service) 363 | { 364 | if (! isset($this->deferredServices[$service])) { 365 | return; 366 | } 367 | $provider = $this->deferredServices[$service]; 368 | if (! isset($this->loadedProviders[$provider])) { 369 | $this->registerDeferredProvider($provider, $service); 370 | } 371 | } 372 | } 373 | ``` 374 | 375 | 由 deferredServices 数组可以得知,BroadcastingFactory 为延迟服务,接着程序会利用函数 loadDeferredProvider 来加载延迟服务提供器,调用服务提供器的 register 函数,若当前的框架还未注册完全部服务。那么将会放入服务启动的回调函数中,以待服务启动时调用: 376 | 377 | ``` 378 | public function registerDeferredProvider($provider, $service = null) 379 | { 380 | if ($service) { 381 | unset($this->deferredServices[$service]); 382 | } 383 | $this->register($instance = new $provider($this)); 384 | if (! $this->booted) { 385 | $this->booting(function () use ($instance) { 386 | $this->bootProvider($instance); 387 | }); 388 | } 389 | } 390 | ``` 391 | 还是拿服务提供器BroadcastServiceProvider来举例: 392 | 393 | ``` 394 | class BroadcastServiceProvider extends ServiceProvider 395 | { 396 | protected $defer = true; 397 | public function register() 398 | { 399 | $this->app->singleton(BroadcastManager::class, function ($app) { 400 | return new BroadcastManager($app); 401 | }); 402 | $this->app->singleton(BroadcasterContract::class, function ($app) { 403 | return $app->make(BroadcastManager::class)->connection(); 404 | }); 405 | //将BroadcastingFactory::class设置为BroadcastManager::class的别名 406 | $this->app->alias( 407 | BroadcastManager::class, BroadcastingFactory::class 408 | ); 409 | } 410 | public function provides() 411 | { 412 | return [ 413 | BroadcastManager::class, 414 | BroadcastingFactory::class, 415 | BroadcasterContract::class, 416 | ]; 417 | } 418 | } 419 | ``` 420 | 421 | 函数 register 为类 `BroadcastingFactory` 向 [服务容器][1]绑定了特定的实现类 `BroadcastManager`,`Application`中的 `make` 函数里执行`parent::make($abstract)` 通过服务容器的make就会正确的解析出服务 `BroadcastingFactory`。 422 | 423 | 因此函数 `provides()` 返回的元素一定都是 `register()` 向 [服务容器][2]中绑定的类名或者别名。这样当我们利用App::make() 解析这些类名的时候,[服务容器][3]才会根据服务提供器的 register() 函数中绑定的实现类,正确解析出服务功能。 424 | 425 | ## 启动Application 426 | Application的启动由类 `\Illuminate\Foundation\Bootstrap\BootProviders` 负责: 427 | 428 | ``` 429 | class BootProviders 430 | { 431 | public function bootstrap(Application $app) 432 | { 433 | $app->boot(); 434 | } 435 | } 436 | class Application extends Container implements ApplicationContract, HttpKernelInterface 437 | { 438 | public function boot() 439 | { 440 | if ($this->booted) { 441 | return; 442 | } 443 | $this->fireAppCallbacks($this->bootingCallbacks); 444 | array_walk($this->serviceProviders, function ($p) { 445 | $this->bootProvider($p); 446 | }); 447 | $this->booted = true; 448 | $this->fireAppCallbacks($this->bootedCallbacks); 449 | } 450 | 451 | protected function bootProvider(ServiceProvider $provider) 452 | { 453 | if (method_exists($provider, 'boot')) { 454 | return $this->call([$provider, 'boot']); 455 | } 456 | } 457 | } 458 | ``` 459 | 460 | 引导应用Application的serviceProviders属性中记录的所有服务提供器,就是依次调用这些服务提供器的boot方法,引导完成后`$this->booted = true` 就代表应用`Application`正式启动了,可以开始处理请求了。这里额外说一句,之所以等到所有服务提供器都注册完后再来进行引导是因为有可能在一个服务提供器的boot方法里调用了其他服务提供器注册的服务,所以需要等到所有即时注册的服务提供器都register完成后再来boot。 461 | 462 | 463 | >本文转载自github 464 | > 465 | >原文链接:https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/ServiceProvider.md -------------------------------------------------------------------------------- /06第六章:继续前行/README.md: -------------------------------------------------------------------------------- 1 | # 第六章:继续前行 2 | 3 | 我们继续分析第四章中提到的四个主要动作中的第三个动作:注册服务提供者, 4 | 5 | - ~~设置基础目录路径~~ 6 | 7 | - ~~注册基础绑定~~ 8 | - 注册服务提供者 9 | - 注册核心别名类 10 | 11 | 对应的是下面这行代码: 12 | 13 | ````php 14 | $this->registerBaseServiceProviders(); 15 | ```` 16 | 17 | 追踪这个方法的具体实现: 18 | 19 | ````php 20 | /** 21 | * Register all of the base service providers. 22 | * 23 | * @return void 24 | */ 25 | protected function registerBaseServiceProviders() 26 | { 27 | $this->register(new EventServiceProvider($this)); 28 | $this->register(new LogServiceProvider($this)); 29 | $this->register(new RoutingServiceProvider($this)); 30 | } 31 | ```` 32 | 33 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 34 | 35 | 继续追踪register: 36 | 37 | ````php 38 | /** 39 | * Register a service provider with the application. 40 | * 41 | * @param \Illuminate\Support\ServiceProvider|string $provider 42 | * @param bool $force 43 | * @return \Illuminate\Support\ServiceProvider 44 | */ 45 | public function register($provider, $force = false) 46 | { 47 | if (($registered = $this->getProvider($provider)) && ! $force) { 48 | return $registered; 49 | } 50 | 51 | // If the given "provider" is a string, we will resolve it, passing in the 52 | // application instance automatically for the developer. This is simply 53 | // a more convenient way of specifying your service provider classes. 54 | if (is_string($provider)) { 55 | $provider = $this->resolveProvider($provider); 56 | } 57 | 58 | $provider->register(); 59 | 60 | // If there are bindings / singletons set as properties on the provider we 61 | // will spin through them and register them with the application, which 62 | // serves as a convenience layer while registering a lot of bindings. 63 | if (property_exists($provider, 'bindings')) { 64 | foreach ($provider->bindings as $key => $value) { 65 | $this->bind($key, $value); 66 | } 67 | } 68 | 69 | if (property_exists($provider, 'singletons')) { 70 | foreach ($provider->singletons as $key => $value) { 71 | $this->singleton($key, $value); 72 | } 73 | } 74 | 75 | $this->markAsRegistered($provider); 76 | 77 | // If the application has already booted, we will call this boot method on 78 | // the provider class so it has an opportunity to do its boot logic and 79 | // will be ready for any usage by this developer's application logic. 80 | if ($this->isBooted()) { 81 | $this->bootProvider($provider); 82 | } 83 | 84 | return $provider; 85 | } 86 | ```` 87 | 88 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 89 | 90 | 要通读这个方法的所有代码,请参考【附录六】。在这一章中,我们关注的核心代码是下面这句: 91 | 92 | ````php 93 | $provider->register(); 94 | ```` 95 | 96 | 即register方法的主要动作,就是执行provider对象本身的register方法,然后就返回对象实例本身了。于是我们继续追踪provider对象身上的register方法。由于provider对象指向哪个实例是不确定(由传入的参数决定)的,因此我们以第一个EventServiceProvider为例,进入这个类中,继续去追踪它的register方法: 97 | 98 | ````php 99 | /** 100 | * Register the service provider. 101 | * 102 | * @return void 103 | */ 104 | public function register() 105 | { 106 | $this->app->singleton('events', function ($app) { 107 | return (new Dispatcher($app))->setQueueResolver(function () use ($app) { 108 | return $app->make(QueueFactoryContract::class); 109 | }); 110 | }); 111 | } 112 | ```` 113 | 114 | > vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php 115 | 116 | 我们看到,register方法,执行了一次singleton,singleton方法我们在第五章中已经讲解过,它的主要作用就是往app这个共享的容器身上挂载键值对。这里,我们看到events这个"键"再次被挂载到一个闭包值上了。 117 | 118 | 除此之外,我们还要重点关注一下,实例化provider的过程中,provider类的构造函数执行了什么,我们发现构造函数在父类\Illuminate\Support\ServiceProvider中: 119 | 120 | ````php 121 | /** 122 | * Create a new service provider instance. 123 | * 124 | * @param \Illuminate\Contracts\Foundation\Application $app 125 | * @return void 126 | */ 127 | public function __construct($app) 128 | { 129 | $this->app = $app; 130 | } 131 | ```` 132 | 133 | > vendor/laravel/framework/src/Illuminate/Support/ServiceProvider.php 134 | 135 | 我们看到,这里只是简单地将传入的app变量值赋值给类的成员变量app。查看每个provider的register方法,你会发现,它们都是使用`$this->app->singleton...`这种方式去实现具体的register动作。我们看到,不管框架在"注册基础服务提供者"这个步骤中执行了多少次register,最后这些注册的服务类,都会被容器app"接收",将来也是通过调用app去进行具体的实现。 136 | 137 | 此外,还值得注意的是,我们发现了下面这段代码: 138 | 139 | ````php 140 | if ($this->isBooted()) { 141 | $this->bootProvider($provider); 142 | } 143 | ```` 144 | 145 | 大家可以通过"var_dump中断测试"(参考【附录一】)来测试一下,在默认情况下,注册第一个EventServiceProvider时,`$this->isBooted()`这里返回值是false。 146 | 147 | 也就是说,最开始register的时候,并没有走到这个if里面去。然而在if里面加exit语句,刷新应用主页时,代码却能成功进入到if中去。 148 | 149 | 如下,我们进行两次var_dump中断测试: 150 | 151 | 第一次: 152 | 153 | ````php 154 | var_dump($provider); 155 | var_dump($this->isBooted()); 156 | exit; 157 | if ($this->isBooted()) { 158 | $this->bootProvider($provider); 159 | } 160 | ```` 161 | 162 | 输出结果: 163 | 164 | ![](../images/test_04.png) 165 | 166 | 【图6.1】 167 | 168 | 第二次: 169 | 170 | ````php 171 | if ($this->isBooted()) { 172 | var_dump($provider);exit('ccc'); 173 | $this->bootProvider($provider); 174 | } 175 | ```` 176 | 177 | 输出结果: 178 | 179 | ![](../images/test_05.png) 180 | 181 | 【图6.2】 182 | 183 | 由于输出的字符串太长,我们仅截取部分显示在这里。 184 | 185 | 很明显,register方法至少被执行了两次:一次是应用启动时,一次是应用启动后。而且我们发现了一个比较奇怪的现象:第二次var_dump中断测试时页面要加载较长时间才能运行完成(对比第一次有明显的延时)。这是什么原因造成的呢? 186 | 187 | 要了解事情的真相,还得从var_dump打印的内容开始入手,按照我们的猜想,应用启动后,输出一个provider对象信息后,程序马上就退出(exit)了,没有道理有这么明显的延时。 188 | 189 | 现在我们来仔细查看var_dump语句输出的内容: 190 | 191 | ````php 192 | object(Illuminate\Hashing\HashServiceProvider)#288 (2) { 193 | ["app":protected]=> 194 | object(Illuminate\Foundation\Application)#2 (31) { 195 | ["basePath":protected]=> 196 | string(23) "/home/vagrant/code/blog" 197 | ["hasBeenBootstrapped":protected]=> 198 | bool(true) 199 | ["booted":protected]=> 200 | bool(true) 201 | ["bootingCallbacks":protected]=> 202 | array(0) { 203 | } 204 | ["bootedCallbacks":protected]=> 205 | array(1) { 206 | [0]=> 207 | object(Closure)#178 (1) { 208 | ["this"]=> 209 | object(App\Providers\RouteServiceProvider)#130 (3) { 210 | ["namespace":protected]=> 211 | string(20) "App\Http\Controllers" 212 | ["app":protected]=> 213 | *RECURSION* 214 | ["defer":protected]=> 215 | bool(false) 216 | } 217 | } 218 | } 219 | ["terminatingCallbacks":protected]=> 220 | array(0) { 221 | } 222 | ["serviceProviders":protected]=> 223 | array(25) { 224 | [0]=> 225 | object(Illuminate\Events\EventServiceProvider)#8 (2) { 226 | ["app":protected]=> 227 | *RECURSION* 228 | ["defer":protected]=> 229 | bool(false) 230 | } 231 | [1]=> 232 | object(Illuminate\Log\LogServiceProvider)#10 (2) { 233 | ["app":protected]=> 234 | *RECURSION* 235 | ["defer":protected]=> 236 | bool(false) 237 | } 238 | [2]=> 239 | ... 240 | ... ... 241 | ```` 242 | 243 | 我们看到,HashServiceProvider对象里面包含app保护成员,这个保护成员正好就是全局唯一的容器对象,并且在很多其他地方我们都看到了:\*RECURSION\*。这是因为每一个ServiceProvider类型的对象都会将app这个容器放到自己的app保护成员变量上,这实际上造成了多次的递归引用。 244 | 245 | >这里的"ServiceProvider类型"的对象,是指所有服务提供者类的实例,这些服务提供者类有一个共同的特征,就是都继承了`\Illuminate\Support\ServiceProvider`类。 246 | 247 | 从app容器这个对象的内容来看,我们发现在应用启动后,app包含了很多provider类型的对象。通过查看isBooted方法的源码,我们能梳理出app的保护成员变量booted是什么时候从false变成true的。在phpstorm IDE中全局搜索"->booted",我们能看到,正是在boot方法之后,booted值被赋值为了true: 248 | 249 | ````php 250 | /** 251 | * Boot the application's service providers. 252 | * 253 | * @return void 254 | */ 255 | public function boot() 256 | { 257 | if ($this->isBooted()) { 258 | return; 259 | } 260 | 261 | // Once the application has booted we will also fire some "booted" callbacks 262 | // for any listeners that need to do work after this initial booting gets 263 | // finished. This is useful when ordering the boot-up processes we run. 264 | $this->fireAppCallbacks($this->bootingCallbacks); 265 | 266 | array_walk($this->serviceProviders, function ($p) { 267 | $this->bootProvider($p); 268 | }); 269 | 270 | $this->booted = true; 271 | 272 | $this->fireAppCallbacks($this->bootedCallbacks); 273 | } 274 | ```` 275 | 276 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 277 | 278 | 上面的代码显示:应用启动后,会逐个去运行每个ServiceProvider类型对象的bootProvider方法。 279 | 280 | 这样我们就明白了:在应用启动之前,app容器已经"接收"完了所有的ServiceProvider类型对象,而每个ServiceProvider类型对象的保护成员app又都指向了容器本身,这必然造成大量的递归引用。所以这时候打印一个provider对象的信息就不是那么容易的事情。这里我们可以简单分析一下:程序先调用语句创建出一个对象A,对象A调用语句创建出对象B,同时把A本身注入到B中去,这时代码中一共包含了3个对象的信息:A,A.B, A.B.A(最后这个A.B.A就是递归引用了)。继续调用语句创建出对象C,情况变成了:A, A.B, A.C, A.B.A, A.C.A。即此时变成了5个对象,其中包含两个递归引用。即我们每多注册一个ServiceProvider,对象数量就增加2,并且多一个递归引用。而在我们的配置文件`config/app.php`文件中定义了多个需要注册的providers: 281 | 282 | ```php 283 | 'providers' => [ 284 | /* 285 | * Laravel Framework Service Providers... 286 | */ 287 | Illuminate\Auth\AuthServiceProvider::class, 288 | Illuminate\Broadcasting\BroadcastServiceProvider::class, 289 | Illuminate\Bus\BusServiceProvider::class, 290 | Illuminate\Cache\CacheServiceProvider::class, 291 | Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, 292 | Illuminate\Cookie\CookieServiceProvider::class, 293 | Illuminate\Database\DatabaseServiceProvider::class, 294 | Illuminate\Encryption\EncryptionServiceProvider::class, 295 | Illuminate\Filesystem\FilesystemServiceProvider::class, 296 | Illuminate\Foundation\Providers\FoundationServiceProvider::class, 297 | Illuminate\Hashing\HashServiceProvider::class, 298 | Illuminate\Mail\MailServiceProvider::class, 299 | Illuminate\Notifications\NotificationServiceProvider::class, 300 | Illuminate\Pagination\PaginationServiceProvider::class, 301 | Illuminate\Pipeline\PipelineServiceProvider::class, 302 | Illuminate\Queue\QueueServiceProvider::class, 303 | Illuminate\Redis\RedisServiceProvider::class, 304 | Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, 305 | Illuminate\Session\SessionServiceProvider::class, 306 | Illuminate\Translation\TranslationServiceProvider::class, 307 | Illuminate\Validation\ValidationServiceProvider::class, 308 | Illuminate\View\ViewServiceProvider::class, 309 | 310 | /* 311 | * Package Service Providers... 312 | */ 313 | 314 | /* 315 | * Application Service Providers... 316 | */ 317 | App\Providers\AppServiceProvider::class, 318 | App\Providers\AuthServiceProvider::class, 319 | // App\Providers\BroadcastServiceProvider::class, 320 | App\Providers\EventServiceProvider::class, 321 | App\Providers\RouteServiceProvider::class, 322 | 323 | ], 324 | ``` 325 | 326 | > 当然还需要加上系统默认注册的3个ServiceProvider类型的类,才是总的需要注册的Providers数。 327 | 328 | 通过前面var_dump的输出,我们能发现除了ServiceProvider类型类的注册会造成递归引用外,容器其他的一些成员变量(保存回调事件的成员)保存的闭包中,也包含了递归引用。因此,即使是打印一个对象的信息,也可能造成浏览器内存溢出,导致最后打印失败。 329 | 330 | 读者可能会问这样一个问题,为什么Laravel在不中断的情况输出首页又没有这么明显的延时呢? 331 | 332 | 答案很简单:var_dump打印某个对象的信息很慢,但是执行对象上的方法却可以很快。就像"我们去拆解一辆汽车的所有零部件,将这些零部件的信息完整呈现出来需要耗费大量时间,但是点火启动汽车却可能不到1秒"一样。 333 | 334 | 接下来,我们分析最后一个动作:注册核心别名类。 335 | 336 | - ~~设置基础目录路径~~ 337 | 338 | - ~~注册基础绑定~~ 339 | - ~~注册服务提供者~~ 340 | - 注册核心别名类 341 | 342 | 对应的是下面这行代码: 343 | 344 | ````php 345 | $this->registerCoreContainerAliases(); 346 | ```` 347 | 348 | 继续追踪registerCoreContainerAliases方法: 349 | 350 | ````php 351 | /** 352 | * Register the core class aliases in the container. 353 | * 354 | * @return void 355 | */ 356 | public function registerCoreContainerAliases() 357 | { 358 | foreach ([ 359 | 'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class], 360 | 'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class], 361 | 'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class], 362 | 'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class], 363 | 'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class], 364 | 'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class], 365 | 'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class], 366 | 'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class], 367 | 'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class], 368 | 'db' => [\Illuminate\Database\DatabaseManager::class], 369 | 'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class], 370 | 'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class], 371 | 'files' => [\Illuminate\Filesystem\Filesystem::class], 372 | 'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class], 373 | 'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class], 374 | 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 375 | 'hash' => [\Illuminate\Hashing\HashManager::class], 376 | 'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class], 377 | 'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class], 378 | 'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class], 379 | 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], 380 | 'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class], 381 | 'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class], 382 | 'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class], 383 | 'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class], 384 | 'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class], 385 | 'redirect' => [\Illuminate\Routing\Redirector::class], 386 | 'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class], 387 | 'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class], 388 | 'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class], 389 | 'session' => [\Illuminate\Session\SessionManager::class], 390 | 'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class], 391 | 'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class], 392 | 'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class], 393 | 'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class], 394 | ] as $key => $aliases) { 395 | foreach ($aliases as $alias) { 396 | $this->alias($key, $alias); 397 | } 398 | } 399 | } 400 | ```` 401 | 402 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 403 | 404 | 这个方法的代码相对来说,是比较容易理解的,相当于是双重foreach循环。第一层循环,从数组中抽出单个的$key和$aliases。第二层循环,因为$aliases可能是多维数组,因此继续遍历$aliases数组,将其中的单个值和之前的$key组合为参数,传递给$alias方法。 405 | 406 | 我们重点关注一下alias方法: 407 | 408 | ````php 409 | /** 410 | * Alias a type to a different name. 411 | * 412 | * @param string $abstract 413 | * @param string $alias 414 | * @return void 415 | * 416 | * @throws \LogicException 417 | */ 418 | public function alias($abstract, $alias) 419 | { 420 | if ($alias === $abstract) { 421 | throw new LogicException("[{$abstract}] is aliased to itself."); 422 | } 423 | 424 | $this->aliases[$alias] = $abstract; 425 | 426 | $this->abstractAliases[$abstract][] = $alias; 427 | } 428 | ```` 429 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 430 | 431 | 这里面的逻辑也很简单,就是对当前对象的aliases和abstractAliases成员变量进行赋值。 432 | 433 | 至此,我们终于讲完了【艰难的开始】这一章中说的全部四个步骤: 434 | 435 | - ~~设置基础目录路径~~ 436 | - ~~注册基础绑定~~ 437 | - ~~注册服务提供者~~ 438 | - ~~注册核心别名类~~ 439 | 440 | 现在,让我们重新回到app.php文件源码: 441 | 442 | ````php 443 | singleton( 472 | Illuminate\Contracts\Http\Kernel::class, 473 | App\Http\Kernel::class 474 | ); 475 | 476 | $app->singleton( 477 | Illuminate\Contracts\Console\Kernel::class, 478 | App\Console\Kernel::class 479 | ); 480 | 481 | $app->singleton( 482 | Illuminate\Contracts\Debug\ExceptionHandler::class, 483 | App\Exceptions\Handler::class 484 | ); 485 | 486 | /* 487 | |-------------------------------------------------------------------------- 488 | | Return The Application 489 | |-------------------------------------------------------------------------- 490 | | 491 | | This script returns the application instance. The instance is given to 492 | | the calling script so we can separate the building of the instances 493 | | from the actual running of the application and sending responses. 494 | | 495 | */ 496 | 497 | return $app; 498 | ```` 499 | 500 | > bootstrap/app.php 501 | 502 | 可以看到,剩下的三行代码,都是采用singleton方法,给app容器"绑定"相关的类。 503 | 504 | > singleton方法我们在第五章中已经讲解过,此处不再赘述 505 | 506 | 这里,我们简单看一下绑定的这几个类的作用: 507 | 508 | - App\Http\Kernel类是处理HTTP请求的,大家可以通过阅读源码看到,这个Kernel类不仅包含处理http请求参数的方法,还内置了"嵌入容器对象","嵌入路由类",处理中间件,执行Laravel核心路由解析等等全部核心过程 509 | - App\Console\Kernel类是处理命令行环境下php执行相关动作的 510 | - App\Exceptions\Handler类是处理异常相关的 511 | 512 | 至此,我们终于讲完了第二章中的"第二阶段"。 -------------------------------------------------------------------------------- /05第五章:陷入困境/README.md: -------------------------------------------------------------------------------- 1 | # 第五章:陷入困境 2 | 3 | 回顾第四章中我们讲到的Application类的构造函数,现在四个主要动作还剩下三个: 4 | 5 | - ~~设置基础目录路径~~ 6 | 7 | - 注册基础绑定 8 | - 注册服务提供者 9 | - 注册核心别名类 10 | 11 | 尽管标题看起来有点吓人,但是Laravel的作者确实像是给我们出了一道又一道数学谜题,光靠脑筋急转弯根本不足以应对这突如其来的 12 | 13 | 迷宫一样的"代码阵"。言归正传,我们来看第二个动作:注册基础绑定,它对应的代码是下面这一行: 14 | 15 | ````php 16 | $this->registerBaseBindings(); 17 | ```` 18 | 19 | 我们直接追踪这个方法的详细实现: 20 | 21 | ````php 22 | /** 23 | * Register the basic bindings into the container. 24 | * 25 | * @return void 26 | */ 27 | protected function registerBaseBindings() 28 | { 29 | static::setInstance($this); 30 | 31 | $this->instance('app', $this); 32 | $this->instance(Container::class, $this); 33 | $this->singleton(Mix::class); 34 | 35 | $this->instance(PackageManifest::class, new PackageManifest( 36 | new Filesystem, $this->basePath(), $this->getCachedPackagesPath() 37 | )); 38 | } 39 | ```` 40 | 41 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 42 | 43 | 在这个方法中,第一行是调用了类本身定义的一个静态方法setInstance: 44 | 45 | ````php 46 | /** 47 | * Set the shared instance of the container. 48 | * 49 | * @param \Illuminate\Contracts\Container\Container|null $container 50 | * @return \Illuminate\Contracts\Container\Container|static 51 | */ 52 | public static function setInstance(ContainerContract $container = null) 53 | { 54 | return static::$instance = $container; 55 | } 56 | ```` 57 | 58 | > vendor/laravel/framework/src/Illuminate/Foundation/Application.php 59 | 60 | 这个方法很简单,就是对类自身的静态成员变量instance进行初始化操作。实际上,instance就是"容器"的一个共享实例,这一点作者在代码注释中也有说明:Set the shared instance of the container. 61 | 62 | 之后,继续调用instance方法,将"容器"本身挂载到instances数组的"app"键上,也同时挂载在"Container"键上。之后,注册一个共享变量Mix。我们来重点关注下singleton方法: 63 | 64 | ````php 65 | /** 66 | * Register a shared binding in the container. 67 | * 68 | * @param string $abstract 69 | * @param \Closure|string|null $concrete 70 | * @return void 71 | */ 72 | public function singleton($abstract, $concrete = null) 73 | { 74 | $this->bind($abstract, $concrete, true); 75 | } 76 | ```` 77 | 78 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 79 | 80 | 继续追踪bind方法: 81 | 82 | ````php 83 | /** 84 | * Register a binding with the container. 85 | * 86 | * @param string $abstract 87 | * @param \Closure|string|null $concrete 88 | * @param bool $shared 89 | * @return void 90 | */ 91 | public function bind($abstract, $concrete = null, $shared = false) 92 | { 93 | $this->dropStaleInstances($abstract); 94 | 95 | // If no concrete type was given, we will simply set the concrete type to the 96 | // abstract type. After that, the concrete type to be registered as shared 97 | // without being forced to state their classes in both of the parameters. 98 | if (is_null($concrete)) { 99 | $concrete = $abstract; 100 | } 101 | 102 | // If the factory is not a Closure, it means it is just a class name which is 103 | // bound into this container to the abstract type and we will just wrap it 104 | // up inside its own Closure to give us more convenience when extending. 105 | if (! $concrete instanceof Closure) { 106 | $concrete = $this->getClosure($abstract, $concrete); 107 | } 108 | 109 | $this->bindings[$abstract] = compact('concrete', 'shared'); 110 | 111 | // If the abstract type was already resolved in this container we'll fire the 112 | // rebound listener so that any objects which have already gotten resolved 113 | // can have their copy of the object updated via the listener callbacks. 114 | if ($this->resolved($abstract)) { 115 | $this->rebound($abstract); 116 | } 117 | } 118 | ```` 119 | 120 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 121 | 122 | 在这个部分,核心的代码是下面这一句: 123 | 124 | ````php 125 | $this->bindings[$abstract] = compact('concrete', 'shared'); 126 | ```` 127 | 128 | > bind方法的详细解说,参考【附录四】 129 | 130 | 通过和instance方法比较,我们发现:bindings数组绑定的键名容易理解(绑定什么键根据传入的参数来定),键值却并不容易理解。大家可以看到,在这一行中,键值使用了一个compact函数,这个函数的作用和extract函数刚好相反。这里我们解释为:`compact('concrete', 'shared')`返回的是一个数组,数组包含两个键值对,一个键值对为:`['concrete' => 变量$concrete的值]`,另一个键值对为:`['shared' => 变量$shared的值]`。当然前提是两个变量都存在值,如果变量不存在,则忽略这个键值对。 131 | 132 | 这样,我们必然会去关注$concrete这个变量的值是如何得到的,也就是下面这几行关键的代码: 133 | 134 | ````php 135 | if (! $concrete instanceof Closure) { 136 | $concrete = $this->getClosure($abstract, $concrete); 137 | } 138 | ```` 139 | 140 | 继续追踪getClosure方法的实现: 141 | 142 | ````php 143 | /** 144 | * Get the Closure to be used when building a type. 145 | * 146 | * @param string $abstract 147 | * @param string $concrete 148 | * @return \Closure 149 | */ 150 | protected function getClosure($abstract, $concrete) 151 | { 152 | return function ($container, $parameters = []) use ($abstract, $concrete) { 153 | if ($abstract == $concrete) { 154 | return $container->build($concrete); 155 | } 156 | 157 | return $container->resolve( 158 | $concrete, $parameters, $raiseEvents = false 159 | ); 160 | }; 161 | } 162 | ```` 163 | 164 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 165 | 166 | ## 闭包 167 | 168 | 这里,我们再次碰到一个比较生僻的用法:**闭包**,抛开闭包的使用方法不太好理解不谈。容易让人困惑的是,为什么这里要返回一个闭包? 169 | 170 | 要明白这一点,我们需要从一个实际的案例出发: 171 | 172 | ```php 173 | amount = $amount; 184 | } 185 | 186 | public function getAmount() 187 | { 188 | return $this->amount; 189 | } 190 | } 191 | 192 | 193 | class HomeController extends Controller 194 | { 195 | 196 | public function index() 197 | { 198 | app()->bind('money', function(){return new Money(0.21);}); 199 | $a = app()->make('money'); 200 | var_dump($a); 201 | return json_encode(['a'=>1,'b'=>2]); 202 | } 203 | } 204 | ```` 205 | 206 | > app/Http/Controllers/HomeController.php 207 | 208 | 我们新建一个HomeController,然后在路由文件中定义一个路由: 209 | 210 | ```php 211 | Route::get('/home', 'HomeController@index'); 212 | ``` 213 | 214 | 在浏览器中访问下面这个URL:http://dev.blog.z/home, 结果是这样的: 215 | 216 | ``` 217 | object(App\Http\Controllers\Money)#240 (1) { ["amount":"App\Http\Controllers\Money":private]=> float(0.21) } {"a":1,"b":2} 218 | ``` 219 | 220 | 在Laravel框架中,使用到singleton方法操作的类,其底层都是先经过bind方法将关键字和要实现的闭包存储在容器的bindings成员变量中后,在需要使用类的地方再调用make方法将这个关键字对应的类解析出来。 221 | 222 | 这里还需要注意的一点是,singleton方法操作关键字时,这个关键字对应的类会设置为全局共享,在make方法首次解析类时会创建一次类实例,之后再次调用就直接从容器对象的bindings数组中取对应值了。关于这一点,可以参考【附录三】。 223 | 224 | 从上面这个实例,我们能看到,我们无需先创建出一个实际的对象,而只需要先保存生成这个对象的闭包。之后在需要调用的地方再调用make方法就能成功解析出需要的对象。这样做的好处有很多,比如: 225 | 226 | 1)不用事先创建好对象,在需要用到该类的地方才创建。这有助于应用在"准备启动"的阶段节省内存。 227 | 228 | 2)能更方便地控制对象的创建和获取,比如resovle方法中实现的各种方式的对象解析:1、singleton单例(首次创建,之后缓存);2、扩展;3、回调事件触发。 229 | 230 | 到目前为止,我们只是解释了为什么这个函数要返回闭包。闭包里面的两个实现,build和resolve这两个方法还需要继续分析: 231 | 232 | build: 233 | 234 | ````php 235 | /** 236 | * Instantiate a concrete instance of the given type. 237 | * 238 | * @param string $concrete 239 | * @return mixed 240 | * 241 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 242 | */ 243 | public function build($concrete) 244 | { 245 | // If the concrete type is actually a Closure, we will just execute it and 246 | // hand back the results of the functions, which allows functions to be 247 | // used as resolvers for more fine-tuned resolution of these objects. 248 | if ($concrete instanceof Closure) { 249 | return $concrete($this, $this->getLastParameterOverride()); 250 | } 251 | 252 | $reflector = new ReflectionClass($concrete); 253 | 254 | // If the type is not instantiable, the developer is attempting to resolve 255 | // an abstract type such as an Interface or Abstract Class and there is 256 | // no binding registered for the abstractions so we need to bail out. 257 | if (! $reflector->isInstantiable()) { 258 | return $this->notInstantiable($concrete); 259 | } 260 | 261 | $this->buildStack[] = $concrete; 262 | 263 | $constructor = $reflector->getConstructor(); 264 | 265 | // If there are no constructors, that means there are no dependencies then 266 | // we can just resolve the instances of the objects right away, without 267 | // resolving any other types or dependencies out of these containers. 268 | if (is_null($constructor)) { 269 | array_pop($this->buildStack); 270 | 271 | return new $concrete; 272 | } 273 | 274 | $dependencies = $constructor->getParameters(); 275 | 276 | // Once we have all the constructor's parameters we can create each of the 277 | // dependency instances and then use the reflection instances to make a 278 | // new instance of this class, injecting the created dependencies in. 279 | try { 280 | $instances = $this->resolveDependencies($dependencies); 281 | } catch (BindingResolutionException $e) { 282 | array_pop($this->buildStack); 283 | 284 | throw $e; 285 | } 286 | 287 | array_pop($this->buildStack); 288 | 289 | return $reflector->newInstanceArgs($instances); 290 | } 291 | ```` 292 | 293 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 294 | 295 | ## 反射 296 | 297 | 上面这段代码,再次使用了PHP中比较生僻的用法:**反射**。什么场景下,我们需要使用反射呢?比如,我们事先定义好了一个类,类中包含类的各个成员变量的定义和初始化,类的构造方法,公有方法和私有方法、保护方法等等。此时,仅告诉你一个类名而不告诉你类中的任何细节,你如何才能完成实例化类的任务呢?因为类的实例化过程中,可能涉及到构造函数的调用,而构造函数的调用过程又可能需要传参。如果不使用反射,根本无从下手,有了反射就好办了。 298 | 299 | 关于反射的详细资料,可以阅读PHP手册上的这个链接:https://www.php.net/manual/zh/book.reflection.php 300 | 301 | 接下来,我们结合一个实例,来理解这部分代码中所有和反射有关的类和方法。 302 | 303 | ````php 304 | name = $name; 323 | $this->year = $year; 324 | } 325 | 326 | public function getValue() 327 | { 328 | return $this->name; 329 | } 330 | 331 | public function setBase(Printer $printer, $name, $year = 10) 332 | { 333 | $this->name = $name; 334 | $this->year = $year; 335 | } 336 | } 337 | ```` 338 | 339 | 首先我们将上面的代码单独保存为一个php文件,命名为base.php。然后我们再编写一个test_88.php的测试文件,代码如下: 340 | 341 | ````php 342 | isInstantiable(); 349 | $bool_ = $reflector_->isInstantiable(); 350 | var_dump($bool); 351 | var_dump($bool_); 352 | exit; 353 | ```` 354 | 355 | ![](../images/test_00.png) 356 | 357 | 【图5.1】 358 | 359 | > isInstantible方法是检测对象能否实例化的,能则返回true,不能则返回false 360 | 361 | 接下来我们继续改写test_88.php: 362 | 363 | ````php 364 | getConstructor(); 371 | $constructor_ = $reflector_->getConstructor(); 372 | var_dump($constructor); 373 | var_dump($constructor_); 374 | exit; 375 | ```` 376 | 377 | ![](../images/test_01.png) 378 | 379 | 【图5.2】 380 | 381 | > getConstructor方法返回对象的构造函数信息,如果对象没有定义构造函数,则返回NULL 382 | 383 | 继续改写test_88.php: 384 | 385 | ````php 386 | getConstructor(); 393 | $dependencies = $constructor->getParameters(); 394 | var_dump($dependencies); 395 | exit; 396 | ```` 397 | 398 | ![](../images/test_02.png) 399 | 400 | 【图5.3】 401 | 402 | > $constructor的getParameters方法返回构造函数需要的参数信息 403 | 404 | 最后一个方法newInstanceArgs,是利用反射类,直接实例化一个类。当然,我们事先需要先处理构造函数的参数。 405 | 406 | 我们回头再重新看build方法: 407 | 408 | ```php 409 | /** 410 | * Instantiate a concrete instance of the given type. 411 | * 412 | * @param string $concrete 413 | * @return mixed 414 | * 415 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 416 | */ 417 | public function build($concrete) 418 | { 419 | // If the concrete type is actually a Closure, we will just execute it and 420 | // hand back the results of the functions, which allows functions to be 421 | // used as resolvers for more fine-tuned resolution of these objects. 422 | if ($concrete instanceof Closure) { 423 | return $concrete($this, $this->getLastParameterOverride()); 424 | } 425 | 426 | $reflector = new ReflectionClass($concrete); 427 | 428 | // If the type is not instantiable, the developer is attempting to resolve 429 | // an abstract type such as an Interface or Abstract Class and there is 430 | // no binding registered for the abstractions so we need to bail out. 431 | if (! $reflector->isInstantiable()) { 432 | return $this->notInstantiable($concrete); 433 | } 434 | 435 | $this->buildStack[] = $concrete; 436 | 437 | $constructor = $reflector->getConstructor(); 438 | 439 | // If there are no constructors, that means there are no dependencies then 440 | // we can just resolve the instances of the objects right away, without 441 | // resolving any other types or dependencies out of these containers. 442 | if (is_null($constructor)) { 443 | array_pop($this->buildStack); 444 | 445 | return new $concrete; 446 | } 447 | 448 | $dependencies = $constructor->getParameters(); 449 | 450 | // Once we have all the constructor's parameters we can create each of the 451 | // dependency instances and then use the reflection instances to make a 452 | // new instance of this class, injecting the created dependencies in. 453 | try { 454 | $instances = $this->resolveDependencies($dependencies); 455 | } catch (BindingResolutionException $e) { 456 | array_pop($this->buildStack); 457 | 458 | throw $e; 459 | } 460 | 461 | array_pop($this->buildStack); 462 | 463 | return $reflector->newInstanceArgs($instances); 464 | } 465 | ``` 466 | 467 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 468 | 469 | 现在我们应该能大体上理解这段代码的含义了,其实这里就是根据传入的concrete参数构造出需要实例化的对象并返回。当然,处理构造函数需要的参数这里,框架自定义了单独的方法:resolveDependencies。这个方法的详细分析,请参考【附录五】。 470 | 471 | 看完build方法后,再来看另一个方法resolve: 472 | 473 | ````php 474 | /** 475 | * Resolve the given type from the container. 476 | * 477 | * @param string $abstract 478 | * @param array $parameters 479 | * @param bool $raiseEvents 480 | * @return mixed 481 | * 482 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 483 | */ 484 | protected function resolve($abstract, $parameters = [], $raiseEvents = true) 485 | { 486 | $abstract = $this->getAlias($abstract); 487 | 488 | $needsContextualBuild = ! empty($parameters) || ! is_null( 489 | $this->getContextualConcrete($abstract) 490 | ); 491 | 492 | // If an instance of the type is currently being managed as a singleton we'll 493 | // just return an existing instance instead of instantiating new instances 494 | // so the developer can keep using the same objects instance every time. 495 | if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { 496 | return $this->instances[$abstract]; 497 | } 498 | 499 | $this->with[] = $parameters; 500 | 501 | $concrete = $this->getConcrete($abstract); 502 | 503 | // We're ready to instantiate an instance of the concrete type registered for 504 | // the binding. This will instantiate the types, as well as resolve any of 505 | // its "nested" dependencies recursively until all have gotten resolved. 506 | if ($this->isBuildable($concrete, $abstract)) { 507 | $object = $this->build($concrete); 508 | } else { 509 | $object = $this->make($concrete); 510 | } 511 | 512 | // If we defined any extenders for this type, we'll need to spin through them 513 | // and apply them to the object being built. This allows for the extension 514 | // of services, such as changing configuration or decorating the object. 515 | foreach ($this->getExtenders($abstract) as $extender) { 516 | $object = $extender($object, $this); 517 | } 518 | 519 | // If the requested type is registered as a singleton we'll want to cache off 520 | // the instances in "memory" so we can return it later without creating an 521 | // entirely new instance of an object on each subsequent request for it. 522 | if ($this->isShared($abstract) && ! $needsContextualBuild) { 523 | $this->instances[$abstract] = $object; 524 | } 525 | 526 | if ($raiseEvents) { 527 | $this->fireResolvingCallbacks($abstract, $object); 528 | } 529 | 530 | // Before returning, we will also set the resolved flag to "true" and pop off 531 | // the parameter overrides for this build. After those two things are done 532 | // we will be ready to return back the fully constructed class instance. 533 | $this->resolved[$abstract] = true; 534 | 535 | array_pop($this->with); 536 | 537 | return $object; 538 | } 539 | ```` 540 | 541 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 542 | 543 | resolve方法中的代码,包含的信息量太大了,这种情况正是因为框架提供了很多丰富的特性:扩展,单例模式的绑定对象具有缓存特性(只在第一次调用时创建对象,后面都从缓存中直接读取对象)等等,这个方法的详细分析请参考【附录三】。 544 | 545 | 546 | 547 | ## 题外话 548 | 549 | 本章标题之所以取名为陷入困境,是因为框架在这个阶段使用了很多生僻的用法:反射、闭包等,这多少会让初学者感到困惑,对框架的广泛流行也是不利的(好在这些都只是表象,整体来说Laravel的设计仍然是优秀的并且值得每一个phper深入学习)。 550 | 551 | 如果你看过早期流行的php框架CI(CodeIgniter)的源码,一定会感同深受。因为CI并没有使用太多冷门的语法,甚至连反射都没有用到,仅仅是结合"引用"和"自动加载函数"(__autoload)的功能,CI出色地完成了MVC模式的搭建。 552 | 553 | 在CI的时代里,PHP简单又不失优雅,成为了名副其实的**代码点火器**。今天,CI依然拥有大量的拥趸,因为php程序员坚信:好的框架不是代码要多么晦涩难懂,功能如何强大可扩展。更重要的,是文档齐全、结构精巧、性能出色,而CI确实满足了这些要求。 554 | 555 | 说到这里,可能有读者会觉得作者似乎已经偏离了预定的行文轨道,原本分析Laravel生命周期的文章,到这里竟然将Laravel和CI做起了比较,甚至于不吝赞美CI的简单优雅去了。 556 | 557 | 既然如此,我们不妨进一步将CI和Laravel做个比较。因为大家在实际的开发过程中,一定会面临框架的选择,这个问题是非解决不可的,不是吗? 558 | 559 | 先来说CI,CI最大的特点就是简单、易上手。配合官方提供的中文文档,你可以很快就上手编写代码。并且它的核心代码少,占用空间和Laravel比起来少多了。但是也因为简单,当项目稍微变得功能多一点的时候,它的问题就暴露出来了: 560 | 561 | - 默认的CI框架并没有集成composer,需要你自己去规划目录。当然这对于一个熟悉PHP的人来说难度很小; 562 | - CI没有提供一个命令行式的框架交互入口,这对于一个擅长在命令行下做开发的人来说简直就是灾难。如果你不能使用命令行,那就意味着,你每次要新建一个model,都只能在IDE中操作鼠标,右键选择菜单。。。而命令行下,只要一条命令便可以快速创建controller、model等各种文件; 563 | - CI的路由功能显然没有Laravel的路由功能强大,Laravel的路由类天然支持中间件、正则匹配、命名、路由分组等常用功能,在CI中你必须手动去自己实现; 564 | - 在大型项目的开发中,Eloquent ORM几乎是框架的必选项,然而CI并没有。同样地,在很多场景中需要使用到的队列、任务调度、数据库迁移。。。CI均不支持; 565 | - 在安全方面,Laravel天然支持防CSRF/XSS攻击。CI框架默认也没有相应的支持。 566 | 567 | 当然,你只要深入地学习过CI,就能清晰地认识到,CI框架在构建过程中并没有很多"面向对象"高级技巧方面的应用。比如Laravel服务容器天然支持的"依赖注入","门面"(Facade)等等。 568 | 569 | 言归正传,本章我们最后简单归纳一下,这一章我们重点分析的是上一章中四个主要动作中的第二个动作:注册基础绑定。它对应的是Application构造函数中的这条语句:`$this->registerBaseBindings();` 570 | 571 | 在追踪代码的过程中,我们重点分析了getClosure方法: 572 | 573 | ````php 574 | protected function getClosure($abstract, $concrete) 575 | { 576 | return function ($container, $parameters = []) use ($abstract, $concrete) { 577 | if ($abstract == $concrete) { 578 | return $container->build($concrete); 579 | } 580 | 581 | return $container->resolve( 582 | $concrete, $parameters, $raiseEvents = false 583 | ); 584 | }; 585 | } 586 | ```` 587 | 588 | > vendor/laravel/framework/src/Illuminate/Container/Container.php 589 | 590 | getClosure方法中代码逻辑可以简单理解为:根据参数$abstract和$concrete的值,分为两种情况去处理: 591 | 592 | - 一种是调用build方法,这个方法中的代码可能用到反射去反向构建类。 593 | - 另一种是调用resolve方法,直接从container中解析出相应的类。 -------------------------------------------------------------------------------- /附录三:Container之resolve方法/README.md: -------------------------------------------------------------------------------- 1 | # 附录三:Container之resolve方法 2 | 3 | 源文件路径:vendor\laravel\framework\src\Illuminate\Container\Container.php 4 | 5 | 方法名:resolve 6 | 7 | ````php 8 | /** 9 | * Resolve the given type from the container. 10 | * 11 | * @param string $abstract 12 | * @param array $parameters 13 | * @param bool $raiseEvents 14 | * @return mixed 15 | * 16 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 17 | */ 18 | protected function resolve($abstract, $parameters = [], $raiseEvents = true) 19 | { 20 | $abstract = $this->getAlias($abstract); 21 | 22 | $needsContextualBuild = ! empty($parameters) || ! is_null( 23 | $this->getContextualConcrete($abstract) 24 | ); 25 | 26 | // If an instance of the type is currently being managed as a singleton we'll 27 | // just return an existing instance instead of instantiating new instances 28 | // so the developer can keep using the same objects instance every time. 29 | if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { 30 | return $this->instances[$abstract]; 31 | } 32 | 33 | $this->with[] = $parameters; 34 | 35 | $concrete = $this->getConcrete($abstract); 36 | 37 | // We're ready to instantiate an instance of the concrete type registered for 38 | // the binding. This will instantiate the types, as well as resolve any of 39 | // its "nested" dependencies recursively until all have gotten resolved. 40 | if ($this->isBuildable($concrete, $abstract)) { 41 | $object = $this->build($concrete); 42 | } else { 43 | $object = $this->make($concrete); 44 | } 45 | 46 | // If we defined any extenders for this type, we'll need to spin through them 47 | // and apply them to the object being built. This allows for the extension 48 | // of services, such as changing configuration or decorating the object. 49 | foreach ($this->getExtenders($abstract) as $extender) { 50 | $object = $extender($object, $this); 51 | } 52 | 53 | // If the requested type is registered as a singleton we'll want to cache off 54 | // the instances in "memory" so we can return it later without creating an 55 | // entirely new instance of an object on each subsequent request for it. 56 | if ($this->isShared($abstract) && ! $needsContextualBuild) { 57 | $this->instances[$abstract] = $object; 58 | } 59 | 60 | if ($raiseEvents) { 61 | $this->fireResolvingCallbacks($abstract, $object); 62 | } 63 | 64 | // Before returning, we will also set the resolved flag to "true" and pop off 65 | // the parameter overrides for this build. After those two things are done 66 | // we will be ready to return back the fully constructed class instance. 67 | $this->resolved[$abstract] = true; 68 | 69 | array_pop($this->with); 70 | 71 | return $object; 72 | } 73 | ```` 74 | 75 | resolve方法其实是Laravel框架容器类中make方法的核心实现: 76 | 77 | ```php 78 | /** 79 | * Resolve the given type from the container. 80 | * 81 | * @param string $abstract 82 | * @param array $parameters 83 | * @return mixed 84 | * 85 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 86 | */ 87 | public function make($abstract, array $parameters = []) 88 | { 89 | return $this->resolve($abstract, $parameters); 90 | } 91 | ``` 92 | 93 | 读者可能会有疑问,从上面的代码来看,make似乎有点多余,直接调用resolve不就好了吗?大家可以仔细比较一下这两个方法的形参: 94 | 95 | ```php 96 | public function make($abstract, array $parameters = []) 97 | { 98 | return $this->resolve($abstract, $parameters); 99 | } 100 | ``` 101 | 102 | ```php 103 | /** 104 | * Resolve the given type from the container. 105 | * 106 | * @param string $abstract 107 | * @param array $parameters 108 | * @param bool $raiseEvents 109 | * @return mixed 110 | * 111 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 112 | */ 113 | protected function resolve($abstract, $parameters = [], $raiseEvents = true) 114 | { 115 | $abstract = $this->getAlias($abstract); 116 | ... 117 | ... ... 118 | ``` 119 | 120 | 很容易看出来,make方法调用resolve时,并没有指定$raiseEvents的参数值,因此可以认为make方法解析实例时,$raiseEvents值就是true。这样我们就能理解了,make解析实例时,一定会去执行Container的fireResolvingCallbacks方法。那这个方法做了什么呢? 121 | 122 | ```php 123 | /** 124 | * Fire all of the resolving callbacks. 125 | * 126 | * @param string $abstract 127 | * @param mixed $object 128 | * @return void 129 | */ 130 | protected function fireResolvingCallbacks($abstract, $object) 131 | { 132 | $this->fireCallbackArray($object, $this->globalResolvingCallbacks); 133 | 134 | $this->fireCallbackArray( 135 | $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) 136 | ); 137 | 138 | $this->fireAfterResolvingCallbacks($abstract, $object); 139 | } 140 | ``` 141 | 142 | 这个方法,实际就是触发执行绑定在解析对象上的回调事件。而回调事件,又分为几种:全局解析中事件,全局解析后事件,类解析中事件,类解析后事件,这些事件全部作为容器的保护成员定义在Container类中: 143 | 144 | ```php 145 | /** 146 | * All of the registered rebound callbacks. 147 | * 148 | * @var array[] 149 | */ 150 | protected $reboundCallbacks = []; 151 | 152 | /** 153 | * All of the global resolving callbacks. 154 | * 155 | * @var \Closure[] 156 | */ 157 | protected $globalResolvingCallbacks = []; 158 | 159 | /** 160 | * All of the global after resolving callbacks. 161 | * 162 | * @var \Closure[] 163 | */ 164 | protected $globalAfterResolvingCallbacks = []; 165 | 166 | /** 167 | * All of the resolving callbacks by class type. 168 | * 169 | * @var array[] 170 | */ 171 | protected $resolvingCallbacks = []; 172 | 173 | /** 174 | * All of the after resolving callbacks by class type. 175 | * 176 | * @var array[] 177 | */ 178 | protected $afterResolvingCallbacks = []; 179 | ``` 180 | 181 | 接下来我们通过一个实例来理解,解析对象上的回调事件是如何执行的: 182 | 183 | 新建一个新的Provider类型文件CuponServiceProvider.php,路径:vendor\laravel\framework\src\Illuminate\Foundation\Providers\CuponServiceProvider.php,源码如下: 184 | 185 | ```php 186 | app->resolving($closure, NULL); 219 | } 220 | } 221 | 222 | ``` 223 | 224 | 接下来,我们修改一下Application.php文件(vendor/laravel/framework/src/Illuminate/Foundation/Application.php)的代码: 225 | 226 | 在registerBaseServiceProviders方法中,加入刚刚我们新建的这个Provider文件的注册: 227 | 228 | ```php 229 | /** 230 | * Register all of the base service providers. 231 | * 232 | * @return void 233 | */ 234 | protected function registerBaseServiceProviders() 235 | { 236 | $this->register(new EventServiceProvider($this)); 237 | $this->register(new LogServiceProvider($this)); 238 | $this->register(new RoutingServiceProvider($this)); 239 | $this->register(new CuponServiceProvider($this)); 240 | } 241 | ``` 242 | 243 | 现在刷新一下首页: 244 | 245 | ![](../images/test_16.png) 246 | 247 | > 要正确完成CuponServiceProvider类的引入,还需在文件按头部添加命名空间 248 | 249 | 我们看到页面输出了很多个"OK,",这是因为在程序完成执行完`registerBaseServiceProvider`方法中的`$this->register(new CuponServiceProvider($this));`代码后,CuponServiceProvider类中的`boot`方法被调用,而这个方法正是完成了一次对全局回调事件($globalResolvingCallbacks)的操作,我们继续阅读容器类中的resolving方法源码: 250 | 251 | ```php 252 | /** 253 | * Register a new resolving callback. 254 | * 255 | * @param \Closure|string $abstract 256 | * @param \Closure|null $callback 257 | * @return void 258 | */ 259 | public function resolving($abstract, Closure $callback = null) 260 | { 261 | if (is_string($abstract)) { 262 | $abstract = $this->getAlias($abstract); 263 | } 264 | 265 | if (is_null($callback) && $abstract instanceof Closure) { 266 | $this->globalResolvingCallbacks[] = $abstract; 267 | } else { 268 | $this->resolvingCallbacks[$abstract][] = $callback; 269 | } 270 | } 271 | ``` 272 | 273 | 大家可以看到,这里我们的测试代码正好进入了上面代码中的这个if分支: 274 | 275 | ``` 276 | if (is_null($callback) && $abstract instanceof Closure) { 277 | $this->globalResolvingCallbacks[] = $abstract; 278 | } 279 | ``` 280 | 281 | 回到前面渲染出来的首页,为什么输出了这么多个"OK,"呢? 282 | 283 | 这正是因为,在完成`$this->register(new CuponServiceProvider($this));`这条语句之后,很多类的解析都是调用了`resolve`方法,并且调用`resolve`方法时,第三个参数$raiseEvents参数值是true。 284 | 285 | 至此,我们知道了在laravel框架中添加全局解析回调事件,以及触发回调事件执行的详细过程。依此类推,Container对象上的其他事件($reboundCallbacks、$globalAfterResolvingCallbacks、$resolvingCallbacks、$afterResolvingCallbacks)用法也类似。只是调用的方式和调用的时机可能不同,读者可自行探索。 286 | 287 | 回到resolve方法中,我们先来看第一行: 288 | 289 | ```php 290 | $abstract = $this->getAlias($abstract); 291 | ``` 292 | 293 | 继续追踪getAlias: 294 | 295 | ```php 296 | /** 297 | * Get the alias for an abstract if available. 298 | * 299 | * @param string $abstract 300 | * @return string 301 | */ 302 | public function getAlias($abstract) 303 | { 304 | if (! isset($this->aliases[$abstract])) { 305 | return $abstract; 306 | } 307 | 308 | return $this->getAlias($this->aliases[$abstract]); 309 | } 310 | ``` 311 | 312 | 我们发现这里用了递归,代码试图去容器类保护成员变量aliases数组中查找$abstract键,如果没找到,直接返回$abstract。找到了,将值换成`$this->aliases[$abstract]`,请大家注意,`$this->aliases`数组中某个键的值(一般是一个字符串)很少会再成为$this->aliases的键名,即使有这种情况出现,也不会出现键名和键值相同的情况,因此这里的递归不会无限进行下去。 313 | 314 | 因此,这里程序优先去查看aliases数组中是否存在相应的键,如果不存在直接返回$abstract变量,否则返回的是alias数组相应键上的值。 315 | 316 | 接着看下面的代码: 317 | 318 | ```php 319 | $needsContextualBuild = ! empty($parameters) || ! is_null( 320 | $this->getContextualConcrete($abstract) 321 | ); 322 | ``` 323 | 324 | 接下来我们继续阅读这个方法后面的代码,可以得知,$needsContextualBuild是在判断根据当前的传参情况是否需要进行"上下文构建"。如何理解这个上下文构建呢?这就离不开Laravel的上下文绑定机制了。 325 | 326 | 什么是上下文呢? 327 | 328 | > 每一段程序都有很多外部变量。只有像 Add 这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。 329 | 330 | 简单说,就是解析一个对象的时候,有些对象是需要外部的一些依赖的。那他在创建的时候就要用到"上下文"把依赖引入。 331 | 332 | 而上下文绑定的意思就是专门处理"实例化时候有依赖关系"情况的一种绑定。下面我们继续看Laravel官方给出的示例: 333 | 334 | ```php 335 | use Illuminate\Support\Facades\Storage; 336 | use App\Http\Controllers\PhotoController; 337 | use App\Http\Controllers\VideoController; 338 | use Illuminate\Contracts\Filesystem\Filesystem; 339 | 340 | $this->app->when(PhotoController::class) 341 | ->needs(Filesystem::class) 342 | ->give(function () { 343 | return Storage::disk('local'); 344 | }); 345 | 346 | $this->app->when(VideoController::class) 347 | ->needs(Filesystem::class) 348 | ->give(function () { 349 | return Storage::disk('s3'); 350 | }); 351 | ``` 352 | 353 | 这是项目中常会用到存储功能,得益于 Laravel 内置集成了 FlySystem 的 Filesystem 接口,我们很容易实现多种存储服务的项目。 354 | 示例中将用户头像存储到本地,将用户上传的小视频存储到云服务。那么这个时就需要区分这样不同的使用场景(即上下文或者说环境)。 355 | 当用户存储头像(PhotoController::class)需要使用存储服务(Filesystem::class)时,我们将本地存储驱动,作为实现给到 PhotoController::class: 356 | 357 | ```php 358 | function () { 359 | return Storage::disk('local'); 360 | } 361 | ``` 362 | 363 | 而当用户上传视频 VideoController::class,需要使用存储服务(Filesystem::class)时,我们则将云服务驱动,作为实现给到 VideoController::class: 364 | 365 | ```php 366 | function () { 367 | return Storage::disk('s3'); 368 | } 369 | ``` 370 | 371 | 接下来我们看Laravel的源码实现: 372 | 373 | 1. when方法 374 | 375 | ```php 376 | public function when($concrete) 377 | { 378 | return new ContextualBindingBuilder($this, $this->getAlias($concrete)); 379 | } 380 | ``` 381 | 382 | 这个方法直接生成一个ContextualBindingBuilder对象,传入container对象和$concrete。$concrete在这个例子中就是`PhotoController::class`和`VideoController::class` 383 | 384 | 2. ContextualBindingBuilder类 385 | 386 | ```php 387 | concrete = $concrete; 428 | $this->container = $container; 429 | } 430 | 431 | /** 432 | * Define the abstract target that depends on the context. 433 | * 434 | * @param string $abstract 435 | * @return $this 436 | */ 437 | public function needs($abstract) 438 | { 439 | $this->needs = $abstract; 440 | 441 | return $this; 442 | } 443 | 444 | /** 445 | * Define the implementation for the contextual binding. 446 | * 447 | * @param \Closure|string $implementation 448 | * @return void 449 | */ 450 | public function give($implementation) 451 | { 452 | foreach (Arr::wrap($this->concrete) as $concrete) { 453 | $this->container->addContextualBinding($concrete, $this->needs, $implementation); 454 | } 455 | } 456 | } 457 | 458 | ``` 459 | 460 | 这个类主要提供两个方法,needs和give。needs方法就是简单把$abstract存储起来,然后返回当前对象,方便后面继续进行链式操作。give方法,重新调用$container的`addContextualBinding`方法,这个就是添加上下文绑定的方法,分别传入的下面三个参数: 461 | 462 | concrete:PhotoController::class的别名(如果有的话) 463 | 464 | abstruct:Filesytem::class 465 | 466 | implemention: 闭包 `Storage::disk('local');`的返回值 467 | 468 | 然后我们看一下Container中的`addContextualBinding`方法: 469 | 470 | 也很简单,就是把这些参数存入contextual数组。注意这里存储的形式:`contextual[PhotoController::class][Filesystem::class] = 闭包函数(也可以是一个类路径)` 471 | 472 | ```php 473 | public function addContextualBinding($concrete, $abstract, $implementation) 474 | { 475 | $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation; 476 | } 477 | ``` 478 | 479 | 到这里,我们弄清楚了Laravel是如何处理上下文绑定的,总结如下: 480 | 481 | 当一个类实例化需要一些外部依赖的时候,就要用到上下文绑定。把外部依赖通过 need 传递给他。还可以通过 give 存储 when 对应的实现(针对抽象类或者接口甚至是子类)。存入到容器中那个负责上下文的那个数组中,这个数组将会在解析的时候 (就是取出某个对象的时候,他对应绑定的依赖也会被取出) 做判断。 482 | 483 | 现在我们重新回到resolve方法中: 484 | 485 | ```php 486 | $needsContextualBuild = ! empty($parameters) || ! is_null( 487 | $this->getContextualConcrete($abstract) 488 | ); 489 | ``` 490 | 491 | 继续看getContextualConcrete方法源码: 492 | 493 | ```php 494 | /** 495 | * Get the contextual concrete binding for the given abstract. 496 | * 497 | * @param string $abstract 498 | * @return \Closure|string|null 499 | */ 500 | protected function getContextualConcrete($abstract) 501 | { 502 | if (! is_null($binding = $this->findInContextualBindings($abstract))) { 503 | return $binding; 504 | } 505 | 506 | // Next we need to see if a contextual binding might be bound under an alias of the 507 | // given abstract type. So, we will need to check if any aliases exist with this 508 | // type and then spin through them and check for contextual bindings on these. 509 | if (empty($this->abstractAliases[$abstract])) { 510 | return; 511 | } 512 | 513 | foreach ($this->abstractAliases[$abstract] as $alias) { 514 | if (! is_null($binding = $this->findInContextualBindings($alias))) { 515 | return $binding; 516 | } 517 | } 518 | } 519 | ``` 520 | 521 | 终于,我们看到这个方法内部调用的方法`findContextualBindings`和我们之前讲的"laravel中对上下文处理"的内容产生了交集: 522 | 523 | ``` 524 | protected function findInContextualBindings($abstract) 525 | { 526 | return $this->contextual[end($this->buildStack)][$abstract] ?? null; 527 | } 528 | ``` 529 | 530 | 到这里为止,我们还只是讲完了上下文处理的部分,继续往后看: 531 | 532 | ```php 533 | // If an instance of the type is currently being managed as a singleton we'll 534 | // just return an existing instance instead of instantiating new instances 535 | // so the developer can keep using the same objects instance every time. 536 | if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { 537 | return $this->instances[$abstract]; 538 | } 539 | 540 | $this->with[] = $parameters; 541 | 542 | $concrete = $this->getConcrete($abstract); 543 | 544 | // We're ready to instantiate an instance of the concrete type registered for 545 | // the binding. This will instantiate the types, as well as resolve any of 546 | // its "nested" dependencies recursively until all have gotten resolved. 547 | if ($this->isBuildable($concrete, $abstract)) { 548 | $object = $this->build($concrete); 549 | } else { 550 | $object = $this->make($concrete); 551 | } 552 | ``` 553 | 554 | 第一个`if这里,是检测要解析的是不是一个单例对象,如果是则直接返回(从容器的instances数组中取,当前要解析的对象必须是无关上下文的),然后将传递进来的参数保存在容器的保护成员with数组中。接着调用getConcrete方法: 555 | 556 | ```php 557 | protected function getConcrete($abstract) 558 | { 559 | if (! is_null($concrete = $this->getContextualConcrete($abstract))) { 560 | return $concrete; 561 | } 562 | 563 | // If we don't have a registered resolver or concrete for the type, we'll just 564 | // assume each type is a concrete name and will attempt to resolve it as is 565 | // since the container should be able to resolve concretes automatically. 566 | if (isset($this->bindings[$abstract])) { 567 | return $this->bindings[$abstract]['concrete']; 568 | } 569 | 570 | return $abstract; 571 | } 572 | ``` 573 | 574 | 这个方法的主要思路是: 575 | 576 | - a) 看看上下文绑定数组中有没有$abstract对应的concrete值,如果有直接返回 577 | - b) 如果没有找到上下文绑定,就是一个普通绑定,就取bindings数组中看看有没有$abstract对应的concrete值,从而确认是不是以前有绑定过。 578 | - c) 都没有,说明没有绑定,直接返回$abstract 579 | 580 | 接下来,获取解析对象: 581 | 582 | ```php 583 | if ($this->isBuildable($concrete, $abstract)) { 584 | $object = $this->build($concrete); 585 | } else { 586 | $object = $this->make($concrete); 587 | } 588 | ``` 589 | 590 | 首先,我们看一下isBuildable方法是怎么判断的: 591 | 592 | ```php 593 | protected function isBuildable($concrete, $abstract) 594 | { 595 | return $concrete === $abstract || $concrete instanceof Closure; 596 | } 597 | ``` 598 | 599 | $concrete和$abstract恒等或者$concrete是一个闭包 600 | 601 | 如果isBuildable返回true,执行build方法创建出这个object,build方法的处理是这样: 602 | 603 | 1)如果concrete是闭包,直接执行闭包函数 604 | 605 | 2)如果不是闭包,使用反射产生当前的$concrete类对象 606 | 607 | 如果isBuildable返回false,调用make进入递归,make 再去 getConcrete 函数,去上下文绑定数组和 binding 数组,查询这个时候这个・类路径下・(就是 abstruct)有没有对应的闭包或类路径。但不管怎么样。最后下来要么闭包,要么相等,他都会进入 build 函数创建对象。 608 | 609 | 至此,我们得到了解析出来的object对象。 610 | 611 | 继续看后面的代码: 612 | 613 | ```php 614 | foreach ($this->getExtenders($abstract) as $extender) { 615 | $object = $extender($object, $this); 616 | } 617 | 618 | // If the requested type is registered as a singleton we'll want to cache off 619 | // the instances in "memory" so we can return it later without creating an 620 | // entirely new instance of an object on each subsequent request for it. 621 | if ($this->isShared($abstract) && ! $needsContextualBuild) { 622 | $this->instances[$abstract] = $object; 623 | } 624 | 625 | if ($raiseEvents) { 626 | $this->fireResolvingCallbacks($abstract, $object); 627 | } 628 | 629 | // Before returning, we will also set the resolved flag to "true" and pop off 630 | // the parameter overrides for this build. After those two things are done 631 | // we will be ready to return back the fully constructed class instance. 632 | $this->resolved[$abstract] = true; 633 | 634 | array_pop($this->with); 635 | 636 | return $object; 637 | ``` 638 | 639 | 首先看是否有扩展extend的处理,执行: 640 | 641 | ```php 642 | foreach ($this->getExtenders($abstract) as $extender) { 643 | $object = $extender($object, $this); 644 | } 645 | ``` 646 | 647 | 接下来,看是否是单例分享的,如果是的话就存入instance: 648 | 649 | ```php 650 | if ($this->isShared($abstract) && ! $needsContextualBuild) { 651 | $this->instances[$abstract] = $object; 652 | } 653 | ``` 654 | 655 | 接着触发各个回调函数,执行回调,这一点在前面我们已经详细阐述过了: 656 | 657 | ```php 658 | if ($raiseEvents) { 659 | $this->fireResolvingCallbacks($abstract, $object); 660 | } 661 | ``` 662 | 663 | 接着,标记$abstract已经解析,并且把参数从with中pop掉: 664 | 665 | ```php 666 | $this->resolved[$abstract] = true; 667 | array_pop($this->with); 668 | ``` 669 | 670 | 最后返回对象: 671 | 672 | ```php 673 | return $object; 674 | ``` 675 | 676 | > 本节内容部分参考自LearnKu社区,以下为转载详情: 677 | > 作者:HarveyNorman 678 | > 链接:https://learnku.com/articles/41092 -------------------------------------------------------------------------------- /附录七:Dispatcher之dispatch方法/README.md: -------------------------------------------------------------------------------- 1 | # 附录七:Dispatcher之dispatch方法 2 | 3 | 源文件路径:vendor\laravel\framework\src\Illuminate\Events\Dispatcher.php 4 | 5 | 方法名:dispatch 6 | 7 | ```php 8 | public function dispatch($event, $payload = [], $halt = false) 9 | { 10 | // When the given "event" is actually an object we will assume it is an event 11 | // object and use the class as the event name and this event itself as the 12 | // payload to the handler, which makes object based events quite simple. 13 | [$event, $payload] = $this->parseEventAndPayload( 14 | $event, $payload 15 | ); 16 | 17 | if ($this->shouldBroadcast($payload)) { 18 | $this->broadcastEvent($payload[0]); 19 | } 20 | 21 | $responses = []; 22 | 23 | foreach ($this->getListeners($event) as $listener) { 24 | $response = $listener($event, $payload); 25 | 26 | // If a response is returned from the listener and event halting is enabled 27 | // we will just return this response, and not call the rest of the event 28 | // listeners. Otherwise we will add the response on the response list. 29 | if ($halt && ! is_null($response)) { 30 | return $response; 31 | } 32 | 33 | // If a boolean false is returned from a listener, we will stop propagating 34 | // the event to any further listeners down in the chain, else we keep on 35 | // looping through the listeners and firing every one in our sequence. 36 | if ($response === false) { 37 | break; 38 | } 39 | 40 | $responses[] = $response; 41 | } 42 | 43 | return $halt ? null : $responses; 44 | } 45 | ``` 46 | 47 | 首先我们看一下调用`dispatch`方法时,传入该方法的两个参数值是什么。为此我们需要追溯到Application类中的`bootstrapWith`方法(vendor\laravel\framework\src\Illuminate\Foundation\Application.php): 48 | 49 | ```php 50 | public function bootstrapWith(array $bootstrappers) 51 | { 52 | $this->hasBeenBootstrapped = true; 53 | 54 | foreach ($bootstrappers as $bootstrapper) { 55 | $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]); 56 | 57 | $this->make($bootstrapper)->bootstrap($this); 58 | 59 | $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]); 60 | } 61 | } 62 | ``` 63 | 64 | 继续往前追溯bootstrapWith方法,我们需要知道这个数组中的内容: 65 | 66 | ```php 67 | /** 68 | * Bootstrap the application for HTTP requests. 69 | * 70 | * @return void 71 | */ 72 | public function bootstrap() 73 | { 74 | if (! $this->app->hasBeenBootstrapped()) { 75 | $this->app->bootstrapWith($this->bootstrappers()); 76 | } 77 | } 78 | ``` 79 | 80 | 这个方法来源于Kernel类(vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php),它是框架在启动过程中执行的一个重要方法。 81 | 82 | 从这里,我们能很清楚地看到传入`bootstrapWith`方法的参数值是`$this->bootstrappers()`,继续追踪`bootstrappers`方法: 83 | 84 | ```php 85 | /** 86 | * Get the bootstrap classes for the application. 87 | * 88 | * @return array 89 | */ 90 | protected function bootstrappers() 91 | { 92 | return $this->bootstrappers; 93 | } 94 | ``` 95 | 96 | 我们直接查看类的成员变量bootstrappers: 97 | 98 | ```php 99 | /** 100 | * The bootstrap classes for the application. 101 | * 102 | * @var array 103 | */ 104 | protected $bootstrappers = [ 105 | \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, 106 | \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 107 | \Illuminate\Foundation\Bootstrap\HandleExceptions::class, 108 | \Illuminate\Foundation\Bootstrap\RegisterFacades::class, 109 | \Illuminate\Foundation\Bootstrap\RegisterProviders::class, 110 | \Illuminate\Foundation\Bootstrap\BootProviders::class, 111 | ]; 112 | ``` 113 | 114 | 这样,我们就明白了,传入`parseEventAndPayload`方法中的两个参数是这样的: 115 | 116 | - $event参数是一个字符串,具体内容是$bootstrappers数组中的元素值和"bootstrapping: "或者"bootstrapped: "拼接得到的(注意这里$boostrappers数组中元素值是往后拼接) 117 | - $payload参数是一个数组,数组中的内容就是容器对象本身 118 | 119 | 接下来,我们继续看`parseEventAndPayload`方法的源码: 120 | 121 | ```php 122 | /** 123 | * Parse the given event and payload and prepare them for dispatching. 124 | * 125 | * @param mixed $event 126 | * @param mixed $payload 127 | * @return array 128 | */ 129 | protected function parseEventAndPayload($event, $payload) 130 | { 131 | if (is_object($event)) { 132 | [$payload, $event] = [[$event], get_class($event)]; 133 | } 134 | 135 | return [$event, Arr::wrap($payload)]; 136 | } 137 | ``` 138 | 139 | 注意这里,默认情况下if中的语句并不会运行,原因是传入parseEventPayload中的参数是下面这两种形式: 140 | 141 | a) bootstrapping: \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables 142 | 143 | b) bootstrapped: \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables 144 | 145 | 显然这两个字符串对应的类并不存在。经过`parseEventAndPayload`处理后,$event和$payload对应的值分别是一个字符串和一个数组。大家可以在`dispatch`方法中做中断测试: 146 | 147 | ```php 148 | public function dispatch($event, $payload = [], $halt = false) 149 | { 150 | // When the given "event" is actually an object we will assume it is an event 151 | // object and use the class as the event name and this event itself as the 152 | // payload to the handler, which makes object based events quite simple. 153 | [$event, $payload] = $this->parseEventAndPayload( 154 | $event, $payload 155 | ); 156 | 157 | var_dump($event); 158 | var_dump($payload); 159 | exit; 160 | ... 161 | ... ... 162 | ``` 163 | 164 | 结果如下: 165 | 166 | ```php 167 | string(71) "bootstrapping: Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables" array(1) { [0]=> object(Illuminate\Foundation\Application)#2 (31) { ["basePath":protected]=> string(23) "/home/vagrant/code/blog" ["hasBeenBootstrapped":protected]=> bool(true) ["booted":protected]=> bool(false) ["bootingCallbacks":protected]=> array(0) { } ["bootedCallbacks":protected]=> array(0) { } ["terminatingCallbacks":protected]=> array(0) { } ["serviceProviders":protected]=> array(3) { [0]=> object(Illuminate\Events\EventServiceProvider)#8 (2) { ["app":protected]=> *RECURSION* ["defer":protected]=> bool(false) } [1]=> object(Illuminate\Log\LogServiceProvider)#10 (2) { ["app":protected]=> *RECURSION* ["defer":protected]=> bool(false) } [2]=> object(Illuminate\Routing\RoutingServiceProvider)#12 (2) { ["app":protected]=> *RECURSION* ["defer":protected]=> bool(false) } } ["loadedProviders":protected]=> array(3) { ["Illuminate\Events\EventServiceProvider"]=> bool(true) ["Illuminate\Log\LogServiceProvider"]=> bool(true) ["Illuminate\Routing\RoutingServiceProvider"]=> bool(true) } ["deferredServices":protected]=> array(0) { } ["appPath":protected]=> NULL ["databasePath":protected]=> NULL ["storagePath":protected]=> NULL ["environmentPath":protected]=> NULL ["environmentFile":protected]=> string(4) ".env" ["namespace":protected]=> NULL ["resolved":protected]=> array(4) { ["events"]=> bool(true) ["router"]=> bool(true) ["App\Http\Kernel"]=> bool(true) ["Illuminate\Contracts\Http\Kernel"]=> bool(true) } ["bindings":protected]=> array(13) { ["Illuminate\Foundation\Mix"]=> array(2) { ["concrete"]=> object(Closure)#5 (3) { ["static"]=> array(2) { ["abstract"]=> string(25) "Illuminate\Foundation\Mix" ["concrete"]=> string(25) "Illuminate\Foundation\Mix" } ["this"]=> *RECURSION* ["parameter"]=> array(2) { ["$container"]=> string(10) "" ["$parameters"]=> string(10) "" } } ["shared"]=> bool(true) } ["events"]=> array(2) { ["concrete"]=> object(Closure)#9 (2) { ["this"]=> object(Illuminate\Events\EventServiceProvider)#8 (2) { ["app":protected]=> *RECURSION* ["defer":protected]=> bool(false) } ["parameter"]=> array(1) { ["$app"]=> string(10) "" } } ["shared"]=> bool(true) } ["log"]=> array(2) { ["concrete"]=> object(Closure)#11 (1) { ["this"]=> object(Illuminate\Log\LogServiceProvider)#10 (2) { ["app":protected]=> *RECURSION* ["defer":protected]=> bool(false) } } ["shared"]=> bool(true) } ["router"]=> array(2) { ["concrete"]=> object(Closure)#13 (2) { ["this"]=>... 168 | ``` 169 | 170 | 接下来我们继续看后面的代码: 171 | 172 | ```php 173 | if ($this->shouldBroadcast($payload)) { 174 | $this->broadcastEvent($payload[0]); 175 | } 176 | ``` 177 | 178 | 这里,先判断$payload是否需要broadcast, 需要则调用`broadcastEvent`方法: 179 | 180 | shouldBroadcast方法: 181 | 182 | ```php 183 | protected function shouldBroadcast(array $payload) 184 | { 185 | return isset($payload[0]) && 186 | $payload[0] instanceof ShouldBroadcast && 187 | $this->broadcastWhen($payload[0]); 188 | } 189 | ``` 190 | 191 | 默认情况下,我们的容器对象并不是ShouldBroadcast类,我们可以手动在`dispatch`方法中做中断测试: 192 | 193 | ```php 194 | public function dispatch($event, $payload = [], $halt = false) 195 | { 196 | // When the given "event" is actually an object we will assume it is an event 197 | // object and use the class as the event name and this event itself as the 198 | // payload to the handler, which makes object based events quite simple. 199 | [$event, $payload] = $this->parseEventAndPayload( 200 | $event, $payload 201 | ); 202 | 203 | var_dump($payload[0] instanceof ShouldBroadcast); 204 | exit; 205 | ... 206 | ... ... 207 | ``` 208 | 209 | 输出结果: 210 | 211 | ```php 212 | bool(false) 213 | ``` 214 | 215 | > ShouldBroadcast实际上是"广播"功能相关的类,本节我们重点介绍一下后面的事件处理机制,弄清楚事件处理的机制后,和广播相关的代码大家可以依葫芦画瓢,用同样的方法去分析,这里我们就暂且略过了。 216 | 217 | 继续看后面的代码: 218 | 219 | ```php 220 | $responses = []; 221 | 222 | foreach ($this->getListeners($event) as $listener) { 223 | $response = $listener($event, $payload); 224 | 225 | if ($halt && ! is_null($response)) { 226 | return $response; 227 | } 228 | 229 | 230 | if ($response === false) { 231 | break; 232 | } 233 | 234 | $responses[] = $response; 235 | } 236 | 237 | return $halt ? null : $responses; 238 | ``` 239 | 240 | 这里简单描述就是:先从$event身上拿到所有的$listener,然后循环处理。如果传递的$halt值为true且$repsonse值不为null,则在第一次$listener执行之后就立刻返回。否则将执行结果保存为数组,最后一句,如果$halt值为true,返回值为null,否则返回$response数组。 241 | 242 | 接下来,我们重点分析`getListeners`方法: 243 | 244 | ```php 245 | /** 246 | * Get all of the listeners for a given event name. 247 | * 248 | * @param string $eventName 249 | * @return array 250 | */ 251 | public function getListeners($eventName) 252 | { 253 | $listeners = $this->listeners[$eventName] ?? []; 254 | 255 | $listeners = array_merge( 256 | $listeners, 257 | $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName) 258 | ); 259 | 260 | return class_exists($eventName, false) 261 | ? $this->addInterfaceListeners($eventName, $listeners) 262 | : $listeners; 263 | } 264 | ``` 265 | 266 | 这个方法中的第一行语句运行后,获取到$listeners值是什么呢?我们仍然使用【var_dump中断测试】的方法来看一下: 267 | 268 | ```php 269 | public function getListeners($eventName) 270 | { 271 | $listeners = $this->listeners[$eventName] ?? []; 272 | var_dump($listeners); 273 | exit; 274 | ``` 275 | 276 | 输出结果如下: 277 | 278 | ```php 279 | array(0) { } 280 | ``` 281 | 282 | 这里为了更加清楚地知道$eventName的值,我们打印一下$eventName看看: 283 | 284 | ```php 285 | public function getListeners($eventName) 286 | { 287 | $listeners = $this->listeners[$eventName] ?? []; 288 | var_dump($eventName); 289 | var_dump($listeners); 290 | exit; 291 | ``` 292 | 293 | 输出结果如下: 294 | 295 | ``` 296 | string(71) "bootstrapping: Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables" array(0) { } 297 | ``` 298 | 299 | 这和我们前面分析的情况一致,现在我们重新调整一下测试的策略,改写一下`getListeners`这个方法: 300 | 301 | ```php 302 | public function getListeners($eventName) 303 | { 304 | $listeners = $this->listeners[$eventName] ?? []; 305 | 306 | $listeners = array_merge( 307 | $listeners, 308 | $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName) 309 | ); 310 | 311 | $ret = class_exists($eventName, false) 312 | ? $this->addInterfaceListeners($eventName, $listeners) 313 | : $listeners; 314 | var_dump($ret); 315 | return $ret; 316 | } 317 | ``` 318 | 319 | 输出结果如下: 320 | 321 | ![](../images/test_20.png) 322 | 323 | 注意看页面上打印的这些数组信息,可以看到返回的结果都是空数组。这是因为,默认情况下,我们并没有为blog应用添加任何事件。 324 | 325 | 接下来,我们就来给blog应用添加事件: 326 | 327 | 1) 首先我们修改EventsServiceProvider文件(app/Providers/EventServiceProvider.php): 328 | 329 | ```php 330 | [ 348 | SendEmailVerificationNotification::class, 349 | ], 350 | 'App\Events\PostSaved'=>[ 351 | 'App\Listeners\SaveDataToCache', 352 | ] 353 | ]; 354 | 355 | /** 356 | * Register any events for your application. 357 | * 358 | * @return void 359 | */ 360 | public function boot() 361 | { 362 | parent::boot(); 363 | 364 | // 365 | } 366 | } 367 | ``` 368 | 369 | 请大家注意,我们在$listen数组中添加了一个事件PostSaved,同时定义了这个事件的监听动作为SaveDataToCache。 370 | 371 | 2)修改完EventServiceProvider.php文件后,打开命令行,输入如下命令: 372 | 373 | ```php 374 | php artisan event:generate 375 | ``` 376 | 377 | 我们就能看到在app根目录下,会新增两个文件夹Events和Listeners,并且这两个文件夹下会有我们定义好的两个php文件:PostSaved.php和SaveDataToCache.php。 378 | 379 | 其中PostSaved.php源码为: 380 | 381 | ```php 382 | middleware('auth'); 485 | } 486 | 487 | /** 488 | * Show the application dashboard. 489 | * 490 | * @return \Illuminate\Contracts\Support\Renderable 491 | */ 492 | public function index() 493 | { 494 | event(new PostSaved()); 495 | return json_encode(['a'=>1,'b'=>2]); 496 | } 497 | } 498 | 499 | ``` 500 | 501 | 现在我们访问这个URL:http://dev.blog.z/home 502 | 503 | > 请大家注意,访问之前要先定义路由,比如像这样:Route::get('/home', 'HomeController@index'); 504 | 505 | 刷新页面之后,显示的结果如下: 506 | 507 | ``` 508 | Save data to cache executed!{"a":1,"b":2} 509 | ``` 510 | 511 | 可以看到,事件被成功运行了。 512 | 513 | 现在我们再次回到Dispatcher类中的`getListeners`方法中,通过改写代码做var_dump中断测试: 514 | 515 | ```php 516 | public function getListeners($eventName) 517 | { 518 | $listeners = $this->listeners[$eventName] ?? []; 519 | if ($eventName=='App\Events\PostSaved') { // 加if语句是因为调用getListeners方法的次数很多, 如果不加if程序会在第一次执行这个方法时就停止 520 | var_dump($listeners); 521 | } 522 | exit; 523 | ``` 524 | 525 | 页面输出结果如下: 526 | 527 | ```php 528 | array(1) {[0]=> object(Closure)#130 (3) { ["static"] => array(2) { ["listener"] => string(29) "App\Listeners\SaveDataToCache"... ... 529 | ``` 530 | 531 | 读者如果真的按照上面的步骤操作下来,不出意外的话,这个页面会一直处于加载的状态直到停止响应。这当然是因为我们打印的这个对象虽然是一个闭包,但是闭包中包含的了很多次容器对象的递归引用。这一点我们在前面【继续前行】这一节中已经给大家讲阐述过了。 532 | 533 | 至此,我们发现:在执行`dispatch`方法之前,$listeners成员变量身上,已经包含了我们注册的这个事件的相关"绑定"。其中键名就是"App\Events\PostSaved",键值是一个闭包。新的问题出现了,这个"绑定"是在什么时候执行的呢? 534 | 535 | 要了解这一点,请大家回到【处理请求】这一节中,在"6个类各自执行自己bootstrap方法的阶段",第6个类BootProviders的bootstrap方法调用了容器的自身的boot方法,而这个方法体中,正好包含了provider类的处理: 536 | 537 | ```php 538 | public function boot() 539 | { 540 | if ($this->isBooted()) { 541 | return; 542 | } 543 | 544 | // Once the application has booted we will also fire some "booted" callbacks 545 | // for any listeners that need to do work after this initial booting gets 546 | // finished. This is useful when ordering the boot-up processes we run. 547 | $this->fireAppCallbacks($this->bootingCallbacks); 548 | 549 | array_walk($this->serviceProviders, function ($p) { 550 | $this->bootProvider($p); 551 | }); 552 | 553 | $this->booted = true; 554 | 555 | $this->fireAppCallbacks($this->bootedCallbacks); 556 | } 557 | ``` 558 | 559 | 这段代码中,我们看看`$this->bootProvider`做了什么: 560 | 561 | ```php 562 | /** 563 | * Boot the given service provider. 564 | * 565 | * @param \Illuminate\Support\ServiceProvider $provider 566 | * @return mixed 567 | */ 568 | protected function bootProvider(ServiceProvider $provider) 569 | { 570 | if (method_exists($provider, 'boot')) { 571 | return $this->call([$provider, 'boot']); 572 | } 573 | } 574 | ``` 575 | 576 | 和事件有关的serviceProvider就是EventServiceProvider,追踪这个类的boot方法: 577 | 578 | ```php 579 | /** 580 | * Register any events for your application. 581 | * 582 | * @return void 583 | */ 584 | public function boot() 585 | { 586 | parent::boot(); 587 | 588 | // 589 | } 590 | ``` 591 | 592 | 继续追踪它的父类的boot方法: 593 | 594 | ```php 595 | /** 596 | * Register the application's event listeners. 597 | * 598 | * @return void 599 | */ 600 | public function boot() 601 | { 602 | $events = $this->getEvents(); 603 | 604 | foreach ($events as $event => $listeners) { 605 | foreach (array_unique($listeners) as $listener) { 606 | Event::listen($event, $listener); 607 | } 608 | } 609 | 610 | foreach ($this->subscribe as $subscriber) { 611 | Event::subscribe($subscriber); 612 | } 613 | } 614 | ``` 615 | 616 | 这段代码中`Event::listen($event, $listener);`这一行,Event类实际是通过"Facade"代理的方式得到的类,它代理的类正是我们本节讲的Dispatcher类。 617 | 618 | 我们去看一下Dispatcher类的listen方法: 619 | 620 | ```php 621 | /** 622 | * Register an event listener with the dispatcher. 623 | * 624 | * @param string|array $events 625 | * @param mixed $listener 626 | * @return void 627 | */ 628 | public function listen($events, $listener) 629 | { 630 | foreach ((array) $events as $event) { 631 | if (Str::contains($event, '*')) { 632 | $this->setupWildcardListen($event, $listener); 633 | } else { 634 | $this->listeners[$event][] = $this->makeListener($listener); 635 | } 636 | } 637 | } 638 | ``` 639 | 640 | 在这个方法中,我们重点关注else分支中的语句:`$this->listeners[$event][] = $this->makeListener($listener);`,继续追踪`makeListener`方法: 641 | 642 | ```php 643 | /** 644 | * Register an event listener with the dispatcher. 645 | * 646 | * @param \Closure|string $listener 647 | * @param bool $wildcard 648 | * @return \Closure 649 | */ 650 | public function makeListener($listener, $wildcard = false) 651 | { 652 | if (is_string($listener)) { 653 | return $this->createClassListener($listener, $wildcard); 654 | } 655 | 656 | return function ($event, $payload) use ($listener, $wildcard) { 657 | if ($wildcard) { 658 | return $listener($event, $payload); 659 | } 660 | 661 | return $listener(...array_values($payload)); 662 | }; 663 | } 664 | ``` 665 | 666 | createClassListener: 667 | 668 | ```php 669 | /** 670 | * Create a class based listener using the IoC container. 671 | * 672 | * @param string $listener 673 | * @param bool $wildcard 674 | * @return \Closure 675 | */ 676 | public function createClassListener($listener, $wildcard = false) 677 | { 678 | return function ($event, $payload) use ($listener, $wildcard) { 679 | if ($wildcard) { 680 | return call_user_func($this->createClassCallable($listener), $event, $payload); 681 | } 682 | 683 | return call_user_func_array( 684 | $this->createClassCallable($listener), $payload 685 | ); 686 | }; 687 | } 688 | ``` 689 | 690 | 这个方法返回的正是一个闭包。 691 | 692 | 总结: 693 | 694 | 1) Dispatcher类的dispatch方法,是触发Laravel框架中事件执行的核心方法。 695 | 696 | 2) Laravel框架在启动阶段,会调用各个ServiceProvider类身上的boot方法,这个阶段完成事件的绑定(将事件转换成闭包绑定到相应事件名称的键值型数组$listeners中) 697 | 698 | 3) 除了框架自定义的几个事件是通过bootstrapWith方法触发执行的,其他用户自定义的事件,需要用户主动调用event函数才能进行触发。 --------------------------------------------------------------------------------