要想了解Laravel的整个运行流程,那么就需要从入口文件下手,再一步步的往下探索,剥开神秘的面纱。在Laravel框架中入口文件就是public文件夹下的index.php文件。Laravel的生命周期也就是从这里开始的,从这里结束。如此优雅的代码却清晰的展示请求到相应的整个生命周期。不妨先看看index.php的源码吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
require __DIR__ .'/../bootstrap/autoload.php' ;
$app = require_once __DIR__ .'/../bootstrap/app.php' ;
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
为了分析上面的代码,将代码分为四块:
第一块
主要用来实现类的自动加载,注册加载composer自动生成的class loader。
第二块
主要来实例化服务容器,Laravel的一些基本服务的注册,核心组件注册等等,当然了也包括容器本身的注册。在注册的过程中服务容器会在对应的属性中记录注册的内容,方便于在程序运行期间提供对应的服务。这部分内容可以称为程序启动准备阶段。
第三块
处理请求,用户发送的请求入口文件是index.php,从生成Illuminate\Http\Request
实例,交给handle()进行处理。将该$request实例绑定到第二步生成的$app容器上。并发送相应
第四块
请求的后期清理处理工作,请求结束并进行回调。
服务容器的实例化(详谈第二块代码) 先看看bootstrap\app.php
源码:里面有注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$app = new Illuminate\Foundation\Application(
realpath(__DIR__ .'/../' )
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
关于Illuminate\Foundation\Application.php
文件查看源码
应用的基础路径setBasePath()
设置注册应用的基础路径,并在容器中绑定这些基础基础路径。
注册基础绑定registerBaseBindings()
1
2
3
4
5
6
7
8
9
10
protected function registerBaseBindings ()
{
static ::setInstance($this );
$this ->instance('app' , $this );
$this ->instance(Container::class, $this );
}
主要绑定容器实例本身,服务容器中设置了一个静态变量$instance
,该变量是在Illuminate\Container\Container.php
中定义,Application
类继承了Container
类,在Container
类中可以通过public static function getInstance()
获取服务容器实例。服务容器实例还绑定不同的服务容器别名,记录在$instances
共享实例数组(在Container
类中)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function instance ($abstract, $instance)
{
$this ->removeAbstractAlias($abstract);
unset ($this ->aliases[$abstract]);
$this ->instances[$abstract] = $instance;
if ($this ->bound($abstract)) {
$this ->rebound($abstract);
}
}
注册基础服务提供者registerBaseServiceProviders()
服务提供者的注册是非常重要的,因为它给服务容器添加各种服务。这里只是注册了最基本的三个服务:
1
2
3
4
5
6
protected function registerBaseServiceProviders ()
{
$this ->register(new EventServiceProvider($this ));
$this ->register(new LogServiceProvider($this ));
$this ->register(new RoutingServiceProvider($this ));
}
在容器里注册一个服务提供者的方法:public function register($provider, $options = [], $force = false)
源码阅读分析:
首先getProvider($provider)
进行判断,如果服务提供者存在则获取这个实例对象。
第二,如果给定$provider是一个string,则通过类名resolveProvider($provider)
进行实例对象。
然后,对实例化完成的$provider进行标记为已经注册,markAsRegistered($provider)
。
最后,启动规定的服务提供者bootProvider($provider)
。
注册核心类别名registerCoreContainerAliases()
$aliases
数组变量中定义了整个框架的核心服务别名,在解析的过程中需要根据实例化的类或者接口名查找服务别名,然后通过服务别名获取具体的服务。
这里放一张图进行小小的总结:
核心类实例化(Kernel类) 为什么要服务容器?服务容器实例化后,就可以通过服务容器自动实例化对象了,可以参考上一篇的服务容器 。index.php
中Kernel类就是通过服务容器自动创建完成的。
在bootstrap\app.php
文件中就注册了三个服务,其中包括了这个核心类接口,在注册服务时,服务名一般是接口。注册的服务是具体的类名,这一般是通过反射基础来实例化的,并通过反射机制解决构造函数的依赖关系,参考上篇的服务容器有讲解。
这里说的核心类是指App\Http\Kernel
类,这个类只是定义了$middleware
,$middlewareGroups
和 $routeMiddleware
是哪个数组属性。这个类是继承Illuminate\Foundation\Http\Kernel
类的,不妨看看这个类中的构造函数,不难看出这个构造函数是存在依赖关系,一个是Illuminate\Contracts\Foundation\Application
,还有一个是Illuminate\Routing\Router
。他们在服务容器初始化的时候都进行了实例化。
请求实例化capture()
在程序启动准备工作完成了之后,就开始请求的实例化。对于请求就是客户端的发送的一个请求报文。这个对应着Illuminate\Http\Request
类的实例对象。请求实例的创建是通过Illuminate\Http\Request
类中的capture()
函数完成的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static function capture ()
{
static ::enableHttpMethodParameterOverride();
return static ::createFromBase(SymfonyRequest::createFromGlobals());
}
public static function createFromBase (SymfonyRequest $request)
{
if ($request instanceof static ) {
return $request;
}
$content = $request->content;
$request = (new static )->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);
$request->content = $content;
$request->request = $request->getInputSource();
return $request;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static function createFromGlobals ()
{
$server = $_SERVER;
if ('cli-server' === PHP_SAPI) {
if (array_key_exists('HTTP_CONTENT_LENGTH' , $_SERVER)) {
$server['CONTENT_LENGTH' ] = $_SERVER['HTTP_CONTENT_LENGTH' ];
}
if (array_key_exists('HTTP_CONTENT_TYPE' , $_SERVER)) {
$server['CONTENT_TYPE' ] = $_SERVER['HTTP_CONTENT_TYPE' ];
}
}
$request = self ::createRequestFromFactory($_GET, $_POST, array (), $_COOKIE, $_FILES, $server);
if (0 === strpos($request->headers->get('CONTENT_TYPE' ), 'application/x-www-form-urlencoded' )
&& in_array(strtoupper($request->server->get('REQUEST_METHOD' , 'GET' )), array ('PUT' , 'DELETE' , 'PATCH' ))
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
}
return $request;
}
private static function createRequestFromFactory (array $query = array() , array $request = array() , array $attributes = array() , array $cookies = array() , array $files = array() , array $server = array() , $content = null)
{
if (self ::$requestFactory) {
$request = call_user_func(self ::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
if (!$request instanceof self ) {
throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.' );
}
return $request;
}
return new static ($query, $request, $attributes, $cookies, $files, $server, $content);
}
处理请求handle()
完成了请求实例化自然需要对请求实例进行处理,最终返回响应。请求处理是通过Illuminate\Foundation\Http\Kernel.php
中handle()
进行的,处理是handle中的sendRequestThroughRouter()
方法实现的,通过路由请求实例。而enableHttpMethodParameterOverride()
方法会是使能请求拒绝,被使能后在请求过程中添加CSRF保护,服务端发送一个CSRF令牌给客户端,也就是一个cookie,在客户端发送POST请求需要将该令牌发送给服务端,否则拒绝处理该请求。
请求前是不是也要准备一下? 直接看源码: 有6个步骤:环境检测、配置加载、异常处理、Facade注册、服务提供者注册、启动服务,通过bootstrap()
方法完成准备工作,会调用服务容器$app实例中bootstrapWith()
函数,进而通过$this->make($bootstrapper)->bootstrap($this)
make方法完成每个准备类的初始化工作,然后调用准备类的bootstrap
方法实现准备工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
protected function sendRequestThroughRouter ($request)
{
$this ->app->instance('request' , $request);
Facade::clearResolvedInstance('request' );
$this ->bootstrap();
return (new Pipeline($this ->app))
->send($request)
->through($this ->app->shouldSkipMiddleware() ? [] : $this ->middleware)
->then($this ->dispatchToRouter());
}
* Bootstrap the application for HTTP requests.
*/
public function bootstrap ()
{
if (! $this ->app->hasBeenBootstrapped()) {
$this ->app->bootstrapWith($this ->bootstrappers());
}
}
public function bootstrapWith (array $bootstrappers)
{
$this ->hasBeenBootstrapped = true ;
foreach ($bootstrappers as $bootstrapper) {
$this ['events' ]->fire('bootstrapping: ' .$bootstrapper, [$this ]);
$this ->make($bootstrapper)->bootstrap($this );
$this ['events' ]->fire('bootstrapped: ' .$bootstrapper, [$this ]);
}
}
环境检测和配置加载(这部分略过了)
Facade注册 这个怎么理解呢,就是为了美观方便而起的别名,通过别名调用对应实例的属性和方法。这个在很多地方都用到了,比如路由,Route::get()
以为是一个Route类,其实不然,只是通过别名实现的。 看源码是最好的老师:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public function bootstrap (Application $app)
{
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance($app->make('config' )->get('app.aliases' , []))->register();
}
public static function getInstance (array $aliases = [])
{
if (is_null(static ::$instance)) {
return static ::$instance = new static ($aliases);
}
$aliases = array_merge(static ::$instance->getAliases(), $aliases);
static ::$instance->setAliases($aliases);
return static ::$instance;
}
public function register ()
{
if (! $this ->registered) {
$this ->prependToLoaderStack();
$this ->registered = true ;
}
}
主要分要两个步骤:完成外观自动加载类的实例化并将外观别名数组添加到实例中,然后完成外观自动加载类中自动加载函数的添加。
在laravel中有两个别名,一个是容器核心别名,定义在Application类中,而存储在中Application实例的$aliases属性中,另一个是外观别名,定义在app.php配置文件中,程序运行后存储在AliasLoader类实例中的$aliases属性中。
那像Route::get()
是怎么调用的呢?程序首先需要加载类Route,注册了外观别名,那么自动加载栈的第一个函数是AliasLoader类的load()函数,此函数会查找外观别名对应的类名,也就是Illuminate\Support\Facades\Route
类,加载这个类,执行get()方法,但是这个类中并没有此静态方法。这个类继承Illuminate\Support\Facades\Facade
,也没有get()方法,但是有一个__callStatic()
魔术方法,然后调用一个getFacadeAccessor()
静态方法,每一个具体的外观类都需要这个静态方法,该方法就是返回别名类所对应的在服务容器中的名称,对于Illuminate\Support\Facades\Route
返回的就是“router”,接着通过服务容器获取对应的实例对象,这里对应的Illuminate\Routing\Router
类的实例,通过static::$app[$name]
实现,最终调用这个类中的get()方法。
服务提供者注册 服务提供注册这位应用程序提供服务支持,在启动的准备阶段进行了基础服务提供者的加载,但这些服务职能应对前期的启动阶段,对于后期请求处理需要用到的数据库服务、认证服务、session服务还不够。
1
2
3
4
5
6
7
8
9
10
11
public function bootstrap (Application $app)
{
$app->registerConfiguredProviders();
}
public function registerConfiguredProviders ()
{
(new ProviderRepository($this , new Filesystem, $this ->getCachedServicesPath()))
->load($this ->config['app.providers' ]);
}
启动服务 服务提供者必须要实现register()
函数,还有一个boot()
函数根据需求实现,主要用于启动服务,不是必须的,不实现会在父类中统一处理。对于实现boot()函数的服务提供者,会通过BootProviders类进行统一管理调用。要实现boot()也比较简短,只需要调用服务容器中的boot()函数即可。
1
2
3
4
5
public function bootstrap (Application $app)
{
$app->boot();
}
中间件 在Laravel程序中有中间件的概念,就是对请求的处理,首先是经过中间的处理,然后经过路由的处理,最终到控制器生成响应。这个过程中基本是以装饰者模式的思想进行的。 看看app/Http/Kernel.php源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
protected function sendRequestThroughRouter ($request)
{
$this ->app->instance('request' , $request);
Facade::clearResolvedInstance('request' );
$this ->bootstrap();
return (new Pipeline($this ->app))
->send($request)
->through($this ->app->shouldSkipMiddleware() ? [] : $this ->middleware)
->then($this ->dispatchToRouter());
}
protected function dispatchToRouter ()
{
return function ($request) {
$this ->app->instance('request' , $request);
return $this ->router->dispatch($request);
};
}
public function __construct (Container $container = null)
{
$this ->container = $container;
}
public function send ($passable)
{
$this ->passable = $passable;
return $this ;
}
public function through ($pipes)
{
$this ->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this ;
}
public function then (Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this ->pipes), $this ->carry(), $this ->prepareDestination($destination)
);
return $pipeline($this ->passable);
}
路由处理生成响应 回到刚才看到的路由分发回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
protected function dispatchToRouter ()
{
return function ($request) {
$this ->app->instance('request' , $request);
return $this ->router->dispatch($request);
};
}
public function dispatch (Request $request)
{
$this ->currentRequest = $request;
return $this ->dispatchToRoute($request);
}
public function dispatchToRoute (Request $request)
{
$route = $this ->findRoute($request);
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this ->events->dispatch(new Events\RouteMatched($route, $request));
$response = $this ->runRouteWithinStack($route, $request);
return $this ->prepareResponse($request, $response);
}
protected function runRouteWithinStack (Route $route, Request $request)
{
$shouldSkipMiddleware = $this ->container->bound('middleware.disable' ) &&
$this ->container->make('middleware.disable' ) === true ;
$middleware = $shouldSkipMiddleware ? [] : $this ->gatherRouteMiddleware($route);
return (new Pipeline($this ->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this ->prepareResponse(
$request, $route->run()
);
});
}
路由的信息都会保存在一个Illuminate\Routing\Router
类实例中,而这个类实例存储在kernel类中。
查到请求对应的路由后,请求传递给对应的路由去处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function run ()
{
$this ->container = $this ->container ?: new Container;
try {
if ($this ->isControllerAction()) {
return $this ->runController();
}
return $this ->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
进而交给相应的controller去处理。这部分,首先根据控制器类名通过服务容器进行实例化,再通过调用控制的实例对应的方法来生成响应的主体部分(并非最终的响应)。经过一系列的处理之后生成响应。响应是封装在Illuminate\Http\Response
实例中的。
响应的发送和请求生命周期的终止 响应发送$response->send()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function send ()
{
$this ->sendHeaders();
$this ->sendContent();
if (function_exists('fastcgi_finish_request' )) {
fastcgi_finish_request();
} elseif ('cli' !== PHP_SAPI) {
static ::closeOutputBuffers(0 , true );
}
return $this ;
}
程序终止——生命周期的最后阶段$kernel->terminate($request, $response)
程序终止,完成终止中间件的调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public function terminate ($request, $response)
{
$this ->terminateMiddleware($request, $response);
$this ->app->terminate();
}
protected function terminateMiddleware ($request, $response)
{
$middlewares = $this ->app->shouldSkipMiddleware() ? [] : array_merge(
$this ->gatherRouteMiddleware($request),
$this ->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue ;
}
list ($name, $parameters) = $this ->parseMiddleware($middleware);
$instance = $this ->app->make($name);
if (method_exists($instance, 'terminate' )) {
$instance->terminate($request, $response);
}
}
}
什么东西都是有始有终的,至此请求生命周期结束。
总结
请求到响应整个执行过程,可以分为四个阶段:程序启动准备阶段,请求实例化阶段,请求处理阶段,响应发送和终止程序。 准备阶段:完成一些文件自动加载,服务容器实例化,基础服务提供者注册和Kernel类的实例化。 请求实例化阶段:将请求信息以对象的实行进行存储。 请求处理阶段:准备请求处理环境,完成环境和配置加载等6大东西。通过中间件处理通过路由和控制器处理,生成相应。 请求终止阶段:将响应发送给客户端并终止程序。