支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
HttpFoundation组件针对HTTP协议定义了一个面向对象层(object-oriented layer)。
在PHP中,reqeust(请求)通过多个超全局变量来呈现($_GET
,$_POST
,$_FILE
,$_COOKIE
,$_SESSION
...),而response(响应)则被若干函数所生成(echo
,header
,setcookie
...)。
Symfony HttpFoundation组件用一个面向对象层替换了PHP默认的全局变量及函数。
你可以通过下述两种方式安装:
通过Composer安装(Packagist上的symfony/http-foundation
)
通过官方Git宝库(https://github.com/symfony/http-foundation)
然后,包容vendor/autoload.php
文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
创建一个request(对象)的最常用方式,是基于当前的PHP全局变量,利用createFromGlobals()
方法来完成。
1 2 3 | use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals(); |
这差不多相当于调用冗长但却更灵活的__construct()
构造方法:
1 2 3 4 5 6 7 8 | $request = new Request(
$_GET,
$_POST,
array(),
$_COOKIE,
$_FILES,
$_SERVER
); |
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
。
所有ParameterBag
参数包实例,都有如下用于取出和更新其数据的方法:
all()
keys()
replace()
add()
get()
set()
has()
true
。remove()
ParameterBag
参数包实例还有如下用于过滤输入值(input value)的方法:
getAlpha()
getAlnum()
getBoolean()
getDigits()
getInt()
filter()
filter_var
函数来过滤参数。所有上述getter方法接收两个参数(arguments):第一个是参数名(parameter name),第二个是当参数不存在时的默认返回值:
1 2 3 4 5 6 7 8 9 10 | // the query string is '?foo=bar'
$request->query->get('foo');
// returns 'bar'
$request->query->get('bar');
// returns null
$request->query->get('bar', 'baz');
// returns 'baz' |
当PHP引入一个request query时,它在处理诸如foo[bar]=bar
这种request parameters时使用一种特殊的方式,即“创建数组”。所以你要得到foo
参数时拿到的是一个含有bar
元素的数组:
1 2 3 4 5 6 7 8 9 10 | // the query string is '?foo[bar]=baz'
$request->query->get('foo');
// returns array('bar' => 'baz')
$request->query->get('foo[bar]');
// returns null
$request->query->get('foo')['bar'];
// returns 'baz' |
得益于公有的(public)attributes
属性,你可以把附加数据存到request(属性)中,request本身也是一个ParameterBag
参数包实例。这种方法往往被用于附着(attach)Request
对象的相关信息,而这些信息要在你程序中的多个不同部分被访问到。
最后,连同请求本体(request body)一起被发送来的原始数据(raw data)可以通过getContent()
来访问到:
1 | $content = $request->getContent(); |
例如,这在处理一个“由[使用了HTTP POST方法的]远程服务发送到程序”的JSON字符串时非常有用。
在程序中,你需要一种识别请求的方式。多数时候,这可以通过请求的“path info”来完成,它可以通过getPathInfo()
来访问到:
1 2 3 4 5 | // for a request to http://example.com/blog/index.php/post/hello-world
// the path info is "/post/hello-world"
// 对于http://example.com/blog/index.php/post/hello-world这个请求,
// path info是 "/post/hello-world"
$request->getPathInfo(); |
除了基于PHP全局变量创建请求之外,你还可以模拟一个请求:
1 2 3 4 5 | $request = Request::create(
'/hello-world',
'GET',
array('name' => 'Fabien')
); |
create()
方法基于一个URI、一个METHOD以及一些参数(query parameter还是request参数,取决于HTTP METHOD)来创建request(对象)。当然,你也可以覆写所有其他的变量(默认情况是,Symfony为所有的PHP全局变量创建了有意义的默认值)。
基于这种(模拟的)请求,你可以通过overrideGlobals()
方法覆写PHP全局变量:
1 | $request->overrideGlobals(); |
你可以通过duplicate()
方法对已经存的请求进行复制,或者,调用一次initialize()
方法来改变一堆参数。
如果你在请求中附带了session,你可以通过getSession()
方法访问到它;hasPreviousSession()
则告诉你请求中“是否包含了一个始于之前某个请求”的session。
通过以下方法,你可以很容易的获得提取自Accept-*
头的基础数据:
getAcceptableContentTypes()
getLanguages()
getCharsets()
getEncodings()
如果你需要全面控制来自Accept
、Accept-Language
、Accept-Charset
和Accept-Encoding
的解析数据,可以使用AcceptHeader
工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | use Symfony\Component\HttpFoundation\AcceptHeader;
$accept = AcceptHeader::fromString($request->headers->get('Accept'));
if ($accept->has('text/html')) {
$item = $accept->get('text/html');
$charset = $item->getAttribute('charset', 'utf-8');
$quality = $item->getQuality();
}
// Accept header items are sorted by descending quality
// Accept头元素被以quality的降序来排序
//(译注:"q"参数详见 https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)
$accepts = AcceptHeader::fromString($request->headers->get('Accept'))
->all(); |
Request
类还有很多其他方法可供你用来访问请求中的信息。看一看the Request API
以了解更多。
Request
类不应被覆写,因为它是一个数据对象,用来呈现一条HTTP信息(HTTP message)。但是当它来自于程序遗产(legacy system)时,添加方法或改变一些默认行为可能是有用的。这种时候,注册一个PHP回调(callable)来创建一个Request
类的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 SpecialRequest::create(
$query,
$request,
$attributes,
$cookies,
$files,
$server,
$content
);
});
$request = Request::createFromGlobals(); |
一个Response
对象持有“从给定请求发送回客户端”的全部信息。其构造器接收三个参数:响应内容、状态码(status code),以及一个HTTP头的数组:
1 2 3 4 5 6 7 | use Symfony\Component\HttpFoundation\Response;
$response = new Response(
'Content',
Response::HTTP_OK,
array('content-type' => 'text/html')
); |
这些(构造参数所对应的)信息可以在响应创建之后进行处理:
1 2 3 4 5 6 7 | $response->setContent('Hello World');
// the headers public attribute is a ResponseHeaderBag
// 公有属性headers,是一个ResponseHeaderBag实例
$response->headers->set('Content-Type', 'text/plain');
$response->setStatusCode(Response::HTTP_NOT_FOUND); |
当设置响应的Content-Type
时,你可以顺带设置字符集,但更好的办法是通过setCharset()
方法来设置:
1 | $response->setCharset('ISO-8859-1'); |
注意,默认情况下,Symfony假定你的响应是UTF-8编码。
在发送响应之前,你可以通过调用prepare()
方法,确保它符合HTTP协议:
1 | $response->prepare($request); |
发送响应到客户端时,只需调用send
方法:
1 | $response->send(); |
响应的cookies,可以通过headers
公有属性来操作:
1 2 3 | use Symfony\Component\HttpFoundation\Cookie;
$response->headers->setCookie(new Cookie('foo', 'bar')); |
setCookie()
方法接收一个Cookie
实例作为参数。
你可以通过clearCookie()
方法清除一个cookie。
Response
类拥有丰富的方法,用来操作“和缓存有关的”HTTP头:
setCache()
方法可以在一个方法调用中完成“最为常见”的缓存信息设置:
1 2 3 4 5 6 7 8 | $response->setCache(array(
'etag' => 'abcdef',
'last_modified' => new \DateTime(),
'max_age' => 600,
's_maxage' => 600,
'private' => false,
'public' => true,
)); |
若要检查Response Validator(Etag
,Last-Modified
)是否匹配客户端请求中指定的一个条件值,使用isNotModified()
方法:
1 2 3 | if ($response->isNotModified($request)) {
$response->send();
} |
如果响应没有被修改,它会设置状态码为304,然后删除真正的响应内容(response content)。
要重定向客户端到另外一个URL,你可以使用RedirectResponse
类:
1 2 3 | use Symfony\Component\HttpFoundation\RedirectResponse;
$response = new RedirectResponse('http://example.com/'); |
StreamedResponse
类允许你把响应流化到客户端。响应内容以一个PHP回调(callable)来呈现,而不是一个字符串:
flush()
函数并未清除缓存(buffering)。如果ob_start()
之前被调用,或者output_buffering
这个php.ini
选项被开启,你必须在flush()
之前调用ob_flush()
。
此外,PHP并非唯一对输出(output)进行缓存的层。你的服务器也可能基于自身配置而缓存。而且,如果你使用了FastCGI,那么缓存根本无法被禁止。
当发送一个文件时,你必须在响应中添加一个Content-Disposition
头。虽然,为最基本的file downloads(文件下载)创建这样一个头是非常简单的,但使用一个non-ASCII文件名是更重要的。makeDiposition()
把繁重的工作抽象成(abstract)一个简单的API:
1 2 3 4 5 6 7 8 9 10 11 12 | use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$fileContent = ...; // the generated file content 生成文件内容
$response = new Response($fileContent);
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'foo.pdf'
);
$response->headers->set('Content-Disposition', $disposition); |
或者,如果你正处理一个静态文件(static file),你可以使用一个
BinaryFileResponse
响应:
1 2 3 4 | use Symfony\Component\HttpFoundation\BinaryFileResponse;
$file = 'path/to/file.txt';
$response = new BinaryFileResponse($file); |
BinaryFileResponse
响应将自动处理请求中的Range
和If-Range
头。它也支持X-Sendfile
(参考Nginx和Apache)。要利用此功能,你需要决定X-Sendfile-Type
头是否应被信任,如果信任,那么就调用trustXSendfileTypeHeader()
:
1 | BinaryFileResponse::trustXSendfileTypeHeader(); |
使用BinaryFileResponse
响应,你仍然能够设置被发送文件的Content-Type
头,或者改变它的Content-Disposition
:
1 2 3 4 5 6 | // ...
$response->headers->set('Content-Type', 'text/plain');
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'filename.txt'
); |
使用deleteFileAfterSend()
方法的话,在请求发送之后对文件进行删除是可能的。但是请注意它在设置了X-Sendfile
头时将不工作。
若你恰好 在相同的请求中创建了文件,这个文件在被发送时可能 不包含任何内容。这是因为缓存文件状态对文件容量返回了0导致的。为了修复此问题,调用clearstatcache(false, $file)
方法,使用binary file的路径(path)作为其参数。
通过Response
类,设置合适的content和headers,即可创建任何类型的响应。一个JSON响应看起来像下面这样:
1 2 3 4 5 6 7 | use Symfony\Component\HttpFoundation\Response;
$response = new Response();
$response->setContent(json_encode(array(
'data' => 123,
)));
$response->headers->set('Content-Type', 'application/json'); |
框架里有一个好用的JsonResponse
类,可以简化类似操作:
1 2 3 4 5 6 | use Symfony\Component\HttpFoundation\JsonResponse;
$response = new JsonResponse();
$response->setData(array(
'data' => 123
)); |
这可以把你的data数组转换成JSON,然后设置其Content-Type
头为application/json
。
为了防止XSS|JSON Hijacking(JSON劫持),你应该传入一个关联数组作为JsonResponse
的最外层数组,而不是一个索引数组,这样一来最终结果就是一个对象(比如,[{"object": "not inside an array"}]
)而不是数组(如,[{"object": "inside an array"}]
)。参考OWASP guidelines了解更多。
如果你正使用JASONP,你可以设置“数据应被传入”的callback function(回调函数):
1 | $response->setCallback('handleResponse') |
这时,Content-Type
头将是text-javascript
,响应内容(response content)可能是下面这样:
1 | handleResponse({'data': 123}); |
Session有自己的独立章节,参考中文组件之Session管理。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。