跳到主要內容

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 應用程式中,一切都已設定完成並可立即使用。請閱讀 Controller (控制器) 文章,以瞭解在建立控制器時如何使用這些功能。

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 函數過濾參數。如果找到無效值,則會拋出 BadRequestHttpExceptionFILTER_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 的實例。這主要用於附加屬於請求且需要在應用程式中許多不同點存取的資訊。

最後,可以使用 getContent() 存取隨請求 body 發送的原始資料

1
$content = $request->getContent();

例如,這對於使用 HTTP POST 方法處理遠端服務傳送給應用程式的 XML 字串可能很有用。

如果請求 body 是 JSON 字串,則可以使用 toArray() 存取

1
$data = $request->toArray();

如果請求資料可能是 $_POST 資料*或* JSON 字串,您可以使用 getPayload() 方法,該方法會傳回包裝此資料的 InputBag 實例

1
$data = $request->getPayload();

識別請求

在您的應用程式中,您需要一種識別請求的方法;大多數時候,這是透過請求的 "path info" 完成的,可以使用 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() 變更一堆參數。

存取 Session (會話)

如果您的請求附加了 session (會話),您可以透過 RequestRequestStack 類別的 getSession() 方法存取它; hasPreviousSession() 方法會告訴您請求是否包含在前一個請求之一中啟動的 session (會話)。

處理 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() 方法

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 編碼的。

發送回應

在傳送回應之前,您可以選擇性地呼叫 prepare() 方法,以修正與 HTTP 規範的任何不相容之處 (例如,錯誤的 Content-Type 標頭)

1
$response->prepare($request);

將回應傳送至用戶端是透過呼叫 send() 方法來完成

1
$response->send();

send() 方法接受一個選填的 flush 引數。如果設定為 false,則不會呼叫像 fastcgi_finish_request()litespeed_finish_request() 這樣的函式。這在偵錯您的應用程式以查看在 TerminateEvent 的監聽器中擲出的例外狀況時非常有用。您可以在關於 Kernel 事件的專門章節中了解更多相關資訊。

設定 Cookie

回應 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);

可以定義分割的 Cookie,也稱為 CHIPS,方法是使用 withPartitioned() 方法

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)是否與用戶端 Request 中指定的條件值相符,請使用 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 不是唯一可以緩衝輸出的層級。您的網路伺服器也可能根據其組態進行緩衝。某些伺服器(例如 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 是某些應用程式中的常見需求。在 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 授權條款授權。
目錄
    版本