Компонент HttpFoundation

Дата оновлення перекладу 2024-05-02

Компонент HttpFoundation

Компонент HttpFoundation визначає об'єктно-орієнтований шар специфікації HTTP.

В PHP, запит представлено деякими глобальними змінними ($_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, ...), а відповідь генерується деякими функціями (echo, header(), setcookie(), ...).

Компонент Symfony HttpFoundation замінює ці глобальні змінні та функції об'єктно- орієнтованним шаром.

Установка

1
$ composer require symfony/http-kernel

Note

Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити файл vendor/autoload.phpу вашому коді для включення механізму автозавантаження класів, наданих Composer. Детальніше можна прочитати у цій статті.

See also

Ця стаття пояснює, як використовувати функції HttpFoundation в якості незалежного компоненту в будь-якому додатку PHP. В додатках Symfony все вже налаштовано і готове до використання. Прочитайте статтю Контролер, щоб розуміти, як використовувати ці функції при створенні контролерів.

Запит

Найрозповсюдженіший спосіб створити запит - заснувати його на поточних глобальних змінних 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: еквівалент $_POST;
  • query: еквівалент $_GET ($request->query->get('name'));
  • cookies: еквівалент $_COOKIE;
  • attributes: еквіваленту немає - використовується вашим додатком для зберігання інших даних (див. below );
  • 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 може бути використано для ігнорування невалідних значень.

Всі гетери мають до двох аргументів: перший - це ім'я параметру, а другий - значення за замовчуванням, яке потрібно повернути, якщо параметр не існує:

1
2
3
4
5
6
7
8
9
10
// рядок запиту - '?foo=bar'

$request->query->get('foo');
// повертає 'bar'

$request->query->get('bar');
// повертає null

$request->query->get('bar', 'baz');
// повертає 'baz'

Коли PHP імпортує запит на запит, він обробляє параметри запиту як foo[bar]=baz, особливим способом, створюючи масив. Метод get() не підтримує повернення масивів, тому вам необхідно використовувати наступний код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// рядок запиту - '?foo[bar]=baz'

// не використовуйте $request->query->get('foo'); замість цього, використовуйте наступне:
$request->query->all()['foo'];
// повертає ['bar' => 'baz']

// якщо запитуваний параметр не існує, повертається порожній масив:
$request->query->all('qux');
// повертає []

$request->query->get('foo[bar]');
// повертає null

$request->query->get('foo')['bar'];
// повертає 'baz'

Завдяки публічній властивості attributes, ви можете зберігвати додаткові дані в запиті, який також є екземпляром ParameterBag. Це в основному використовується для приєднання інформації, яка належить Запиту, та має бути доступною з багатьох точок вашого додатку.

Нарешті, сирі дані, відправлені в тілі запиту, можуть бути доступними, за використання getContent():

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

Наприклад, це може бути корисним при обробці XML-радку, відправленого додатку віддаленим сервісом, що використовує метод HTTP POST.

Якщо тіло запиту - це JSON-рядом, доступ до нього можна отримати, використовуючи toArray():

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

Якщо дані запиту можуть бути даними $_POST або* JSON-рядком, ви можете використовувати метод getPayload() який повертає екземпляр InputBag, що огортає ці дані:

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

Ідентифікація запиту

В вашому додатку вам потрібний спосіб ідентифікувати запит; в більшості випадків, це робиться через "шлях інформації" запиту, який доступний через метод getPathInfo():

1
2
3
// для запиту до http://example.com/blog/index.php/post/hello-world
// шлях інформації - "/post/hello-world"
$request->getPathInfo();

Симуляція запиту

Замість створення запиту, заснованого на глобальних PHP, ви також можете симулювати запит:

1
2
3
4
5
$request = Request::create(
    '/hello-world',
    'GET',
    array('name' => 'Fabien')
);

Метод create() створює запит, заснований на URI, методі та деяких параметрах (параметри запиту або запиту (query), в залежності від HTTP-методу); і, звичайно, ви також можете перевизначити всі інши змінні (за замовчуванням, Symfony створює розумні значення за замовчуванням для всіх глобальних змінних PHP).

Засновуючись на такому запиті, ми можете перевизначити глобальні змінні PHP через overrideGlobals():

1
$request->overrideGlobals();

Tip

Ви також можете дублювати існуючий запит через duplicate() або змінити купу параметрів одним викликом до initialize().

Доступ до сессії

Якщо у вас є сесія, приєднана до запиту, ви можете отримати до неї доступ через метод getSession() класів Request або RequestStack; 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
use Symfony\Component\HttpFoundation\HeaderUtils;

// Розбиває заголовок HTTP за допомогою одного або декількох роздільників
HeaderUtils::split('da, en-gb;q=0.8', ',;')
// => [['da'], ['en-gb','q=0.8']]

// Об'єднує масив масивів в один асоціативний масив
HeaderUtils::combine(array(array('foo', 'abc'), array('bar')))
// => array('foo' => 'abc', 'bar' => true)

// Зашифровує рядок як рядок у лапках, якщо це необхідно
HeaderUtils::quote('foo "bar"');
// => '"foo \"bar\""'

// Розшифровує рядок у лапках
HeaderUtils::unquote('"foo \"bar\""');
// => 'foo "bar"'

// Аналізує рядок запиту, але залишає крапки (PHP parse_str() замінює на '.' на '_')
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();
}

// Прийнятні об'єкти заголовків сортуються в порядку зниження якості
$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-адрес до логування та їх зберігання в цілях аналізу. Використовуйте метод anonymize() з IpUtils, щоб зробити це:

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::'

Перевірка належності IP до приватної підмережі

Якщо вам потрібно дізнатися, чи належить IP-адреса до приватної підмережі, ви можете скористатися методом isPrivateIp() з
IpUtils для цього:

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;

// викристовувати лише один критерій для співвідношення запиту
$schemeMatcher = new SchemeRequestMatcher('https');
if ($schemeMatcher->matches($request)) {
    // ...
}

// використовувати набір критеріїв для співвідношення запиту
$matcher = new ChainRequestMatcher([
    new HostRequestMatcher('example.com'),
    new PathRequestMatcher('/admin'),
]);

if ($matcher->matches($request)) {
    // ...
}

7.1

HeaderRequestMatcher та QueryParameterRequestMatcher були представлені в Symfony 7.1.

Доступ до інших даних

Клас 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 містить всю інформацію, яку потрібно відправити назад клієнту з заданого запиту. Конструктор має до трьох аргументів: зміст відповіді, статус-код та масив HTTP-заголовків:

1
2
3
4
5
6
7
use Symfony\Component\HttpFoundation\Response;

$response = new Response(
    'Content',
    Response::HTTP_OK,
    ['content-type' => 'text/html']
);

Цю інформацію можна також змінювати після створення об'єкту Відповідь:

1
2
3
4
5
6
$response->setContent('Hello World');

// публічний атрибут заголовку - 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 (наприклад, неправильний заголовок Content-Type):

1
$response->prepare($request);

Відправка відповіді клієнту в такому випадку полягає в простому виклику send():

1
$response->send();

Метод end() приймає необов'язковий аргумент flush. Якщо він має значення false, такі функції, як fastcgi_finish_request() або litespeed_finish_request() не викликаються. Це корисно під час налагодження вашого додатку, щоб побачити, які винятки викликаються у слухачах TerminateEvent. Ви можете дізнатися більше про це у спеціальному розділі про події Kernel .

Установка куки

Куки відповіді можуть бути змінені через публічний атрибут headers:

1
2
3
use Symfony\Component\HttpFoundation\Cookie;

$response->headers->setCookie(Cookie::create('foo', 'bar'));

Метод setCookie() бере екземпляр Cookie в якості аргументу.

Ви можете очистити куки методом clearCookie().

В додаток до методу Cookie::create(), ви можете створити об'єкт Cookie з початкового значення заголовку, використовуючи метод fromString(). Ви також можете використовувати методи with*(), щоб змінити якусь властивість куки (або створити весь куки, використовуючи вільний інтерфейс). Кожний метод 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);

Можна визначити розділені файли куки, також відомі як CHIPS, за допомогою методу withPartitioned():

1
2
3
4
5
6
$cookie = Cookie::create('foo')
    ->withValue('bar')
    ->withPartitioned();

// ви також можете встановити аргумент розділеності як true при використанні методу фабрики `create()`.
$cookie = Cookie::create('name', 'value', partitioned: true);

Управління HTTP-кешем

Клас Response має багатий набір методів для управління HTTP-заголовками, що відносяться до кешу:

Note

Методи 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',
]);

Щоб перевірити, чи відповідають валідатори Відповіді (ETag, Last-Modified) умовному значенню, вказаному в Запиті клієнта, використовуйте метод isNotModified():

1
2
3
if ($response->isNotModified($request)) {
    $response->send();
}

Якщо Відповідь не було змінено, він встановлює статус-код 304, та видаляє справжній зміст відповіді.

Перенаправлення користувача

Щоб перенаправити клієнта по іншому URL, ви можете використовувати клас RedirectResponse:

1
2
3
use Symfony\Component\HttpFoundation\RedirectResponse;

$response = new RedirectResponse('http://example.com/');

Потокова передача відповіді

Клас StreamedResponse дозволяє вам створювати потік з Відповіддю для клієнта. Зміст відповіді надається 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();

Note

Функція flush() не скидає буферизацію. Якщо ob_start() було викликано до цього, або увімкнена опція output_buffering php.ini, то ви маєте викликати ob_flush() до flush().

Окрім того, PHP - не єдиний шар, що буферізує виведення. Ваш веб-сервер може також використовувати буфер, в залежності від конфігурації. Деякі сервери, на кшталт Nginx, дозволяють вам відключати буферизацію на рівні конфігурації, або додавати спеціальний заголовок HTTP у відповіді:

1
2
// відключає буферизацію FastCGI в Nginx тільки для цієї відповіді
$response->headers->set('X-Accel-Buffering', 'no');

Потокова передача JSON-відповіді

Клас StreamedJsonResponse дозволяє потоково передавати великі JSON-відповіді, використовуючи PHP-генератори, щоб зберегти низький рівень використання ресурсів.

Конструктор класу очікує масив, який представляє структуру JSON і включає в себе список змісту, який потрібно пердавати потоково. На додаток до генераторів PHP, які рекомендуються для мінімізації використання пам'яті, він також підтримує будь-який тип PHP Traversable, що містить дані у форматі JSON, які можна серіалізувати:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Symfony\Component\HttpFoundation\StreamedJsonResponse;

// будь-який метод чи функція, що повертають генератор PHP
function loadArticles(): \Generator {
     yield ['title' => 'Article 1'];
     yield ['title' => 'Article 2'];
     yield ['title' => 'Article 3'];
};

$response = new StreamedJsonResponse(
    // JSON-структура з генераторами, в яких буде потокова передача у вигляді списку
    [
        '_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
{
    // якимось чином отримати $entityManager (наприклад, через впровадження конструктора)
    $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();

Note

BinaryFileResponse буде обробляти X-Sendfile лише якщо присутній певний заголовок. Для Apache, це за замовчуванням не так.

Щоб додати заголовок, використовуйте модуль Apache mod_headers, та додайте наступне в конфігурацію Apache:

1
2
3
4
5
6
7
8
9
10
<IfModule mod_xsendfile.c>
  # Це вже десь присутнє...
  XSendFile on
  XSendFilePath ...якийсь шлях...

  # Це необхідно додати:
  <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

Підтримка \SplTempFileObject та BinaryFileResponse була представлена в Symfony 7.1.

Якщо розмір наданого файлу невідомий (наприклад, тому що він створюється на ходу, або тому що в ньому зареєстрований фільтр потоку PHP і т.д.), то ви можете передати екземпляр Stream в BinaryFileResponse. Це відключить обробку Range та Content-Length, так переключиться на механізм передачу даних chunked encoding:

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

Note

Якщо ви лише створили файл під час цього ж запиту, файл може бути відправлений без змісту. Це може статися у зв'язку зі статистикою кешованого файлу, яка повертає нуль в якості розміру файлу. Щоб виправити цю проблему, викличте 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;

// якщо ви не знаєте, які дані відправляти при створенні запиту
$response = new JsonResponse(['data' => 123]);

// якщо ви не знаєте, які дані відправляти при створенні запиту
$response = new JsonResponse();
// ...
// сконфігурувати будь-які користувацькі опції шифрування (якщо необхідно, має бути викликане до "setData()")
//$response->setEncodingOptions(JsonResponse::DEFAULT_ENCODING_OPTIONS | \JSON_PRESERVE_ZERO_FRACTION);
$response->setData(['data' => 123]);

// якщо дані для відправки вже зашифровані в JSON
$response = JsonResponse::fromJsonString('{ "data": 123 }');

Клас JsonResponse встановлює заголовок Content-Type в application/json та шифрує ваші дані в JSON за необхідності.

Caution

Щоб уникнути XSSI перехоплення JSON, вам варто передати асоціативний масив в JsonResponse в якості крайнього масиву, а не індексованого масиву, щоб фінальний результат був об'єктом (наприклад, {"object": "not inside an array"}) замість масиву (наприклад, [{"object": "inside an array"}]). Прочитайте довідник OWASP, щоб дізнатися більше.

Лише методи, що відповідають на запити GET, вразливі до XSSI 'перехоплення JSON'. Методи, що відповідають на запити POST, залишаються невразливими.

Зворотній виклик JSONP

Якщо ви використовуєте JSONP, ви можете встановити функцію зворотнього виклику, в яку мають бути передані дані:

1
$response->setCallback('handleResponse');

В цьому випадку, заголовок Content-Type буде text/javascript, а зміст відповіді виглядатиме так:

1
handleResponse({'data': 123});

Сесія

Інформація сесії зберігається в окремому документі: Управління сесіями.

Параметр безпечного контенту

Деякі веб-сайти мають "безпечний" режим, щоб допомогти тим, хто не хоче бачити контент, який може їх образити. Специфікація RFC 8674 визначає спосіб, яким агенти користувача запитують безпечний контент у серверу.

Специфікація не визначає, який контент може вважатися образливим, тому концепція "безпечності" визначена не точно. Скоріш цей термін інтерпретується сервером, і в рамках повноважень кожного сайту вирішувати, як діяти з цією інформацією.

Symfony пропонує два методи взаємодії з цим параметром:

Наступний приклад демонструє, як визначити, що агент користувача надає перевагу "безпечному" контенту:

1
2
3
4
5
6
7
if ($request->preferSafeContent()) {
    $response = new Response($alternativeContent);
    // повідомляє користувача, що ми врахували його побажання
    $response->setContentSafe();

    return $response;
}

Генерування відносних та абсолютних URL

Генерування відносних та абсолютних URL для заданого шляху - розповсюджена необхідність в деяких додатках. В шаблонах Twig ви можете використовувати функції absolute_url() та relative_path() , щоб зробити це.

Клас UrlHelper надає ті ж самі функції для PHP-коду через методи getAbsoluteUrl() та getRelativePath(). Ви можете впровадити це в якості сервісу де завгодно у вашому додатку:

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()),
        ];
    }
}

Дізнайтеся більше