第二部分 第1章 项目访问解析
第1章 项目访问解析
1.1 PHP 项目访问解析
PHP 交互的前提条件 ?
PHP 是一门动态的脚本编程语言,PHP可以通过 SAPI 来实现不同 PHP 脚本的执行方式,以满足在不同环境下的 PHP 代码执行,其中 Web 执行模式就属 FastCGI 的工作模式。FastCGI 由 FPM 实现,那 FPM 是什么呢 ?
FPM 是 PHP 的进程管理器,即 PHP 软件程序启动后,就会在后台运行 PHP 相关的进程。那如果进程意外挂掉、接受指令要完成什么工作。这时要怎么办呢? 所以就需要 FPM 来统一的进行进程管理。
在 linux 下,通过 ps -ef | grep php 可查看所有启动的 PHP 进程,但你好像发现了,php-fpm 有很多啊。这个是咋回事呢?
这就不得不说下 PHP 的进程架构,它并不是一个人,因为你一个人做事太慢,所以 PHP 就做了个分工,进程由 Master+Worker 模式构成,也就是进程有多个,但不同进程负责的工作是不一样,也就是 2 种。
PHP 在启动程序后就会先启动 Master ,然后 fork 出多个 Worker 进程,最后由 Worker 直接与客户端建立请求连接。也就是 Master 做进程管理的工作,一个 worker 做请求的连接与数据的处理。
有了进程咱们是不是就可以执行工作啦 ?
并不是,因为你进程要执行工作,是不需要让别人把原材料(数据)给你,也就数据。 但这个数据不可能无缘无故过来,一般都是通过 Nginx 传输过来的。但 Nginx 和 PHP 是 2 个程序呀,彼此都互不认识。就像你和快递小哥一样,他要把包裹交到你手上,但你们彼此根本都不认识。但你们会通过
手机号码+包裹签收
来进行识别呢 !
所以 FPM 也一样,它也需要实现一种规则,用来接受原材料,也就是进行数据交互。这也就是为啥每个 PHP 进程都支持 fast-cgi 协议的原因。有了它,咱们就可以让 Nginx 和 PHP 打交道啦。
上面谈到了,PHP 的工作模式与处理,那么在 PHP 的项目中,完整 PHP 请求的处理模式是怎么实现的 ?
用户访问域名,域名进行 DNS 解析 -> 请求到对应 IP 服务器和端口。
根据 IP 地址与端口发送请求到 IP 地址服务器上的 Nginx 软件监听的对应端口中。
Nginx 对请求 url 进行 location 匹配,执行匹配 location 下的规则,然后由 Nginx 转发请求给 php 。
php-fpm 的 master 进程监听到 nginx 请求,然后 Master 进程将请求分配给其中一个闲置的 worker进程。
最后由 worker 进程执行请求,并返回执行结果给 nginx 。
Nginx 返回结果给用户。
注意:这里的 Nginx 就是 web 服务器,类似的还有 Apache、IIS 等。
你可能会觉得,这个是PHP访问的方式,那项目如何访问呢 ?
你的项目基于 PHP 开发,一般 PHP 部署在线上的环境都为 LNMP 架构,就是基于 Linux操作系统来搭建的 PHP 开发环境,那这时候,你 HTTP 请求访问到服务器后,就自然把请求交给 Nginx,在由 Nginx 把请求交给项目的执行文件,最后在进行执行处理的。
1.2 框架基准请求响应解析
MVC
谈框架的基准请求都不能离开 MVC 模式处理,一切请求都会先进入到框架的 index.php 的入口文件中,然后在引入相关文件类库,做框架初始化、请求验证、路由分析,最后实例化控制器并调用方法去查找数据库的数据,最后在响应给客户端。
如上图,为 MVC 原理图,分别代表什么意思 ?
Model(模型)
- 模型代表一个存取数据的对象。它也可以带有逻辑,在数据变化时更新控制器。
View(视图)
- 视图代表模型包含的数据的可视化
Controller(控制器)
- 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开 。
优点 :
视图控制模型分离, 提高代码重用性。
提高开发效率。
便于后期维护, 降低维护成本。
方便多开发人员间的分工。
缺点 :
方法越来越大,运行效率相对较低 。
控制层和表现层有时会过于紧密,导致没有真正分离和重用 。类本身也是越来越大,职责也会越来越多。
框架的请求执行处理。
有了框架通用的 MVC 模式,接下来就可以看下框架的请求响应流程。这里就以 laravel 请求周期来进行举例,其它一些框架与 Laravel 框架是类型的。如图所示 :
注意:以下涉及代码都为laravel框架核心代码解析。
1 . 用户的 HTTP 请求发送到 index.php 入口处。
2 . index.php 开始引入 app 应用对象、异常处理机制、HTTP 等相关核心类库,并进行进行相关初始化。
//laraveldemo\public\index.php
$app = require_once __DIR__.'/../bootstrap/app.php';
-----------------------------------------------------
//laraveldemo\bootstrap\app.php
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__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
);
3 . 在 app 应用内部,注册容器对象和文件类、路由、事件、日志、相关别名与类的关系等等到容器中。从而来完成框架的基础初始化。这样框架需要的基本服务功能就都有了。
文件位置:laraveldemo\vendor\laravel\framework\src\Illuminate\Foundation\Application.php
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
//$this->registerBaseBindings(); 将容器别名、文件类等基础信息注入到容器
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->singleton(PackageManifest::class, function () {
return new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
);
});
}
-----------------------------------------------------
// $this->registerBaseServiceProviders(); 注册相关基础服务
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
-----------------------------------------------------
//$this->registerCoreContainerAliases();注册别名和类的关系
public function registerCoreContainerAliases()
{
foreach ([
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' =>
//.......省略些核心代码
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
}
4 . 完成了框架组件服务组装,咱们就需要开始来解析 HTTP 请求啦。现在可基于容器获取到 HTTP 对象,在这里会通过 make 方法直接反射拿取 HTTP 实例。并调用 HTTP 类下面的 capture 方法获取 HTTP 请求中的参数,并用服务提供者把相关的服务类绑定到容器中,然后对请求通过中间件进行过滤,通过后在进行路由的匹配与代码执行操作。执行完最后返回给客户端。
//laraveldemo\public\index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture());
//文件位置:laraveldemo\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php
-----------------------------------------------------
//应用服务提供者,用来注册其它相关的服务组件
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 $middleware = [
\App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
//1、开始分发请求的路由
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response));
return $response;
}
//2、开启路由分发之前,先获取请求对象,然后开始注册服务提供者。并在管道模式下面进行中间件对HTTP请求的过滤检查
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());
}
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
protected function bootstrappers()
{
return $this->bootstrappers;
}
5 . 请求首先通过中间件进行请求的过滤,如果不满足相关中间件的条件,就直接结束请求。
6 . 如果中间件处理成功,就会获取请求中的 URL 地址与注册的路由地址进行比较,如果匹配到,就根据路由拿到控制器和方法名,然后进行控制器实例化并调用注册方法,在处理当前的 HTTP 请求
public function gatherRouteMiddleware(Route $route)
{
$excluded = collect($route->excludedMiddleware())->map(function ($name) {
return (array) MiddlewareNameResolver::resolve(
$name, $this->middleware, $this->middlewareGroups);
})->flatten()->values()->all();
$middleware = collect($route->gatherMiddleware())->map(function ($name) {
return (array) MiddlewareNameResolver::resolve(
$name, $this->middleware, $this->middlewareGroups
);
})->flatten()->reject(function ($name) use ($excluded) {
return in_array($name, $excluded, true);
})->values();
return $this->sortMiddleware($middleware);
}
////获取 路由中 和 控制器中 定义的中间件单词标识
public function gatherMiddleware()
{
if (! is_null($this->computedMiddleware)) {
return $this->computedMiddleware;
}
$this->computedMiddleware = [];
return $this->computedMiddleware = Router::uniqueMiddleware(array_merge(
$this->middleware(), $this->controllerMiddleware()
));
}
public function controllerMiddleware()
{
if (! $this->isControllerAction()) {
return [];
}
// 调用 `controllerDispatcher` 获取 控制器调度器 对象
// 调用 控制器调度器 对象中的 getMiddleware 方法,以 控制器对象 和 方法名 为参数
return $this->controllerDispatcher()->getMiddleware(
// 调用 getController 方法,获取 控制器对象
$this->getController(), $this->getControllerMethod()
);
}
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)
{
return $this->runRoute($request, $this->findRoute($request));
}
7 . 请求处理完成后,通过 Responce 直接响应业务数据到客户端。