第二部分 第1章 項目訪問解析
第1章 項目訪問解析
1.1 PHP 項目訪問解析
PHP 交互的前提條件 ?
PHP 是一門動態(tài)的腳本編程語言,PHP可以通過 SAPI 來實現(xiàn)不同 PHP 腳本的執(zhí)行方式,以滿足在不同環(huán)境下的 PHP 代碼執(zhí)行,其中 Web 執(zhí)行模式就屬 FastCGI 的工作模式。FastCGI 由 FPM 實現(xiàn),那 FPM 是什么呢 ?
FPM 是 PHP 的進(jìn)程管理器,即 PHP 軟件程序啟動后,就會在后臺運(yùn)行 PHP 相關(guān)的進(jìn)程。那如果進(jìn)程意外掛掉、接受指令要完成什么工作。這時要怎么辦呢? 所以就需要 FPM 來統(tǒng)一的進(jìn)行進(jìn)程管理。
在 linux 下,通過 ps -ef | grep php 可查看所有啟動的 PHP 進(jìn)程,但你好像發(fā)現(xiàn)了,php-fpm 有很多啊。這個是咋回事呢?
這就不得不說下 PHP 的進(jìn)程架構(gòu),它并不是一個人,因為你一個人做事太慢,所以 PHP 就做了個分工,進(jìn)程由 Master+Worker 模式構(gòu)成,也就是進(jìn)程有多個,但不同進(jìn)程負(fù)責(zé)的工作是不一樣,也就是 2 種。
PHP 在啟動程序后就會先啟動 Master ,然后 fork 出多個 Worker 進(jìn)程,最后由 Worker 直接與客戶端建立請求連接。也就是 Master 做進(jìn)程管理的工作,一個 worker 做請求的連接與數(shù)據(jù)的處理。
有了進(jìn)程咱們是不是就可以執(zhí)行工作啦 ?
并不是,因為你進(jìn)程要執(zhí)行工作,是不需要讓別人把原材料(數(shù)據(jù))給你,也就數(shù)據(jù)。 但這個數(shù)據(jù)不可能無緣無故過來,一般都是通過 Nginx 傳輸過來的。但 Nginx 和 PHP 是 2 個程序呀,彼此都互不認(rèn)識。就像你和快遞小哥一樣,他要把包裹交到你手上,但你們彼此根本都不認(rèn)識。但你們會通過
手機(jī)號碼+包裹簽收
來進(jìn)行識別呢 !
所以 FPM 也一樣,它也需要實現(xiàn)一種規(guī)則,用來接受原材料,也就是進(jìn)行數(shù)據(jù)交互。這也就是為啥每個 PHP 進(jìn)程都支持 fast-cgi 協(xié)議的原因。有了它,咱們就可以讓 Nginx 和 PHP 打交道啦。
上面談到了,PHP 的工作模式與處理,那么在 PHP 的項目中,完整 PHP 請求的處理模式是怎么實現(xiàn)的 ?
用戶訪問域名,域名進(jìn)行 DNS 解析 -> 請求到對應(yīng) IP 服務(wù)器和端口。
根據(jù) IP 地址與端口發(fā)送請求到 IP 地址服務(wù)器上的 Nginx 軟件監(jiān)聽的對應(yīng)端口中。
Nginx 對請求 url 進(jìn)行 location 匹配,執(zhí)行匹配 location 下的規(guī)則,然后由 Nginx 轉(zhuǎn)發(fā)請求給 php 。
php-fpm 的 master 進(jìn)程監(jiān)聽到 nginx 請求,然后 Master 進(jìn)程將請求分配給其中一個閑置的 worker進(jìn)程。
最后由 worker 進(jìn)程執(zhí)行請求,并返回執(zhí)行結(jié)果給 nginx 。
Nginx 返回結(jié)果給用戶。
注意:這里的 Nginx 就是 web 服務(wù)器,類似的還有 Apache、IIS 等。
你可能會覺得,這個是PHP訪問的方式,那項目如何訪問呢 ?
你的項目基于 PHP 開發(fā),一般 PHP 部署在線上的環(huán)境都為 LNMP 架構(gòu),就是基于 Linux操作系統(tǒng)來搭建的 PHP 開發(fā)環(huán)境,那這時候,你 HTTP 請求訪問到服務(wù)器后,就自然把請求交給 Nginx,在由 Nginx 把請求交給項目的執(zhí)行文件,最后在進(jìn)行執(zhí)行處理的。
1.2 框架基準(zhǔn)請求響應(yīng)解析
MVC
談框架的基準(zhǔn)請求都不能離開 MVC 模式處理,一切請求都會先進(jìn)入到框架的 index.php 的入口文件中,然后在引入相關(guān)文件類庫,做框架初始化、請求驗證、路由分析,最后實例化控制器并調(diào)用方法去查找數(shù)據(jù)庫的數(shù)據(jù),最后在響應(yīng)給客戶端。
如上圖,為 MVC 原理圖,分別代表什么意思 ?
Model(模型)
- 模型代表一個存取數(shù)據(jù)的對象。它也可以帶有邏輯,在數(shù)據(jù)變化時更新控制器。
View(視圖)
- 視圖代表模型包含的數(shù)據(jù)的可視化
Controller(控制器)
- 控制器作用于模型和視圖上。它控制數(shù)據(jù)流向模型對象,并在數(shù)據(jù)變化時更新視圖。它使視圖與模型分離開 。
優(yōu)點 :
視圖控制模型分離, 提高代碼重用性。
提高開發(fā)效率。
便于后期維護(hù), 降低維護(hù)成本。
方便多開發(fā)人員間的分工。
缺點 :
方法越來越大,運(yùn)行效率相對較低 。
控制層和表現(xiàn)層有時會過于緊密,導(dǎo)致沒有真正分離和重用 。類本身也是越來越大,職責(zé)也會越來越多。
框架的請求執(zhí)行處理。
有了框架通用的 MVC 模式,接下來就可以看下框架的請求響應(yīng)流程。這里就以 laravel 請求周期來進(jìn)行舉例,其它一些框架與 Laravel 框架是類型的。如圖所示 :
注意:以下涉及代碼都為laravel框架核心代碼解析。
1 . 用戶的 HTTP 請求發(fā)送到 index.php 入口處。
2 . index.php 開始引入 app 應(yīng)用對象、異常處理機(jī)制、HTTP 等相關(guān)核心類庫,并進(jìn)行進(jìn)行相關(guān)初始化。
//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 應(yīng)用內(nèi)部,注冊容器對象和文件類、路由、事件、日志、相關(guān)別名與類的關(guān)系等等到容器中。從而來完成框架的基礎(chǔ)初始化。這樣框架需要的基本服務(wù)功能就都有了。
文件位置: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(); 將容器別名、文件類等基礎(chǔ)信息注入到容器
??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(); 注冊相關(guān)基礎(chǔ)服務(wù)
?protected function registerBaseServiceProviders()
?{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
?}
-----------------------------------------------------
//$this->registerCoreContainerAliases();注冊別名和類的關(guān)系
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 . 完成了框架組件服務(wù)組裝,咱們就需要開始來解析 HTTP 請求啦?,F(xiàn)在可基于容器獲取到 HTTP 對象,在這里會通過 make 方法直接反射拿取 HTTP 實例。并調(diào)用 HTTP 類下面的 capture 方法獲取 HTTP 請求中的參數(shù),并用服務(wù)提供者把相關(guān)的服務(wù)類綁定到容器中,然后對請求通過中間件進(jìn)行過濾,通過后在進(jìn)行路由的匹配與代碼執(zhí)行操作。執(zhí)行完最后返回給客戶端。
//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
-----------------------------------------------------
//應(yīng)用服務(wù)提供者,用來注冊其它相關(guān)的服務(wù)組件??
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,?
];
//框架自帶中間件,用于請求上的參數(shù)過濾??
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、開始分發(fā)請求的路由??????
$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、開啟路由分發(fā)之前,先獲取請求對象,然后開始注冊服務(wù)提供者。并在管道模式下面進(jìn)行中間件對HTTP請求的過濾檢查
protected function sendRequestThroughRouter($request)
{???
$this->app->instance('request', $request);????
Facade::clearResolvedInstance('request');
??//注冊服務(wù)提供者????
?? $this->bootstrap();
??//把相關(guān)需要做檢查的中間件放入管道對象中,基于遞歸函數(shù)進(jìn)行類的處理????
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 . 請求首先通過中間件進(jìn)行請求的過濾,如果不滿足相關(guān)中間件的條件,就直接結(jié)束請求。
6 . 如果中間件處理成功,就會獲取請求中的 URL 地址與注冊的路由地址進(jìn)行比較,如果匹配到,就根據(jù)路由拿到控制器和方法名,然后進(jìn)行控制器實例化并調(diào)用注冊方法,在處理當(dāng)前的 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);
}
////獲取 路由中 和 控制器中 定義的中間件單詞標(biāo)識
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 [];
}
// 調(diào)用 `controllerDispatcher` 獲取 控制器調(diào)度器 對象
// 調(diào)用 控制器調(diào)度器 對象中的 getMiddleware 方法,以 控制器對象 和 方法名 為參數(shù)
return $this->controllerDispatcher()->getMiddleware(
// 調(diào)用 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);
?}
//查找路由并開始運(yùn)行路由對應(yīng)的控制器下的方法
public function dispatchToRoute(Request $request)
?{
? return $this->runRoute($request, $this->findRoute($request));
?}
7 . 請求處理完成后,通過 Responce 直接響應(yīng)業(yè)務(wù)數(shù)據(jù)到客戶端。