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

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

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

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

Установка

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

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

Объект Request

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

  1. use Symfony\Component\HttpFoundation\Request;
  2. $request = Request::createFromGlobals();

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

  1. $request = new Request(
  2. $_GET,
  3. $_POST,
  4. array(),
  5. $_COOKIE,
  6. $_FILES,
  7. $_SERVER
  8. );

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

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

  1. // строка запроса ‘?foo=bar’
  2. $request>query>get(‘foo’);
  3. // возвращается ‘bar’
  4. $request>query>get(‘bar’);
  5. // null
  6. $request>query>get(‘bar’, ‘baz’);
  7. // ‘baz’

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

  1. // строка запроса ‘?foo[bar]=baz’
  2. $request>query>get(‘foo’);
  3. // вернется array(‘bar’ => ‘baz’)
  4. $request>query>get(‘foo[bar]’);
  5. // null
  6. $request>query>get(‘foo’)[‘bar’];
  7. // ‘baz’

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

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

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

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

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

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

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

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

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

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

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

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

  1. $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:

  1. use Symfony\Component\HttpFoundation\AcceptHeader;
  2. $accept = AcceptHeader::fromString($request>headers>get(‘Accept’));
  3. if ($accept>has(‘text/html’)) {
  4. $item = $accept>get(‘text/html’);
  5. $charset = $item>getAttribute(‘charset’, ‘utf-8’);
  6. $quality = $item>getQuality();
  7. }
  8. // Данные из заголовков отсортированы в убывающем порядке, по приоритету.
  9. $accepts = AcceptHeader::fromString($request>headers>get(‘Accept’))
  10. >all();

Доступ к остальным данным

Класс Request содержит множество других методов для доступа к информации о запросе. Изучите документацию Request API для более полной картины.

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

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

  1. use AppBundle\Http\SpecialRequest;
  2. use Symfony\Component\HttpFoundation\Request;
  3. Request::setFactory(function (
  4. array $query = array(),
  5. array $request = array(),
  6. array $attributes = array(),
  7. array $cookies = array(),
  8. array $files = array(),
  9. array $server = array(),
  10. $content = null
  11. ) {
  12. return SpecialRequest::create(
  13. $query,
  14. $request,
  15. $attributes,
  16. $cookies,
  17. $files,
  18. $server,
  19. $content
  20. );
  21. });
  22. $request = Request::createFromGlobals();

Объект Response

Содержит всю необходимую информацию, для отправки ответа пользователю, на его запрос. Конструктор принимает до трех аргументов: контент, код статуса и массив с HTTP заголовками.

  1. use Symfony\Component\HttpFoundation\Response;
  2. $response = new Response(
  3. ‘Content’,
  4. Response::HTTP_OK,
  5. array(‘content-type’ => ‘text/html’)
  6. );

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

  1. $response>setContent(‘Hello World’);
  2. // headers — свойство public, которое содержит экземпляр ResponseHeaderBag
  3. $response>headers>set(‘Content-Type’, ‘text/plain’);
  4. $response>setStatusCode(Response::HTTP_NOT_FOUND);

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

  1. $response>setCharset(‘ISO-8859-1’);

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

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

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

  1. $response>prepare($request);

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

  1. $response>send();

Установка Cookies

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

  1. use Symfony\Component\HttpFoundation\Cookie;
  2. $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(), для установки одной командой:

  1. $response>setCache(array(
  2. ‘etag’ => ‘abcdef’,
  3. ‘last_modified’ => new \DateTime(),
  4. ‘max_age’ => 600,
  5. ‘s_maxage’ => 600,
  6. ‘private’ => false,
  7. ‘public’ => true,
  8. ));

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

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

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

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

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

  1. use Symfony\Component\HttpFoundation\RedirectResponse;
  2. $response = new RedirectResponse(‘http://example.com/’);

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

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

  1. use Symfony\Component\HttpFoundation\StreamedResponse;
  2. $response = new StreamedResponse();
  3. $response>setCallback(function () {
  4. var_dump(‘Hello World’);
  5. flush();
  6. sleep(2);
  7. var_dump(‘Hello World’);
  8. flush();
  9. });
  10. $response>send();

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

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

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

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

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

  1. use Symfony\Component\HttpFoundation\Response;
  2. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  3. $fileContent = …; // сгенерированное содержимое файла
  4. $response = new Response($fileContent);
  5. $disposition = $response>headers>makeDisposition(
  6. ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  7. ‘foo.pdf’
  8. );
  9. $response>headers>set(‘Content-Disposition’, $disposition);

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

  1. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  2. $file = ‘path/to/file.txt’;
  3. $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. $response>headers>set(‘Content-Type’, ‘text/plain’);
  3. $response>setContentDisposition(
  4. ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  5. ‘filename.txt’
  6. );

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

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

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

  1. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  2. use Symfony\Component\HttpFoundation\File\Stream;
  3. $stream = new Stream(‘path/to/stream’);
  4. $response = new BinaryFileResponse($stream);

Если же вы прямо сейчас сгенерировали файл, в ходе текущего запроса, он может отправиться пустым. Такое случается из-за кэширования статуса файла, в результате чего, будет возвращен нулевой размер. Чтобы избежать данной проблемы, вызовите clearstatcache(true, $file), передав путь к файлу.

Генерирование JSON ответа

Любой тип ответа может быть создан через класс Response, посредствам установки соответствующих заголовков. Отправка JSON может выглядеть так:

  1. use Symfony\Component\HttpFoundation\Response;
  2. $response = new Response();
  3. $response>setContent(json_encode(array(
  4. ‘data’ => 123,
  5. )));
  6. $response>headers>set(‘Content-Type’, ‘application/json’);

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

  1. use Symfony\Component\HttpFoundation\JsonResponse;
  2. // когда данные для отправки заранее известны
  3. $response = new JsonResponse(array(‘data’ => 123));
  4. // если данных для отправки пока что нет
  5. $response = new JsonResponse();
  6. // …
  7. $response>setData(array(‘data’ => 123));
  8. // если данные уже в формате JSON
  9. $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 функцию, в которую должны быть переданы данные.

  1. $response>setCallback(‘handleResponse’);

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

  1. handleResponse({‘data’: 123});

Сессии

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