不少框架都会未来自客户端的请求抽象成类方便应用程序使用,在Laravel中也不例外。Illuminate\Http\Request
类在Laravel框架中就是对客户端请求的抽象,它是构建在Symfony
框架提供的Request组件基础之上的。今天这篇文章就简单来看看Laravel是怎么建立请求Request对象的,而关于Request对象为应用提供的能力我并不会过多去说,在我讲完建立过程后你也就知道去源码哪里找Request对象提供的方法了,网上有些速查表列举了一些Request提供的方法不过不够全而且有的也没有解释,因此我仍是推荐在开发中若是好奇Request是否已经实现了你想要的能力时去Request的源码里看下有没有提供对应的方法,方法注释里都清楚地标明了每一个方法的执行结果。下面让咱们进入正题吧。php
咱们能够在Laravel应用程序的index.php
文件中看到,在Laravel应用程序正式启动完成前Request对象就已经被建立好了:git
//public/index.php $app = require_once __DIR__.'/../bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( //建立request对象 $request = Illuminate\Http\Request::capture() );
客户端的HTTP请求是Illuminate\Http\Request
类的对象github
class Request extends SymfonyRequest implements Arrayable, ArrayAccess { //新建Request实例 public static function capture() { static::enableHttpMethodParameterOverride(); return static::createFromBase(SymfonyRequest::createFromGlobals()); } }
经过Illuminate\Http\Request
类的源码能够看到它是继承自Symfony Request
类的,因此Illuminate\Http\Request
类中实现的不少功能都是以Symfony Reques
提供的功能为基础来实现的。上面的代码就能够看到capture
方法新建Request对象时也是依赖于Symfony Request
类的实例的。web
namespace Symfony\Component\HttpFoundation; class Request { /** * 根据PHP提供的超级全局数组来建立Smyfony Request实例 * * @return static */ public static function createFromGlobals() { // With the php's bug #66606, the php's built-in web server // stores the Content-Type and Content-Length header values in // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields. $server = $_SERVER; if ('cli-server' === PHP_SAPI) { if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) { $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH']; } if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) { $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE']; } } $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server); if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH')) ) { parse_str($request->getContent(), $data); $request->request = new ParameterBag($data); } return $request; } }
上面的代码有一处须要额外解释一下,自PHP5.4开始PHP内建的builtin web server能够经过命令行解释器来启动,例如:bootstrap
php -S localhost:8000 -t htdocs数组
-S <addr>:<port> Run with built-in web server. -t <docroot> Specify document root <docroot> for built-in web server.
可是内建web server有一个bug是将CONTENT_LENGTH
和CONTENT_TYPE
这两个请求首部存储到了HTTP_CONTENT_LENGTH
和HTTP_CONTENT_TYPE
中,为了统一内建服务器和真正的server中的请求首部字段因此在这里作了特殊处理。服务器
Symfony Request 实例的建立是经过PHP中的超级全局数组来建立的,这些超级全局数组有$_GET
,$_POST
,$_COOKIE
,$_FILES
,$_SERVER
涵盖了PHP中全部与HTTP请求相关的超级全局数组,建立Symfony Request实例时会根据这些全局数组建立Symfony Package里提供的ParamterBag
ServerBag
FileBag
HeaderBag
实例,这些Bag都是Symfony提供地针对不一样HTTP组成部分的访问和设置API, 关于Symfony提供的ParamterBag
这些实例有兴趣的读者本身去源码里看看吧,这里就很少说了。cookie
class Request { /** * @param array $query The GET parameters * @param array $request The POST parameters * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array $cookies The COOKIE parameters * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param string|resource|null $content The raw body data */ public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) { $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); } public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) { $this->request = new ParameterBag($request); $this->query = new ParameterBag($query); $this->attributes = new ParameterBag($attributes); $this->cookies = new ParameterBag($cookies); $this->files = new FileBag($files); $this->server = new ServerBag($server); $this->headers = new HeaderBag($this->server->getHeaders()); $this->content = $content; $this->languages = null; $this->charsets = null; $this->encodings = null; $this->acceptableContentTypes = null; $this->pathInfo = null; $this->requestUri = null; $this->baseUrl = null; $this->basePath = null; $this->method = null; $this->format = null; } }
能够看到Symfony Request类除了上边说到的那几个,还有不少属性,这些属性在一块儿构成了对HTTP请求完整的抽象,咱们能够经过实例属性方便地访问Method
,Charset
等这些HTTP请求的属性。app
拿到Symfony Request实例后, Laravel会克隆这个实例并重设其中的一些属性:框架
namespace Illuminate\Http; class Request extends .... { //在Symfony request instance的基础上建立Request实例 public static function createFromBase(SymfonyRequest $request) { if ($request instanceof static) { return $request; } $content = $request->content; $request = (new static)->duplicate( $request->query->all(), $request->request->all(), $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all() ); $request->content = $content; $request->request = $request->getInputSource(); return $request; } public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) { return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server); } } //Symfony Request中的 duplicate方法 public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) { $dup = clone $this; if (null !== $query) { $dup->query = new ParameterBag($query); } if (null !== $request) { $dup->request = new ParameterBag($request); } if (null !== $attributes) { $dup->attributes = new ParameterBag($attributes); } if (null !== $cookies) { $dup->cookies = new ParameterBag($cookies); } if (null !== $files) { $dup->files = new FileBag($files); } if (null !== $server) { $dup->server = new ServerBag($server); $dup->headers = new HeaderBag($dup->server->getHeaders()); } $dup->languages = null; $dup->charsets = null; $dup->encodings = null; $dup->acceptableContentTypes = null; $dup->pathInfo = null; $dup->requestUri = null; $dup->baseUrl = null; $dup->basePath = null; $dup->method = null; $dup->format = null; if (!$dup->get('_format') && $this->get('_format')) { $dup->attributes->set('_format', $this->get('_format')); } if (!$dup->getRequestFormat(null)) { $dup->setRequestFormat($this->getRequestFormat(null)); } return $dup; }
Request对象建立好后在Laravel应用中咱们就能方便的应用它提供的能力了,在使用Request对象时若是你不知道它是否实现了你想要的功能,很简单直接去Illuminate\Http\Request
的源码文件里查看就行了,全部方法都列在了这个源码文件里,好比:
/** * Get the full URL for the request. * 获取请求的URL(包含host, 不包括query string) * * @return string */ public function fullUrl() { $query = $this->getQueryString(); $question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?'; return $query ? $this->url().$question.$query : $this->url(); } /** * Get the full URL for the request with the added query string parameters. * 获取包括了query string 的完整URL * * @param array $query * @return string */ public function fullUrlWithQuery(array $query) { $question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?'; return count($this->query()) > 0 ? $this->url().$question.http_build_query(array_merge($this->query(), $query)) : $this->fullUrl().$question.http_build_query($query); }
建立完Request对象后, Laravel的Http Kernel会接着往下执行:加载服务提供器引导Laravel应用、启动应用、让Request通过基础的中间件、经过Router匹配查找Request对应的路由、执行匹配到的路由、Request通过路由上到中间件到达控制器方法。
随着Request最终到达对应的控制器方法后它的使命基本上也就完成了, 在控制器方法里从Request中获取输入参数而后执行应用的某一业务逻辑得到结果,结果会被转化成Response响应对象返回给发起请求的客户端。
这篇文章主要梳理了Laravel中Request对象,主要是想让你们知道如何去查找Laravel中Request现有提供了哪些能力供咱们使用避免咱们在业务代码里从新造轮子去实现Request已经提供的方法。
本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。