mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-24 00:32:14 +01:00
1160 lines
41 KiB
ReStructuredText
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
|