HttpFoundation组件是在HTTP基础上提供了一个面向对象操作封装。在PHP中,我们通常使用\$_GET
,\$_POST
,\$_FILES
,\$_COOKIE
,\$_SESSION
,…等全局变量来读取HTTP请求内容(request),使用函数:echo
,header()
, setcookie()
,…**等来生成响应内容(response)。**Symfony HttpFoundation对这些全局变量和函数进行了封装,提供了一个面向对象操作层。
如果你已经安装了composer包管理器,那么可以使用下面的命令进行安装:
$ composer require symfony/http-foundation
或者,直接克隆git项目:https://github.com/symfony/http-foundation 。
如果你是在Symfony应用之外单独使用HttpFoundation组件,那么你需要在脚本中引入vendor/autoload.php
文件,这是HttpFoundation组件类加载文件。
通常,我们可以使用函数::createFromGlobals()
来创建一个request
。
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
这等同于下面通过构造函数__construct()
来创建request
对象的方式:
$request = new Request(
$_GET,
$_POST,
array(),
$_COOKIE,
$_FILES,
$_SERVER
);
Request对象中包含着一个客户端请求request的数据,我们可以通过下面几个Request
对象的属性来进行读取:
request
: 等同于$_POST
;query
: 等同于$_GET
($request->query->get('name')
);cookies
: 等同于$_COOKIE
;attributes
: 用于存储一些其它的数据files
: 等同于$_FILES
;server
: 等同于$_SERVER
;headers
: 相当于$_SERVER
的子集 ($request->headers->get('User-Agent')
).每个属性实质上都是一个ParameterBag
实例,如下:
request
:ParameterBag
;query
:ParameterBag
;cookies
:ParameterBag
;attributes
:ParameterBag
;files
:FileBag
;server
:ServerBag
;headers
:HeaderBag
。为便于操作和复用,HttpFoundation组件对这些参数进行了统一封装,ParameterBag
、FileBag
、ServerBag
、HeaderBag
和Request
类图如下:
经过这样的参数封装之后,我们可以使用add
方法,存储一些其它的数据到request
对象中,在我们的应用中就可以很方便的进行读取。
我们可以使用content()
方法获取原始请求体(request body)的内容,这对于处理客户端通过POST方法提交过来的JSON字符串非常有用:
$content = $request->getContent();
我们可以通过下面的方式模拟一个request请求
$request = Request::create(
'/hello-world',
'GET',
array('name' => 'Fabien')
);
在这个request
对象的基础上,可以使用overrideGlobals()
方法来覆盖PHP全局的变量。
$request->overrideGlobals();
如果请求中包含session信息,可以使用getSession()
方法来读取会话信息,返回的是一个Session
类
$session = $request->getSession();
可以使用hasSession()
方法来检测请求中是否包含session信息。
Symfony提供了一个HeaderUtils
类,专门用于处理头部的空白和转义等字符。
use Symfony\Component\HttpFoundation\HeaderUtils;
// 使用一个或多个字符对HTTP header进行分割
HeaderUtils::split('da, en-gb;q=0.8', ',;');
// 返回结果:array(array('da'), array('en-gb','q=0.8'))
// Combines an array of arrays into one associative array
HeaderUtils::combine(array(array('foo', 'abc'), array('bar')));
// 返回结果:array('foo' => 'abc', 'bar' => true)
// Joins an associative array into a string for use in an HTTP header
HeaderUtils::toString(array('foo' => 'abc', 'bar' => true, 'baz' => 'a b c'), ',');
// 返回结果:'foo=abc, bar, baz="a b c"'
// 对引号字符进行转义
HeaderUtils::quote('foo "bar"');
// 返回结果: '"foo \"bar\""'
// 去掉转义字符,返回原字符串
HeaderUtils::unquote('"foo \"bar\""');
// 返回结果:'foo "bar"'
getAcceptableContentTypes()
:返回一个可接受的内容类型列表,按照容忍系数的大小降序排列。
getLanguages()
:返回可接受的语言类型列表,按照容忍系数的大小降序排列。
getCharsets()
:返回可接受的字符编码集列表,按照容忍系数的大小降序排列。
getEncodings()
:返回可接受的传输编码格式列表,按照容忍系数的大小降序排列。
一般情况下,我们不应该对HTTP请求进行覆盖操作,但是作为应用代理的角色时,我们需要一些修改HTTP Request内容的操作。如下所示:
use App\Http\SpecialRequest;
use Symfony\Component\HttpFoundation\Request;
Request::setFactory(function (
array $query = array(),
array $request = array(),
array $attributes = array(),
array $cookies = array(),
array $files = array(),
array $server = array(),
$content = null
) {
return new SpecialRequest(
$query,
$request,
$attributes,
$cookies,
$files,
$server,
$content
);
});
$request = Request::createFromGlobals();
Response
对象中包含了需要返回给客户端的全部信息,它的构造需要三个参数:响应内容、状态码、头部字段数组。如下所示:
use Symfony\Component\HttpFoundation\Response;
$response = new Response(
'Content',
Response::HTTP_OK,
array('content-type' => 'text/html')
);
Response
对象创建完成后,我们还可以继续对其进行修改。
$response->setContent('Hello World');
// 头部的公共属性是一个ResponseHeaderBag对象
$response->headers->set('Content-Type', 'text/plain');
$response->setStatusCode(Response::HTTP_NOT_FOUND);
发送响应之前,可选择调用prepare()
方法,主要用于处理HTTP标准的相关兼容性问题。
$response->prepare($request);
调用send()
方法,发送响应给客户端。
$response->send();
响应的cookie的设置主要通过配置header
的公共属性来实现:
use Symfony\Component\HttpFoundation\Cookie;
$response->headers->setCookie(new Cookie('foo', 'bar'));
setCookie()
方法需要Cookie
类的实例作为参数。
clearCookie()
方法可以清除cookie。
涉及到缓存设置的方法如下:
setPublic()
:设置cache-directive
的值为public
,所有内容都将被缓存(客户端和代理服务器都可缓存)。setPrivate()
:设置cache-directive
的值为private
,内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)。expire()
:setExpires()
:设置缓存过期时间。setMaxAge()
:设定max-age
。指示客户端愿意接收其绝对时间不大于指定的时间,以秒计。除非还包含 max-stale 指令,否则客户端不期望接收一个陈旧的响应。setSharedMaxAge()
:设置s-maxage
。如果一个响应包含 s-maxage 指令,那么对于共享缓存(而不是对私有缓存),由该指令规定的最大绝对时间会覆盖由 max-age 指令或 Expires 头规定的最绝对时间。s-maxage 指令也隐含 proxy-revalidate 指令的语义(将在本文“控制缓存重新验证和重新加载”小节介绍),也就是说,当共享缓存对接下来的请求的响应变得陈旧后,该请求没有与源服务器重新验证,共享缓存不能使用缓存条目。私有缓存总是忽略 s-maxage 指令。一般情况下,我们使用setCache()
方法就可以完成大多数cache设置。
$response->setCache(array(
'etag' => 'abcdef',
'last_modified' => new \DateTime(),
'max_age' => 600,
's_maxage' => 600,
'private' => false,
'public' => true,
));
将客户端的请求重定位到指定的URL,需要引入RedirectResponse
类。
use Symfony\Component\HttpFoundation\RedirectResponse;
$response = new RedirectResponse('http://example.com/');
脚本执行过程中输出到客户端的数据先暂存到缓存中,最后一并发送给客户端,StreamedResponse
类会将输出缓存流中的数据全部发送到客户端。
use Symfony\Component\HttpFoundation\StreamedResponse;
$response = new StreamedResponse();
$response->setCallback(function () {
var_dump('Hello World');
flush();
sleep(2);
var_dump('Hello World');
flush();
});
$response->send();
发送文件时,必须要在Response的头部添加Content-Dispositon
字段。Content-disposition是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名(请注意,这是设计导致的;无法使用此功能将文档保存到用户的计算机上,而不向用户询问保存位置)。
例:Content-Disposition: attachment; filename=FileName.txt
,就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
Symfony将相关的设置操作进行了封装,提供了makeDisposition()
方法。
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$fileContent = ...; // 生成好的文件内容
$response = new Response($fileContent);
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'foo.pdf'
);
$response->headers->set('Content-Disposition', $disposition);
如果提供的文件是一个静态文件,也可以使用BinaryFileResponse
类方便操作。
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$file = 'path/to/file.txt';
$response = new BinaryFileResponse($file);
文件过大需要分段传输时,BinaryFileResponse
类会自动处理请求头部的Range
和If-Range
参数。如果服务器支持X-Sendfile
,可以使用trustXSendfileTypeHeader()
方法来启用。
BinaryFileResponse::trustXSendfileTypeHeader();
知识介绍:X-Sendfile
是一种将文件下载请求由后端应用转交给前端 web 服务器处理的机制,它可以消除后端程序既要读文件又要处理发送的压力,从而显著提高服务器效率,特别是处理大文件下载的情形下。X-Sendfile
通过一个特定的 HTTP header 来实现:在 X-Sendfile
头中指定一个文件的地址来通告前端 web 服务器。当 web 服务器检测到后端发送的这个 header 后,它将忽略后端的其他输出,而使用自身的组件包括缓存头和断点重连等优化机制将文件发送给用户。不过,在使用 X-Sendfile
之前,我们必须明白这并不是一个标准特性,在默认情况下它是被大多数 web 服务器禁用的。而不同的 web 服务器的实现也不一样,包括规定了不同的 X-Sendfile
头格式。如果配置失当,用户可能下载到 0 字节的文件。使用 X-Sendfile
将允许下载非 web 目录中的文件例如/root/
,即使文件在 .htaccess 保护下禁止访问,也会被下载。
对于BinaryFileResponse
,我们仍然可以对文件的Content-Type
属性和以及响应头部的Content-Disposition
字段进行修改。
// ...
$response->headers->set('Content-Type', 'text/plain');
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'filename.txt'
);
Symfnoy提供了deleteFileAfterSend()
方法,用于文件发送完成后删除缓存的文件,释放内存。需要注意的是:对于使用了X-Sendfile
文件发送机制来说,不会起作用。
$response->deleteFileAfterSend(true);
通过设置Response
类的内容属性和头部属性,我们就可以生成想要的响应。如返回一个JSON响应,设置正确的响应内容和头部字段:
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
// 填充内容
$response->setContent(json_encode(array(
'data' => 123,
)));
// 设置头部字段
$response->headers->set('Content-Type', 'application/json');
可以使用工具类JsonResponse
,简化我们的操作:
use Symfony\Component\HttpFoundation\JsonResponse;
// 创建JsonResponse对象的同时设置响应的内容属性
$response = new JsonResponse(array('data' => 123));
// 创建一个内容属性为空的JsonResponse对象
$response = new JsonResponse();
// 设置内容属性
$response->setData(array('data' => 123));
// 直接从JSON字符串生成JsonResponse对象
$response = JsonResponse::fromJsonString('{ "data": 123 }');
从上面的过程中我们可以看出,JsonResponse
类实际上完成了将Content-Type
设置为application/json
和将数据编码成JSON格式的操作。
使用JSONP时,需要设置一个回调函数,在这个回调函数中,将要发送的数据作为参数赋值给回调函数。
$response->setCallback('handleResponse');
handleResponse({'data': 123});
返回给客户端的响应头部中的Content-Type
会自动被设置为text/javascript
。
Symfony HttpFoundation Session组件对底层的session存储驱动也进行了封装,支持多种存储方式。Session
类实现列SessionInterface
借口,提供了对Session的全部操作。
如下代码:
use Symfony\Component\HttpFoundation\Session\Session;
$session = new Session();
$session->start();
// 设置session
$session->set('name', 'Drak');
// 读取session
$session->get('name');
// 设置 flash messages
$session->getFlashBag()->add('notice', 'Profile updated');
// 重新读取 messages
foreach ($session->getFlashBag()->get('notice', array()) as $message) {
echo ''.$message.'';
}
注意:应避免使用PHP提供的session_start()
,session_regenerate_id()
,session_id()
,session_name()
和 session_destroy()
函数。这些函数只对PHP提供的会话机制有效,对于第三方代理会话系统无效。
start()
:启动会话migrate()
:invalidate()
:清除全部会话数据和会话IDgetId()
:获取会话IDsetId()
:设置会话IDgetName()
:获取会话namesetName()
:设置会话nameset()
:设置用户会话属性get()
:读取用户会话属性all()
:以关联数组的形式读取全部的用户属性has()
:检测用户会话属性是否存在replace()
:覆盖指定键名的用户会话属性remove()
:移除指定键名的用户会话属性clear()
:清除全部用户存储的会话属性示例:
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
$session = new Session(new NativeSessionStorage(), new AttributeBag());
$session->set('token', 'a6c1e0b6');
// ...
$token = $session->get('token');
// 如果用户会话属性不存在,返回默认值 'default-attribute-value'
$token = $session->get('attribute-name', 'default-attribute-value');
// ...
$session->clear();