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 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
if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !');
define('APP_DEBUG',False);
define('APP_PATH','./Application/');
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() { 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 = $app->make('Illuminate\Contracts\Http\Kernel');
$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',
];
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/