Symfony. Компонент HttpFoundation.
Symfony

Вольный перевод официальной документации.

Компонент HttpFoundation, позволяет взаимодействовать с HTTP через объектно-ориентированный подход.

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

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

Установка

Вы можете его установить двумя способами:

Затем, подключить через require автолоадер – require ‘vendor/autoload.php’, без него, ваше приложение не сможет найти необходимые классы компонента.

Объект Request

Наиболее часто применяемый способ создать Request из глобальных переменных – использовать метод createFromGlobals():

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();

Который почти в точности соответствует более громоздкому, зато гибкому – через конструктор:

$request = new Request(
    $_GET,
    $_POST,
    array(),
    $_COOKIE,
    $_FILES,
    $_SERVER
);

Доступ к данным Request.

Объект Request содержит информацию о запросе клиента. Ее можно получить через определенные публичные (public) свойства:

  • 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;
  • query: ParameterBag;
  • cookies: ParameterBag;
  • attributes: ParameterBag;
  • files: FileBag;
  • server: ServerBag;
  • headers: HeaderBag;

Все экземпляры класса ParameterBag, содержат методы для чтения и записи данных:

  • all() – возвращает параметры
  • keys() – возвращает ключи (имена) параметров
  • replace() – заменяет текущий параметр новыми данными
  • add() – добавляет параметры
  • get() – возвращает параметр по его имени
  • set() – устанавливает параметр по имени
  • has() – вернет true, если параметр определен
  • remove() – удаляет параметр

Экземпляр ParameterBag также содержит некоторые методы для фильтрации значений:

  • getAlpha() – вернет алфавитные символы значения параметра
  • getAlnum() – алфавитные символы и цифры.
  • getBoolean() – приведет значение параметра к типу boolean;
  • getDigits – цифры.
  • getInt() – приведет к типу integer
  • filter() – отфильтрует значение, через встроенную в PHP функцию filter_var

Все геттеры принимают до двух аргументов. Первый это имя параметра, второй – значение по умолчанию, если параметра не существует:

// строка запроса '?foo=bar'

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

$request->query->get('bar');
// null

$request->query->get('bar', 'baz');
// 'baz'

Когда импортируется строка запроса вида foo[bar]=baz, она обрабатывается особым образом, в результате чего создается массив. То есть, если запросить параметр foo, вернется ассоциативный массив с элементом bar:

// строка запроса '?foo[bar]=baz'

$request->query->get('foo');
// вернется array('bar' => 'baz')

$request->query->get('foo[bar]');
// null

$request->query->get('foo')['bar'];
// 'baz'

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

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

$content = $request->getContent();

К примеру, может использоваться для извлечения JSON строки, которую отправил вашему приложению удаленный сервер, методом POST.

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

Вашему приложению необходим способ идентификации полученного запроса. В большинстве случаев это делается через «path info» (информация о пути), которую можно извлечь посредствам метода getPathInfo():

// В запросе http://example.com/blog/index.php/post/hello-world
$request->getPathInfo();
// «path info» - "/post/hello-world"

Эмуляция запроса

Вместо того, чтобы создавать объект Request на основе суперглобальных переменных ($_GET, $_POST и т.д.), вы можете сделать следующее:

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

Метод create() создает Request с указанным в первом параметре URI, затем HTTP методом и параметрами. И, конечно же, вы можете переопределить остальные переменные. По умолчанию, Symfony эмулирует (заполняет) адекватными значениями все данные, которые присутствуют в суперглобальных переменных при обычном запросе.

Можно переопределить данные, из глобальных переменных PHP, через метод overrideGlobals():

$request->overrideGlobals();

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

Доступ к сессии

Если у вас была стартована сессия, можете получить данные из нее – getSession(). Либо проверить, существует ли сессия (запущенная в ходе предыдущего запроса) – hasPreviousSession().

Доступ к заголовкам Accept-*

Можно легко получить данные, извлеченные из заголовков Accept-*, используя следующие методы:

  • getAcceptableContentTypes() – возвращает список поддерживаемых типов (accepted content types), с приоритетом в убывающем порядке
  • getLanguages() – вернет список поддерживаемых языков (accepted languages), в убывающем порядке
  • getCharsets() – список поддерживаемых кодировок, тоже в убывающем порядке.
  • getEncodings() – список поддерживаемых encoders (архиваторов) — gzip, deflate …, c убывающим приоритетом.

При необходимости полного доступа к заголовкам Accept, Accept-Language, Accept-Charset или Accept-Encoding, используйте утилитный класс AcceptHeader:

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 содержит множество других методов для доступа к информации о запросе. Изучите документацию Request API для более полной картины.

Переопределение Request

Класс Request не желательно переопределять, поскольку его объекты всего лишь представляют HTTP данные. Но при переходе с устаревших проектов, бывает удобно добавить определенные методы или изменить некоторое стандартное поведение. Чтобы этого добиться, зарегистрируйте PHP callable (функцию, метод, — то, что можно вызвать), который будет создавать объект класса Request:

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 заголовками.

use Symfony\Component\HttpFoundation\Response;

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

Перечисленные данные можно заменять после того, как объект был создан:

$response->setContent('Hello World');

// headers - свойство public, которое содержит экземпляр ResponseHeaderBag
$response->headers->set('Content-Type', 'text/plain');

$response->setStatusCode(Response::HTTP_NOT_FOUND);

Можете также указать кодировку вместе с Content-Type, но лучше это делать через метод setCharset():

$response->setCharset('ISO-8859-1');

Кстати, по умолчанию, Symfony устанавливает кодировку в UTF-8

Отправка ответа (Response)

Перед тем, как отправить ответ, вызов prepare() исправляет любые несовместимые с HTTP установки (например, неверный заголовок Content-Type), хотя данный шаг не обязателен.

$response->prepare($request);

Сама отправка ответа крайне проста:

$response->send();

Установка Cookies

Нужные Cookies устанавливаются через public свойство headers:

use Symfony\Component\HttpFoundation\Cookie;

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

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

Сбросить Cookie позволяет метод clearCookie()

Кстати, есть возможность создать объект Cookie из «сырого» заголовка (raw header), — вызвать fromString(), которая появилась, начиная с Symfony 3.3.

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

Класс Response включает богатый набор методов для управления HTTP заголовками, отвечающими за кэш.

  • setPublic();
  • setPrivate();
  • expire();
  • setExpires();
  • setMaxAge();
  • setSharedMaxAge();
  • setTtl();
  • setClientTtl();
  • setLastModified();
  • setEtag();
  • setVary();

Наиболее часто используется setCache(), для установки одной командой:

$response->setCache(array(
    'etag'          => 'abcdef',
    'last_modified' => new \DateTime(),
    'max_age'       => 600,
    's_maxage'      => 600,
    'private'       => false,
    'public'        => true,
));

Для валидации Response (ETag, Last-Modified), что он соответствуют условиям запроса клиента, используйте isNotModified():

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

Если ответ не содержит изменений, устанавливается код 304 и удаляется весь контент из объекта.

Перенаправление пользователя (Redirect)

Чтобы перенаправить пользователя на другой URL, используйте класс RedirectResponse:

use Symfony\Component\HttpFoundation\RedirectResponse;

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

Отправка ответа потоком (Streaming a Response)

Класс StreamedResponse, позволяет вам отправлять поток обратно клиенту. Контентом является вызываемая функция (PHP callable), вместо строки:

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

Имейте ввиду, что flush() на очищает буфер. Если была запущена функция ob_start() или в файле php.ini активирован output_buffering, необходимо вызвать ob_flush(), перед flush().

В дополнение следует отметить, что не только PHP, как таковой, может буферизировать вывод, но и ваш веб сервер. Некоторые из них, например Nginx, позволяют отключить буферизацию в конфигурационном файле, либо с помощью специального заголовка в ответе:

// отключает буферизацию FastCGI в Nginx, для текущего ответа
$response->headers->set('X-Accel-Buffering', 'no')

Обработка файлов

При отправке файлов, вы должны добавить заголовок Content-Disposition в ответ. Создать данный заголовок для простого скачивания легко. Чего нельзя сказать, если используются имена файлов отличные от ASCII. Метод makeDisposition() выполняет всю сложную работу, предоставляя простой интерфейс:

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

$fileContent = ...; // сгенерированное содержимое файла
$response = new Response($fileContent);

$disposition = $response->headers->makeDisposition(
    ResponseHeaderBag::DISPOSITION_ATTACHMENT,
    'foo.pdf'
);

$response->headers->set('Content-Disposition', $disposition);

В качестве альтернативы, когда отправляются готовые файлы, используйте BinaryFileResponse:

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():

BinaryFileResponse::trustXSendfileTypeHeader();

Вместе с BinaryFileResponse, возможно устанавливать заголовок Content-Type или менять Content-Disposition.

// ...
$response->headers->set('Content-Type', 'text/plain');
$response->setContentDisposition(
    ResponseHeaderBag::DISPOSITION_ATTACHMENT,
    'filename.txt'
);

При необходимости, после отправки файла его можно удалить через deleteFileAfterSend(). Обратите внимание, прием не сработает при установленном заголовке X-Sendfile.

Класс Stream был впервые реализован в Symfony 3.3

Когда размер отправляемого файла не известен (например, потому, что он генерируется «на лету», или зарегистрирован в PHP stream filter и т.д.) вы можете передать экземпляр Stream в BinaryFileResponse. Это отключит обработку заголовков Range и Content-Length; переключится в chunked encoding.

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 может выглядеть так:

use Symfony\Component\HttpFoundation\Response;

$response = new Response();
$response->setContent(json_encode(array(
    'data' => 123,
)));
$response->headers->set('Content-Type', 'application/json');

Класс JsonResponse также может быть полезен, он сделает отправку еще проще:

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

Метод fromJsonString() был добавлен в версии Symfony 3.2

Упомянутый класс устанавливает заголовок Content-Type в значение application/json и сериализует ваши данные в JSON, если необходимо.

Внимание!
Во избежание XSSI JSON Hijacking, следует передавать ассоциативный массив, в качестве внешнего, в JsonResponse, а не индексный. Чтобы получился объект, например {«object»: «not inside an array»} вместо [{«object»: «inside an array»}]. Для более подробной информации изучите рекомендации OWASP.

Только методы связанные с GET запросами подвержены XSSI ‘JSON Hijacking’.

JSONP Callback

Если вы используете JSONP, можете установить callback функцию, в которую должны быть переданы данные.

$response->setCallback('handleResponse');

В таком случае, Content-Type будет установлен в значение text/javascript. Пример ответа:

handleResponse({'data': 123});

Сессии

Подробная информация о работе с сессией, описана в отдельной статье – Session Management.

Добавить комментарий