Компонент HttpFoundation, позволяет взаимодействовать с HTTP через объектно-ориентированный подход.
В PHP, запрос представлен, как совокупность глобальных переменных ($_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, …), а ответ формируется с помощью некоторых функций (echo, header(), setcookie(), …)
Компонент Symfony – HttpFoundation – заменяет упомянутые глобальные переменные и функции, предоставляя объектно-ориентированный слой.
Установка
Вы можете его установить двумя способами:
- Через Composer (symfony/http-foundation – проект packagist.org)
- Скачать с официального репозитория Git (http://github.com/symfony/http-foundation)
Затем, подключить через 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.