Компонент HttpFoundation

Компонент HttpFoundation

Компонент HttpFoundation определяет объектно-ориентированный слой спецификации HTTP.

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

Компонент Symfony HttpFoundation заменяет эти глобальные переменные и функции объектно-ориентироанным слоем.

Установка

Вы можете установить компонент 2 разными способами:

Then, require the vendor/autoload.php file to enable the autoloading mechanism provided by Composer. Otherwise, your application won't be able to find the classes of this Symfony component.

Запрос

Наиболее распространённый способ создать запрос - основать его на текущих глобальных переменных PHP c 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: эквивалент $_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()
Возвращает значение параметра, преобразованное в число;
filter()
Фильтрует параметр, используя функцию filter_var.

Все геттеры имеют до двух аргументов: первый - это имя параметра, а второй - значение по умолчанию, которое нужно вернуть, если параметр не существует:

 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]=bar, особенным способом, создавая массив. Так что выможете получить параметр foo и вы получит обратно массив с элементом bar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// строка запроса - '?foo[bar]=baz'

$request->query->get('foo');
// возвращает массив ('bar' => 'baz')

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

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

Благодаря публичному свойству attributes, вы можете хранить дополнительные данные в запросе, который также является экземпляром ParameterBag. Это в основном используется для присоединения информации, которая принадлежит Запросу и должна быть доступна из множества точек вашего приложения.

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

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

Например, это может быть полезно для обработки JSON строки, отправленной приложению удалённым сервисом, использующим метод HTTP POST.

Идентификация запроса

В вашем приложении,вам нужен способ идентифицировать запрос; в большинстве случаев, это делается через "путь информации" запроса, который доступен через метод 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(); метод hasPreviousSession() сообщает вам, содержит ли запрос сессию, которая была запущена в одном из предыдущих запросов.

Доступ к данным заголовков 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;

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

// Приемлемые объекты заголовков сортируются в порядке снижения качества
$accepts = AcceptHeader::fromString($request->headers->get('Accept'))
    ->all();

Доступ к другим данным

Класс 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 AppBundle\Http\SpecialRequest;
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 содержит всю информацию, которую нужно отпрвить обратно клиенту из заданного запроса. Конструктор имеет до трёх аргументов: содержимое ответа, статус-код и массив 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
$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.

Отправка ответа

До отправки Ответа вы можете убедиться, что он соответствуется HTTP спецификации, вызвав метод prepare():

1
$response->prepare($request);

Отправка ответа клиенту в таком случае заключается в простом вызове send():

1
$response->send();

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

Класс 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,
));

Чтобы проверить, соответствуют ли валидаторы Ответа (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 () {
    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 не единственный слой, буферизирующий вывод. Ваш веб-сервер может также использовать буфер, в зависимости от конфигурации. Более того, если вы используете FastCGI, буферизацию отключить невозможно в принципе.

Подача файлов

При отправке файла вы должны добавлять заголовок Content-Disposition к вашему ответу. И хотя создание этого заголовка для базовых загрузок файлов - это просто, использование не ASCII имён файлов требует больших усилий. makeDisposition() абстрагирует тяжелую работу, скрывающуюся за простым 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);

Как вариант, если вы подаёте статичный файл, вы можете использовать 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.

New in version 3.3: Класс Stream был представлен Symfony 3.3.

Если размер поданого файла неизвестен (например, потому что он создаётся на лету, или потому что в нём зарегистрирован фильтр потока 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(array(
    'data' => 123,
)));
$response->headers->set('Content-Type', 'application/json');

Также существует полезный класс JsonResponse, который может сделать это ешё проще:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use Symfony\Component\HttpFoundation\JsonResponse;

// если вы знаете, какие данные отправлять при создании запроса
$response = new JsonResponse(array('data' => 123));

// если вы не знаете, какие данные отправлять при создании запроса
$response = new JsonResponse();
// ...
$response->setData(array('data' => 123));

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

New in version 3.2: Метод fromJsonString() был добавлен в Symfony 3.2.

Класс 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});

Сессия

Информация сессии хранится в отдельном документе: Session Management.

Узнайте больше

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.