Files
archived-symfony-docs/components/http_foundation.rst
Javier Eguiluz 0403aae2cb Merge branch '6.4' into 7.3
* 6.4:
  Remove some idioms and simplify expressions
2026-01-09 17:21:54 +01:00

1160 lines
41 KiB
ReStructuredText

The HttpFoundation Component
============================
The HttpFoundation component defines an object-oriented layer for the HTTP
specification.
In PHP, the request is represented by some global variables (``$_GET``,
``$_POST``, ``$_FILES``, ``$_COOKIE``, ``$_SESSION``, ...) and the response is
generated by some functions (``echo``, ``header()``, ``setcookie()``, ...).
The Symfony HttpFoundation component replaces these default PHP global
variables and functions by an object-oriented layer.
Installation
------------
.. code-block:: terminal
$ composer require symfony/http-foundation
.. include:: /components/require_autoload.rst.inc
.. seealso::
This article explains how to use the HttpFoundation features as an
independent component in any PHP application. In Symfony applications
everything is already configured and ready to use. Read the :doc:`/controller`
article to learn about how to use these features when creating controllers.
.. _component-http-foundation-request:
Request
-------
The most common way to create a request is to base it on the current PHP global
variables with
:method:`Symfony\\Component\\HttpFoundation\\Request::createFromGlobals`::
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
which is almost equivalent to the more verbose, but also more flexible,
:method:`Symfony\\Component\\HttpFoundation\\Request::__construct` call::
$request = new Request(
$_GET,
$_POST,
[],
$_COOKIE,
$_FILES,
$_SERVER
);
.. _accessing-request-data:
Accessing Request Data
~~~~~~~~~~~~~~~~~~~~~~
A Request object holds information about the client request. This information
can be accessed via several public properties:
* ``request``: equivalent of ``$_POST``;
* ``query``: equivalent of ``$_GET`` (``$request->query->get('name')``);
* ``cookies``: equivalent of ``$_COOKIE``;
* ``attributes``: no equivalent - used by your app to store other data (see :ref:`below <component-foundation-attributes>`);
* ``files``: equivalent of ``$_FILES``;
* ``server``: equivalent of ``$_SERVER``;
* ``headers``: mostly equivalent to a subset of ``$_SERVER``
(``$request->headers->get('User-Agent')``).
Each property is a :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`
instance (or a subclass of), which is a data holder class:
* ``request``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` or
:class:`Symfony\\Component\\HttpFoundation\\InputBag` if the data is
coming from ``$_POST`` parameters;
* ``query``: :class:`Symfony\\Component\\HttpFoundation\\InputBag`;
* ``cookies``: :class:`Symfony\\Component\\HttpFoundation\\InputBag`;
* ``attributes``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`;
* ``files``: :class:`Symfony\\Component\\HttpFoundation\\FileBag`;
* ``server``: :class:`Symfony\\Component\\HttpFoundation\\ServerBag`;
* ``headers``: :class:`Symfony\\Component\\HttpFoundation\\HeaderBag`.
All :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` instances have
methods to retrieve and update their data:
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::all`
Returns the parameters.
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::keys`
Returns the parameter keys.
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::replace`
Replaces the current parameters by a new set.
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::add`
Adds parameters.
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::get`
Returns a parameter by name.
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::set`
Sets a parameter by name.
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::has`
Returns ``true`` if the parameter is defined.
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::remove`
Removes a parameter.
The :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` instance also
has some methods to filter the input values:
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlpha`
Returns the alphabetic characters of the parameter value;
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlnum`
Returns the alphabetic characters and digits of the parameter value;
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getBoolean`
Returns the parameter value converted to boolean;
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getDigits`
Returns the digits of the parameter value;
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getInt`
Returns the parameter value converted to integer;
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getEnum`
Returns the parameter value converted to a PHP enum;
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getString`
Returns the parameter value as a string;
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::filter`
Filters the parameter by using the PHP :phpfunction:`filter_var` function.
If invalid values are found, a
:class:`Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException`
is thrown. The ``FILTER_NULL_ON_FAILURE`` flag can be used to ignore invalid
values.
All getters take up to two arguments: the first one is the parameter name
and the second one is the default value to return if the parameter does not
exist::
// the query string is '?foo=bar'
$request->query->get('foo');
// returns 'bar'
$request->query->get('bar');
// returns null
$request->query->get('bar', 'baz');
// returns 'baz'
When PHP imports the request query, it handles request parameters like
``foo[bar]=baz`` in a special way as it creates an array. The ``get()`` method
doesn't support returning arrays, so you need to use the following code::
// the query string is '?foo[bar]=baz'
// don't use $request->query->get('foo'); use the following instead:
$request->query->all('foo');
// returns ['bar' => 'baz']
// if the requested parameter does not exist, an empty array is returned:
$request->query->all('qux');
// returns []
$request->query->get('foo[bar]');
// returns null
$request->query->all()['foo']['bar'];
// returns 'baz'
.. _component-foundation-attributes:
Thanks to the public ``attributes`` property, you can store additional data
in the request, which is also an instance of
:class:`Symfony\\Component\\HttpFoundation\\ParameterBag`. This is mostly used
to attach information that belongs to the Request and that needs to be
accessed from many different points in your application.
Finally, the raw data sent with the request body can be accessed using
:method:`Symfony\\Component\\HttpFoundation\\Request::getContent`::
$content = $request->getContent();
For instance, this may be useful to process an XML string sent to the
application by a remote service using the HTTP POST method.
If the request body is a JSON string, it can be accessed using
:method:`Symfony\\Component\\HttpFoundation\\Request::toArray`::
$data = $request->toArray();
If the request data could be ``$_POST`` data *or* a JSON string, you can use
the :method:`Symfony\\Component\\HttpFoundation\\Request::getPayload` method
which returns an instance of :class:`Symfony\\Component\\HttpFoundation\\InputBag`
wrapping this data::
$data = $request->getPayload();
Identifying a Request
~~~~~~~~~~~~~~~~~~~~~
In your application, you need a way to identify a request; most of the time,
this is done via the "path info" of the request, which can be accessed via the
:method:`Symfony\\Component\\HttpFoundation\\Request::getPathInfo` method::
// for a request to http://example.com/blog/index.php/post/hello-world
// the path info is "/post/hello-world"
$request->getPathInfo();
Simulating a Request
~~~~~~~~~~~~~~~~~~~~
Instead of creating a request based on the PHP globals, you can also simulate
a request::
$request = Request::create(
'/hello-world',
'GET',
['name' => 'Fabien']
);
The :method:`Symfony\\Component\\HttpFoundation\\Request::create` method
creates a request based on a URI, a method and some parameters (the
query parameters or the request ones depending on the HTTP method); and of
course, you can also override all other variables as well (by default, Symfony
creates sensible defaults for all the PHP global variables).
Based on such a request, you can override the PHP global variables via
:method:`Symfony\\Component\\HttpFoundation\\Request::overrideGlobals`::
$request->overrideGlobals();
.. tip::
You can also duplicate an existing request via
:method:`Symfony\\Component\\HttpFoundation\\Request::duplicate` or
change a bunch of parameters with a single call to
:method:`Symfony\\Component\\HttpFoundation\\Request::initialize`.
Accessing the Session
~~~~~~~~~~~~~~~~~~~~~
If you have a session attached to the request, you can access it via the
``getSession()`` method of the :class:`Symfony\\Component\\HttpFoundation\\Request`
or :class:`Symfony\\Component\\HttpFoundation\\RequestStack` class;
the :method:`Symfony\\Component\\HttpFoundation\\Request::hasPreviousSession`
method tells you if the request contains a session which was started in one of
the previous requests.
Processing HTTP Headers
~~~~~~~~~~~~~~~~~~~~~~~
Processing HTTP headers is not a trivial task because of the escaping and white
space handling of their contents. Symfony provides a
:class:`Symfony\\Component\\HttpFoundation\\HeaderUtils` class that abstracts
this complexity and defines some methods for the most common tasks::
use Symfony\Component\HttpFoundation\HeaderUtils;
// Splits an HTTP header by one or more separators
HeaderUtils::split('da, en-gb;q=0.8', ',;');
// => [['da'], ['en-gb','q=0.8']]
// Combines an array of arrays into one associative array
HeaderUtils::combine([['foo', 'abc'], ['bar']]);
// => ['foo' => 'abc', 'bar' => true]
// Joins an associative array into a string for use in an HTTP header
HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',');
// => 'foo=abc, bar, baz="a b c"'
// Encodes a string as a quoted string, if necessary
HeaderUtils::quote('foo "bar"');
// => '"foo \"bar\""'
// Decodes a quoted string
HeaderUtils::unquote('"foo \"bar\""');
// => 'foo "bar"'
// Parses a query string but maintains dots (PHP parse_str() replaces '.' by '_')
HeaderUtils::parseQuery('foo[bar.baz]=qux');
// => ['foo' => ['bar.baz' => 'qux']]
Accessing ``Accept-*`` Headers Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can access basic data extracted from ``Accept-*`` headers
by using the following methods:
:method:`Symfony\\Component\\HttpFoundation\\Request::getAcceptableContentTypes`
Returns the list of accepted content types ordered by descending quality.
:method:`Symfony\\Component\\HttpFoundation\\Request::getLanguages`
Returns the list of accepted languages ordered by descending quality.
:method:`Symfony\\Component\\HttpFoundation\\Request::getCharsets`
Returns the list of accepted charsets ordered by descending quality.
:method:`Symfony\\Component\\HttpFoundation\\Request::getEncodings`
Returns the list of accepted encodings ordered by descending quality.
If you need to get full access to parsed data from ``Accept``, ``Accept-Language``,
``Accept-Charset`` or ``Accept-Encoding``, you can use
:class:`Symfony\\Component\\HttpFoundation\\AcceptHeader` utility class::
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();
}
// Accept header items are sorted by descending quality
$acceptHeaders = AcceptHeader::fromString($request->headers->get('Accept'))
->all();
The default values that can be optionally included in the ``Accept-*`` headers
are also supported::
$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
Anonymizing IP Addresses
~~~~~~~~~~~~~~~~~~~~~~~~
An increasingly common need for applications to comply with user protection
regulations is to anonymize IP addresses before logging and storing them for
analysis purposes. Use the ``anonymize()`` method from the
:class:`Symfony\\Component\\HttpFoundation\\IpUtils` to do that::
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::'
If you need even more anonymization, you can use the second and third parameters
of the ``anonymize()`` method to specify the number of bytes that should be
anonymized depending on the IP address format::
$ipv4 = '123.234.235.236';
$anonymousIpv4 = IpUtils::anonymize($ipv4, 3);
// $anonymousIpv4 = '123.0.0.0'
$ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
// (you must define the second argument (bytes to anonymize in IPv4 addresses)
// even when you are only anonymizing IPv6 addresses)
$anonymousIpv6 = IpUtils::anonymize($ipv6, 3, 10);
// $anonymousIpv6 = '2a01:198:603::'
.. versionadded:: 7.2
The ``v4Bytes`` and ``v6Bytes`` parameters of the ``anonymize()`` method
were introduced in Symfony 7.2.
Check If an IP Belongs to a CIDR Subnet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to know if an IP address is included in a CIDR subnet, you can use
the ``checkIp()`` method from :class:`Symfony\\Component\\HttpFoundation\\IpUtils`::
use Symfony\Component\HttpFoundation\IpUtils;
$ipv4 = '192.168.1.56';
$CIDRv4 = '192.168.1.0/16';
$isIpInCIDRv4 = IpUtils::checkIp($ipv4, $CIDRv4);
// $isIpInCIDRv4 = true
$ipv6 = '2001:db8:abcd:1234::1';
$CIDRv6 = '2001:db8:abcd::/48';
$isIpInCIDRv6 = IpUtils::checkIp($ipv6, $CIDRv6);
// $isIpInCIDRv6 = true
Check if an IP Belongs to a Private Subnet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to know if an IP address belongs to a private subnet, you can
use the ``isPrivateIp()`` method from the
:class:`Symfony\\Component\\HttpFoundation\\IpUtils` to do that::
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
Matching a Request Against a Set of Rules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The HttpFoundation component provides some matcher classes that allow you to
check if a given request meets certain conditions (e.g. it comes from some IP
address, it uses a certain HTTP method, etc.):
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\AttributesRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\ExpressionRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\HeaderRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\HostRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\IpsRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\IsJsonRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\MethodRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\PathRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\PortRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\QueryParameterRequestMatcher`
* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\SchemeRequestMatcher`
You can use them individually or combine them using the
:class:`Symfony\\Component\\HttpFoundation\\ChainRequestMatcher` class::
use Symfony\Component\HttpFoundation\ChainRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher;
use Symfony\Component\HttpFoundation\RequestMatcher\SchemeRequestMatcher;
// use only one criteria to match the request
$schemeMatcher = new SchemeRequestMatcher('https');
if ($schemeMatcher->matches($request)) {
// ...
}
// use a set of criteria to match the request
$matcher = new ChainRequestMatcher([
new HostRequestMatcher('example.com'),
new PathRequestMatcher('/admin'),
]);
if ($matcher->matches($request)) {
// ...
}
.. versionadded:: 7.1
The ``HeaderRequestMatcher`` and ``QueryParameterRequestMatcher`` were
introduced in Symfony 7.1.
Accessing other Data
~~~~~~~~~~~~~~~~~~~~
The ``Request`` class has many other methods that you can use to access the
request information. Have a look at
:class:`the Request API <Symfony\\Component\\HttpFoundation\\Request>`
for more information about them.
Overriding the Request
~~~~~~~~~~~~~~~~~~~~~~
The ``Request`` class should not be overridden as it is a data object that
represents an HTTP message. But when moving from a legacy system, adding
methods or changing some default behavior might help. In that case, register a
PHP callable that is able to create an instance of your ``Request`` class::
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();
.. _component-http-foundation-response:
Response
--------
A :class:`Symfony\\Component\\HttpFoundation\\Response` object holds all the
information that needs to be sent back to the client from a given request. The
constructor takes up to three arguments: the response content, the status
code, and an array of HTTP headers::
use Symfony\Component\HttpFoundation\Response;
$response = new Response(
'Content',
Response::HTTP_OK,
['content-type' => 'text/html']
);
This information can also be manipulated after the Response object creation::
$response->setContent('Hello World');
// the headers public attribute is a ResponseHeaderBag
$response->headers->set('Content-Type', 'text/plain');
$response->setStatusCode(Response::HTTP_NOT_FOUND);
When setting the ``Content-Type`` of the Response, you can set the charset,
but it is better to set it via the
:method:`Symfony\\Component\\HttpFoundation\\Response::setCharset` method::
$response->setCharset('ISO-8859-1');
Note that by default, Symfony assumes that your Responses are encoded in
UTF-8.
Sending the Response
~~~~~~~~~~~~~~~~~~~~
Before sending the Response, you can optionally call the
:method:`Symfony\\Component\\HttpFoundation\\Response::prepare` method to fix any
incompatibility with the HTTP specification (e.g. a wrong ``Content-Type`` header)::
$response->prepare($request);
Sending the response to the client is done by calling the method
:method:`Symfony\\Component\\HttpFoundation\\Response::send`::
$response->send();
The ``send()`` method takes an optional ``flush`` argument. If set to
``false``, functions like ``fastcgi_finish_request()`` or
``litespeed_finish_request()`` are not called. This is useful when debugging
your application to see which exceptions are thrown in listeners of the
:class:`Symfony\\Component\\HttpKernel\\Event\\TerminateEvent`. You can learn
more about it in
:ref:`the dedicated section about Kernel events <http-kernel-creating-listener>`.
Setting Cookies
~~~~~~~~~~~~~~~
The response cookies can be manipulated through the ``headers`` public
attribute::
use Symfony\Component\HttpFoundation\Cookie;
$response->headers->setCookie(Cookie::create('foo', 'bar'));
The
:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::setCookie`
method takes an instance of
:class:`Symfony\\Component\\HttpFoundation\\Cookie` as an argument.
You can clear a cookie via the
:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::clearCookie` method.
In addition to the ``Cookie::create()`` method, you can create a ``Cookie``
object from a raw header value using :method:`Symfony\\Component\\HttpFoundation\\Cookie::fromString`
method. You can also use the ``with*()`` methods to change some Cookie property (or
to build the entire Cookie using a fluent interface). Each ``with*()`` method returns
a new object with the modified property::
$cookie = Cookie::create('foo')
->withValue('bar')
->withExpires(strtotime('Fri, 20-May-2011 15:25:52 GMT'))
->withDomain('.example.com')
->withSecure(true);
It is possible to define partitioned cookies, also known as `CHIPS`_, by using the
:method:`Symfony\\Component\\HttpFoundation\\Cookie::withPartitioned` method::
$cookie = Cookie::create('foo')
->withValue('bar')
->withPartitioned();
// you can also set the partitioned argument to true when using the `create()` factory method
$cookie = Cookie::create('name', 'value', partitioned: true);
Managing the HTTP Cache
~~~~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\HttpFoundation\\Response` class has a rich set
of methods to manipulate the HTTP headers related to the cache:
* :method:`Symfony\\Component\\HttpFoundation\\Response::setPublic`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setPrivate`
* :method:`Symfony\\Component\\HttpFoundation\\Response::expire`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setMaxAge`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setSharedMaxAge`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setStaleIfError`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setStaleWhileRevalidate`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setTtl`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setClientTtl`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setEtag`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setVary`
.. note::
The methods :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`,
:method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified` and
:method:`Symfony\\Component\\HttpFoundation\\Response::setDate` accept any
object that implements ``\DateTimeInterface``, including immutable date objects.
The :method:`Symfony\\Component\\HttpFoundation\\Response::setCache` method
can be used to set the most commonly used cache information in one method
call::
$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',
]);
To check if the Response validators (``ETag``, ``Last-Modified``) match a
conditional value specified in the client Request, use the
:method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
method::
if ($response->isNotModified($request)) {
$response->send();
}
If the Response is not modified, it sets the status code to 304 and removes the
actual response content.
.. _redirect-response:
Redirecting the User
~~~~~~~~~~~~~~~~~~~~
To redirect the client to another URL, you can use the
:class:`Symfony\\Component\\HttpFoundation\\RedirectResponse` class::
use Symfony\Component\HttpFoundation\RedirectResponse;
$response = new RedirectResponse('http://example.com/');
.. _streaming-response:
Streaming a Response
~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` class allows
you to stream the Response back to the client. The response content can be
represented by a string iterable::
use Symfony\Component\HttpFoundation\StreamedResponse;
$chunks = ['Hello', ' World'];
$response = new StreamedResponse();
$response->setChunks($chunks);
$response->send();
For most complex use cases, the response content can be instead represented by
a PHP callable::
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::
The ``flush()`` function does not flush buffering. If ``ob_start()`` has
been called before or the ``output_buffering`` ``php.ini`` option is enabled,
you must call ``ob_flush()`` before ``flush()``.
Additionally, PHP isn't the only layer that can buffer output. Your web
server might also buffer based on its configuration. Some servers, such as
nginx, let you disable buffering at the config level or by adding a special HTTP
header in the response::
// disables FastCGI buffering in nginx only for this response
$response->headers->set('X-Accel-Buffering', 'no');
.. versionadded:: 7.3
Support for using string iterables was introduced in Symfony 7.3.
Streaming a JSON Response
~~~~~~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\HttpFoundation\\StreamedJsonResponse` allows you to
stream large JSON responses using PHP generators to keep the used resources low.
The class constructor expects an array which represents the JSON structure and
includes the list of contents to stream. In addition to PHP generators, which are
recommended to minimize memory usage, it also supports any kind of PHP Traversable
containing JSON serializable data::
use Symfony\Component\HttpFoundation\StreamedJsonResponse;
// any method or function returning a PHP Generator
function loadArticles(): \Generator {
yield ['title' => 'Article 1'];
yield ['title' => 'Article 2'];
yield ['title' => 'Article 3'];
};
$response = new StreamedJsonResponse(
// JSON structure with generators in which will be streamed as a list
[
'_embedded' => [
'articles' => loadArticles(),
],
],
);
When loading data via Doctrine, you can use the ``toIterable()`` method to
fetch results row by row and minimize resources consumption.
See the `Doctrine Batch processing`_ documentation for more::
public function __invoke(): Response
{
return new StreamedJsonResponse(
[
'_embedded' => [
'articles' => $this->loadArticles(),
],
],
);
}
public function loadArticles(): \Generator
{
// get the $entityManager somehow (e.g. via constructor injection)
$entityManager = ...
$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder->from(Article::class, 'article');
$queryBuilder->select('article.id')
->addSelect('article.title')
->addSelect('article.description');
return $queryBuilder->getQuery()->toIterable();
}
If you return a lot of data, consider calling the :phpfunction:`flush` function
after some specific item count to send the contents to the browser::
public function loadArticles(): \Generator
{
// ...
$count = 0;
foreach ($queryBuilder->getQuery()->toIterable() as $article) {
yield $article;
if (0 === ++$count % 100) {
flush();
}
}
}
Alternatively, you can also pass any iterable to ``StreamedJsonResponse``,
including generators::
public function loadArticles(): \Generator
{
yield ['title' => 'Article 1'];
yield ['title' => 'Article 2'];
yield ['title' => 'Article 3'];
}
public function __invoke(): Response
{
// ...
return new StreamedJsonResponse(loadArticles());
}
.. _component-http-foundation-sse:
Streaming Server-Sent Events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\HttpFoundation\\EventStreamResponse` class
allows you to implement `Server-Sent Events (SSE)`_, a standard for pushing
real-time updates from the server to the client over HTTP.
.. versionadded:: 7.3
The ``EventStreamResponse`` and ``ServerEvent`` classes were introduced in Symfony 7.3.
Basic usage with a generator::
use Symfony\Component\HttpFoundation\EventStreamResponse;
use Symfony\Component\HttpFoundation\ServerEvent;
$response = new EventStreamResponse(function (): iterable {
yield new ServerEvent('First message');
sleep(1);
yield new ServerEvent('Second message');
});
$response->send();
The ``EventStreamResponse`` automatically sets the required headers:
.. code-block:: text
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
The :class:`Symfony\\Component\\HttpFoundation\\ServerEvent` class represents an
individual SSE event following `the WHATWG SSE specification`_. It accepts the
following constructor arguments:
``data``
The event data (a string or an iterable for multi-line data).
``type``
The event type. Clients can listen for specific types using
``eventSource.addEventListener('type', ...)``.
``id``
The event ID. The browser sends this as the ``Last-Event-ID`` header when
reconnecting, allowing you to resume from where the client left off.
``retry``
The reconnection time in milliseconds. Tells the browser how long to wait
before reconnecting if the connection is lost.
``comment``
A comment line (prefixed with ``:`` in the SSE protocol). Useful for
keep-alive messages to prevent connection timeouts.
.. tip::
For usage in Symfony controllers with additional features like automatic
reconnection handling, see :ref:`controller-server-sent-events`.
.. warning::
SSE keeps HTTP connections open, consuming server resources for each client.
For applications with many concurrent connections, consider using
:doc:`Mercure </mercure>` instead, which uses a dedicated hub for efficient
connection management.
.. _component-http-foundation-serving-files:
Serving Files
~~~~~~~~~~~~~
When sending a file, you must add a ``Content-Disposition`` header to your
response. While creating this header for basic file downloads is straightforward,
using non-ASCII filenames is more involved. The
:method:`Symfony\\Component\\HttpFoundation\\HeaderUtils::makeDisposition`
abstracts the hard work behind a simple API::
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);
Alternatively, if you are serving a static file, you can use a
:class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`::
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$file = 'path/to/file.txt';
$response = new BinaryFileResponse($file);
The ``BinaryFileResponse`` will automatically handle ``Range`` and
``If-Range`` headers from the request. It also supports ``X-Sendfile``
(see `FrankenPHP X-Sendfile and X-Accel-Redirect headers`_,
`nginx X-Accel-Redirect header`_ and `Apache mod_xsendfile module`_). To make use
of it, you need to determine whether or not the ``X-Sendfile-Type`` header should
be trusted and call :method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
if it should::
BinaryFileResponse::trustXSendfileTypeHeader();
.. note::
The ``BinaryFileResponse`` will only handle ``X-Sendfile`` if the particular header is present.
For Apache, this is not the default case.
To add the header use the ``mod_headers`` Apache module and add the following to the Apache configuration:
.. code-block:: apache
<IfModule mod_xsendfile.c>
# This is already present somewhere...
XSendFile on
XSendFilePath ...some path...
# This needs to be added:
<IfModule mod_headers.c>
RequestHeader set X-Sendfile-Type X-Sendfile
</IfModule>
</IfModule>
With the ``BinaryFileResponse``, you can still set the ``Content-Type`` of the sent file,
or change its ``Content-Disposition``::
// ...
$response->headers->set('Content-Type', 'text/plain');
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'filename.txt'
);
It is possible to delete the file after the response is sent with the
:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::deleteFileAfterSend` method.
Please note that this will not work when the ``X-Sendfile`` header is set.
Alternatively, ``BinaryFileResponse`` supports instances of ``\SplTempFileObject``.
This is useful when you want to serve a file that has been created in memory
and that will be automatically deleted after the response is sent::
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$file = new \SplTempFileObject();
$file->fwrite('Hello World');
$file->rewind();
$response = new BinaryFileResponse($file);
.. versionadded:: 7.1
The support for ``\SplTempFileObject`` in ``BinaryFileResponse``
was introduced in Symfony 7.1.
If the size of the served file is unknown (e.g. because it's being generated dynamically,
or because a PHP stream filter is registered on it, etc.), you can pass a ``Stream``
instance to ``BinaryFileResponse``. This will disable ``Range`` and ``Content-Length``
handling, switching to chunked encoding instead::
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Stream;
$stream = new Stream('path/to/stream');
$response = new BinaryFileResponse($stream);
.. note::
If you *just* created the file during this same request, the file *may* be sent
without any content. This may be due to cached file stats that return zero for
the size of the file. To fix this issue, call ``clearstatcache(true, $file)``
with the path to the binary file.
.. _component-http-foundation-json-response:
Creating a JSON Response
~~~~~~~~~~~~~~~~~~~~~~~~
Any type of response can be created via the
:class:`Symfony\\Component\\HttpFoundation\\Response` class by setting the
right content and headers. A JSON response might look like this::
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
$response->setContent(json_encode([
'data' => 123,
]));
$response->headers->set('Content-Type', 'application/json');
There is also a helpful :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`
class, which can make this even easier::
use Symfony\Component\HttpFoundation\JsonResponse;
// if you know the data to send when creating the response
$response = new JsonResponse(['data' => 123]);
// if you don't know the data to send or if you want to customize the encoding options
$response = new JsonResponse();
// ...
// configure any custom encoding options (if needed, it must be called before "setData()")
//$response->setEncodingOptions(JsonResponse::DEFAULT_ENCODING_OPTIONS | \JSON_PRESERVE_ZERO_FRACTION);
$response->setData(['data' => 123]);
// if the data to send is already encoded in JSON
$response = JsonResponse::fromJsonString('{ "data": 123 }');
The ``JsonResponse`` class sets the ``Content-Type`` header to
``application/json`` and encodes your data to JSON when needed.
.. danger::
To avoid XSSI `JSON Hijacking`_, you should pass an associative array
as the outermost array to ``JsonResponse`` and not an indexed array so
that the final result is an object (e.g. ``{"object": "not inside an array"}``)
instead of an array (e.g. ``[{"object": "inside an array"}]``). Read
the `OWASP guidelines`_ for more information.
Only methods that respond to GET requests are vulnerable to XSSI 'JSON Hijacking'.
Methods responding to POST requests only remain unaffected.
.. warning::
The ``JsonResponse`` constructor exhibits non-standard JSON encoding behavior
and will treat ``null`` as an empty object if passed as a constructor argument,
despite null being a `valid JSON top-level value`_.
This behavior cannot be changed without backwards-compatibility concerns, but
it's possible to call ``setData`` and pass the value there to opt-out of the
behavior.
JSONP Callback
~~~~~~~~~~~~~~
If you're using JSONP, you can set the callback function that the data should
be passed to::
$response->setCallback('handleResponse');
In this case, the ``Content-Type`` header will be ``text/javascript`` and
the response content will look like this:
.. code-block:: javascript
handleResponse({'data': 123});
Session
-------
The session information is in its own document: :doc:`/session`.
Safe Content Preference
-----------------------
Some web sites have a "safe" mode to assist those who don't want to be exposed
to content to which they might object. The `RFC 8674`_ specification defines a
way for user agents to ask for safe content to a server.
The specification does not define what content might be considered objectionable,
so the concept of "safe" is not precisely defined. Rather, the term is interpreted
by the server and within the scope of each web site that chooses to act upon this information.
Symfony offers two methods to interact with this preference:
* :method:`Symfony\\Component\\HttpFoundation\\Request::preferSafeContent`;
* :method:`Symfony\\Component\\HttpFoundation\\Response::setContentSafe`;
The following example shows how to detect if the user agent prefers "safe" content::
if ($request->preferSafeContent()) {
$response = new Response($alternativeContent);
// this informs the user we respected their preferences
$response->setContentSafe();
return $response;
Generating Relative and Absolute URLs
-------------------------------------
Generating absolute and relative URLs for a given path is a common need
in some applications. In Twig templates you can use the
:ref:`absolute_url() <reference-twig-function-absolute-url>` and
:ref:`relative_path() <reference-twig-function-relative-path>` functions to do that.
The :class:`Symfony\\Component\\HttpFoundation\\UrlHelper` class provides the
same functionality for PHP code via the ``getAbsoluteUrl()`` and ``getRelativePath()``
methods. You can inject this as a service anywhere in your application::
// 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()),
];
}
}
Learn More
----------
.. toctree::
:maxdepth: 1
:glob:
/controller
/controller/*
/session
/http_cache/*
.. _`FrankenPHP X-Sendfile and X-Accel-Redirect headers`: https://frankenphp.dev/docs/x-sendfile/
.. _`nginx X-Accel-Redirect header`: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
.. _`Apache mod_xsendfile module`: https://github.com/nmaier/mod_xsendfile
.. _`JSON Hijacking`: https://haacked.com/archive/2009/06/25/json-hijacking.aspx/
.. _`valid JSON top-level value`: https://www.json.org/json-en.html
.. _OWASP guidelines: https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside
.. _RFC 8674: https://tools.ietf.org/html/rfc8674
.. _Doctrine Batch processing: https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/batch-processing.html#iterating-results
.. _`CHIPS`: https://developer.mozilla.org/en-US/docs/Web/Privacy/Partitioned_cookies
.. _`Server-Sent Events (SSE)`: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
.. _`the WHATWG SSE specification`: https://html.spec.whatwg.org/multipage/server-sent-events.html