跳到内容

HttpFoundation 组件

编辑此页

HttpFoundation 组件为 HTTP 规范定义了一个面向对象的层。

在 PHP 中,请求由一些全局变量($_GET$_POST$_FILES$_COOKIE$_SESSION 等)表示,响应由一些函数(echoheader()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 实例(或其子类),它是一个数据持有者类

所有 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() 更改一堆参数。

访问会话

如果你的请求附加了会话,你可以通过 RequestRequestStack 类的 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()
返回按质量降序排列的可接受编码列表。

如果你需要完全访问从 AcceptAccept-LanguageAccept-CharsetAccept-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() 方法的 v4Bytesv6Bytes 参数在 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 方法等等)

您可以单独使用它们,也可以使用 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

HeaderRequestMatcherQueryParameterRequestMatcher 在 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 标头

注意

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 验证器(ETagLast-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 将自动处理来自请求的 RangeIf-Range 标头。它还支持 X-Sendfile(有关信息,请参阅 nginxApache)。要使用它,您需要确定是否应信任 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。这将禁用 RangeContent-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});

Session

会话信息在其自己的文档中:会话

安全内容偏好

一些网站具有“安全”模式,以帮助那些不想接触他们可能反对的内容的人。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()),
        ];
    }
}
这项工作,包括代码示例,均根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本