PHP作为世界上最好的编程语音,被广泛的运用到Web开发中。因为其语法和C类似,有着非常平缓的学习曲线,越来越多的人使用PHP进行Web产品的快速开发。PHP世界里也涌现了很多开发框架,比如Laravel、ThinkPHP等,但不论何总框架,他们在处理Web请求时的模式都是一样的,本文首先阐述PHP开发Web应用的基本架构,然后分别分析Laravel和ThinkPHP在处理Web请求时的处理流程。

PHP开发Web应用的基本架构


PHP开发Web应用时所以的请求需要指向具体的入口文件。WebServer是一个内容分发者,他接受用户的请求后,如果是请求的是css、js等静态文件,WebServer会找到这个文件,然后发送给浏览器;如果请求的是/index.php,根据配置文件,WebServer知道这个不是静态文件,需要去找PHP解析器来处理,那么他会把这个请求简单处理后交给PHP解析器。
enter image description here

WebServer会依据CGI协议,将请求的Url、数据、Http Header等信息发送给PHP解析器,接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以CGI规定的格式返回处理后的结果,退出进程。web server再把结果返回给浏览器。整个处理过程如上图所示。

FastCGI


这里的PHP解析器就是实现了CGI协议的程序,每次请求到来时他会解析php.ini文件,初始化执行环境,这就导致PHP解析器性能低下,于是就出现了CGI的改良升级版FastCGI。FastCGI是一种语言无关的协议,用来沟通程序(如PHP, Python, Java)和Web服务器(Apache2, Nginx), 理论上任何语言编写的程序都可以通过FastCGI来提供Web服务。它的特点是会在动态分配处理进程给请求,以达到提高效率的目的,大多数FastCGI实现都会维护一个进程池。FastCGI会先启一个master进程,解析配置文件,初始化执行环境,然后再启动多个worker进程。当请求过来时,master进程会这个请求传递给一个worker进程,然后立即接受下一个请求。而且当worker进程不够用时,master可以根据配置预先启动几个worker进程等待;当然空闲worker进程太多时,也会自动关闭,这样就提高了性能,节约了系统资源。整个过程FastCGI扮演着对CGI进程进行管理的角色。

PHP-FPM


PHP-FPM是一个专门针对PHP实现了FastCGI协议的程序,它实际上就是一个PHP FastCGI进程管理器,负责管理一个进程池,调用PHP解析器来处理来自Web服务器的请求。PHP-FPM能够对php.ini文件的修改进行平滑过度。

新建一个helloworld.php文件,写入下列代码

1
2
3
4
5
<?php
echo "helloworld,";
echo "this is my first php script.";
echo phpinfo();
?>

配置好WebServer和PHP-FPM等php运行环境后,在浏览器中访问该文件就可以直接得到输出。

基于PHP的Web框架


PHP Web框架是

基于某模式将PHP开发常用功能封装实现使开发者快速开发的工具

它主要的任务包括:

  • 代码重用:定义包、类、函数的放置和加载规则,建议直接整合Composer及其AutoLoad特性。
  • 请求的分发管理:这个就是路由,Rest风的框架喜欢Rewrite,简单的一点的框架主要通过参数来定位模块和方法所在。
  • 配置文件管理:加载和动态加载配置数据
  • 错误和异常管理:异常捕捉、错误日志记录以及错误码规范。
  • Layout和模板引擎:如何规划页面布局、widget如何重用、ajax页面如何结合、过期- session如何重定向;数据和模板怎么渲染成HTML,是否压缩和设置过期头。
  • 数据库:如何融入控制器;支持什么样的driver;考虑主从分离的扩展性;以及是否使用ORM

ThinkPHP3.2框架处理流程分析

TP的设计逻辑就是简单粗暴,面对问题解决问题,所以他的处理流程是基于面向过程的思想,而没有采用面向对象的依赖注入、控制反转等思路。他的自动加载、错误处理通过php原生函数的回调来实现。TP处理每次请求要经过四个步骤如下图所示:
enter image description here

调用应用路口index.php

index.php是TP的入口文件,所有的请求都由该文件接管,它的工作也很简单主要是引入ThinkPHP入口文件

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
<?php


// 应用入口文件





// 检测PHP环境


if(version_compare(PHP_VERSION,'5.3.0','<')) die('require
PHP > 5.3.0 !');





// 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false


define('APP_DEBUG',False);





// 定义应用目录


define('APP_PATH','./Application/');





// 引入ThinkPHP入口文件


require
'./ThinkPHP/ThinkPHP.php';

载入框架入口文件ThinkPHP.php

在ThinkPHP.php中主要记录初始运行时间和内存开销,然后完成系统常量判断及定义,最后载入框架引导类(Think\Think)并执行Think::start方法进行应用初始化。

应用初始化Think\Think:start()

应用初始化首先设置错误处理机制和自动加载机制

1
2
3
4
5
6
7
static public function start() {
// 注册AUTOLOAD方法
spl_autoload_register('Think\Think::autoload');
// 设定错误和异常处理
register_shutdown_function('Think\Think::fatalError');
set_error_handler('Think\Think::appError');
set_exception_handler('Think\Think::appException');

然后加载相关配置文件和运行模式定义文件,最后调用Think\App类的run方法启动应用

运行应用App::run()

此后TP进入请求处理管道,TP为管道中定义了14个事件,每个事件都可以绑定回调函数,请求到达管道后依次触发这些事件,事件触发后就会调用绑定到事件的回调函数,整个管道的生命周期由app_init开始,由app_end结束。具体实现上,TP将这些事件命名为标签(位),也可以称之为钩子,将回调函数命名为行为,当应用程序运行到标签的时候,就会被拦截下来,统一执行相关的行为。

Laravel框架处理流程分析

统一入口

Laravel框架使用了统一入口,入口文件:/public/index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
//自动加载文件设置
require __DIR__.'/../bootstrap/autoload.php';

//初始化服务容器(可以查看一下关于‘服务容器’的相关文档)
$app = require_once __DIR__.'/../bootstrap/app.php';

//通过服务容器生成一个kernel类的实例(Illuminate\Contracts\Http\Kernel实际上只是一个接口,真正生成的实例是App\Http\Kernel类,至于怎么把接口和类关联起来,请查看Contracts相关文档)
$kernel = $app->make('Illuminate\Contracts\Http\Kernel');

//运行Kernel类的handle方法,主要动作是运行middleware和启动URL相关的Contrller
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

//控制器返回结果之后的操作,暂时还没看,以后补上
$response->send();

$kernel->terminate($request, $response);

自动加载文件

laravel的自动加载,其实也就是Composer的自动加载
Composer根据声明的依赖关系,从相关库的源下载代码文件,并根据依赖关系在 Composer 目录下生成供类自动加载的 PHP 脚本,使用的时候,项目开始处引入 “/vendor/autoload.php” 文件,就可以直接实例化这些第三方类库中的类了。

服务容器——Laravel真正的核心

服务容器,也叫IoC容器,其实包含了依赖注入(DI)和控制反转(IoC)两部分,是Laravel的真正核心。其他的各种功能模块比如 Route(路由)、Eloquent ORM(数据库 ORM 组件)、Request and Response(请求和响应)等等等等,实际上都是与核心无关的类模块提供的,这些类从注册到实例化,最终被使用,其实都是 Laravel 的服务容器负责的。

启动Kernel代码

Kernel实例调用handle方法,意味着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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php
namespace
App\Http;


use
Illuminate\Foundation\Http\Kernel
as
HttpKernel;


class
Kernel
extends
HttpKernel
{


//这是在调用路由之前需要启动的中间件,一般都是核心文件,不要修改


protected
$middleware
=
[


'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',


'Illuminate\Cookie\Middleware\EncryptCookies',


'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',


'Illuminate\Session\Middleware\StartSession',


'Illuminate\View\Middleware\ShareErrorsFromSession',


'App\Http\Middleware\VerifyCsrfToken',


];


//这是我们在router.php文件里面或者Controller文件里面,可以使用的Middleware元素,可以自定义加入很多


protected
$routeMiddleware
=
[


'auth'
=>
'App\Http\Middleware\Authenticate',


'auth.basic'
=>
'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',


'guest'
=>
'App\Http\Middleware\RedirectIfAuthenticated',


'test'
=>
'App\Http\Middleware\testMiddleWare',


];


}

可以看到,其实这个文件里面没有handle方法,只有一些属性定义,所以真正的handle方法,实在父类里面实现的

代码清单…/Illuminate/Foundation/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
56
57
58
//这个很重要,是项目的一些启动引导项,Kernel的重要步骤中,首先就是启动这些文件的bootstrap方法
protected $bootstrappers = [
//检测环境变量文件是否正常
'Illuminate\Foundation\Bootstrap\DetectEnvironment',
//取得配置文件,即把/config/下的所有配置文件读取到容器(app()->make('config')可以查看所有配置信息)
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
//绑定一个名字为log的实例到容器,怎么访问??(app()->make('log'))
'Illuminate\Foundation\Bootstrap\ConfigureLogging',
//设置异常抓取信息,这个还没仔细看,但大概就是这个意思
'Illuminate\Foundation\Bootstrap\HandleExceptions',
//把/config/app.php里面的aliases项利用PHP库函数class_alias创建别名,从此,我们可以使用App::make('app')方式取得实例
'Illuminate\Foundation\Bootstrap\RegisterFacades',
//把/config/app.php里面的providers项,注册到容器
'Illuminate\Foundation\Bootstrap\RegisterProviders',
//运行容器中注册的所有的ServiceProvider中得boot方法
'Illuminate\Foundation\Bootstrap\BootProviders',
];

//真正的handle方法
public function handle($request)
{
try
{
//主要是这行,调度了需要运行的方法
return $this->sendRequestThroughRouter($request);
}
catch (Exception $e)
{
$this->reportException($e);
return $this->renderException($request, $e);
}
}

protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
//运行上述$bootstrappers里面包含的文件的bootstrap方法,运行的作用,上面已经注释
$this->bootstrap();
//这是在对URL进行调度之前,也就是运行Route之前,进行的一些准备工作
return (new Pipeline($this->app))
->send($request)
//需要运行$this->middleware里包含的中间件
->through($this->middleware)
//运行完上述中间件之后,调度dispatchToRouter方法,进行Route的操作
->then($this->dispatchToRouter());
}

//前奏执行完毕之后,进行Route操作
protected function dispatchToRouter()
{
return function($request)
{
$this->app->instance('request', $request);
//跳转到Router类的dispatch方法
return $this->router->dispatch($request);
};
}

下面就需要根据URL和/app/Http/routes.php文件,进行Route操作

文件清单…/Illuminate/Routing/Router.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
public
function
dispatch(Request
$request)


{


$this->currentRequest
=
$request;


//在4.2版本里面,Route有一个筛选属性;5.0之后的版本,被Middleware代替


$response
=
$this->callFilter('before',
$request);


if
(is_null($response))


{


//继续调度


$response
=
$this->dispatchToRoute($request);


}


$response
=
$this->prepareResponse($request,
$response);


//在4.2版本里面,Route有一个筛选属性;5.0之后的版本,被Middleware代替


$this->callFilter('after',
$request,
$response);


return
$response;


}





public
function
dispatchToRoute(Request
$request)


{


$route
=
$this->findRoute($request);


$request->setRouteResolver(function()
use
($route)


{


return
$route;


});


$this->events->fire('router.matched',
[$route,
$request]);


$response
=
$this->callRouteBefore($route,
$request);


if
(is_null($response))


{


//
只看这一行,还是调度文件


$response
=
$this->runRouteWithinStack(


$route,
$request


);


}


$response
=
$this->prepareResponse($request,
$response);


$this->callRouteAfter($route,
$request,
$response);


return
$response;


}





protected
function
runRouteWithinStack(Route
$route,
Request
$request)


{


//
取得routes.php里面的Middleware节点


$middleware
=
$this->gatherRouteMiddlewares($route);


//这个有点眼熟


return
(new
Pipeline($this->container))


->send($request)


//执行上述的中间件


->through($middleware)


->then(function($request)
use
($route)


{


//到Controller类了


return
$this->prepareResponse(


$request,


//run控制器


$route->run($request)


);


});


}





public
function
run(Request
$request)


{


$this->container
=
$this->container
?:
new
Container;


try


{


if
(
!
is_string($this->action['uses']))


return
$this->runCallable($request);


if
($this->customDispatcherIsBound())


//实际上是运行了这行


return
$this->runWithCustomDispatcher($request);





//其实我是直接想运行这行


return
$this->runController($request);


}


catch
(HttpResponseException
$e)


{


return
$e->getResponse();


}


}





//继续调度,最终调度到.../Illuminate/Routing/ControllerDispatcher.php文件的dispatch方法


protected
function
runWithCustomDispatcher(Request
$request)


{


list($class,
$method)
=
explode('@',
$this->action['uses']);





$dispatcher
=
$this->container->make('illuminate.route.dispatcher');


return
$dispatcher->dispatch($this,
$request,
$class,
$method);
}

文件清单…/Illuminate/Routing/ControllerDispatcher.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public
function
dispatch(Route
$route,
Request
$request,
$controller,
$method)


{


$instance
=
$this->makeController($controller);


$this->assignAfter($instance,
$route,
$request,
$method);


$response
=
$this->before($instance,
$route,
$request,
$method);


if
(is_null($response))


{


//还要调度


$response
=
$this->callWithinStack(


$instance,
$route,
$request,
$method


);


}


return
$response;


}





protected
function
callWithinStack($instance,
$route,
$request,
$method)


{


//又是Middleware......有没有忘记,官方文档里面Middleware可以加在控制器的构造函数中!!没错,这个Middleware就是在控制器里面申明的


$middleware
=
$this->getMiddleware($instance,
$method);


//又是这个,眼熟吧


return
(new
Pipeline($this->container))


->send($request)


//再次运行Middleware


->through($middleware)


->then(function($request)
use
($instance,
$route,
$method)


{


运行控制器,返回结果


return
$this->call($instance,
$route,
$method);


});


}

终于到达控制器

转自:http://www.eurekao.com/PHP-processing-web-request-analysis/