xiaohuilam / laravel Goto Github PK
View Code? Open in Web Editor NEWThis project forked from laravel/laravel
Laravel 深入详解 —— 源代码解析,新手进阶指南
This project forked from laravel/laravel
Laravel 深入详解 —— 源代码解析,新手进阶指南
在 public/index.php
中第 24 行
Line 24 in 7028b17
逻辑为,引入 ../vendor/autoload.php
,即 composer
产生的 vendor/autoload.php
。
接着第 38 行
Line 38 in 7028b17
这里面做的事情不少,具体的我们看 bootstrap/app.php
的代码。
在第 [14-16行]
Lines 14 to 16 in 7028b17
这是 Laravel
框架内为数不多的直接 new
出来的对象,也就是框架的应用对象,
此对象继承于 Illuminate\Container\Container
(容器),
所以, Laravel
框架中的容器对象一般就是指的 Illuminate\Foundation\Application
对象(因在实际运行过程中,不会直接 new Illuminate\Container\Container
的)。
在第 [29-42行]
Lines 29 to 42 in 7028b17
这里的singleton是 public/index.php
中,用 Illuminate\Contracts\Http\Kernel
能注入后取出 App\Http\Kernel
的关键。
关于
singleton()
的解析,请见 10. 容器的 singleton 和 bind 的实现
第 55行
Line 55 in 7028b17
在前面 public/index.php
中的
Line 38 in 7028b17
return
的 $app
。
在 public/index.php
第 52 行
Line 52 in 7028b17
这里调用了 Illuminate\Foundation\Application
的 make 方法(具体解析请见本篇结尾的链接),获得了 http 处理器 ( $kernel
)
然后第 54-56 行
Lines 54 to 56 in 7028b17
Illuminate\Http\Request::capture()
抓出一个 Illuminate\Http\Request
HTTP请求对象
Illuminate\Http\Request::capture()
的代码为
laravel/vendor/laravel/framework/src/Illuminate/Http/Request.php
Lines 55 to 60 in d081c91
其中调用了 Symfony\Component\HttpFoundation\Request::createFromGlobals()
laravel/vendor/symfony/http-foundation/Request.php
Lines 279 to 291 in d081c91
key => value
格式生成 Symfony\Component\HttpFoundation\ParameterBag
对象。ParameterBag
的 $request
对象交给 Illuminate\Http\Request::createFromBase()
laravel/vendor/laravel/framework/src/Illuminate/Http/Request.php
Lines 404 to 422 in d081c91
(new static)->duplicate()
最终执行了
laravel/vendor/symfony/http-foundation/Request.php
Lines 427 to 469 in d081c91
在后面的
Request::getInputSource()
的代码是laravel/vendor/laravel/framework/src/Illuminate/Http/Request.php
Lines 351 to 358 in d081c91
作用是调用 $request->input()
之时
GET、HEAD 请求,能取到 GET 参数,
POST 请求,能取到 POST 参数。
至此,Request::capture()
就执行完了。
然后交给 App\Http\Kernel
的 handle
方法进行处理,获得 $response (Illuminate\Http\Request
对象)。
Laravel 框架的设计绝大可扩展部分(如服务提供者、中间件)都是在这个阶段处理和触发的,
所以我单独把 02. HTTP Kernel Handle 解析 拆出去了。
最后,在第 58-60行
Lines 58 to 60 in 7028b17
调用 Illuminate\Http\Response 的 send 方法,将响应的状态码/头/内容返回给客户端(浏览器),具体过程可见本篇末尾的引用。
Illuminate\Foundation\Application
的 singleton
、bind
方法和 make
解析请见 10. 容器的 singleton 和 bind 的实现App\Http\Kernel
的 handle 方法解析请见 02. Kernel Handle解析App\Http\Kernel
的 terminate 方法解析请见 14. Response 对象和 Kernel::terminate一图胜千言,如果配上图,是不是更好表达,您觉得了?
Laravel 提供了完善的用户状态判断鉴定、验证机制,这篇文章我们用十五分钟左右的时间来深入框架内部来了解它。
在学习之前,建议先查阅 Laravel China 文档库 > Laravel 5.7 > 用户认证,熟悉用法后,学习效率更高。
类 | 描述 |
---|---|
Illuminate\Auth\SessionGuard |
当 guard 定义为 web 生效 |
Illuminate\Auth\EloquentUserProvider |
默认的用户提供者 |
App\Http\Middleware\Authenticate |
中间件 |
App\User |
应用自定义 User 模型 |
Illuminate\Auth\Authenticatable |
这是一个 trait,被 App\User 的父类 Illuminate\Foundation\Auth\User 所使用 |
Auth::guard 可用的守护者定义在:
Lines 38 to 48 in 4d76a68
这些配置文件中的 providers
定义在
Lines 67 to 77 in 4d76a68
而
Line 40 in 4d76a68
Line 45 in 4d76a68
session
和 token
分别指的是 Illuminate\Auth\SessionGuard
和 Illuminate\Auth\TokenGuard
。
具体的构建方法见
AuthManager::createSessionDriver
和AuthManager::createTokenDriver
,这两个方法是在AuthManager::resolve
时用以下代码调用的。
Illuminate\Auth\AuthManager
类
Illuminate\Auth\CreatesUserProviders
trait在 Illuminate\Foundation\Application
的 registerCoreContainerAliases
方法中,注册 auth
时, Illuminate\Auth\AuthManager
被使用。
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 1074 to 1117 in 4d76a68
registerCoreContainerAliases
方法是在 __construct
阶段被触发的。
在 Illuminate\Auth\AuthServiceProvider
中的 register
方法,
registerAuthenticator
方法为
registerUserResolver
方法为
这里便将 auth
和 Illuminate\Contracts\Auth\Authenticatable
成功的从 Application
中的 alias 升级为 singleton 。
在我们的 config/app.php
的 providers
第一个便是 Illuminate\Auth\AuthServiceProvider
Lines 122 to 127 in 4d76a68
到这里,web 环境框架启动部分所有初始化用户状态维护机制就完成。
当一个路由声明必须登陆后可访问,也就是 auth
中间件,会触发:
AuthManager
在容器构建 AuthManager
时,会触发 __construct
laravel/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php
Lines 49 to 56 in 4d76a68
中间件的 handle
调用了中间件 authenticate
方法
如果没登陆,报错
如果手工在业务中使用 auth()->user()
会触发
laravel/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php
Lines 290 to 293 in 4d76a68
AuthManager
的 guard()
默认返回的是 SessionGuard
,所以 auth()->user()
调用的是
laravel/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
Lines 113 to 151 in 4d76a68
其 131 行的 $this->provider->retrieveById($id)
调用了 EloquentUserProvider::retrieveById
$this->createModel()
返回的是 $this->model
,是在
在 AuthManager
调用 createUserProvider
后调用 createEloquentProvider
方法时,
其传进去的 $config
的获取逻辑为
也就是这里设定的 'model' => App\User::class
了
Lines 67 to 77 in 4d76a68
前面 getAuthIdentifierName
的方法,就是调用 App\User::getAuthIdentifierName
来查找用户了。
本文章采用「署名 4.0 国际」创作共享协议,
转载前请阅读 相关说明 »
根据 Laravel 的 Events 文档 (中文版:事件系统), EventServiceProvider
的使用方法为:
/**
* 注册应用中的其它事件。
*
* @return void
*/
public function boot()
{
parent::boot();
Event::listen('event.name', function ($foo, $bar) {
//
});
}
如果花点时间研究 Event
这个 Facade,我们可以找到他穿透类其实为 Illuminate\Events\Dispatcher
。
Illuminate\Events\Dispatcher
类的 __construct 方法为:
laravel/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
Lines 59 to 62 in d081c91
作用是将容器设置到
$this->container
属性
我们刚刚在 EventServiceProvider
调用的 Event::listen
方法的代码为:
laravel/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
Lines 71 to 80 in d081c91
如果监听了通配事件,
setupWildcardListen
在辗转后跟else
一样的调用到了makeListener
laravel/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
Lines 349 to 362 in d081c91
注意,返回的是闭包!并不会立即执行。
在存放到 $this->listeners
后,事件们就静静地等待被触发
在我们调用 event()
辅助方法后,触发的逻辑其实走到了 dispatch
方法
laravel/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
Lines 193 to 229 in d081c91
核心逻辑就这句:
还记得那个《高速修车》的陈年老梗吗:
公司的业务呢,就像跑在高速路上的车,车不能停,但是新需求和修bug也不能停。
这里要跟大家扒代码的 Macroable
呢,有点像上面这个悖论的出路;但其实也不是,因为他没有实现 修
且不停,只实现了 增
且不停。请原谅我这尴尬的幽默。
网上很多教程都是提及了,Laravel 绝大部分类,都可以扩展方法。比如这个例子:
use Illuminate\Support\Collection;
Collection::macro('bcsum', function () {
$sum = 0;
foreach ($this as $item) {
$sum = bcadd($sum, $item, 2);
}
return $sum;
});
dump((new Collection([1,2,3,4]))->bcsum()); // it says "10.00"
我们可以看到,在不改变代码的前提下,我们在使用的过程中给 Illuminate\Support\Collection
扩充了 bcsum
的功能。就如同在其中加入了如下代码:
<?php
class Collection ...
{
public function bcsum()
{
$sum = 0;
foreach ($this as $item) {
$sum = bcadd($sum, $item, 2);
}
return $sum;
}
}
我们看到,Illuminate\Support\Collection
使用了 Illuminate\Support\Traits\Macroable
这个 trait。
macro
的代码作用是将闭包存进static::$macros
在调用我们的方法时,按名字匹配出来,参数传入触发。
这里之所以定义了两个方法,__call
和 __callStatic
,是因为 laravel
很多类,部分可以静态调用,部分可以动态调用。macroable
如果要能被这两种类可用,那么就需要定义两个魔术方法。
一. 魔术一个魔术方法,不可行
我们现在知道了 Macroable
的神奇,但是其实Macroable
也有失灵的时候。举个例子:
<?php
class JoeDoe
{
use \Illuminate\Support\Traits\Macroable;
}
JoeDoe::macro('__toString', function () {
return '123';
});
echo new JoeDoe();
而运行时,其还是报了这个错:
PHP Recoverable fatal error: Object of class JoeDoe could not be converted to string
但是如果我们在 JoeDoe
这个类中手工硬编码 __toString
这个方法却是可以运行的的;
为什么?
其实道理很简单,因为 echo JoeDoe
对象时候,php 内核检查其中有没有 __toString
这个方法时,就已经报错了,根本还来不及走到 __call
这个魔术方法。
总结: Facade 就是一个标记便捷的调用容器盛放对象的方法的设计。
一般的,Facade
类都继承于 Illuminate\Support\Facades\Facade
。
比如 Illuminate\Support\Facades\Gate
这个门面:
<?php
namespace Illuminate\Support\Facades;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
class Gate extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return GateContract::class;
}
}
代码中我们只需要留意 getFacadeAccessor
方法是在 Illuminate\Support\Facades\Gate
中定义的就好了。
我们来分析 Illuminate\Support\Facades\Facade
:
首先我们来看末尾的 __callStatic
方法:
laravel/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
Lines 206 to 224 in ca57c28
还记得 Facade 类我们是怎么调用的吗?
静态调用!比如 Gate::allows()
。
因为在 Facade 类中定义了 __callStatic
魔术方法,所以只要静态调用了里面没有提供的方法,都会走到魔术方法里面。
这个魔术方法内部有一个取出实例的逻辑:
$instance = static::getFacadeRoot();
代码位于
laravel/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
Lines 123 to 131 in ca57c28
前面,我们留意了 static::getFacadeAccessor()
是位于 Illuminate\Support\Facades\Gate
中的,而 static::getFacadeAccessor()
运行后作为参数给了 static::resolveFacadeInstance()
,后者实现为
laravel/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
Lines 145 to 162 in ca57c28
而 static::$app
就是容器对象
这句
数组取
操作,就是调用了 make
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 1208 to 1211 in ca57c28
就是从容器取出对象。
getFacadeRoot
的逻辑大概为static::getFacadeAccessor()
返回是否对象,如果对象则直接使用。Facade::$resolvedInstance
数组中尝试根据 $name
取出对象,避免多次调用时,每次都取。Illuminate\Foundation\Application
容器取出对象。拿到 $instance
后,先验证 $instance
是否为空。如果是就报错 A facade root has not been set.
。
laravel/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
Lines 219 to 221 in ca57c28
所以如果遇到
A facade root has not been set.
这个错误,一般都是 a. 相应的服务提供者未注册; b. 对象绑定到容器的别名跟static::getFacadeAccessor()
返回不一致。
最后,直接把方法和参数穿透到背后的 $instance
去执行。
Laravel 有很多 facade 门面,背后穿透的不只一个类(譬如 DB
、 View
、Session
),这是怎么回事呢?
请看 15. Laravel 神奇的 Manager 类
App\Http\Kernel
继承自 Illuminate\Foundation\Http\Kernel
类,所以本文章的分析主要集中在 app/Http/Kernel.php 和 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 两个类中
因为 App\Http\Kernel
没有 __construct 方法,所以穿透到了 Illuminate\Foundation\Http\Kernel
的 __construct:
首先是此方法传入参数的 __construct(Application $app, Router $router)
声明,这里恰好使用了 Laravel 容器的依赖注入(Dependency Injection,Inversion of Control的一种)设计。具体的在本篇不讲述,可参见本篇末尾的单独分析的链接的文章。
执行到 __construct 时, Illuminate\Foundation\Application
容器会将 Illuminate\Foundation\Application
(即应用容器自身)和 Illuminate\Routing\Router
注入到方法内。然后逻辑代码将两个对象赋给 App\Http\Kernel
的 $this
属性中。
然后将 Illuminate\Foundation\Http\Kernel
的 $middlewarePriority 属性
Illuminate\Routing\Router
的 $middlewarePriority 属性
根据其注释,此属性是强制对 middleward 中间件执行顺序进行排序的作用。
在后面将 App\Http\Kernel
中声明的 $middlewareGroup
Lines 24 to 44 in d081c91
Illuminate\Foundation\Http\Kernel
的 96-98行,调用 Illuminate\Routing\Router
的 middlewareGroup 方法,存到 $router 的 $middlewareGroup 中laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 854 to 866 in d081c91
紧接着,将
Lines 46 to 63 in d081c91
Illuminate\Routing\Router
的 aliasMiddleware 的方法,绑定到 $router 的 $middleware 中laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 819 to 831 in d081c91
至此, Kernel::__construct()
解析完毕。
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 105 to 132 in d081c91
Symfony\Component\HttpFoundation\Request
的 enableHttpMethodParameterOverride 方法
Illuminate\Http\Request
继承自Symfony\Component\HttpFoundation\Request
并且Illuminate\Http\Request
未覆盖 enableHttpMethodParameterOverride
laravel/vendor/symfony/http-foundation/Request.php
Lines 638 to 652 in d081c91
然后,调用 Illuminate\Foundation\Http\Kernel
的 sendRequestThroughRouter
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 134 to 152 in d081c91
通过第142行,将 $request 注入进 Illuminate\Foundation\Application
容器。
144行,将门面类中的 request
数据清理掉
laravel/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
Lines 164 to 173 in d081c91
然后146行,调用 Illuminate\Foundation\Http\Kernel
的 bootstrap 方法
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 154 to 164 in d081c91
第161执行到容器的 hasBeenBootstrapped 方法
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 249 to 257 in d081c91
第162实际得到这个数组
bootstrapWith
方法laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 193 to 210 in d081c91
特别留意206行的 make
调用
关于容器
make
方法的细节
请查阅 10. 容器的 singleton 和 bind 的实现 的 “揭开 Container::make() 神秘的面纱” 段落
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 716 to 734 in d081c91
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 691 to 714 in d081c91
boot()
阶段的 $this->fireAppCallbacks($this->bootingCallbacks)
才会真正被创建。 关联阅读请见 04. ServiceProvider Boot 解析
然后结果就是依次执行
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables
\Illuminate\Foundation\Bootstrap\LoadConfiguration
\Illuminate\Foundation\Bootstrap\HandleExceptions
\Illuminate\Foundation\Bootstrap\RegisterFacades
\Illuminate\Foundation\Bootstrap\RegisterProviders
\Illuminate\Foundation\Bootstrap\BootProviders
这些 Bootstrap 类的 bootstrap 方法
上面列出的清单中,分别作用为
类 | 作用 |
---|---|
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables |
加载环境变量 |
\Illuminate\Foundation\Bootstrap\LoadConfiguration |
加载config |
\Illuminate\Foundation\Bootstrap\HandleExceptions |
错误处理者 |
\Illuminate\Foundation\Bootstrap\RegisterFacades |
注册门面类 |
\Illuminate\Foundation\Bootstrap\RegisterProviders |
注册服务提供者 |
\Illuminate\Foundation\Bootstrap\BootProviders |
启动服务提供者 |
其中最后两项在 laravel 请求的生命周期中是至关重要的,我们将在后续文章中重点讲解。
在 bootstrap
阶段结束后,Kernel::sendRequestThroughRouter
后面带 pipeline
关键字的代码就是管道。
关于管道请查阅 05. Pipeline 解析
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 148 to 151 in d081c91
在进入管道前, 调用了 dispatchToRouter
返回一个闭包对象
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 166 to 178 in d081c91
匹配路由的逻辑清晰可见
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 601 to 682 in d081c91
如果一路抽丝剥茧,我们便能找到 Router 调用 controller 的逻辑了。 请见06. RouteServiceProvider 详解 最后段落。
本章节以前面没有分析完成的 02. HTTP Kernel Handle 解析 和 06. RouteServiceProvider 详解 做铺垫,通过讲述其最后
runController
的内部逻辑,抛砖引玉,分析 Laravel 依赖注入的原理,因为前面我们讲了容器对象的绑定的实现 (见:10. 容器的 singleton 和 bind 的实现),所以在这里不再详述。
在大型项目中,因为类的繁多,彼此需要调用到的代码的更多,如果我们在用到对象的每一个地方去
new FooClass(... $parameters )
,那么我们的项目重复代码太多,早晚会失去优雅性和可维护性。所以依赖注入就是在用到一个对象的方法入参声明,你要什么对象,他基于什么实现。
框架
(亦或独立的容器服务
)在执行到这里的时候,发现你声明了需要那几个对象,在容器服务
里找到这几个对象的实例化逻辑,实例化出来扔进去执行。
laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php
Lines 202 to 214 in d081c91
这里的 Route
对象本身已经是单个路由了,所以 $this->getController()
和 $this->getControllerMethod()
取得的具体的 Controller 类名和方法名。
核心代码其实就是这个 $this->controllerDispatcher()->dispatch()
方法了
第一步 解析参数
resolveClassMethodDependencies
的实现
其中
为什么没报错,是考虑到 controller 也是可以用 __call
魔术方法来绑定路由的。
resolveMethodDependencies
的逻辑就是将传入的反射对象的入参解析完成
紧接前文代码
[划重点,前方高能]
如果此参数为一个类,且不是路由中出现的参数,返回 默认值(如有),否则从容器创建
dispatch
方法的下半段
如果控制器定义了 callAction
,执行。
接着直接调用 controller 的方法。
前言还没想好。
SessionServiceProvider
没有 boot
方法,其 register
方法为
作用
单例注册 Illuminate\Session\SessionManager
为 session
单例注册 Illuminate\Session\Store
为 session.store
具体生成
Illuminate\Session\Store
对象的过程,由你.env
配置SESSION_DRIVER
而调用Illuminate\Session\SessionManager
不同方法:
createArrayDriver
createCookieDriver
createFileDriver
createNativeDriver
其实 createFileDriver
就是调用的此方法createDatabaseDriver
createApcDriver
createMemcachedDriver
createRedisDriver
单例注册 Illuminate\Session\Middleware\StartSession
中间件
下面,我们以 SESSION_DRIVER=file
继续分析
当 SESSION_DRIVER
为 file
时,会调用到 createFileDriver
createFileDriver
调用了 createNativeDriver
这里 $this->app['config']['session.files']
得到的是
Line 60 in ca57c28
然后通过此处实例化 Illuminate\Session\FileSessionHandler
然后调用 buildSession
获得 \Illuminate\Session\Store
对象
laravel/vendor/laravel/framework/src/Illuminate/Session/SessionManager.php
Lines 163 to 170 in ca57c28
当触发 Session::start
时,
laravel/vendor/laravel/framework/src/Illuminate/Session/Store.php
Lines 69 to 78 in ca57c28
调用的 loadSession
为
laravel/vendor/laravel/framework/src/Illuminate/Session/Store.php
Lines 85 to 88 in ca57c28
调用的 readFromHandler
为
laravel/vendor/laravel/framework/src/Illuminate/Session/Store.php
Lines 95 to 106 in ca57c28
这里的 read
方法,就是 FileSessionHandler::read
执行到这里时, session 中的数据就被存放到了 Illuminate\Session\Store::$attributes
了。
而执行 Session::get
时,
laravel/vendor/laravel/framework/src/Illuminate/Session/Store.php
Lines 205 to 208 in ca57c28
正好就能从 Store::$attributes
中取出 session 的数据了。
当我们使用 Illuminate\Support\Facades\Session
时,操作是调用到的 session
,也就是 Illuminate\Session\SessionManager
。但是在 Illuminate\Support\Manager
的 __call
魔术方法,实现了将 session.driver
载入到 session
中被执行的能力。
laravel/vendor/laravel/framework/src/Illuminate/Support/Manager.php
Lines 144 to 147 in ca57c28
有兴趣的同学请阅读 15. Laravel 神奇的 Manager 类
假如我们执行 Session::put('key', 'value')
会穿透到
laravel/vendor/laravel/framework/src/Illuminate/Session/Store.php
Lines 265 to 274 in ca57c28
当我们手动 Session::save
时,或者程序终止运行触发 register_shutdown_function
时,会触发
laravel/vendor/laravel/framework/src/Illuminate/Session/Store.php
Lines 124 to 133 in ca57c28
也就是 FileSessionHandler::write
至此,写入 session 的流程走完。
Laravel 的管道机制,是其中间件 (middleware) 得以被序列执行的基础
在开始之前我们先帖一段管理的应用代码,位于 02. HTTP Kernel Handle解析
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 148 to 151 in d081c91
语义化得出的分析为
携带 send
请求对象,经过中间件的处理,然后进入路由。
send 携带方法
laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
Lines 53 to 64 in d081c91
$this
through 经过方法
laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
Lines 66 to 77 in d081c91
$this
then 异步方法
laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
Lines 92 to 105 in d081c91
之前的都只是做登记一下就GG的作用,这个 then
才是根本大法,
使用 array_reduce
处理得到 $pipeline
。这个 array_reduce
是何方神圣呢?我们查文档得到:
Iteratively reduce the array to a single value using a callback function(中文意思就是 使用回调函数,迭代地将数组减少为单个值)。
但在走到 array_reduce 之前,调用了 carry 方法 (在 5.3 及以前版本,carry 方法其实为 getSlice
方法,而过程几乎一样 来源),其内部逻辑为:
laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
Lines 120 to 159 in d081c91
Laravel
的这个 carry 方法返回的是一个闭包,执行返回的闭包所返回的又是一个闭包,只不过前一个返回是 array_reduce 批量执行时拿到的,后面是处理原始对象(请求)时,处理中间件时候拿到的。
$stack
为 null 或者闭包对象,第二个参数 $pipe
为从1开始的调用顺序。具体 handle 代码我们举一个例子:
其中的 $next($request)
表示执行下一个中间件, $next 指的就是下面这个闭包
laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
Lines 128 to 157 in d081c91
等上面执行完成,Laravel 就要执行这个闭包了
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 166 to 178 in d081c91
也就是执行路由的 dispatch 逻辑(包含被调用的逻辑一共 82 行代码)。
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 601 to 682 in d081c91
你可能会因为这里又有中间件而诧异
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 674 to 681 in d081c91
于是乎,Laravel 鬼使神差的实现了中间件的管道执行机制。
接下来,请查看 09. 容器的依赖注入机制 了解
$route->run()
背后的故事。
前面 《02. Kernel Handle解析》 结尾,我们留下了
注册服务提供者
和启动服务提供者
是两个比较重要的步骤的悬念,接下来两篇文章,我们来分别解析这两大流程。
整个 RegisterProviders 都是粮衣,其没有任何功能功能代码,而是调用到了 Illuminate\Foundation\ Application
的 registerConfiguredProviders
注意运行时实际并没有调用 Illuminate\Contracts\Foundation\Application 的 registerConfiguredProviders,而是调用了其具体实现类 Illuminate\Foundation\ Application 的 registerConfiguredProviders。不要被上面代码中的注解表象所迷惑
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 533 to 549 in d081c91
上面第 540-543 行是将 config/app.php
中配置的 providers
Lines 122 to 163 in d081c91
Illuminate\
开头,归为两组:
Illuminate\Support\Collection {#2825
all: [
Illuminate\Support\Collection {#2822
all: [
"Illuminate\Auth\AuthServiceProvider",
"Illuminate\Broadcasting\BroadcastServiceProvider",
"Illuminate\Bus\BusServiceProvider",
"Illuminate\Cache\CacheServiceProvider",
"Illuminate\Foundation\Providers\ConsoleSupportServiceProvider",
"Illuminate\Cookie\CookieServiceProvider",
"Illuminate\Database\DatabaseServiceProvider",
"Illuminate\Encryption\EncryptionServiceProvider",
"Illuminate\Filesystem\FilesystemServiceProvider",
"Illuminate\Foundation\Providers\FoundationServiceProvider",
"Illuminate\Hashing\HashServiceProvider",
"Illuminate\Mail\MailServiceProvider",
"Illuminate\Notifications\NotificationServiceProvider",
"Illuminate\Pagination\PaginationServiceProvider",
"Illuminate\Pipeline\PipelineServiceProvider",
"Illuminate\Queue\QueueServiceProvider",
"Illuminate\Redis\RedisServiceProvider",
"Illuminate\Auth\Passwords\PasswordResetServiceProvider",
"Illuminate\Session\SessionServiceProvider",
"Illuminate\Translation\TranslationServiceProvider",
"Illuminate\Validation\ValidationServiceProvider",
"Illuminate\View\ViewServiceProvider",
],
},
Illuminate\Support\Collection {#2823
all: [
"22" => "App\Providers\AppServiceProvider",
"22" => "App\Providers\AuthServiceProvider",
"22" => "App\Providers\EventServiceProvider",
"22" => "App\Providers\RouteServiceProvider",
],
},
],
}
接着 $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()])
这一行会得到如下结果
Illuminate\Support\Collection {#33
#items: array:3 [
0 => Illuminate\Support\Collection {#21
#items: array:22 [
0 => "Illuminate\Auth\AuthServiceProvider"
1 => "Illuminate\Broadcasting\BroadcastServiceProvider"
2 => "Illuminate\Bus\BusServiceProvider"
3 => "Illuminate\Cache\CacheServiceProvider"
4 => "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
5 => "Illuminate\Cookie\CookieServiceProvider"
6 => "Illuminate\Database\DatabaseServiceProvider"
7 => "Illuminate\Encryption\EncryptionServiceProvider"
8 => "Illuminate\Filesystem\FilesystemServiceProvider"
9 => "Illuminate\Foundation\Providers\FoundationServiceProvider"
10 => "Illuminate\Hashing\HashServiceProvider"
11 => "Illuminate\Mail\MailServiceProvider"
12 => "Illuminate\Notifications\NotificationServiceProvider"
13 => "Illuminate\Pagination\PaginationServiceProvider"
14 => "Illuminate\Pipeline\PipelineServiceProvider"
15 => "Illuminate\Queue\QueueServiceProvider"
16 => "Illuminate\Redis\RedisServiceProvider"
17 => "Illuminate\Auth\Passwords\PasswordResetServiceProvider"
18 => "Illuminate\Session\SessionServiceProvider"
19 => "Illuminate\Translation\TranslationServiceProvider"
20 => "Illuminate\Validation\ValidationServiceProvider"
21 => "Illuminate\View\ViewServiceProvider"
]
}
1 => array:5 [
0 => "BeyondCode\DumpServer\DumpServerServiceProvider"
1 => "Fideloper\Proxy\TrustedProxyServiceProvider"
2 => "Laravel\Tinker\TinkerServiceProvider"
3 => "Carbon\Laravel\ServiceProvider"
4 => "NunoMaduro\Collision\Adapters\Laravel\CollisionServiceProvider"
]
2 => Illuminate\Support\Collection {#31
#items: array:4 [
22 => "App\Providers\AppServiceProvider"
23 => "App\Providers\AuthServiceProvider"
24 => "App\Providers\EventServiceProvider"
25 => "App\Providers\RouteServiceProvider"
]
}
]
}
最后一句代码
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
第一步是将加载缓存好的 bootstrap/cache/services.php
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 855 to 863 in d081c91
其实就是执行 ProviderRepository::load()
$providers-> collapse() 得到的数组 (这个数组是前面 splice
拆分后再并入的)。
这句 loadManifest 即为加载前面传入的 bootstrap/cache/services.php
第60行调用到的的 shouldRecompile
逻辑为判断是否 provider
不符合,代码
如果需要重新编译此服务提供者缓存的 bootstrap/cache/services.php
,把数据 $manifest
提取到 bootstrap/cache/services.php
,具体过程为:
到这时,ProviderRepository::load() 的服务提供者缓存的判断和执行流程就结束了。我们回到 ProviderRepository::load() 的后面流程,接下来就是
没错,触发 registerLoadEvents
方法了。感谢 laravel 代码做到了足够语义化,我们猜到了这里就是触发服务提供者注册事件的。具体流程为:
再继续后面,就是 ProviderRepository::load() 触发容器的 register
的流程了
Illuminate\Foundation\Container\Appplication
容器的 register
方法代码为
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 551 to 600 in d081c91
步骤
getProvider
这个服务,如果能取到证明注册过了,除非要求强硬方式 $force
就不再注册$provier
为具体的 ServiceProvider 对象$provider
有无 register
方法,有则运行$provider
有无定义 $bindings
属性,若有,则依次 $key
,$value
为参,进入 Illuminate\Foundation\Container\Appplication::bind($key, $value)
执行$provider
有无定义 $singletons
属性,若有,则依次 $key
,$value
为参,进入 Illuminate\Foundation\Container\Appplication::singleton($key, $value)
执行Illuminate\Foundation\Container\Appplication::$loadedProviders[ServiceProvider::class] = true
bootProvider($provider)
Laravel 有不少门面类,如
Session
、View
初一看令人诈舌,这些类的功能类不只一个,那么 Laravel 是怎么让这些奇葩的门面通往不止一个后门的?
这是一个抽象类。
laravel/vendor/laravel/framework/src/Illuminate/Support/Manager.php
Lines 10 to 29 in ca57c28
$app
是容器,这个 $customCreators
是什么鬼没看懂我们可以不管它。后面这个 $drivers
,是一个数组,放的什么东西呢?
driver 在英语中是驱动器的意思。
算了。。。再这么讲下去太拖了。我们拉到最后
laravel/vendor/laravel/framework/src/Illuminate/Support/Manager.php
Lines 144 to 147 in ca57c28
我们看到,当调用到当前实现类不存在方法时会调用 $this->driver()
下的同名方法。
laravel/vendor/laravel/framework/src/Illuminate/Support/Manager.php
Lines 57 to 75 in ca57c28
然后 getDefaultDriver
,
这是抽象函数,我们到具体的例子中看吧。比如 session
laravel/vendor/laravel/framework/src/Illuminate/Session/SessionManager.php
Lines 200 to 203 in ca57c28
所以,这些继承于 Illuminate\Support\Manager
的门后类,实现了向 driver
的穿透。
总结: Manager first, driver end.
经理先行,司机垫后
在前面 01. HTTP 入口解析 的第 54-56 行代码分析时
Lines 54 to 56 in 7028b17
我们只分析了 $request
的产生,却没有分析 $response
。
这节,我们就来研究下 Laravel 的 Response 对象: Illuminate\Http\Response
。
不难看出,这句代码拿到的一定是个对象。为什么?
因为在下文,就调用了他的 ->send()
方法。
Line 58 in 7028b17
那么 Illuminate\Http\Response
对象是从哪里来的呢?
在从 入口 到 Kernel::handle()
到 管道 的过程尾声,我们拿到了这句逻辑
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 674 to 681 in d081c91
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 678 to 680 in d081c91
这个 prepareResponse
是干嘛的呢?
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 717 to 720 in d081c91
调用了
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 729 to 755 in d081c91
过程为:
PsrResponseInterface
对象;statusCoe
强制覆盖成 HTTP 201 created
;JsonResponse
;Response
;于是乎,我们才可以在 controller 中大胆的直接返回字符串而转换成
Response
对象。因为在这里他帮我们转了。
经过上面这一波加工后,在 public/index.php 里面才敢执行 $response->send()
laravel/vendor/symfony/http-foundation/Response.php
Lines 366 to 378 in d081c91
调用的 sendContent
逻辑为
laravel/vendor/symfony/http-foundation/Response.php
Lines 354 to 359 in d081c91
echo
代码清晰可见,非常简单。
先于 sendContent
的 sendHeaders
的代码要稍微复杂一点
laravel/vendor/symfony/http-foundation/Response.php
Lines 324 to 347 in d081c91
先是将 http 响应头依次输出,然后将 Cookie 处理后输出。
在 send
方法的后面,还有几句
laravel/vendor/symfony/http-foundation/Response.php
Lines 371 to 375 in d081c91
fastcgi
或者 cli
模式调用,如果是就关闭连接、刷新缓冲区。
至此,send
逻辑就讲完了。
App\Http\Kernel
并没有 terminate
方法,一路穿透到了 Illuminate\Foundation\Http\Kernel
里面
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 187 to 192 in d081c91
Illuminate\Foundation\Http\Kernel::terminateMiddleware()
逻辑
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
Lines 201 to 221 in d081c91
bind
过的中间件 terminate()
掉。
而这句就比较好理解了,直接调用了容器的 terminate()
容器的 terminate()
方法也比较简单,触发 $terminatingCallbacks
注册的回调。
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 962 to 967 in d081c91
PHP的不常驻特性,一个请求完了就自动回收资源,那么除了需要触发 $terminatingCallbacks
回调外,为什么还要实现 terminate()
呢?
Laravel 为我们提供了一个 php artisan serve
的便捷服务器,启动后 Application
对象是常驻的。所以需要单独回收需要回收的资源。
php artisan serve
的本质也是php -S
,详细资料 PHP 的命令行模式>内置Web Server。
扩展阅读:在 Laravel 5.3 以前,
Kernel::terminate()
还调用了fastcgi_finish_request()
。后来移除了,主要原因是因为这个函数会阻断 http 响应的头和 body,造成部分特殊情况的 session 问题,不过也可能还有其他原因。
容器的
singleton
和bind
方法在整个 Laravel 框架或扩展中是调用的比较频繁的底层方法,掌握其原理能帮助我们加深 laravel 容器的理解。
singleton()
代码单身狗模式
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 338 to 348 in d081c91
bind()
代码分裂模式
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 206 to 240 in d081c91
初略看二者差异可能看不大出来。
我们从他们暴露的接口入手,先用,再啪(扒)源码。
祭出大杀器:php artisan tinker
>>> app()->bind('test', function(){dump(1); return new \StdClass();});
1
=> null
>>>
>>> app()->make('test')
1
=> {#2895}
>>>
>>> app()->make('test')
1
=> {#2896}
可以看出,调用 bind
时,每次取出被绑定的对象时,都会重新去构建一次。
如果用singleton
呢。
>>> app()->singleton('test', function(){dump(1); return new \StdClass();});
1
=> null
>>> app()->make('test')
=> {#2915}
>>> app()->make('test')
=> {#2915}
>>>
我们回头再看下 bind()
方法
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 206 to 240 in d081c91
逻辑步骤为:
$abstract
、$concrete
dropStaleInstances
移除之前 bind 过的 $abstract
的数据(旧的 $concret
)$concrete
,则将 $concrete
设置成 $abstract
Container::getClosure()
方法将闭包改成一个接受 $container
、$parameters
的包裹后的闭包。此闭包实质就是在需要运行的时候,运行闭包。Container::$bindings
数组 $abstract
为 key 的值设置为 compact('concrete', 'shared')
,即 ["concrete" => $concrete, "shared" => $shared]
bind
的对象已经 resolved
过了,通过 Container::rebound()
触发 reboundCallbacks
中的回调。到目前为止,暂时还没看出 $share
对 singleton
和 bind
的影响。只知道 bind
在执行过程中,将 $shared
和 $concrete
一起存到了 Container::$bindings
中。
用句四川话,莫慌,我们马上就要揭开关键的面纱了。。。
Container::make()
神秘的面纱laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 592 to 602 in d081c91
其实
make
是直接透传给了resolve
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 616 to 675 in d081c91
第一步骤的
是做了森么呢?
还原用Container::alias()
方法设置过别名的原始$abstract
。下一步,
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 627 to 629 in d081c91
Container::getContextualConcrete()
其实是取这里设置进取的上下文绑定
的数据
ContextualBindingBuilder::give()
暂时就不在这里暂开研究了。咱们继续往下看
$needsContextualBuild
值为make()
方法是否传了第二个参数,也就是$parameters
。needs contextual build
意思是构建时,是否具有上下文关联性
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 634 to 636 in d081c91
判断Container::$instances
数组中是否能通过$abstract
找到对象。如果找得到且不具有上下文关联性,就用之前创建好的对象。laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 638 to 640 in d081c91
getConcrete()
代码为
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 680 to 697 in d081c91
相信你能看出来
return $this->bindings[$abstract]['concrete']
,就是根据$abstract
把前面bind
或singleton
的数据返回。这里的
isBuildable
是判断$abstract
是否与$concrete
一致或$concrete
为闭包如果不是,那么可能是还有递归调用,需要再走一次
make
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 645 to 649 in d081c91
645 行调用的
isBuildable()
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 745 to 748 in d081c91
接着调用了
Container::build()
方法laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 758 to 801 in d081c91
其中的
和是通过反射解析待构建类所需参数中其他对象参数,从容器中取出填入。laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 789 to 800 in d081c91
再往下面就是执行
Container::extend
设置的扩充流程了。
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 654 to 656 in d081c91
再后面,才是对我们
singleton()
和bind()
产生深远影响的代码:
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 661 to 663 in d081c91
我们在前面知道,如果为
singleton()
执行到resolve()
的第二个参数$shared
为true
,bind
为false
。
前面$needsContextualBuild
判断的位置,判断了Container::$instance
中是否有$abstract
, 所以在这里如果resolve
没有将$object
绑入Container::$instance
,那么下次Container::make()
依旧会在去build()
一次,这就产生了singleton()
和bind()
的特性。再接着,就是触发事件了。
最后是一些收尾工作
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 670 to 674 in d081c91
此章节为什么叫做单身狗呢?因为
Container::make()
严格遵守$shared
,如果声明了true
,他保证不搞一个分身(所以是单身)给你。
随着前面 《02. Kernel Handle解析》 和 《03. ServiceProvider Register 解析》 的结束,我们接下来要分析的便是 启动服务提供者 这个步骤。
同 ServiceProvider Register
一样的,在 BootProviders 中也是调用了 Illuminate\Foundation\Application::boot()
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 759 to 782 in d081c91
首先判断是否 boot 过。
然后通过触发 $bootingCallbacks 钩子
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 823 to 834 in d081c91
$bootingCallbacks 是来自 02. HTTP Kernel Handle解析 登记的闭包,主要是服务于声明了
$defer = true
的服务提供者。
然后依次遍历 $this->serviceProviders
执行 Illuminate\Foundation\Application::bootProvider()
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 784 to 795 in d081c91
其实就是运行一遍 ServiceProvider 的 boot 方法
接着将 Illuminate\Foundation\Application::$boot
设置为 true
最后触发 bootedCallbacks 钩子
在 config/app.php
中我们能看到 Illuminate\Broadcasting\BroadcastServiceProvider
这个服务提供者被注册:
Line 128 in 72bccf5
boot
或 register
方法之一,这 BroadcastServiceProvider
就是一个包含而且只包含了 boot
方法的服务提供者:laravel/app/Providers/BroadcastServiceProvider.php
Lines 10 to 20 in 72bccf5
第17行调用到了 Illuminate\Support\Facades\Broadcast::routes()
假面方法。
顾名思义,就是注册路由的。而这个方法,最终是穿透到了 Illuminate\Broadcasting\BroadcastManager::routes()
第19行是为了在 config/app.php
卸载 BroadcastServiceProvider
时,直观的让 routes/channels.php
失效的做法,所以就不放在别处
第61~63行是如果缓存过路由,则跳出。
第62~第72行相当于与执行了
Route::group(['middleware' => ['web']], function () {
Route::match(['get', 'post'], '/broadcasting/auth', '\\'.BroadcastController::class.'@authenticate');
});
方法调用到的 controller action
其实是
这个假面方法 Broadcast::auth()
比较特殊,是根据 config('broadcasting.default')
分发到 Illuminate/Broadcasting/Broadcasters 具体的类中:
我们以 redis
的配置为例 (laravel-echo-server
) 。
第一步,如果 $request->channel_name
不以 private-
或者 presence-
开头,或者没有提供用户授权导致请求取不到用户,抛出 Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
异常
第二步,将 $request->channel_name
中的 private-
或者 presence-
还原移除。
第三步,调用 Broadcaster:: verifyUserCanAccessChannel()
将 routes/channels.php
中定义的 channel_name
匹配上的通道的鉴权回调执行,拿到返回值。如果 if ($result = $handler($request->user(), ...$parameters))
返回成功的 RedisBroadcaster::validAuthenticationResponse()
,否则抛出 Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
异常
而成功的响应 RedisBroadcaster::validAuthenticationResponse()
逻辑为返回 user_id
和 user_info
给 websocket 客户端:
至此,鉴权部分完成。
broadcast()
方法定义于
laravel/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
Lines 216 to 219 in 72bccf5
此方法最终走到了
PendingBroadcast
类比较特殊,有个 __destruct
解构方法:
走到
laravel/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
Lines 185 to 229 in 72bccf5
调用了
laravel/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
Lines 272 to 281 in 72bccf5
还是回到了 BroadcastManager
config('broadcasting.default')
配置,
第127~129行,是将广播事件 BroadcastEvent
入队。
根据我们对队列的了解,BroadcastEvent
应该有 handle()
方法
第46~49行,是执行广播,调用了 RedisBroadcaster::broadcast()
消息进入 redis。
// TODO: laravel-echo-server 部分。
在
RouteServiceProvider::boot
阶段,所有路由都只是执行登记,匹配的逻辑是在 02. HTTP Kernel Handle解析 的 dispatchToRouter 阶段。
我们先找到 RouteServiceProvider.php
在 config/app.php
的 providers
中定义的 RouteServiceProvider 其实是:
Line 161 in 6d9215c
Illuminate\Foundation\Support\Providers\RouteServiceProvider
laravel/app/Providers/RouteServiceProvider.php
Lines 6 to 9 in 6d9215c
我们分析 Illuminate\Foundation\Support\Providers\RouteServiceProvider
的代码
其 register
方法是空的
而在 boot
阶段,几乎运行了 RouteServiceProvider
中的所有方法
Illuminate\Foundation\Application::routesAreCached
,
其实就是判断文件 bootstrap/cache/routes.php
是否存在
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 895 to 903 in d081c91
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 905 to 913 in d081c91
map
方法在这里laravel/app/Providers/RouteServiceProvider.php
Lines 31 to 43 in d081c91
laravel/app/Providers/RouteServiceProvider.php
Lines 45 to 72 in d081c91
mapWebRoutes
和 mapApiRoutes
是分别将 routes/web.php 和 routes/api.php 用 Route
门面类的 group 加载了一遍。 Route::group
实质运行到的是 Illuminate\Routing\Router::group
(关于门面类的文章请见 [TODO]),代码为laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 356 to 416 in d081c91
Route::group
去执行里面的 Route::get
/ Route::post
...laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 133 to 214 in d081c91
addRoute
将这个路由的属性登记了一下:laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 434 to 445 in d081c91
$this->routes
是一个 \Illuminate\Routing\RouteCollection
的集合类,add
由这个方法生成的路由laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 447 to 478 in d081c91
路由登记完成后,就是 Kernel 触发管道层层剥洋葱调用中间件最后触发路由 dispatch (当然,这已经运行到 RouteServiceProvider 的外面了)。
剥洋葱的过程请查阅 05. Pipeline 解析
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 660 to 682 in d081c91
第679行的 $route->run
是至关重要的,调用到了 controller (本质其实是使用 09. 容器的依赖注入机制 将路由方法所依赖参数解析出来,并运行)
laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php
Lines 158 to 176 in d081c91
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.