HttpFoundation 组件
HttpFoundation 组件为 HTTP 规范定义了一个面向对象的层。
在 PHP 中,请求由一些全局变量($_GET
、$_POST
、$_FILES
、$_COOKIE
、$_SESSION
等)表示,响应由一些函数(echo
、header()
、setcookie()
等)生成。
Symfony HttpFoundation 组件用一个面向对象的层替换了这些默认的 PHP 全局变量和函数。
安装
1
$ composer require symfony/http-foundation
注意
如果在 Symfony 应用程序之外安装此组件,则必须在代码中引入 vendor/autoload.php
文件,以启用 Composer 提供的类自动加载机制。阅读这篇文章了解更多详情。
另请参阅
本文介绍了如何在任何 PHP 应用程序中将 HttpFoundation 功能用作独立组件。在 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,
[],
$_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 或 InputBag,如果数据来自$_POST
参数;query
:InputBag;cookies
:InputBag;attributes
:ParameterBag;files
:FileBag;server
:ServerBag;headers
:HeaderBag。
所有 ParameterBag 实例都有检索和更新其数据的方法
- all()
- 返回参数。
- keys()
- 返回参数键。
- replace()
- 用一组新参数替换当前参数。
- add()
- 添加参数。
- get()
- 按名称返回参数。
- set()
- 按名称设置参数。
- has()
- 如果参数已定义,则返回
true
。 - remove()
- 移除参数。
ParameterBag 实例还具有一些过滤输入值的方法
- getAlpha()
- 返回参数值的字母字符;
- getAlnum()
- 返回参数值的字母字符和数字;
- getBoolean()
- 返回转换为布尔值的参数值;
- getDigits()
- 返回参数值的数字;
- getInt()
- 返回转换为整数的参数值;
- getEnum()
- 返回转换为 PHP 枚举的参数值;
- getString()
- 返回字符串格式的参数值;
- filter()
- 通过使用 PHP filter_var 函数过滤参数。如果找到无效值,则会抛出 BadRequestHttpException 异常。
FILTER_NULL_ON_FAILURE
标志可用于忽略无效值。
所有 getter 最多接受两个参数:第一个是参数名称,第二个是在参数不存在时返回的默认值
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 导入请求查询时,它会以特殊方式处理类似 foo[bar]=baz
的请求参数,因为它会创建一个数组。get()
方法不支持返回数组,因此你需要使用以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// the query string is '?foo[bar]=baz'
// don't use $request->query->get('foo'); use the following instead:
$request->query->all('foo');
// returns ['bar' => 'baz']
// if the requested parameter does not exist, an empty array is returned:
$request->query->all('qux');
// returns []
$request->query->get('foo[bar]');
// returns null
$request->query->all()['foo']['bar'];
// returns 'baz'
由于公共 attributes
属性,你可以在请求中存储其他数据,它也是 ParameterBag 的一个实例。这主要用于附加属于 Request 的信息,并且需要在应用程序中的许多不同点访问这些信息。
最后,可以使用 getContent() 访问随请求正文发送的原始数据
1
$content = $request->getContent();
例如,这对于处理远程服务使用 HTTP POST 方法发送到应用程序的 XML 字符串可能很有用。
如果请求正文是 JSON 字符串,则可以使用 toArray() 访问它
1
$data = $request->toArray();
如果请求数据可能是 $_POST
数据或 JSON 字符串,你可以使用 getPayload() 方法,该方法返回 InputBag 的一个实例,包装着这些数据
1
$data = $request->getPayload();
识别请求
在你的应用程序中,你需要一种识别请求的方法;大多数时候,这是通过请求的“路径信息”完成的,可以通过 getPathInfo() 方法访问
1 2 3
// for a request to http://example.com/blog/index.php/post/hello-world
// the path info is "/post/hello-world"
$request->getPathInfo();
模拟请求
除了基于 PHP 全局变量创建请求之外,你还可以模拟请求
1 2 3 4 5
$request = Request::create(
'/hello-world',
'GET',
['name' => 'Fabien']
);
create() 方法基于 URI、方法和一些参数(查询参数或请求参数,取决于 HTTP 方法)创建一个请求;当然,你也可以覆盖所有其他变量(默认情况下,Symfony 为所有 PHP 全局变量创建合理的默认值)。
基于这样的请求,你可以通过 overrideGlobals() 覆盖 PHP 全局变量
1
$request->overrideGlobals();
提示
你还可以通过 duplicate() 复制现有请求,或通过单次调用 initialize() 更改一堆参数。
访问会话
如果你的请求附加了会话,你可以通过 Request 或 RequestStack 类的 getSession()
方法访问它;hasPreviousSession() 方法告诉你请求是否包含在前一个请求之一中启动的会话。
处理 HTTP 头部
处理 HTTP 头部不是一项简单的任务,因为它们的内容需要进行转义和空格处理。Symfony 提供了 HeaderUtils 类,它抽象了这种复杂性,并为最常见的任务定义了一些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
use Symfony\Component\HttpFoundation\HeaderUtils;
// Splits an HTTP header by one or more separators
HeaderUtils::split('da, en-gb;q=0.8', ',;');
// => [['da'], ['en-gb','q=0.8']]
// Combines an array of arrays into one associative array
HeaderUtils::combine([['foo', 'abc'], ['bar']]);
// => ['foo' => 'abc', 'bar' => true]
// Joins an associative array into a string for use in an HTTP header
HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',');
// => 'foo=abc, bar, baz="a b c"'
// Encodes a string as a quoted string, if necessary
HeaderUtils::quote('foo "bar"');
// => '"foo \"bar\""'
// Decodes a quoted string
HeaderUtils::unquote('"foo \"bar\""');
// => 'foo "bar"'
// Parses a query string but maintains dots (PHP parse_str() replaces '.' by '_')
HeaderUtils::parseQuery('foo[bar.baz]=qux');
// => ['foo' => ['bar.baz' => 'qux']]
访问 Accept-*
头部数据
你可以使用以下方法访问从 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
use Symfony\Component\HttpFoundation\AcceptHeader;
$acceptHeader = AcceptHeader::fromString($request->headers->get('Accept'));
if ($acceptHeader->has('text/html')) {
$item = $acceptHeader->get('text/html');
$charset = $item->getAttribute('charset', 'utf-8');
$quality = $item->getQuality();
}
// Accept header items are sorted by descending quality
$acceptHeaders = AcceptHeader::fromString($request->headers->get('Accept'))
->all();
还支持可以可选地包含在 Accept-*
头部中的默认值
1 2 3 4 5
$acceptHeader = 'text/plain;q=0.5, text/html, text/*;q=0.8, */*;q=0.3';
$accept = AcceptHeader::fromString($acceptHeader);
$quality = $accept->get('text/xml')->getQuality(); // $quality = 0.8
$quality = $accept->get('application/xml')->getQuality(); // $quality = 0.3
匿名化 IP 地址
应用程序为了遵守用户保护法规,越来越需要匿名化 IP 地址,然后在记录和存储它们以进行分析。使用 IpUtils 中的 anonymize()
方法来做到这一点
1 2 3 4 5 6 7 8 9
use Symfony\Component\HttpFoundation\IpUtils;
$ipv4 = '123.234.235.236';
$anonymousIpv4 = IpUtils::anonymize($ipv4);
// $anonymousIpv4 = '123.234.235.0'
$ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
$anonymousIpv6 = IpUtils::anonymize($ipv6);
// $anonymousIpv6 = '2a01:198:603:10::'
如果你需要更高级别的匿名化,你可以使用 anonymize()
方法的第二个和第三个参数来指定应根据 IP 地址格式匿名化的字节数
1 2 3 4 5 6 7 8 9
$ipv4 = '123.234.235.236';
$anonymousIpv4 = IpUtils::anonymize($ipv4, 3);
// $anonymousIpv4 = '123.0.0.0'
$ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
// (you must define the second argument (bytes to anonymize in IPv4 addresses)
// even when you are only anonymizing IPv6 addresses)
$anonymousIpv6 = IpUtils::anonymize($ipv6, 3, 10);
// $anonymousIpv6 = '2a01:198:603::'
7.2
anonymize()
方法的 v4Bytes
和 v6Bytes
参数在 Symfony 7.2 中引入。
检查 IP 是否属于 CIDR 子网
如果您需要知道 IP 地址是否包含在 CIDR 子网中,您可以使用 IpUtils
中的 checkIp()
方法,该方法来自 IpUtils
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\HttpFoundation\IpUtils;
$ipv4 = '192.168.1.56';
$CIDRv4 = '192.168.1.0/16';
$isIpInCIDRv4 = IpUtils::checkIp($ipv4, $CIDRv4);
// $isIpInCIDRv4 = true
$ipv6 = '2001:db8:abcd:1234::1';
$CIDRv6 = '2001:db8:abcd::/48';
$isIpInCIDRv6 = IpUtils::checkIp($ipv6, $CIDRv6);
// $isIpInCIDRv6 = true
检查 IP 是否属于私有子网
如果您需要知道 IP 地址是否属于私有子网,您可以使用 IpUtils 中的 isPrivateIp()
方法来实现
1 2 3 4 5 6 7 8 9
use Symfony\Component\HttpFoundation\IpUtils;
$ipv4 = '192.168.1.1';
$isPrivate = IpUtils::isPrivateIp($ipv4);
// $isPrivate = true
$ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
$isPrivate = IpUtils::isPrivateIp($ipv6);
// $isPrivate = false
根据规则集匹配请求
HttpFoundation 组件提供了一些匹配器类,允许您检查给定的请求是否满足某些条件(例如,它是否来自某个 IP 地址,它是否使用了某种 HTTP 方法等等)
- AttributesRequestMatcher
- ExpressionRequestMatcher
- HeaderRequestMatcher
- HostRequestMatcher
- IpsRequestMatcher
- IsJsonRequestMatcher
- MethodRequestMatcher
- PathRequestMatcher
- PortRequestMatcher
- QueryParameterRequestMatcher
- SchemeRequestMatcher
您可以单独使用它们,也可以使用 ChainRequestMatcher 类将它们组合起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use Symfony\Component\HttpFoundation\ChainRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\SchemeRequestMatcher;
// use only one criteria to match the request
$schemeMatcher = new SchemeRequestMatcher('https');
if ($schemeMatcher->matches($request)) {
// ...
}
// use a set of criteria to match the request
$matcher = new ChainRequestMatcher([
new HostRequestMatcher('example.com'),
new PathRequestMatcher('/admin'),
]);
if ($matcher->matches($request)) {
// ...
}
7.1
HeaderRequestMatcher
和 QueryParameterRequestMatcher
在 Symfony 7.1 中引入。
访问其他数据
Request
类还有许多其他方法,您可以使用它们来访问请求信息。请查看 Request API 以获取有关它们的更多信息。
覆盖请求
Request
类不应被覆盖,因为它是一个表示 HTTP 消息的数据对象。但是,当从遗留系统迁移时,添加方法或更改某些默认行为可能会有所帮助。在这种情况下,注册一个 PHP 可调用对象,该对象能够创建您的 Request
类的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
use App\Http\SpecialRequest;
use Symfony\Component\HttpFoundation\Request;
Request::setFactory(function (
array $query = [],
array $request = [],
array $attributes = [],
array $cookies = [],
array $files = [],
array $server = [],
$content = null
) {
return new SpecialRequest(
$query,
$request,
$attributes,
$cookies,
$files,
$server,
$content
);
});
$request = Request::createFromGlobals();
Response
Response 对象保存了从给定请求需要发送回客户端的所有信息。构造函数最多接受三个参数:响应内容、状态码和 HTTP 标头数组
1 2 3 4 5 6 7
use Symfony\Component\HttpFoundation\Response;
$response = new Response(
'Content',
Response::HTTP_OK,
['content-type' => 'text/html']
);
这些信息也可以在 Response 对象创建后进行操作
1 2 3 4 5 6
$response->setContent('Hello World');
// the headers public attribute is a ResponseHeaderBag
$response->headers->set('Content-Type', 'text/plain');
$response->setStatusCode(Response::HTTP_NOT_FOUND);
当设置 Response 的 Content-Type
时,您可以设置字符集,但最好通过 setCharset() 方法来设置
1
$response->setCharset('ISO-8859-1');
请注意,默认情况下,Symfony 假定您的 Response 以 UTF-8 编码。
发送响应
在发送 Response 之前,您可以选择调用 prepare() 方法来修复与 HTTP 规范的任何不兼容之处(例如,错误的 Content-Type
标头)
1
$response->prepare($request);
通过调用 send() 方法完成向客户端发送响应
1
$response->send();
send()
方法接受一个可选的 flush
参数。如果设置为 false
,则不会调用诸如 fastcgi_finish_request()
或 litespeed_finish_request()
之类的函数。当调试应用程序以查看 TerminateEvent 的侦听器中抛出的异常时,这很有用。您可以在 关于 Kernel 事件的专门章节中了解更多信息。
设置 Cookies
响应 cookie 可以通过 headers
公共属性进行操作
1 2 3
use Symfony\Component\HttpFoundation\Cookie;
$response->headers->setCookie(Cookie::create('foo', 'bar'));
setCookie() 方法接受 Cookie 的实例作为参数。
您可以通过 clearCookie() 方法清除 cookie。
除了 Cookie::create()
方法之外,您还可以使用 fromString() 方法从原始标头值创建 Cookie
对象。您还可以使用 with*()
方法来更改某些 Cookie 属性(或使用流畅的接口构建整个 Cookie)。每个 with*()
方法都会返回一个具有修改后属性的新对象
1 2 3 4 5
$cookie = Cookie::create('foo')
->withValue('bar')
->withExpires(strtotime('Fri, 20-May-2011 15:25:52 GMT'))
->withDomain('.example.com')
->withSecure(true);
可以通过使用 withPartitioned() 方法来定义分区 cookie,也称为 CHIPS
1 2 3 4 5 6
$cookie = Cookie::create('foo')
->withValue('bar')
->withPartitioned();
// you can also set the partitioned argument to true when using the `create()` factory method
$cookie = Cookie::create('name', 'value', partitioned: true);
管理 HTTP 缓存
Response 类有一组丰富的方法来操作与缓存相关的 HTTP 标头
- setPublic()
- setPrivate()
- expire()
- setExpires()
- setMaxAge()
- setSharedMaxAge()
- setStaleIfError()
- setStaleWhileRevalidate()
- setTtl()
- setClientTtl()
- setLastModified()
- setEtag()
- setVary()
注意
setExpires()、setLastModified() 和 setDate() 方法接受任何实现 \DateTimeInterface
的对象,包括不可变的日期对象。
setCache() 方法可用于在一个方法调用中设置最常用的缓存信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
$response->setCache([
'must_revalidate' => false,
'no_cache' => false,
'no_store' => false,
'no_transform' => false,
'public' => true,
'private' => false,
'proxy_revalidate' => false,
'max_age' => 600,
's_maxage' => 600,
'stale_if_error' => 86400,
'stale_while_revalidate' => 60,
'immutable' => true,
'last_modified' => new \DateTime(),
'etag' => 'abcdef',
]);
要检查 Response 验证器(ETag
、Last-Modified
)是否与客户端请求中指定的条件值匹配,请使用 isNotModified() 方法
1 2 3
if ($response->isNotModified($request)) {
$response->send();
}
如果 Response 未被修改,它会将状态码设置为 304 并删除实际的响应内容。
重定向用户
要将客户端重定向到另一个 URL,您可以使用 RedirectResponse 类
1 2 3
use Symfony\Component\HttpFoundation\RedirectResponse;
$response = new RedirectResponse('http://example.com/');
流式传输响应
StreamedResponse 类允许您将 Response 流式传输回客户端。响应内容由 PHP 可调用对象而不是字符串表示
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\HttpFoundation\StreamedResponse;
$response = new StreamedResponse();
$response->setCallback(function (): void {
var_dump('Hello World');
flush();
sleep(2);
var_dump('Hello World');
flush();
});
$response->send();
注意
flush()
函数不刷新缓冲。如果在之前调用了 ob_start()
或启用了 output_buffering
php.ini
选项,则必须在 flush()
之前调用 ob_flush()
。
此外,PHP 不是唯一可以缓冲输出的层。您的 Web 服务器也可能基于其配置进行缓冲。某些服务器(例如 nginx)允许您在配置级别或通过在响应中添加特殊的 HTTP 标头来禁用缓冲
1 2
// disables FastCGI buffering in nginx only for this response
$response->headers->set('X-Accel-Buffering', 'no');
流式传输 JSON 响应
StreamedJsonResponse 允许使用 PHP 生成器流式传输大型 JSON 响应,以保持较低的资源使用率。
类构造函数需要一个数组,该数组表示 JSON 结构并包含要流式传输的内容列表。除了推荐用于最小化内存使用量的 PHP 生成器之外,它还支持任何包含 JSON 可序列化数据的 PHP Traversable 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use Symfony\Component\HttpFoundation\StreamedJsonResponse;
// any method or function returning a PHP Generator
function loadArticles(): \Generator {
yield ['title' => 'Article 1'];
yield ['title' => 'Article 2'];
yield ['title' => 'Article 3'];
};
$response = new StreamedJsonResponse(
// JSON structure with generators in which will be streamed as a list
[
'_embedded' => [
'articles' => loadArticles(),
],
],
);
当通过 Doctrine 加载数据时,您可以使用 toIterable()
方法逐行获取结果并最小化资源消耗。有关更多信息,请参阅 Doctrine 批量处理文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
public function __invoke(): Response
{
return new StreamedJsonResponse(
[
'_embedded' => [
'articles' => $this->loadArticles(),
],
],
);
}
public function loadArticles(): \Generator
{
// get the $entityManager somehow (e.g. via constructor injection)
$entityManager = ...
$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder->from(Article::class, 'article');
$queryBuilder->select('article.id')
->addSelect('article.title')
->addSelect('article.description');
return $queryBuilder->getQuery()->toIterable();
}
如果您返回大量数据,请考虑在特定项目计数后调用 flush 函数,将内容发送到浏览器
1 2 3 4 5 6 7 8 9 10 11 12 13
public function loadArticles(): \Generator
{
// ...
$count = 0;
foreach ($queryBuilder->getQuery()->toIterable() as $article) {
yield $article;
if (0 === ++$count % 100) {
flush();
}
}
}
或者,您也可以将任何可迭代对象传递给 StreamedJsonResponse
,包括生成器
1 2 3 4 5 6 7 8 9 10 11 12 13
public function loadArticles(): \Generator
{
yield ['title' => 'Article 1'];
yield ['title' => 'Article 2'];
yield ['title' => 'Article 3'];
}
public function __invoke(): Response
{
// ...
return new StreamedJsonResponse(loadArticles());
}
提供文件
发送文件时,您必须向响应添加 Content-Disposition
标头。虽然为基本文件下载创建此标头很简单,但使用非 ASCII 文件名则更为复杂。makeDisposition() 抽象了简单 API 背后的繁重工作
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$fileContent = ...; // the generated file content
$response = new Response($fileContent);
$disposition = HeaderUtils::makeDisposition(
HeaderUtils::DISPOSITION_ATTACHMENT,
'foo.pdf'
);
$response->headers->set('Content-Disposition', $disposition);
或者,如果您正在提供静态文件,则可以使用 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
才会处理 X-Sendfile
。对于 Apache,这不是默认情况。
要添加标头,请使用 mod_headers
Apache 模块,并将以下内容添加到 Apache 配置中
1 2 3 4 5 6 7 8 9 10
<IfModule mod_xsendfile.c>
# This is already present somewhere...
XSendFile on
XSendFilePath ...some path...
# This needs to be added:
<IfModule mod_headers.c>
RequestHeader set X-Sendfile-Type X-Sendfile
</IfModule>
</IfModule>
使用 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
标头时,这将不起作用。
或者,BinaryFileResponse
支持 \SplTempFileObject
的实例。当您想要提供一个在内存中创建并在发送响应后自动删除的文件时,这很有用
1 2 3 4 5 6 7
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$file = new \SplTempFileObject();
$file->fwrite('Hello World');
$file->rewind();
$response = new BinaryFileResponse($file);
7.1
Symfony 7.1 中引入了 BinaryFileResponse
中对 \SplTempFileObject
的支持。
如果服务文件的大小未知(例如,因为它是在运行时生成的,或者因为它注册了 PHP 流过滤器等),您可以将 Stream
实例传递给 BinaryFileResponse
。这将禁用 Range
和 Content-Length
处理,而是切换到分块编码
1 2 3 4 5
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Stream;
$stream = new Stream('path/to/stream');
$response = new BinaryFileResponse($stream);
注意
如果您刚刚在此同一请求期间创建了文件,则该文件可能在没有任何内容的情况下发送。这可能是由于缓存的文件统计信息返回的文件大小为零。要解决此问题,请使用二进制文件的路径调用 clearstatcache(true, $file)
。
创建 JSON 响应
可以通过 Response 类创建任何类型的响应,方法是设置正确的内容和标头。JSON 响应可能如下所示
1 2 3 4 5 6 7
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
$response->setContent(json_encode([
'data' => 123,
]));
$response->headers->set('Content-Type', 'application/json');
还有一个有用的 JsonResponse 类,它可以使此操作更加容易
1 2 3 4 5 6 7 8 9 10 11 12 13 14
use Symfony\Component\HttpFoundation\JsonResponse;
// if you know the data to send when creating the response
$response = new JsonResponse(['data' => 123]);
// if you don't know the data to send or if you want to customize the encoding options
$response = new JsonResponse();
// ...
// configure any custom encoding options (if needed, it must be called before "setData()")
//$response->setEncodingOptions(JsonResponse::DEFAULT_ENCODING_OPTIONS | \JSON_PRESERVE_ZERO_FRACTION);
$response->setData(['data' => 123]);
// if the data to send is already encoded in JSON
$response = JsonResponse::fromJsonString('{ "data": 123 }');
JsonResponse
类将 Content-Type
标头设置为 application/json
,并在需要时将您的数据编码为 JSON。
危险
为避免 XSSI JSON 劫持,您应该将关联数组作为最外层数组传递给 JsonResponse
,而不是索引数组,以便最终结果是一个对象(例如 {"object": "not inside an array"}
),而不是数组(例如 [{"object": "inside an array"}]
)。阅读 OWASP 指南以获取更多信息。
只有响应 GET 请求的方法容易受到 XSSI “JSON 劫持”的影响。响应 POST 请求的方法仍然不受影响。
警告
JsonResponse
构造函数表现出非标准的 JSON 编码行为,如果将 null
作为构造函数参数传递,则会将其视为空对象,尽管 null 是 有效的 JSON 顶级值。
如果不考虑向后兼容性问题,则无法更改此行为,但是可以调用 setData
并在那里传递值以选择退出此行为。
JSONP 回调
如果您正在使用 JSONP,则可以设置数据应传递到的回调函数
1
$response->setCallback('handleResponse');
在这种情况下,Content-Type
标头将是 text/javascript
,响应内容将如下所示
1
handleResponse({'data': 123});
安全内容偏好
一些网站具有“安全”模式,以帮助那些不想接触他们可能反对的内容的人。RFC 8674 规范定义了用户代理向服务器请求安全内容的方式。
该规范未定义哪些内容可能被认为是令人反感的内容,因此“安全”的概念并未精确定义。相反,该术语由服务器解释,并在选择根据此信息采取行动的每个网站的范围内解释。
Symfony 提供了两种与此首选项交互的方法
以下示例显示了如何检测用户代理是否首选“安全”内容
1 2 3 4 5 6
if ($request->preferSafeContent()) {
$response = new Response($alternativeContent);
// this informs the user we respected their preferences
$response->setContentSafe();
return $response;
生成相对和绝对 URL
为给定路径生成绝对 URL 和相对 URL 是某些应用程序中的常见需求。在 Twig 模板中,您可以使用 absolute_url() 和 relative_path() 函数来执行此操作。
UrlHelper 类通过 getAbsoluteUrl()
和 getRelativePath()
方法为 PHP 代码提供了相同的功能。您可以将其作为服务注入到应用程序中的任何位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// src/Normalizer/UserApiNormalizer.php
namespace App\Normalizer;
use Symfony\Component\HttpFoundation\UrlHelper;
class UserApiNormalizer
{
public function __construct(
private UrlHelper $urlHelper,
) {
}
public function normalize($user): array
{
return [
'avatar' => $this->urlHelper->getAbsoluteUrl($user->avatar()->path()),
];
}
}