转自:http://www.eurekao.com/php-processing-web-request-analysis/
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解析器。
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处理每次请求要经过四个步骤如下图所示:
调用应用路口index.php
index.php是TP的入口文件,所有的请求都由该文件接管,它的工作也很简单主要是引入ThinkPHP入口文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?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
8
|
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将这些事件命名为标签(位),也可以称之为钩子,将回调函数命名为行为,当应用程序运行到标签的时候,就会被拦截下来,统一执行相关的行为。
系统核心提供的标签位置 | 触发时机 |
app_init | 应用初始化标签位 |
module_check | 模块检测标签位 |
path_info | PATH_INFO检测标签位 |
app_begin | 应用开始标签位 |
action_name | 操作方法名标签位 |
action_begin | 控制器开始标签位 |
view_begin | 视图输出开始标签位 |
view_template | 视图模板解析标签位 |
view_parse | 视图解析标签位 |
template_filter | 模板解析过滤标签位 |
view_filter | 视图输出过滤标签位 |
view_end | 视图输出结束标签位 |
action_end | 控制器结束标签位 |
app_end | 应用结束标签位 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static
public
function
run
(
)
{
// 应用初始化标签
Hook::
listen
(
'app_init'
)
;
App::
init
(
)
;
// 应用开始标签
Hook::
listen
(
'app_begin'
)
;
// Session初始化
if
(
!
IS_CLI
)
{
session
(
C
(
'SESSION_OPTIONS'
)
)
;
}
// 记录应用初始化时间
G
(
'initTime'
)
;
App::
exec
(
)
;
// 应用结束标签
Hook::
listen
(
'app_end'
)
;
return
;
}
|
该阶段App:init()主要完成的工作:
- 加载动态应用公共文件和配置
- 定义当前请求的系统常量
- URL调度
App::exec()主要是查找控制器和操作,完成应用程序的执行。
具体的执行流程可以参考文档:《ThinkPHP3.2完全开发手册——系统流程》
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
|
<?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
|
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
|
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
)
;
}
)
;
}
|
终于到达控制器
参考文献
(1)wish123 《laravel的启动过程》:http://www.cnblogs.com/wish123/p/4756669.html
(2)veda《概念了解:CGI,FastCGI,PHP-CGI与PHP-FPM》:http://www.nowamagic.net/librarys/veda/detail/1319
(3)田靖荣 《原来如此!一分钟了解什么是框架》:http://segmentfault.com/a/1190000003871804
(4)Yuansir-web菜鸟 《理解PHP 依赖注入|Laravel IoC容器》:http://www.yuansir-web.com/2014/03/20/%E7%90%86%E8%A7%A3php-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5laravel-ioc%E5%AE%B9%E5%99%A8/
- 本文固定链接: http://www.eurekao.com/php-processing-web-request-analysis/
- 转载请注明: subjectwa 2015年10月18日 于 Eureka 发表