我们在前面的一篇文章 ​​laravel学习笔记——路由(基础)​​ 中提到了 HTTP 请求的大致内容。关于 HTTP 协议,一定要有个大致了解。

本文主要是帮助大家理解 laravel 的请求和响应部分。但我们在学习这一块之前,我会脱离 laravel 框架,单独讲述一些概念,让大家消除很多疑虑,这有助于降低 laravel 的学习难度(至少从思想层面)。本文还希望通过介绍,让大家清晰一些概念,让框架本身不再神秘。

很多人常常疑惑,为什么在 laravel 中控制器的方法、路由的匿名函数里,我们用的不是类似 TP 框架中的 ​​$this->display()​​ 输出视图,而是用 ​​return view()​​ 这种方式,或者说,为什么不是通过 echo、print这类输出内容而是一定要 ​​return​​ 呢?返回的数据究竟怎么被框架所处理?这期间发生了什么?直接输出和返回两者的区别在哪里?我们这篇文章不但要告诉你发生什么,还会就这一块诸多已有的疑惑、可能有的疑惑做出详细讲解。

本文中的例子依旧大致参考官方文档(或各类翻译的版本),因此在阅读本文的同时,一定不要脱离文档。

再次强调:阅读本文之前,务必大致了解 HTTP 协议!


先不谈 laravel

再复杂的框架,也只是帮你做了更多事情、帮你写了更多经常使用的代码的一个程序包。无论如何,一个框架再怎么神秘,也只是思想上的。

我们先不谈 laravel。为了让大家消除过多的疑虑,我们需要一点小小的变。

假设通过设置,我们访问根域名 ​​http://yourdomain​​ 实际上都会请求至 ​​index.php​​,这个文件就算是一个入口文件。一般大家学习的开始,都是这样。我们现在编辑 ​​index.php​​:

  1. <?php
  2. echo 'hello, world';

php 初学者都会写的代码,对吗?任何一个请求,都会响应一段文字 ​​Hello, world​​。事实上,框架做的事,只不过多了些判断、提供了一些工具,本质上而言,就是输出了一堆数据而已(学院派勿喷)。

我们再看一段代码:

  1. <?php
  2. if(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)=='/about'){
  3. echo 'about page';
  4. }else{
  5. echo 'other page';
  6. }

这段代码就比较有意义了。其实这段代码就是一个路由的粗糙实现原理。判断语句中的​​'/home'​​ 就是我们的路由匹配规则。假如你的服务器配置好了 Rewrite,当我们访问​​http://yourdomain/​​ 页面会出现 ​​other page​​,当访问 ​​http://yourdomain/about​​ 页面则会出现 ​​about page​​。

我们也可以叫这段代码为一个控制器,因为响应请求的逻辑都定义在这里了。这也算是个视图,因为呈现的样式和内容全部由这段代码负责。

其实,一个强大的框架只是将很多东西拆分开来,并且做得更多、更细致。我们在 laravel 定义了路由规则,当请求开始,laravel 会根据我们所定义的规则去匹配、判断,并将任务派发至具体的逻辑代码,比如我们定义在路由里的代码、或者外部的控制器等等。这期间的调度都是由框架执行,而不需要我们自己去引用文件、派发任务、渲染视图、连接数据库等等。框架的意义,就是这些。只是一个优秀的框架做的更为优雅而已。

一切都从一个请求开始

做 web 开发,尤其是基于 HTTP 协议的,一定要贯彻一个意识:没有来自客户端的请求,程序就不会有任何响应。

Laravel 框架为什么会执行?就是因为客户端发送一个 HTTP 请求,递交到你的服务器,服务器的 web 服务端程序(Apache、Nginx、IIS 等)将请求的数据派发至一个指定的(入口) CGI 程序,这个入口程序的启动意味着一切开始了。

一般的,在 laravel 中,我们默认的入口 CGI 程序是 laravel 框架下的​​public\index.php​​(学院派看到这里准备开喷前请务必往下阅读)。

说到 CGI,其实我们刚刚提到的 ​​index.php​​ 这个文件其实并不是 CGI 程序,但是由于我们通过在 apache、nginx、iis 之类的软件的设置(如 fastCGI),这类文件会统一由 PHP CGI 解释器进行解释执行,因此我们可以变相的认为一个 php 脚本文件算是一个 CGI 程序。虽然这样说会被学院派喷,但大家理解这个意思就行。

框架和原生程序的区别只是在于框架帮你做了一些基本上都会做的事情,这一点我们已经在上文中讲解了。

Laravel 也是一样的,当请求(Request)和数据被派发至入口文件,框架开始启动,载入一系列组件,最后调用用户自己创建的一些逻辑:定义的路由、控制器等。最后将这些逻辑中产生的数据反馈给客户端,反馈数据的过程可称作一个响应(Response)。


我们称一个请求到最终响应的过程,为一个请求的生命周期。


我们获取的外部数据,基本来自于请求。通过 ​​GET​​ 方式的请求,数据体现在查询字符串(query string)上,php 引擎会自动帮你将字符串化为一个数组 ​​$_GET​​。来自于​​POST​​、​​PUT​​提交的数据除了查询字符串上的,还有请求体上的请求正文中的数据(文本、二进制流等)。我们整个程序,基本都是在为来自客户端的请求服务。Laravel 提供了一系列处理 HTTP 请求的方法,这些强大的功能在处理请求数据时得心应手。


有时候,令人郁闷的是官方文档在请求、响应部分资料无比稀缺,但实际上,很多功能在文档上并没有体现。为什么呢?大家需要知道,在这一块,laravel 的 HTTP 功能库是基于 Symfony 框架的 HTTP 组件开发的,因此很多 API、文档都应该去 Symfony 框架的文档上去寻找。虽然这点很恶心,但是我还是要说—— laravel 的文档还是够用。


Laravel 关于请求部分的文档,大多数是在讲如何获取来自客户端的数据,一般的我们通过 Request 类获取:

  1. $name =Request::input('name');

假如你的类或函数在某一命名空间下,需要使用 ​​use Request;​​ 导入该类。上面的 ​​Request​​ 是一个 ​​Facade​​ ,关于这个 ​​Facade​​ 我们会在另一篇文章讲述,现在大致理解为访问一个系统组件的快捷方式。

那么还能怎样使用 HTTP 的 Request 组件呢?我们从文档中的例子来讲:

  1. <?php
  2. namespaceApp\Http\Controllers;
  3. useIlluminate\Http\Request;
  4. useIlluminate\Routing\Controller;
  5. classUserControllerextendsController{
  6. /**
  7. * Store a new user.
  8. *
  9. * @param Request $request
  10. * @return Response
  11. */
  12. publicfunction store(Request $request)
  13. {
  14. $name = $request->input('name');
  15. //
  16. }
  17. }

我们在方法 ​​store​​ 中定义了一个只接受 ​​Request​​ 对象的参数。当这个控制器的这个方法被执行,laravel 框架会分析这个方法需要的依赖,现在这个依赖是一个 ​​Request​​ 类,那么此时 laravel 便会在 IoC 容器里寻找这个类的可创建的或直接可用的实例,并注入至该参数,我们便直接可以使用咯。一般框架提供的组件和开发者在 IoC 注册的组件都可以通过这种方式访问到,而不需要手动 ​​new YourComponent();​​。


关于这个 IoC 容器,我们会在 laravel 的服务容器部分讲述,并会详细介绍依赖注入( DI)的思想和相关的设计模式。


其他关于 HTTP 请求在 laravel 中的使用方法,基本在文档中有着详细的讲解,没有的,请查阅 Symfony 框架的相关文档。在此不再赘述。

响应,用户所需

响应,顾名思义,就是一种反馈。

当一个 HTTP 请求发送到服务器上,若得到的响应是客户端所期待的,我们就视为这一次请求是成功的,反之则是失败的。请求成功通常服务端会响应的状态代码是 200。

状态码

代表意义

1xx

指示信息--表示请求已接收,继续处理

2xx

成功--表示请求已被成功接收、理解、接受,如:200

3xx

重定向--要完成请求必须进行更进一步的操作,如:302

4xx

客户端错误--请求有语法错误或请求无法实现,如:403,404

5xx

服务器端错误--服务器未能实现合法的请求:如:500

响应的内容相当丰富,客户端会根据响应的情况合理的展现在客户端,因此合理利用 HTTP 响应可以构建一个规范化的应用。Laravel 的 HTTP 组件我们很容易根据逻辑创建任何一个具体的响应。


除了 symfony、laravel等,其他大多数国产或者旧的国外框架,在这一块思想十分保守。打个比方,实现一个提示用户没有权限访问某一模块时,很多框架只是简单粗暴的输出内容,并不会通过 header 产生一个 403 状态。一个标准的 HTTP 响应不但会让程序更为规范,也更具有兼容性。


由于客户端所看到的、接收到的内容都来自于服务端 HTTP 响应,因此,将响应(包括前面的请求)抽象出来,这样的设计在开发时更易于组织逻辑。这样,无论是输出一段 html 代码还是 json,或者二进制数据(图片、音频、视频、其他文件等等),我们都可以通过 HTTP 响应组件进行组织,而不需要关心文件是怎么输出的,HTTP 组件会根据情况合理选择响应头、代码等等。

这时候回到一个开始提到的问题:


在控制器、路由闭包中,使用 echo 输出内容和使用 return 输出内容有什么区别?


控制器和路由闭包中返回的数据,最终会交由 laravel 的 HTTP 组件的 Response(响应)类处理,而直接输出是由 php 引擎处理,php 会以默认的文件格式、响应头输出,除非使用 ​​header​​ 函数改变。因此与其自己去调取 ​​header()​​ 调整响应头还是其他,都不如 laravel 的 Response 来的简洁实惠。