第二部分 第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 协议的原因。有了它,咱们就可以让 NginxPHP 打交道啦。

上面谈到了,PHP 的工作模式与处理,那么在 PHP 的项目中,完整 PHP 请求的处理模式是怎么实现的 ?

  1. 用户访问域名,域名进行 DNS 解析 -> 请求到对应 IP 服务器和端口。
  2. 根据 IP 地址与端口发送请求到 IP 地址服务器上的 Nginx 软件监听的对应端口中。
  3. Nginx 对请求 url 进行 location 匹配,执行匹配 location 下的规则,然后由 Nginx 转发请求给 php 。
  4. php-fpm 的 master 进程监听到 nginx 请求,然后 Master 进程将请求分配给其中一个闲置的 worker进程。
  5. 最后由 worker 进程执行请求,并返回执行结果给 nginx 。
  6. 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 直接响应业务数据到客户端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纳西妲爱编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值