Skip to content

Latest commit

 

History

History
779 lines (552 loc) · 26.6 KB

http_foundation.rst

File metadata and controls

779 lines (552 loc) · 26.6 KB
.. index::
   single: HTTP
   single: HttpFoundation
   single: Components; HttpFoundation

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

$ composer require symfony/http-foundation
.. 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.

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

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 sub-class of), which is a data holder class:

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::filter`
Filters the parameter by using the PHP :phpfunction:`filter_var` function.

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. So you can get the foo parameter and you will get back an array with a bar element:

// the query string is '?foo[bar]=baz'

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

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

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

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 a JSON string sent to the application by a remote service using the HTTP POST method.

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:https://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 :method:`Symfony\\Component\\HttpFoundation\\Request::getSession` method; 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"'

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::'

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

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

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')
    ->withExpiresTime(strtotime('Fri, 20-May-2011 15:25:52 GMT'))
    ->withDomain('.example.com')
    ->withSecure(true);
.. versionadded:: 5.1

    The ``with*()`` methods were introduced in Symfony 5.1.

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:

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,
    'immutable'        => true,
    'last_modified'    => new \DateTime(),
    'etag'             => 'abcdef'
]);
.. versionadded:: 5.1

    The ``must_revalidate``, ``no_cache``, ``no_store``, ``no_transform`` and
    ``proxy_revalidate`` directives were introduced in Symfony 5.1.

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.

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:https://example.com/');

Streaming a Response

The :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` class allows you to stream the Response back to the client. The response content is represented by a PHP callable instead of a string:

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

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

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 for nginx and Apache). 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:

<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 request 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.

If the size of the served file is unknown (e.g. because it's being generated on the fly, 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.

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 when creating the response
$response = new JsonResponse();
// ...
$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.

Caution!

To avoid XSSI JSON Hijacking, you should pass an associative array as the outer-most 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.

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:

handleResponse({'data': 123});

Session

The session information is in its own document: :doc:`/components/http_foundation/sessions`.

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:

.. versionadded:: 5.1

    The ``preferSafeContent()`` and ``setContentSafe()`` methods were introduced
    in Symfony 5.1.

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

Learn More

.. toctree::
    :maxdepth: 1
    :glob:

    /components/http_foundation/*
    /controller
    /controller/*
    /session/*
    /http_cache/*