diff --git a/_images/quick_tour/no_routes_page.png b/_images/quick_tour/no_routes_page.png index 8c8c4d508d1..382950b6ef5 100644 Binary files a/_images/quick_tour/no_routes_page.png and b/_images/quick_tour/no_routes_page.png differ diff --git a/bundles.rst b/bundles.rst index cdef74b5c87..c7a836481e5 100644 --- a/bundles.rst +++ b/bundles.rst @@ -85,8 +85,7 @@ Bundle Directory Structure The directory structure of a bundle is meant to help to keep code consistent between all Symfony bundles. It follows a set of conventions, but is flexible -to be adjusted if needed. Take a look at AcmeDemoBundle, as it contains some -of the most common elements of a bundle: +to be adjusted if needed: ``Controller/`` Contains the controllers of the bundle (e.g. ``RandomController.php``). @@ -118,6 +117,35 @@ database, create and validate forms, create translations for your application, write tests and much more. Each of these has their own place and role within the bundle. +Overridding the Bundle Directory Structure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some of the bundle directories are conventions that can be overridden if needed. +For example, the **public directory**, which by default is located at +``/Resources/public/``, can be changed by defining the +``getPublicDir()`` method in the bundle class:: + + // src/Acme/TestBundle/AcmeTestBundle.php + namespace App\Acme\TestBundle; + + use Symfony\Component\HttpKernel\Bundle\Bundle; + + class AcmeTestBundle extends Bundle + { + // ... + + // the returned value must be relative to the bundle root directory + // (public dir is now /public/ instead of /Resources/public/) + public function getPublicDir(): string + { + return 'public/'; + } + } + +.. versionadded:: 4.4 + + The ``getPublicDir()`` method was introduced in Symfony 4.4. + Learn more ---------- diff --git a/cache.rst b/cache.rst index 0babe192897..21bea9e120c 100644 --- a/cache.rst +++ b/cache.rst @@ -234,7 +234,7 @@ You can also create more customized pools: - + @@ -390,6 +390,10 @@ If an error happens when storing an item in a pool, Symfony stores it in the other pools and no exception is thrown. Later, when the item is retrieved, Symfony stores the item automatically in all the missing pools. +.. versionadded:: 4.4 + + Support for configuring a chain using ``framework.cache.pools`` was introduced in Symfony 4.4. + .. configuration-block:: .. code-block:: yaml @@ -399,23 +403,11 @@ Symfony stores the item automatically in all the missing pools. cache: pools: my_cache_pool: - adapter: cache.adapter.psr6 - provider: app.my_cache_chain_adapter - cache.my_redis: - adapter: cache.adapter.redis - provider: 'redis://user:password@example.com' - cache.apcu: - adapter: cache.adapter.apcu - cache.array: - adapter: cache.adapter.array - - - services: - app.my_cache_chain_adapter: - class: Symfony\Component\Cache\Adapter\ChainAdapter - arguments: - - ['@cache.array', '@cache.apcu', '@cache.my_redis'] - - 31536000 # One year + default_lifetime: 31536000 # One year + adapters: + - cache.adapter.array + - cache.adapter.apcu + - {name: cache.adapter.redis, provider: 'redis://user:password@example.com'} .. code-block:: xml @@ -429,23 +421,13 @@ Symfony stores the item automatically in all the missing pools. - - - - + + + + + - - - - - - - - - 31536000 - - .. code-block:: php @@ -455,39 +437,17 @@ Symfony stores the item automatically in all the missing pools. 'cache' => [ 'pools' => [ 'my_cache_pool' => [ - 'adapter' => 'cache.adapter.psr6', - 'provider' => 'app.my_cache_chain_adapter', - ], - 'cache.my_redis' => [ - 'adapter' => 'cache.adapter.redis', - 'provider' => 'redis://user:password@example.com', - ], - 'cache.apcu' => [ - 'adapter' => 'cache.adapter.apcu', - ], - 'cache.array' => [ - 'adapter' => 'cache.adapter.array', + 'default_lifetime' => 31536000, // One year + 'adapters' => [ + 'cache.adapter.array', + 'cache.adapter.apcu', + ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'], + ], ], ], ], ]); - $container->getDefinition('app.my_cache_chain_adapter', \Symfony\Component\Cache\Adapter\ChainAdapter::class) - ->addArgument([ - new Reference('cache.array'), - new Reference('cache.apcu'), - new Reference('cache.my_redis'), - ]) - ->addArgument(31536000); - -.. note:: - - In this configuration the ``my_cache_pool`` pool is using the ``cache.adapter.psr6`` - adapter and the ``app.my_cache_chain_adapter`` service as a provider. That is - because ``ChainAdapter`` does not support the ``cache.pool`` tag. So it is decorated - with the ``ProxyAdapter``. - - Using Cache Tags ---------------- diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index 608fd8e44d2..53b28c58466 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -106,6 +106,10 @@ name of your service group:: The option to define multiple servers in a single DSN was introduced in Symfony 4.2. +.. versionadded:: 4.4 + + Redis Sentinel support was introduced in Symfony 4.4. + .. note:: See the :class:`Symfony\\Component\\Cache\\Traits\\RedisTrait` for more options diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst index a5076cf22cb..c1c524cbef6 100644 --- a/components/console/helpers/progressbar.rst +++ b/components/console/helpers/progressbar.rst @@ -50,10 +50,16 @@ you can also set the current progress by calling the If your platform doesn't support ANSI codes, updates to the progress bar are added as new lines. To prevent the output from being flooded, - adjust the + use the method :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::minSecondsBetweenRedraws` + to limit the number of redraws and the method :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setRedrawFrequency` - accordingly. By default, when using a ``max``, the redraw frequency - is set to *10%* of your ``max``. + to redraw every N iterations. By default, redraw frequency is + **100ms** or **10%** of your ``max``. + + .. versionadded:: 4.4 + + The ``minSecondsBetweenRedraws()`` and ``maxSecondsBetweenRedraws()`` + methods were introduced in Symfony 4.4. If you don't know the exact number of steps in advance, set it to a reasonable value and then call the ``setMaxSteps()`` method to update it as needed:: @@ -289,17 +295,19 @@ to display it can be customized:: .. caution:: - For performance reasons, be careful if you set the total number of steps - to a high number. For example, if you're iterating over a large number of - items, consider setting the redraw frequency to a higher value by calling - :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setRedrawFrequency`, - so it updates on only some iterations:: + For performance reasons, Symfony redraws screen every 100ms. If this is too + fast or to slow for your application, use the methods + :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::minSecondsBetweenRedraws` and + :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::maxSecondsBetweenRedraws`:: $progressBar = new ProgressBar($output, 50000); $progressBar->start(); - // update every 100 iterations + // this redraws the screen every 100 iterations, but sets additional limits: + // don't redraw slower than 200ms (0.2) or faster than 100ms (0.1) $progressBar->setRedrawFrequency(100); + $progressBar->maxSecondsBetweenRedraws(0.2); + $progressBar->minSecondsBetweenRedraws(0.1); $i = 0; while ($i++ < 50000) { @@ -308,6 +316,11 @@ to display it can be customized:: $progressBar->advance(); } + .. versionadded:: 4.4 + + The ``minSecondsBetweenRedraws`` and ``maxSecondsBetweenRedraws()`` methods + were introduced in Symfony 4.4. + Custom Placeholders ~~~~~~~~~~~~~~~~~~~ diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index 6a699db89ed..2869b82560b 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -218,6 +218,30 @@ provide a callback function to dynamically generate suggestions:: The ``setAutocompleterCallback()`` method was introduced in Symfony 4.3. +Do not Trim the Answer +~~~~~~~~~~~~~~~~~~~~~~ + +You can also specify if you want to not trim the answer by setting it directly with +:method:`Symfony\\Component\\Console\\Question\\Question::setTrimmable`:: + + use Symfony\Component\Console\Question\Question; + + // ... + public function execute(InputInterface $input, OutputInterface $output) + { + // ... + $helper = $this->getHelper('question'); + + $question = new Question('What is the name of the child?'); + $question->setTrimmable(false); + // if the users inputs 'elsa ' it will not be trimmed and you will get 'elsa ' as value + $name = $helper->ask($input, $output, $question); + } + +.. versionadded:: 4.4 + + The ``setTrimmable()`` method was introduced in Symfony 4.4. + Hiding the User's Response ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/css_selector.rst b/components/css_selector.rst index 2c15ef432dd..8bed3daa4db 100644 --- a/components/css_selector.rst +++ b/components/css_selector.rst @@ -91,11 +91,16 @@ Pseudo-elements (``:before``, ``:after``, ``:first-line``, ``:first-letter``) are not supported because they select portions of text rather than elements. -Several pseudo-classes are not yet supported: +Pseudo-classes are partially supported: -* ``*:first-of-type``, ``*:last-of-type``, ``*:nth-of-type``, - ``*:nth-last-of-type``, ``*:only-of-type``. (These work with an element - name (e.g. ``li:first-of-type``) but not with ``*``). +* Not supported: ``*:first-of-type``, ``*:last-of-type``, ``*:nth-of-type`` and + ``*:nth-last-of-type`` (all these work with an element name (e.g. + ``li:first-of-type``) but not with the ``*`` selector). +* Supported: ``*:only-of-type``. + +.. versionadded:: 4.4 + + The support for ``*:only-of-type`` was introduced in Symfony 4.4. Learn more ---------- diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst index cdf2d6faa0b..71c61a0247a 100644 --- a/components/dom_crawler.rst +++ b/components/dom_crawler.rst @@ -165,6 +165,14 @@ Namespaces can be explicitly registered with the $crawler->registerNamespace('m', 'http://search.yahoo.com/mrss/'); $crawler = $crawler->filterXPath('//m:group//yt:aspectRatio'); +Verify if the current node matches a selector:: + + $crawler->matches('p.lorem'); + +.. versionadded:: 4.4 + + The ``matches()`` method was introduced in Symfony 4.4. + Node Traversing ~~~~~~~~~~~~~~~ @@ -195,6 +203,14 @@ Get all the direct child nodes matching a CSS selector:: $crawler->filter('body')->children('p.lorem'); +Get the first parent (heading toward the document root) of the element that matches the provided selector:: + + $crawler->closest('p.lorem'); + +.. versionadded:: 4.4 + + The ``closest()`` method was introduced in Symfony 4.4. + .. note:: All the traversal methods return a new :class:`Symfony\\Component\\DomCrawler\\Crawler` @@ -216,10 +232,18 @@ Access the value of the first node of the current selection:: // avoid the exception passing an argument that text() returns when node does not exist $message = $crawler->filterXPath('//body/p')->text('Default text content'); + // pass TRUE as the second argument of text() to remove all extra white spaces, including + // the internal ones (e.g. " foo\n bar baz \n " is returned as "foo bar baz") + $crawler->filterXPath('//body/p')->text('Default text content', true); + .. versionadded:: 4.3 The default argument of ``text()`` was introduced in Symfony 4.3. +.. versionadded:: 4.4 + + The option to trim white spaces in ``text()`` was introduced in Symfony 4.4. + Access the attribute value of the first node of the current selection:: $class = $crawler->filterXPath('//body/p')->attr('class'); @@ -337,6 +361,15 @@ and :phpclass:`DOMNode` objects:: The default argument of ``html()`` was introduced in Symfony 4.3. + Or you can get the outer HTML of the first node using + :method:`Symfony\\Component\\DomCrawler\\Crawler::outerHtml`:: + + $html = $crawler->outerHtml(); + + .. versionadded:: 4.4 + + The ``outerHtml()`` method was introduced in Symfony 4.4. + Expression Evaluation ~~~~~~~~~~~~~~~~~~~~~ @@ -485,8 +518,12 @@ The :class:`Symfony\\Component\\DomCrawler\\Form` object has lots of very useful methods for working with forms:: $uri = $form->getUri(); - $method = $form->getMethod(); + $name = $form->getName(); + +.. versionadded:: 4.4 + + The ``getName()`` method was introduced in Symfony 4.4. The :method:`Symfony\\Component\\DomCrawler\\Form::getUri` method does more than just return the ``action`` attribute of the form. If the form method diff --git a/components/dotenv.rst b/components/dotenv.rst index 333e73b6ec7..08141e0ea28 100644 --- a/components/dotenv.rst +++ b/components/dotenv.rst @@ -149,6 +149,17 @@ Use environment variables in values by prefixing variables with ``$``: its value will depend on the ``DB_USER`` value defined in other files instead of the value defined in this file. +Define a default value in case the environment variable is not set: + +.. code-block:: terminal + + DB_USER= + DB_PASS=${DB_USER:-root}pass # results in DB_PASS=rootpass + +.. versionadded:: 4.4 + + The support for default values has been introduced in Symfony 4.4. + Embed commands via ``$()`` (not supported on Windows): .. code-block:: terminal diff --git a/components/error_handler.rst b/components/error_handler.rst new file mode 100644 index 00000000000..f5b1e7b9c11 --- /dev/null +++ b/components/error_handler.rst @@ -0,0 +1,170 @@ +.. index:: + single: Debug + single: Error + single: Exception + single: Components; ErrorHandler + +The ErrorHandler Component +========================== + + The ErrorHandler component provides tools to manage errors and ease + debugging PHP code. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/error-handler + +.. include:: /components/require_autoload.rst.inc + +Usage +----- + +The ErrorHandler component provides several tools to help you debug PHP code: + +* An **error handler** that turns PHP errors into exceptions; +* An **exception handler** that turns uncaught PHP exceptions into nice PHP responses; +* A **debug class loader** that provides better errors when a class is not found. + +Call this method (e.g. in your :ref:`front controller `) +to enable all these features in your application:: + + // public/index.php + use Symfony\Component\ErrorHandler\Debug; + + if ($_SERVER['APP_DEBUG']) { + Debug::enable(); + } + + // ... + +Keep reading this article to learn more about each feature, including how to +enable each of them separately. + +.. caution:: + + You should never enable the debug tools, except for the error handler, in a + production environment as they might disclose sensitive information to the user. + +Turning PHP Errors into Exceptions +---------------------------------- + +The :class:`Symfony\\Component\\ErrorHandler\\ErrorHandler` class catches PHP +errors and turns them into PHP's :phpclass:`ErrorException` objects, except for +fatal PHP errors, which are turned into Symfony's +:class:`Symfony\\Component\\ErrorHandler\\Exception\\FatalErrorException` objects. + +If the application uses the FrameworkBundle, this error handler is enabled by +default in the :ref:`production environment ` +because it generates better error logs. + +Use the following code (e.g. in your :ref:`front controller `) +to enable this error handler:: + + use Symfony\Component\ErrorHandler\ErrorHandler; + + ErrorHandler::register(); + +.. tip:: + + If you want to get even better exception pages, install the + :doc:`ErrorRenderer component ` too. + +Catching PHP Function Errors and Turning Them into Exceptions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Consider the following example:: + + $data = json_decode(file_get_contents($filename), true); + $data['read_at'] = date($datetimeFormat); + file_put_contents($filename, json_encode($data)); + +Most PHP core functions were written before exception handling was introduced, +so they return ``false`` or ``null`` in case of error instead of throwing an +exception. That's why you need to add something like these to check for errors:: + + $content = @file_get_contents($filename); + if (false === $content) { + throw new \RuntimeException('Could not load file.'); + } + + // since PHP 7.3 json_decode() defines an option to throw JSON_THROW_ON_ERROR + // but you need to enable that option explicitly + $data = @json_decode($content, true); + if (null === $data) { + throw new \RuntimeException('File does not contain valid JSON.'); + } + + $datetime = @date($datetimeFormat); + if (false === $datetime) { + throw new \RuntimeException('Invalid datetime format.'); + } + +To simplify this code, the :class:`Symfony\\Component\\ErrorHandler\\ErrorHandler` +class provides a :method:`Symfony\\Component\\ErrorHandler\\ErrorHandler::call` +method that throws an exception automatically when a PHP error occurs:: + + $content = ErrorHandler::call('file_get_contents', $filename); + +The first argument of ``call()`` is the name of the PHP function to execute and +the rest of arguments are passed to the PHP function. The result of the PHP +function is returned as the result of ``call()``. + +You can pass any PHP callable as the first argument of ``call()``, so you can +wrap several function calls inside an anonymous function:: + + $data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) { + // if any code executed inside this anonymous function fails, a PHP exception + // will be thrown, even if the code uses the '@' PHP silence operator + $data = json_decode(file_get_contents($filename), true); + $data['read_at'] = date($datetimeFormat); + file_put_contents($filename, json_encode($data)); + + return $data; + }); + +Debugging Uncaught PHP Exceptions +--------------------------------- + +The :class:`Symfony\\Component\\ErrorHandler\\ExceptionHandler` class catches +uncaught PHP exceptions and turns them into a nice response, so you can debug +them. It is useful in :ref:`debug mode ` to replace the default +PHP/XDebug output with something prettier and more useful. + +If the :doc:`HttpFoundation component ` is +available, the handler returns a Symfony's +:class:`Symfony\\Component\\HttpFoundation\\Response` object. Otherwise, it returns +a generic PHP response. + +Use the following code (e.g. in your :ref:`front controller `) +to enable this exception handler:: + + use Symfony\Component\ErrorHandler\ExceptionHandler; + + ExceptionHandler::register(); + +.. tip:: + + If you want to get even better exception pages, install the + :doc:`ErrorRenderer component ` too. + +.. _component-debug-class-loader: + +Class Loading Debugger +---------------------- + +The :class:`Symfony\\Component\\ErrorHandler\\DebugClassLoader` class throws +more useful exceptions when a class isn't found by the registered autoloaders +(e.g. looks for typos in the class names and suggest the right class name). + +In practice, this debugger looks for all registered autoloaders that implement a +``findFile()`` method and replaces them by its own method to find class files. + +Use the following code (e.g. in your :ref:`front controller `) +to enable this class loading debugger:: + + use Symfony\Component\ErrorHandler\DebugClassLoader; + + DebugClassLoader::enable(); diff --git a/components/error_renderer.rst b/components/error_renderer.rst new file mode 100644 index 00000000000..607935aa6bb --- /dev/null +++ b/components/error_renderer.rst @@ -0,0 +1,159 @@ +.. index:: + single: Error + single: Exception + single: Components; ErrorRenderer + +The ErrorRenderer Component +=========================== + + The ErrorRenderer component converts PHP errors and exceptions into other + formats such as JSON and HTML and renders them. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/error-renderer + +.. include:: /components/require_autoload.rst.inc + +Usage +----- + +The ErrorRenderer component provides several renderers to convert PHP errors and +exceptions into other formats such as JSON and HTML easier to debug when working +with HTTP applications:: + + use Symfony\Component\ErrorRenderer\ErrorRenderer; + use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; + use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer; + use Symfony\Component\ErrorRenderer\Exception\FlattenException; + use Symfony\Component\HttpFoundation\Response; + + $renderers = [ + new HtmlErrorRenderer(), + new JsonErrorRenderer(), + // ... + ]; + $errorRenderer = new ErrorRenderer($renderers); + + try { + // ... + } catch (\Throwable $e) { + $e = FlattenException::createFromThrowable($e); + + return new Response($errorRenderer->render($e, 'json'), 500, ['Content-Type' => 'application/json']); + } + +Built-in Error Renderers +------------------------ + +This component provides error renderers for the most common needs: + + * :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\HtmlErrorRenderer` + renders errors in HTML format; + * :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\JsonErrorRenderer` + renders errors in JSON format and it's compliant with the `RFC 7807`_ standard; + * :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\XmlErrorRenderer` + renders errors in XML and Atom formats. It's compliant with the `RFC 7807`_ + standard; + * :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\TxtErrorRenderer` + renders errors in plain text format. + +Adding a Custom Error Renderer +------------------------------ + +Error renderers are PHP classes that implement the +:class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\ErrorRendererInterface`. +For example, if you need to render errors in `JSON-LD format`_, create this +class anywhere in your project:: + + namespace App\ErrorRenderer; + + use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; + use Symfony\Component\ErrorRenderer\Exception\FlattenException; + + class JsonLdErrorRenderer implements ErrorRendererInterface + { + private $debug; + + public function __construct(bool $debug = true) + { + $this->debug = $debug; + } + + public static function getFormat(): string + { + return 'jsonld'; + } + + public function render(FlattenException $exception): string + { + $content = [ + '@id' => 'https://example.com', + '@type' => 'error', + '@context' => [ + 'title' => $exception->getTitle(), + 'code' => $exception->getStatusCode(), + 'message' => $exception->getMessage(), + ], + ]; + + if ($this->debug) { + $content['@context']['exceptions'] = $exception->toArray(); + } + + return (string) json_encode($content); + } + } + +.. tip:: + + If the ``getFormat()`` method of your error renderer matches one of formats + supported by the built-in renderers, the built-in renderer is replaced by + your custom renderer. + +To enable the new error renderer in the application, +:ref:`register it as a service ` and +:doc:`tag it ` with the ``error_renderer.renderer`` +tag. + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\ErrorRenderer\JsonLdErrorRenderer: + arguments: ['%kernel.debug%'] + tags: ['error_renderer.renderer'] + + .. code-block:: xml + + + + + + + + %kernel.debug% + + + + + + .. code-block:: php + + // config/services.php + use App\ErrorRenderer\JsonLdErrorRenderer; + + $container->register(JsonLdErrorRenderer::class) + ->setArguments([$container->getParameter('kernel.debug')]); + ->addTag('error_renderer.renderer'); + +.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807 +.. _`JSON-LD format`: https://en.wikipedia.org/wiki/JSON-LD diff --git a/components/expression_language/syntax.rst b/components/expression_language/syntax.rst index 147782b4c9f..5b7ee72ec7a 100644 --- a/components/expression_language/syntax.rst +++ b/components/expression_language/syntax.rst @@ -19,6 +19,11 @@ The component supports: * **hashes** - using JSON-like notation (e.g. ``{ foo: 'bar' }``) * **booleans** - ``true`` and ``false`` * **null** - ``null`` +* **exponential** - also known as scientific (e.g. ``1.99E+3`` or ``1e-2``) + + .. versionadded:: 4.4 + + The ``exponential`` literal was introduced in Symfony 4.4. .. caution:: diff --git a/components/http_client.rst b/components/http_client.rst index e5d88cc004e..1217c6f8606 100644 --- a/components/http_client.rst +++ b/components/http_client.rst @@ -11,8 +11,7 @@ The HttpClient Component .. versionadded:: 4.3 - The HttpClient component was introduced in Symfony 4.3 and it's still - considered an :doc:`experimental feature `. + The HttpClient component was introduced in Symfony 4.3. Installation ------------ @@ -129,16 +128,20 @@ The HTTP client supports different authentication mechanisms. They can be defined globally when creating the client (to apply it to all requests) and to each request (which overrides any global authentication):: - // Use the same authentication for all requests - $client = HttpClient::create([ - // HTTP Basic authentication with only the username and not a password + // Use the same authentication for all requests to https://example.com/ + $client = HttpClient::createForBaseUri('https://example.com/', [ + // HTTP Basic authentication (there are multiple ways of configuring it) 'auth_basic' => ['the-username'], - - // HTTP Basic authentication with a username and a password 'auth_basic' => ['the-username', 'the-password'], + 'auth_basic' => 'the-username:the-password', // HTTP Bearer authentication (also called token authentication) 'auth_bearer' => 'the-bearer-token', + + // Microsoft NTLM authentication (there are multiple ways of configuring it) + 'auth_ntlm' => ['the-username'], + 'auth_ntlm' => ['the-username', 'the-password'], + 'auth_ntlm' => 'the-username:the-password', ]); $response = $client->request('GET', 'https://...', [ @@ -148,6 +151,16 @@ each request (which overrides any global authentication):: // ... ]); +.. note:: + + The NTLM authentication mechanism requires using the cURL transport. + By using ``HttpClient::createForBaseUri()``, we ensure that the auth credentials + won't be sent to any other hosts than https://example.com/. + +.. versionadded:: 4.4 + + The ``auth_ntlm`` option and the ``HttpClient::createForBaseUri()`` method were introduced in Symfony 4.4. + Query String Parameters ~~~~~~~~~~~~~~~~~~~~~~~ @@ -333,6 +346,9 @@ following methods:: // casts the response JSON contents to a PHP array $content = $response->toArray(); + // casts the response content to a PHP stream resource + $content = $response->toStream(); + // cancels the request/response $response->cancel(); @@ -345,6 +361,10 @@ following methods:: // returns detailed logs about the requests and responses of the HTTP transaction $httpLogs = $response->getInfo('debug'); +.. versionadded:: 4.4 + + The ``toStream()`` method was introduced in Symfony 4.4. + .. note:: ``$response->getInfo()`` is non-blocking: it returns *live* information @@ -360,10 +380,7 @@ Call the ``stream()`` method of the HTTP client to get *chunks* of the response sequentially instead of waiting for the entire response:: $url = 'https://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-desktop-amd64.iso'; - $response = $client->request('GET', $url, [ - // optional: if you don't want to buffer the response in memory - 'buffer' => false, - ]); + $response = $client->request('GET', $url); // Responses are lazy: this code is executed as soon as headers are received if (200 !== $response->getStatusCode()) { @@ -377,6 +394,14 @@ response sequentially instead of waiting for the entire response:: fwrite($fileHandler, $chunk->getContent()); } +.. note:: + + By default, ``text/*``, JSON and XML response bodies are buffered in a local + ``php://temp`` stream. You can control this behavior by using the ``buffer`` + option: set it to ``true``/``false`` to enable/disable buffering, or to a + closure that should return the same based on the response headers it receives + as argument. + Canceling Responses ~~~~~~~~~~~~~~~~~~~ @@ -399,6 +424,13 @@ Or throw an exception from a progress callback:: The exception will be wrapped in an instance of ``TransportExceptionInterface`` and will abort the request. +In case the response was canceled using ``$response->cancel()``, +``$response->getInfo('canceled')`` will return ``true``. + +.. versionadded:: 4.4 + + The possibility to get information about canceled or not was introduced in Symfony 4.4. + Handling Exceptions ~~~~~~~~~~~~~~~~~~~ @@ -516,6 +548,8 @@ response and get remaining contents that might come back in a new timeout, etc. is idle*. Big responses can last as long as needed to complete, provided they remain active during the transfer and never pause for longer than specified. + Use the ``max_duration`` option to limit the time a full request/response can last. + Dealing with Network Errors ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -641,15 +675,15 @@ or if it matches the ``https://api.github.com/`` base URI. Interoperability ---------------- -The component is interoperable with two different abstractions for HTTP clients: -`Symfony Contracts`_ and `PSR-18`_. If your application uses libraries that need -any of them, the component is compatible with both. They also benefit from -:ref:`autowiring aliases ` when the -:ref:`framework bundle ` is used. +The component is interoperable with four different abstractions for HTTP +clients: `Symfony Contracts`_, `PSR-18`_, `HTTPlug`_ v1/v2 and native PHP streams. +If your application uses libraries that need any of them, the component is compatible +with all of them. They also benefit from :ref:`autowiring aliases ` +when the :ref:`framework bundle ` is used. If you are writing or maintaining a library that makes HTTP requests, you can decouple it from any specific HTTP client implementations by coding against -either Symfony Contracts (recommended) or PSR-18. +either Symfony Contracts (recommended), PSR-18 or HTTPlug v2. Symfony Contracts ~~~~~~~~~~~~~~~~~ @@ -676,17 +710,19 @@ interface you need to code against when a client is needed:: All request options mentioned above (e.g. timeout management) are also defined in the wordings of the interface, so that any compliant implementations (like this component) is guaranteed to provide them. That's a major difference with -the PSR-18 abstraction, which provides none related to the transport itself. +the other abstractions, which provide none related to the transport itself. Another major feature covered by the Symfony Contracts is async/multiplexing, as described in the previous sections. -PSR-18 -~~~~~~ +PSR-18 and PSR-17 +~~~~~~~~~~~~~~~~~ This component implements the `PSR-18`_ (HTTP Client) specifications via the :class:`Symfony\\Component\\HttpClient\\Psr18Client` class, which is an adapter to turn a Symfony ``HttpClientInterface`` into a PSR-18 ``ClientInterface``. +This class also implements the relevant methods of `PSR-17`_ to ease creating +request objects. To use it, you need the ``psr/http-client`` package and a `PSR-17`_ implementation: @@ -699,20 +735,147 @@ To use it, you need the ``psr/http-client`` package and a `PSR-17`_ implementati # with autowiring aliases provided by Symfony Flex $ composer require nyholm/psr7 + # alternatively, install the php-http/discovery package to auto-discover + # any already installed implementations from common vendors: + # composer require php-http/discovery + Now you can make HTTP requests with the PSR-18 client as follows:: - use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Component\HttpClient\Psr18Client; - $psr17Factory = new Psr17Factory(); - $psr18Client = new Psr18Client(); + $client = new Psr18Client(); $url = 'https://symfony.com/versions.json'; - $request = $psr17Factory->createRequest('GET', $url); - $response = $psr18Client->sendRequest($request); + $request = $client->createRequest('GET', $url); + $response = $client->sendRequest($request); $content = json_decode($response->getBody()->getContents(), true); +.. versionadded:: 4.4 + + The PSR-17 factory methods of ``Psr18Client`` were introduced in Symfony 4.4. + +HTTPlug +~~~~~~~ + +.. versionadded:: 4.4 + + Support for HTTPlug was introduced in Symfony 4.4. + +The `HTTPlug`_ v1 specification was published before PSR-18 and is superseded by +it. As such, you should not use it in newly written code. The component is still +interoperable with libraries that require it thanks to the +:class:`Symfony\\Component\\HttpClient\\HttplugClient` class. Similarly to +``Psr18Client`` implementing relevant parts of PSR-17, ``HttplugClient`` also +implements the factory methods defined in the related ``php-http/message-factory`` +package. + +.. code-block:: terminal + + # Let's suppose php-http/httplug is already required by the lib you want to use + + # installs an efficient implementation of response and stream factories + # with autowiring aliases provided by Symfony Flex + $ composer require nyholm/psr7 + + # alternatively, install the php-http/discovery package to auto-discover + # any already installed implementations from common vendors: + # composer require php-http/discovery + +Let's say you want to instantiate a class with the following constructor, +that requires HTTPlug dependencies:: + + use Http\Client\HttpClient; + use Http\Message\RequestFactory; + use Http\Message\StreamFactory; + + class SomeSdk + { + public function __construct( + HttpClient $httpClient, + RequestFactory $requestFactory, + StreamFactory $streamFactory + ) + // [...] + } + +Because ``HttplugClient`` implements the three interfaces, you can use it this way:: + + use Symfony\Component\HttpClient\HttplugClient; + + $httpClient = new HttplugClient(); + $apiClient = new SomeSdk($httpClient, $httpClient, $httpClient); + +If you'd like to work with promises, ``HttplugClient`` also implements the +``HttpAsyncClient`` interface. To use it, you need to install the +``guzzlehttp/promises`` package: + +.. code-block:: terminal + + $ composer require guzzlehttp/promises + +Then you're ready to go:: + + use Psr\Http\Message\ResponseInterface; + use Symfony\Component\HttpClient\HttplugClient; + + $httpClient = new HttplugClient(); + $request = $httpClient->createRequest('GET', 'https://my.api.com/'); + $promise = $httpClient->sendRequest($request) + ->then( + function (ResponseInterface $response) { + echo 'Got status '.$response->getStatusCode(); + + return $response; + }, + function (\Throwable $exception) { + echo 'Error: '.$exception->getMessage(); + + throw $exception; + } + ); + + // after you're done with sending several requests, + // you must wait for them to complete concurrently + + // wait for a specific promise to resolve while monitoring them all + $response = $promise->wait(); + + // wait maximum 1 second for pending promises to resolve + $httpClient->wait(1.0); + + // wait for all remaining promises to resolve + $httpClient->wait(); + +Native PHP Streams +~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 4.4 + + Support for native PHP streams was introduced in Symfony 4.4. + +Responses implementing :class:`Symfony\\Contracts\\HttpClient\\ResponseInterface` +can be cast to native PHP streams with +:method:`Symfony\\Component\\HttpClient\\Response\\StreamWrapper::createResource``. +This allows using them where native PHP streams are needed:: + + use Symfony\Component\HttpClient\HttpClient; + use Symfony\Component\HttpClient\Response\StreamWrapper; + + $client = HttpClient::create(); + $response = $client->request('GET', 'https://symfony.com/versions.json'); + + $streamResource = StreamWrapper::createResource($response, $client); + + // alternatively and contrary to the previous one, this returns + // a resource that is seekable and potentially stream_select()-able + $streamResource = $response->toStream(); + + echo stream_get_contents($streamResource); // outputs the content of the response + + // later on if you need to, you can access the response from the stream + $response = stream_get_meta_data($streamResource)['wrapper_data']->getResponse(); + Symfony Framework Integration ----------------------------- @@ -835,4 +998,5 @@ However, using ``MockResponse`` allows simulating chunked responses and timeouts .. _`cURL PHP extension`: https://php.net/curl .. _`PSR-17`: https://www.php-fig.org/psr/psr-17/ .. _`PSR-18`: https://www.php-fig.org/psr/psr-18/ +.. _`HTTPlug`: https://github.com/php-http/httplug/#readme .. _`Symfony Contracts`: https://github.com/symfony/contracts diff --git a/components/http_foundation.rst b/components/http_foundation.rst index 8eadc81717a..6ef462e404a 100644 --- a/components/http_foundation.rst +++ b/components/http_foundation.rst @@ -316,6 +316,28 @@ are also supported:: $quality = $accept->get('text/xml')->getQuality(); // $quality = 0.8 $quality = $accept->get('application/xml')->getQuality(); // $quality = 0.3 +Anonymizing IP Addresses +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 4.4 + + The ``anonymize()`` method was introduced in Symfony 4.4. + +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 ~~~~~~~~~~~~~~~~~~~~ diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 4c320d12dbf..b1c98d7cae2 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -526,10 +526,22 @@ to the exception. Each listener to this event is passed a :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` object, which you can use to access the original exception via the -:method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::getException` +:method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::getThrowable` method. A typical listener on this event will check for a certain type of exception and create an appropriate error ``Response``. +.. versionadded:: 4.4 + + The :method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::getThrowable` and + :method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::setThrowable` methods + were introduced in Symfony 4.4. + +.. deprecated:: 4.4 + + The :method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::getException` and + :method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::setException` methods + are deprecated since Symfony 4.4. + For example, to generate a 404 page, you might throw a special type of exception and then add a listener on this event that looks for this exception and creates and returns a 404 ``Response``. In fact, the HttpKernel component @@ -554,7 +566,7 @@ below for more details). The listener has several goals: 1) The thrown exception is converted into a - :class:`Symfony\\Component\\Debug\\Exception\\FlattenException` + :class:`Symfony\\Component\\ErrorRenderer\\Exception\\FlattenException` object, which contains all the information about the request, but which can be printed and serialized. diff --git a/components/intl.rst b/components/intl.rst index 94ee45ff018..04a1752fa49 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -77,28 +77,55 @@ according to the `ISO 639-1 alpha-2`_ list and the `ISO 639-2 alpha-3`_ list:: // ('languageCode' => 'languageName') // => ['ab' => 'Abkhazian', 'ace' => 'Achinese', ...] + $languages = Languages::getAlpha3Names(); + // ('languageCode' => 'languageName') + // => ['abk' => 'Abkhazian', 'ace' => 'Achinese', ...] + $language = Languages::getName('fr'); // => 'French' + $language = Languages::getAlpha3Name('fra'); + // => 'French' + All methods accept the translation locale as the last, optional parameter, which defaults to the current default locale:: $languages = Languages::getNames('de'); // => ['ab' => 'Abchasisch', 'ace' => 'Aceh', ...] + $languages = Languages::getAlpha3Names('de'); + // => ['abk' => 'Abchasisch', 'ace' => 'Aceh', ...] + $language = Languages::getName('fr', 'de'); // => 'Französisch' + $language = Languages::getAlpha3Name('fra', 'de'); + // => 'Französisch' + If the given locale doesn't exist, the methods trigger a :class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition to catching the exception, you can also check if a given language code is valid:: $isValidLanguage = Languages::exists($languageCode); +Or if you have a alpha3 language code you want to check:: + + $isValidLanguage = Languages::alpha3CodeExists($alpha3Code); + +You may convert codes between two-letter alpha2 and three-letter alpha3 codes:: + + $alpha3Code = Languages::getAlpha3Code($alpha2Code); + + $alpha2Code = Languages::getAlpha2Code($alpha3Code); + .. versionadded:: 4.3 The ``Languages`` class was introduced in Symfony 4.3. +.. versionadded:: 4.4 + + The full support for alpha3 codes was introduced in Symfony 4.4. + The ``Scripts`` class provides access to the optional four-letter script code that can follow the language code according to the `Unicode ISO 15924 Registry`_ (e.g. ``HANS`` in ``zh_HANS`` for simplified Chinese and ``HANT`` in ``zh_HANT`` @@ -138,39 +165,66 @@ Country Names ~~~~~~~~~~~~~ The ``Countries`` class provides access to the name of all countries according -to the `ISO 3166-1 alpha-2`_ list of officially recognized countries and -territories:: +to the `ISO 3166-1 alpha-2`_ list and the `ISO 3166-1 alpha-3`_ list +of officially recognized countries and territories:: use Symfony\Component\Intl\Countries; \Locale::setDefault('en'); $countries = Countries::getNames(); - // ('countryCode' => 'countryName') + // ('alpha2Code' => 'countryName') // => ['AF' => 'Afghanistan', 'AX' => 'Åland Islands', ...] + $countries = Countries::getAlpha3Names(); + // ('alpha3Code' => 'countryName') + // => ['AFG' => 'Afghanistan', 'ALA' => 'Åland Islands', ...] + $country = Countries::getName('GB'); // => 'United Kingdom' + $country = Countries::getAlpha3Name('NOR'); + // => 'Norway' + All methods accept the translation locale as the last, optional parameter, which defaults to the current default locale:: $countries = Countries::getNames('de'); // => ['AF' => 'Afghanistan', 'EG' => 'Ägypten', ...] + $countries = Countries::getAlpha3Names('de'); + // => ['AFG' => 'Afghanistan', 'EGY' => 'Ägypten', ...] + $country = Countries::getName('GB', 'de'); // => 'Vereinigtes Königreich' + $country = Countries::getAlpha3Name('GBR', 'de'); + // => 'Vereinigtes Königreich' + If the given country code doesn't exist, the methods trigger a :class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition to catching the exception, you can also check if a given country code is valid:: - $isValidCountry = Countries::exists($countryCode); + $isValidCountry = Countries::exists($alpha2Code); + +Or if you have a alpha3 country code you want to check:: + + $isValidCountry = Countries::alpha3CodeExists($alpha3Code); + +You may convert codes between two-letter alpha2 and three-letter alpha3 codes:: + + $alpha3Code = Countries::getAlpha3Code($alpha2Code); + + $alpha2Code = Countries::getAlpha2Code($alpha3Code); .. versionadded:: 4.3 The ``Countries`` class was introduced in Symfony 4.3. +.. versionadded:: 4.4 + + The support for alpha3 codes was introduced in Symfony 4.4. + Locales ~~~~~~~ @@ -355,6 +409,7 @@ Learn more .. _ICU library: http://site.icu-project.org/ .. _`Unicode ISO 15924 Registry`: https://www.unicode.org/iso15924/iso15924-codes.html .. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 +.. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 .. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets .. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time .. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1 diff --git a/components/lock.rst b/components/lock.rst index fc4f1add491..7869c2e019d 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -24,16 +24,21 @@ Locks are used to guarantee exclusive access to some shared resource. In Symfony applications, you can use locks for example to ensure that a command is not executed more than once at the same time (on the same or different servers). -Locks are created using a :class:`Symfony\\Component\\Lock\\Factory` class, +Locks are created using a :class:`Symfony\\Component\\Lock\\LockFactory` class, which in turn requires another class to manage the storage of locks:: - use Symfony\Component\Lock\Factory; + use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\Store\SemaphoreStore; $store = new SemaphoreStore(); - $factory = new Factory($store); + $factory = new LockFactory($store); -The lock is created by calling the :method:`Symfony\\Component\\Lock\\Factory::createLock` +.. versionadded:: 4.4 + + The ``Symfony\Component\Lock\LockFactory`` class was introduced in Symfony + 4.4. In previous versions it was called ``Symfony\Component\Lock\Factory``. + +The lock is created by calling the :method:`Symfony\\Component\\Lock\\LockFactory::createLock` method. Its first argument is an arbitrary string that represents the locked resource. Then, a call to the :method:`Symfony\\Component\\Lock\\LockInterface::acquire` method will try to acquire the lock:: @@ -56,7 +61,7 @@ method can be safely called repeatedly, even if the lock is already acquired. Unlike other implementations, the Lock Component distinguishes locks instances even when they are created for the same resource. If a lock has to be used by several services, they should share the same ``Lock`` instance - returned by the ``Factory::createLock`` method. + returned by the ``LockFactory::createLock`` method. .. tip:: @@ -79,13 +84,13 @@ until the lock is acquired. Some of the built-in ``Store`` classes support this feature. When they don't, they can be decorated with the ``RetryTillSaveStore`` class:: - use Symfony\Component\Lock\Factory; + use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\Store\RedisStore; use Symfony\Component\Lock\Store\RetryTillSaveStore; $store = new RedisStore(new \Predis\Client('tcp://localhost:6379')); $store = new RetryTillSaveStore($store); - $factory = new Factory($store); + $factory = new LockFactory($store); $lock = $factory->createLock('notification-flush'); $lock->acquire(true); @@ -210,8 +215,10 @@ Available Stores ---------------- Locks are created and managed in ``Stores``, which are classes that implement -:class:`Symfony\\Component\\Lock\\StoreInterface`. The component includes the -following built-in store types: +:class:`Symfony\\Component\\Lock\\PersistingStoreInterface` and, optionally, +:class:`Symfony\\Component\\Lock\\BlockingStoreInterface`. + +The component includes the following built-in store types: ============================================ ====== ======== ======== Store Scope Blocking Expiring @@ -224,6 +231,12 @@ Store Scope Blocking Expiring :ref:`ZookeeperStore ` remote no no ============================================ ====== ======== ======== +.. versionadded:: 4.4 + + The ``PersistingStoreInterface`` and ``BlockingStoreInterface`` interfaces were + introduced in Symfony 4.4. In previous versions there was only one interface + called ``Symfony\Component\Lock\StoreInterface``. + .. _lock-store-flock: FlockStore diff --git a/components/mailer.rst b/components/mailer.rst index c4aa5efaa14..f4290bed825 100644 --- a/components/mailer.rst +++ b/components/mailer.rst @@ -12,8 +12,7 @@ If you're using the Symfony Framework, read the .. versionadded:: 4.3 - The Mailer component was introduced in Symfony 4.3 and it's still - considered an :doc:`experimental feature `. + The Mailer component was introduced in Symfony 4.3. Installation ------------ @@ -65,10 +64,10 @@ it: Then, use the SMTP Gmail transport:: - use Symfony\Component\Mailer\Bridge\Google\Smtp\GmailTransport; + use Symfony\Component\Mailer\Bridge\Google\Transport\GmailSmtpTransport; use Symfony\Component\Mailer\Mailer; - $transport = new GmailTransport('user', 'pass'); + $transport = new GmailSmtpTransport('user', 'pass'); $mailer = new Mailer($transport); $mailer->send($email); @@ -87,19 +86,35 @@ DSN:: $transport = Transport::fromDsn($dsn); Where ``$dsn`` depends on the provider you want to use. For plain SMTP, use -``smtp://user:pass@example.com`` or ``smtp://sendmail`` to use the ``sendmail`` -binary. For third-party providers, refers to the following table: - -==================== ================================== ================================== ================================ - Provider SMTP HTTP API -==================== ================================== ================================== ================================ - Amazon SES smtp://ACCESS_KEY:SECRET_KEY@ses http://ACCESS_KEY:SECRET_KEY@ses api://ACCESS_KEY:SECRET_KEY@ses - Google Gmail smtp://USERNAME:PASSWORD@gmail n/a n/a - Mailchimp Mandrill smtp://USERNAME:PASSWORD@mandrill http://KEY@mandrill api://KEY@mandrill - Mailgun smtp://USERNAME:PASSWORD@mailgun http://KEY:DOMAIN@mailgun api://KEY:DOMAIN@mailgun - Postmark smtp://ID:ID@postmark n/a api://KEY@postmark - Sendgrid smtp://apikey:KEY@sendgrid n/a api://KEY@sendgrid -==================== ================================== ================================== ================================ +``smtp://user:pass@example.com`` or ``sendmail+smtp://default`` to use the +``sendmail`` binary. To disable the transport, use ``null://null``. + +For third-party providers, refer to the following table: + +==================== ========================================== =========================================== ======================================== + Provider SMTP HTTP API +==================== ========================================== =========================================== ======================================== + Amazon SES ses+smtp://ACCESS_KEY:SECRET_KEY@default ses+https://ACCESS_KEY:SECRET_KEY@default ses+api://ACCESS_KEY:SECRET_KEY@default + Google Gmail gmail+smtp://USERNAME:PASSWORD@default n/a n/a + Mailchimp Mandrill mandrill+smtp://USERNAME:PASSWORD@default mandrill+https://KEY@default mandrill+api://KEY@default + Mailgun mailgun+smtp://USERNAME:PASSWORD@default mailgun+https://KEY:DOMAIN@default mailgun+api://KEY:DOMAIN@default + Postmark postmark+smtp://ID:ID@default n/a postmark+api://KEY@default + Sendgrid sendgrid+smtp://apikey:KEY@default n/a sendgrid+api://KEY@default +==================== ========================================== =========================================== ======================================== + +Instead of choosing a specific protocol, you can also let Symfony pick the +best one by omitting it from the scheme: for instance, ``mailgun://KEY:DOMAIN@default`` +is equivalent to ``mailgun+https://KEY:DOMAIN@default``. + +If you want to override the default host for a provider (to debug an issue using +a service like ``requestbin.com``), change ``default`` by your host: + +.. code-block:: bash + + mailgun+https://KEY:DOMAIN@example.com + mailgun+https://KEY:DOMAIN@example.com:99 + +Note that the protocol is *always* HTTPs and cannot be changed. High Availability ----------------- @@ -107,10 +122,10 @@ High Availability Symfony's mailer supports `high availability`_ via a technique called "failover" to ensure that emails are sent even if one mailer server fails . -A failover transport is configured with two or more transports joined by the -``||`` operator:: +A failover transport is configured with two or more transports and the +``failover`` keyword:: - $dsn = 'api://id@postmark || smtp://key@sendgrid'; + $dsn = 'failover(postmark+api://ID@default sendgrid+smtp://KEY@default)'; The mailer will start using the first transport. If the sending fails, the mailer won't retry it with the other transports, but it will switch to the next @@ -122,10 +137,10 @@ Load Balancing Symfony's mailer supports `load balancing`_ via a technique called "round-robin" to distribute the mailing workload across multiple transports . -A round-robin transport is configured with two or more transports joined by the -``&&`` operator:: +A round-robin transport is configured with two or more transports and the +``roundrobin`` keyword:: - $dsn = 'api://id@postmark && smtp://key@sendgrid' + $dsn = 'roundrobin(postmark+api://ID@default sendgrid+smtp://KEY@default)' The mailer will start using the first transport and if it fails, it will retry the same delivery with the next transports until one of them succeeds (or until @@ -143,10 +158,10 @@ If you want to send emails asynchronously, install the :doc:`Messenger component Then, instantiate and pass a ``MessageBus`` as a second argument to ``Mailer``:: + use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\Messenger\MessageHandler; use Symfony\Component\Mailer\Messenger\SendEmailMessage; - use Symfony\Component\Mailer\SmtpEnvelope; use Symfony\Component\Mailer\Transport; use Symfony\Component\Messenger\Handler\HandlersLocator; use Symfony\Component\Messenger\MessageBus; @@ -168,7 +183,7 @@ Then, instantiate and pass a ``MessageBus`` as a second argument to ``Mailer``:: $mailer->send($email); // you can pass an optional Envelope - $mailer->send($email, new SmtpEnvelope( + $mailer->send($email, new Envelope( new Address('sender@example.com'), [ new Address('recipient@example.com'), diff --git a/components/mime.rst b/components/mime.rst index 501b10a8dc5..dc02abd8d6e 100644 --- a/components/mime.rst +++ b/components/mime.rst @@ -11,8 +11,7 @@ The Mime Component .. versionadded:: 4.3 - The Mime component was introduced in Symfony 4.3 and it's still - considered an :doc:`experimental feature `. + The Mime component was introduced in Symfony 4.3. Installation ------------ diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index 7272bcc02a0..01b2583e17e 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -28,6 +28,10 @@ It comes with the following features: constraints to apply; 2. running tests in parallel when a test suite is split in several phpunit.xml files; 3. recording and replaying skipped tests; +* It allows to create tests that are compatible with multiple PHPUnit versions + (because it provides polyfills for missing methods, namespaced aliases for + non-namespaced classes, etc.). + Installation ------------ @@ -371,6 +375,83 @@ Running the following command will display the full stack trace: $ SYMFONY_DEPRECATIONS_HELPER='/Doctrine\\Common\\ClassLoader is deprecated\./' ./vendor/bin/simple-phpunit +Testing with Multiple PHPUnit Versions +-------------------------------------- + +When testing a library that has to be compatible with several versions of PHP, +the test suite cannot use the latest versions of PHPUnit because: + +* PHPUnit 8 deprecated several methods in favor of other methods which are not + available in older versions (e.g. PHPUnit 4); +* PHPUnit 8 added the ``void`` return type to the ``setUp()`` method, which is + not compatible with PHP 5.5; +* PHPUnit switched to namespaced classes starting from PHPUnit 6, so tests must + work with and without namespaces. + +Polyfills for the Unavailable Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 4.4 + + This feature was introduced in Symfony 4.4. + +When using the ``simple-phpunit`` script, PHPUnit Bridge injects polyfills for +most methods of the ``TestCase`` and ``Assert`` classes (e.g. ``expectException()``, +``expectExceptionMessage()``, ``assertContainsEquals()``, etc.). This allows writing +test cases using the latest best practices while still remaining compatible with +older PHPUnit versions. + +Removing the Void Return Type +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 4.4 + + This feature was introduced in Symfony 4.4. + +When running the ``simple-phpunit`` script with the ``SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT`` +environment variable set to ``1``, the PHPUnit bridge will alter the code of +PHPUnit to remove the return type (introduced in PHPUnit 8) from ``setUp()``, +``tearDown()``, ``setUpBeforeClass()`` and ``tearDownAfterClass()`` methods. +This allows you to write a test compatible with both PHP 5 and PHPUnit 8. + +Alternatively, you can use the trait :class:`Symfony\\Bridge\\PhpUnit\\SetUpTearDownTrait`, +which provides the right signature for the ``setUp()``, ``tearDown()``, +``setUpBeforeClass()`` and ``tearDownAfterClass()`` methods and delegates the +call to the ``doSetUp()``, ``doTearDown()``, ``doSetUpBeforeClass()`` and +``doTearDownAfterClass()`` methods:: + + use PHPUnit\Framework\TestCase; + use Symfony\Bridge\PhpUnit\SetUpTearDownTrait; + + class MyTest extends TestCase + { + // when using the SetUpTearDownTrait, methods like doSetup() can + // be defined with and without the 'void' return type, as you wish + use SetUpTearDownTrait; + + private function doSetup() + { + // ... + } + + protected function doSetup(): void + { + // ... + } + } + +Using Namespaced PHPUnit Classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 4.4 + + This feature was introduced in Symfony 4.4. + +The PHPUnit bridge adds namespaced class aliases for most of the PHPUnit classes +declared without namespaces (e.g. ``PHPUnit_Framework_Assert``), allowing you to +always use the namespaced class declaration even when the test is executed with +PHPUnit 4. + Time-sensitive Tests -------------------- @@ -743,9 +824,20 @@ its ``bin/simple-phpunit`` command. It has the following features: The script writes the modified PHPUnit it builds in a directory that can be configured by the ``SYMFONY_PHPUNIT_DIR`` env var, or in the same directory as -the ``simple-phpunit`` if it is not provided. - -It's also possible to set this env var in the ``phpunit.xml.dist`` file. +the ``simple-phpunit`` if it is not provided. It's also possible to set this +env var in the ``phpunit.xml.dist`` file. + +By default, these are the PHPUnit versions used depending on the installed PHP versions: + +===================== =============================== +Installed PHP version PHPUnit version used by default +===================== =============================== +PHP <= 5.5 PHPUnit 4.8 +PHP 5.6 PHPUnit 5.7 +PHP 7.0 PHPUnit 6.5 +PHP 7.1 PHPUnit 7.5 +PHP >= 7.2 PHPUnit 8.3 +===================== =============================== If you have installed the bridge through Composer, you can run it by calling e.g.: diff --git a/components/process.rst b/components/process.rst index ec15c942e6d..4819c023813 100644 --- a/components/process.rst +++ b/components/process.rst @@ -96,6 +96,17 @@ with a non-zero code):: echo $exception->getMessage(); } +.. tip:: + + You can get the last output time in seconds by using the + :method:`Symfony\\Component\\Process\\Process::getLastOutputTime` method. + This method returns ``null`` if the process wasn't started! + + .. versionadded:: 4.4 + + The :method:`Symfony\\Component\\Process\\Process::getLastOutputTime` + method was introduced in Symfony 4.4. + Using Features From the OS Shell -------------------------------- @@ -357,6 +368,27 @@ instead:: ); $process->run(); +Using a Prepared Command Line +----------------------------- + +You can run the process by using a a prepared command line using the +double bracket notation. You can use a placeholder in order to have a +process that can only be changed with the values and without changing +the PHP code:: + + use Symfony\Component\Process\Process; + + $process = Process::fromShellCommandline('echo "$name"'); + $process->run(null, ['name' => 'Elsa']); + +.. caution:: + + A prepared command line will not be escaped automatically! + +.. versionadded:: 4.4 + + Prepared command lines were introduced in Symfony 4.4. + Process Timeout --------------- diff --git a/components/security/authentication.rst b/components/security/authentication.rst index 849ea53a992..c26b3ef37d7 100644 --- a/components/security/authentication.rst +++ b/components/security/authentication.rst @@ -290,9 +290,10 @@ Authentication Success and Failure Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a provider authenticates the user, a ``security.authentication.success`` -event is dispatched. But beware - this event will fire, for example, on *every* -request if you have session-based authentication. See ``security.interactive_login`` -below if you need to do something when a user *actually* logs in. +event is dispatched. But beware - this event may fire, for example, on *every* +request if you have session-based authentication, if ``always_authenticate_before_granting`` +is enabled or if token is not authenticated before AccessListener is invoked. +See ``security.interactive_login`` below if you need to do something when a user *actually* logs in. When a provider attempts authentication but fails (i.e. throws an ``AuthenticationException``), a ``security.authentication.failure`` event is dispatched. You could listen on diff --git a/components/validator/resources.rst b/components/validator/resources.rst index a7cf3678c9d..1455c2518bc 100644 --- a/components/validator/resources.rst +++ b/components/validator/resources.rst @@ -147,17 +147,21 @@ can slow down your application because each file needs to be parsed, validated and converted into a :class:`Symfony\\Component\\Validator\\Mapping\\ClassMetadata` instance. -To solve this problem, call the :method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMetadataCache` +To solve this problem, call the :method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMappingCache` method of the Validator builder and pass your own caching class (which must -implement :class:`Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface`):: +implement the PSR-6 interface :class:`Psr\\Cache\\CacheItemPoolInterface`):: use Symfony\Component\Validator\Validation; $validator = Validation::createValidatorBuilder() // ... add loaders - ->setMetadataCache(new SomeImplementCacheInterface()); + ->setMappingCache(new SomePsr6Cache()); ->getValidator(); +.. versionadded:: 4.4 + + Support for PSR-6 compatible mapping caches was introduced in Symfony 4.4. + .. note:: The loaders already use a singleton load mechanism. That means that the diff --git a/components/var_dumper.rst b/components/var_dumper.rst index 1c48a021855..a607ddeb59b 100644 --- a/components/var_dumper.rst +++ b/components/var_dumper.rst @@ -224,6 +224,13 @@ option. Read more about this and other options in are supported (``Ctrl. + G`` or ``Cmd. + G``, ``F3``, etc.) When finished, press ``Esc.`` to hide the box again. + If you want to use your browser search input, press ``Ctrl. + F`` or + ``Cmd. + F`` again while having focus on VarDumper's search input. + + .. versionadded:: 4.4 + + The feature to use the browser search input was introduced in Symfony 4.4. + Using the VarDumper Component in your PHPUnit Test Suite -------------------------------------------------------- @@ -241,6 +248,21 @@ This will provide you with two new assertions: is like the previous method but accepts placeholders in the expected dump, based on the ``assertStringMatchesFormat()`` method provided by PHPUnit. +The ``VarDumperTestTrait`` also includes these other methods: + +:method:`Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait::setUpVarDumper` + is used to configure the available casters and their options, which is a way + to only control the fields you're expecting and allows writing concise tests. + +:method:`Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait::tearDownVarDumper` + is called automatically after each case to reset the custom configuration + made in ``setUpVarDumper()``. + +.. versionadded:: 4.4 + + The ``setUpVarDumper()`` and ``tearDownVarDumper()`` methods were introduced + in Symfony 4.4. + Example:: use PHPUnit\Framework\TestCase; @@ -250,14 +272,33 @@ Example:: { use VarDumperTestTrait; + protected function setUp() + { + $casters = [ + \DateTimeInterface::class => static function (\DateTimeInterface $date, array $a, Stub $stub): array { + $stub->class = 'DateTime'; + return ['date' => $date->format('d/m/Y')]; + }, + ]; + + $flags = CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR; + + // this configures the casters & flags to use for all the tests in this class. + // If you need custom configurations per test rather than for the whole class, + // call this setUpVarDumper() method from those tests instead. + $this->setUpVarDumper($casters, $flags); + } + public function testWithDumpEquals() { $testedVar = [123, 'foo']; + // the expected dump contents don't have the default VarDumper structure + // because of the custom casters and flags used in the test $expectedDump = << 123 - 1 => "foo" + [ + 123, + "foo", ] EOTXT; diff --git a/components/var_dumper/advanced.rst b/components/var_dumper/advanced.rst index 9f07db3234f..0f429c52012 100644 --- a/components/var_dumper/advanced.rst +++ b/components/var_dumper/advanced.rst @@ -382,6 +382,7 @@ can use: objects/strings/*etc.* by ellipses; * :class:`Symfony\\Component\\VarDumper\\Caster\\CutArrayStub` to keep only some useful keys of an array; +* :class:`Symfony\\Component\\VarDumper\\Caster\\ImgStub` to wrap an image; * :class:`Symfony\\Component\\VarDumper\\Caster\\EnumStub` to wrap a set of virtual values (*i.e.* values that do not exist as properties in the original PHP data structure, but are worth listing alongside with real ones); diff --git a/components/web_link.rst b/components/web_link.rst index 634da8b81b7..4a89d0c0b48 100644 --- a/components/web_link.rst +++ b/components/web_link.rst @@ -22,9 +22,9 @@ Usage The following example shows the component in action:: - use Fig\Link\GenericLinkProvider; - use Fig\Link\Link; + use Symfony\Component\WebLink\GenericLinkProvider; use Symfony\Component\WebLink\HttpHeaderSerializer; + use Symfony\Component\WebLink\Link; $linkProvider = (new GenericLinkProvider()) ->withLink(new Link('preload', '/bootstrap.min.css')); diff --git a/components/yaml.rst b/components/yaml.rst index b60cb89e4c8..572c2286ced 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -377,6 +377,23 @@ objects, they are automatically transformed into YAML tags:: $dumped = Yaml::dump($data); // $dumped = '!my_tag { foo: bar }' +Dumping Null Values +~~~~~~~~~~~~~~~~~~~ + +The official YAML specification uses both ``null`` and ``~`` to represent null +values. This component uses ``null`` by default when dumping null values but +you can dump them as ``~`` with the ``DUMP_NULL_AS_TILDE`` flag:: + + $dumped = Yaml::dump(['foo' => null]); + // foo: null + + $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_TILDE); + // foo: ~ + +.. versionadded:: 4.4 + + The flag to dump ``null`` as ``~`` was introduced in Symfony 4.4. + Syntax Validation ~~~~~~~~~~~~~~~~~ diff --git a/configuration/front_controllers_and_kernel.rst b/configuration/front_controllers_and_kernel.rst index 00f34f96b35..ba702cbdc49 100644 --- a/configuration/front_controllers_and_kernel.rst +++ b/configuration/front_controllers_and_kernel.rst @@ -21,6 +21,8 @@ process, you need to understand three parts that work together: ``Kernel`` class as Symfony provides sensible default implementations. This article is provided to explain what is going on behind the scenes. +.. _architecture-front-controller: + The Front Controller -------------------- diff --git a/console/style.rst b/console/style.rst index 88e605f0146..5dac6052669 100644 --- a/console/style.rst +++ b/console/style.rst @@ -140,6 +140,39 @@ Content Methods ] ); +:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::horizontalTable` + It displays the given array of headers and rows as a compact horizontal table:: + + $io->horizontalTable( + ['Header 1', 'Header 2'], + [ + ['Cell 1-1', 'Cell 1-2'], + ['Cell 2-1', 'Cell 2-2'], + ['Cell 3-1', 'Cell 3-2'], + ] + ); + + .. versionadded:: 4.4 + + The ``horizontalTable()`` method was introduced in Symfony 4.4. + +:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::definitionList` + It displays the given ``key => value`` pairs as a compact list of elements:: + + $io->definitionList( + 'This is a title', + ['foo1' => 'bar1'], + ['foo2' => 'bar2'] + ['foo3' => 'bar3'] + new TableSeparator(), + 'This is another title', + ['foo4' => 'bar4'] + ); + + .. versionadded:: 4.4 + + The ``definitionList()`` method was introduced in Symfony 4.4. + :method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::newLine` It displays a blank line in the command output. Although it may seem useful, most of the times you won't need it at all. The reason is that every helper diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst index d2336e21e86..f302faa087f 100644 --- a/contributing/community/releases.rst +++ b/contributing/community/releases.rst @@ -59,7 +59,7 @@ ones are considered **standard versions**: ======================= ===================== ================================ Version Type Bugs are fixed for... Security issues are fixed for... ======================= ===================== ================================ -Standard 8 months 14 months +Standard 8 months 8 months Long-Term Support (LTS) 3 years 4 years ======================= ===================== ================================ diff --git a/controller/error_pages.rst b/controller/error_pages.rst index a4026b75a66..482ba6edd95 100644 --- a/controller/error_pages.rst +++ b/controller/error_pages.rst @@ -247,7 +247,7 @@ the request that will be dispatched to your controller. In addition, your contro will be passed two parameters: ``exception`` - A :class:`\\Symfony\\Component\\Debug\\Exception\\FlattenException` + A :class:`\\Symfony\\Component\\ErrorRenderer\\Exception\\FlattenException` instance created from the exception being handled. ``logger`` diff --git a/controller/service.rst b/controller/service.rst index c9095be1eb0..18ee56ee707 100644 --- a/controller/service.rst +++ b/controller/service.rst @@ -183,7 +183,7 @@ look at the `ControllerTrait`_ that holds its logic. If you want to know what type-hints to use for each service, see the ``getSubscribedServices()`` method in `AbstractController`_. -.. _`Controller class source code`: https://github.com/symfony/symfony/blob/4.3/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php -.. _`ControllerTrait`: https://github.com/symfony/symfony/blob/4.3/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php +.. _`Controller class source code`: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php +.. _`ControllerTrait`: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php .. _`AbstractController`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php .. _`ADR pattern`: https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder diff --git a/create_framework/http_kernel_httpkernel_class.rst b/create_framework/http_kernel_httpkernel_class.rst index f9f8f16932f..5845f4219dc 100644 --- a/create_framework/http_kernel_httpkernel_class.rst +++ b/create_framework/http_kernel_httpkernel_class.rst @@ -69,7 +69,7 @@ Our code is now much more concise and surprisingly more robust and more powerful than ever. For instance, use the built-in ``ExceptionListener`` to make your error management configurable:: - $errorHandler = function (Symfony\Component\Debug\Exception\FlattenException $exception) { + $errorHandler = function (Symfony\Component\ErrorRenderer\Exception\FlattenException $exception) { $msg = 'Something went wrong! ('.$exception->getMessage().')'; return new Response($msg, $exception->getStatusCode()); @@ -91,7 +91,7 @@ The error controller reads as follows:: // example.com/src/Calendar/Controller/ErrorController.php namespace Calendar\Controller; - use Symfony\Component\Debug\Exception\FlattenException; + use Symfony\Component\ErrorRenderer\Exception\FlattenException; use Symfony\Component\HttpFoundation\Response; class ErrorController diff --git a/deployment/proxies.rst b/deployment/proxies.rst index 254c0f0e02d..02f3e587d12 100644 --- a/deployment/proxies.rst +++ b/deployment/proxies.rst @@ -65,13 +65,18 @@ In this case, you'll need to - *very carefully* - trust *all* proxies. // ... Request::setTrustedProxies( - // trust *all* requests - ['127.0.0.1', $request->server->get('REMOTE_ADDR')], + // trust *all* requests (the 'REMOTE_ADDR' string is replaced at + // run time by $_SERVER['REMOTE_ADDR']) + ['127.0.0.1', 'REMOTE_ADDR'], // if you're using ELB, otherwise use a constant from above Request::HEADER_X_FORWARDED_AWS_ELB ); +.. versionadded:: 4.4 + + The support for the ``REMOTE_ADDR`` option was introduced in Symfony 4.4. + That's it! It's critical that you prevent traffic from all non-trusted sources. If you allow outside traffic, they could "spoof" their true IP address and other information. diff --git a/doctrine/events.rst b/doctrine/events.rst index bb2828e81fe..6d376d027d6 100644 --- a/doctrine/events.rst +++ b/doctrine/events.rst @@ -272,7 +272,8 @@ with the ``doctrine.orm.entity_listener`` tag: entity_manager: 'custom' # by default, Symfony looks for a method called after the event (e.g. postUpdate()) - # but you can configure a custom method name with the 'method' option + # if it doesn't exist, it tries to execute the '__invoke()' method, but you can + # configure a custom method name with the 'method' option method: 'checkUserChanges' .. code-block:: xml @@ -291,7 +292,8 @@ with the ``doctrine.orm.entity_listener`` tag: * 'entity_manager': define it if the listener is not associated to the * default entity manager * 'method': by default, Symfony looks for a method called after the event (e.g. postUpdate()) - * but you can configure a custom method name with the 'method' option + * if it doesn't exist, it tries to execute the '__invoke()' method, but + * you can configure a custom method name with the 'method' option --> 'custom', - // by default, Symfony looks for a method called after the event (e.g. postUpdate()), - // but you can configure a custom method name with the 'method' option + // by default, Symfony looks for a method called after the event (e.g. postUpdate()) + // if it doesn't exist, it tries to execute the '__invoke()' method, but you can + // configure a custom method name with the 'method' option 'method' => 'checkUserChanges', ]) ; +.. versionadded:: 4.4 + + Support for invokable listeners (using the ``__invoke()`` method) was introduced in Symfony 4.4. + Doctrine Lifecycle Subscribers ------------------------------ diff --git a/event_dispatcher.rst b/event_dispatcher.rst index c1c7d1c6f95..8df171497a5 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -35,7 +35,7 @@ The most common way to listen to an event is to register an **event listener**:: public function onKernelException(ExceptionEvent $event) { // You get the exception object from the received event - $exception = $event->getException(); + $exception = $event->getThrowable(); $message = sprintf( 'My Error says: %s with code: %s', $exception->getMessage(), diff --git a/form/bootstrap4.rst b/form/bootstrap4.rst index 330ed2a165e..7c3a58bd8e6 100644 --- a/form/bootstrap4.rst +++ b/form/bootstrap4.rst @@ -91,14 +91,22 @@ for **all** users. Custom Forms ------------ +.. versionadded:: 4.4 + + Support for the ``switch-custom`` class was introduced in Symfony 4.4. + Bootstrap 4 has a feature called "`custom forms`_". You can enable that on your -Symfony Form ``RadioType`` and ``CheckboxType`` by adding a class called ``radio-custom`` -and ``checkbox-custom`` respectively. +Symfony Form ``RadioType`` and ``CheckboxType`` by adding some classes to the label: + +* For a `custom radio`_, use ``radio-custom``; +* For a `custom checkbox`_, use ``checkbox-custom``; +* For having a `switch instead of a checkbox`_, use ``switch-custom``. .. code-block:: twig {{ form_row(form.myRadio, {label_attr: {class: 'radio-custom'} }) }} {{ form_row(form.myCheckbox, {label_attr: {class: 'checkbox-custom'} }) }} + {{ form_row(form.myCheckbox, {label_attr: {class: 'switch-custom'} }) }} Labels and Errors ----------------- @@ -112,4 +120,7 @@ is a strong connection between the error and its ````, as required by the `WCAG 2.0 standard`_. .. _`WCAG 2.0 standard`: https://www.w3.org/TR/WCAG20/ -.. _`custom forms`: https://getbootstrap.com/docs/4.1/components/forms/#custom-forms +.. _`custom forms`: https://getbootstrap.com/docs/4.3/components/forms/#custom-forms +.. _`custom radio`: https://getbootstrap.com/docs/4.3/components/forms/#radios +.. _`custom checkbox`: https://getbootstrap.com/docs/4.3/components/forms/#checkboxes +.. _`switch instead of a checkbox`: https://getbootstrap.com/docs/4.3/components/forms/#switches diff --git a/logging.rst b/logging.rst index 324be442433..633cd90ad8d 100644 --- a/logging.rst +++ b/logging.rst @@ -371,6 +371,7 @@ Learn more logging/channels_handlers logging/formatter logging/processors + logging/handlers logging/monolog_exclude_http_codes logging/monolog_console diff --git a/logging/handlers.rst b/logging/handlers.rst new file mode 100644 index 00000000000..1682040e504 --- /dev/null +++ b/logging/handlers.rst @@ -0,0 +1,105 @@ +Handlers +======== + +ElasticsearchLogstashHandler +---------------------------- + +.. versionadded:: 4.4 + + The ``ElasticsearchLogstashHandler`` was introduced in Symfony 4.4. + +This handler deals directly with the HTTP interface of Elasticsearch. This means +it will slow down your application if Elasticsearch takes times to answer. Even +if all HTTP calls are done asynchronously. + +In a development environment, it's fine to keep the default configuration: for +each log, an HTTP request will be made to push the log to Elasticsearch. + +In a production environment, it's highly recommended to wrap this handler in a +handler with buffering capabilities (like the ``FingersCrossedHandler`` or +``BufferHandler``) in order to call Elasticsearch only once with a bulk push. For +even better performance and fault tolerance, a proper `ELK stack`_ is recommended. + +To use it, declare it as a service: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + Symfony\Bridge\Monolog\Handler\ElasticsearchLogstashHandler: ~ + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/services.php + use Symfony\Bridge\Monolog\Handler\ElasticsearchLogstashHandler; + + $container->register(ElasticsearchLogstashHandler::class); + +Then reference it in the Monolog configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/prod/monolog.yaml + monolog: + handlers: + es: + type: service + id: Symfony\Bridge\Monolog\Handler\ElasticsearchLogstashHandler + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/packages/prod/monolog.php + use Symfony\Bridge\Monolog\Handler\ElasticsearchLogstashHandler; + + $container->loadFromExtension('monolog', [ + 'handlers' => [ + 'es' => [ + 'type' => 'service', + 'id' => ElasticsearchLogstashHandler::class, + ], + ], + ]); + +.. _`ELK stack`: https://www.elastic.co/what-is/elk-stack diff --git a/mailer.rst b/mailer.rst index aecb585eb87..acf196c0ebc 100644 --- a/mailer.rst +++ b/mailer.rst @@ -3,17 +3,12 @@ Sending Emails with Mailer .. versionadded:: 4.3 - The Mailer component was added in Symfony 4.3 and is currently experimental. - The previous solution - Swift Mailer - is still valid: :doc:`Swift Mailer`. + The Mailer component was introduced in Symfony 4.3. The previous solution, + called Swift Mailer, is still valid: :doc:`Swift Mailer`. Installation ------------ -.. caution:: - - The Mailer component is experimental in Symfony 4.3: some backwards compatibility - breaks could occur before 4.4. - Symfony's Mailer & :doc:`Mime ` components form a *powerful* system for creating and sending emails - complete with support for multipart messages, Twig integration, CSS inlining, file attachments and a lot more. Get them installed with: @@ -68,22 +63,28 @@ You'll now have a new line in your ``.env`` file that you can uncomment: .. code-block:: bash # .env - SENDGRID_KEY= - MAILER_DSN=smtp://$SENDGRID_KEY@sendgrid + MAILER_DSN=sendgrid://KEY@default -The ``MAILER_DSN`` isn't a *real* SMTP address: it's a simple format that offloads -most of the configuration work to mailer. The ``@sendgrid`` part of the address -activates the SendGrid mailer library that you just installed, which knows all -about how to deliver messages to SendGrid. +The ``MAILER_DSN`` isn't a *real* address: it's a simple format that offloads +most of the configuration work to mailer. The ``sendgrid`` scheme activates the +SendGrid provider that you just installed, which knows all about how to deliver +messages to SendGrid. -The *only* part you need to change is to set ``SENDGRID_KEY`` to your key (in +The *only* part you need to change is to replace ``KEY`` in the ``MAILER_DSN`` (in ``.env`` or ``.env.local``). -Each transport will have different environment variables that the library will use -to configure the *actual* address and authentication for delivery. Some also have -options that can be configured with query parameters on end of the ``MAILER_DSN`` - -like ``?region=`` for Amazon SES. Some transports support sending via ``http`` -or ``smtp`` - both work the same, but ``http`` is recommended when available. +Each provider has different environment variables that the Mailer uses to +configure the *actual* protocol, address and authentication for delivery. Some +also have options that can be configured with query parameters at the end of the +``MAILER_DSN`` - like ``?region=`` for Amazon SES. Some providers support +sending via ``http``, ``api`` or ``smtp``. Symfony chooses the best available +transport, but you can force to use one: + +.. code-block:: bash + + # .env + # force to use SMTP instead of HTTP (which is the default) + MAILER_DSN=sendgrid+smtp://$SENDGRID_KEY@default .. tip:: @@ -137,7 +138,6 @@ both strings or address objects:: // ... use Symfony\Component\Mime\Address; - use Symfony\Component\Mime\NamedAddress; $email = (new Email()) // email address as a simple string @@ -148,7 +148,11 @@ both strings or address objects:: // defining the email address and name as an object // (email clients will display the name) - ->from(new NamedAddress('fabien@example.com', 'Fabien')) + ->from(new Address('fabien@example.com', 'Fabien')) + + // defining the email address and name as a string + // (the format must match: 'Name ') + ->from(Address::fromString('Fabien Potencier ')) // ... ; @@ -258,6 +262,23 @@ images inside the HTML contents:: ->html(' ... ...') ; +Debugging Emails +---------------- + +The :class:`Symfony\\Component\\Mailer\\SentMessage` object returned by the +``send()`` method of the :class:`Symfony\\Component\\Mailer\\Transport\\TransportInterface` +provides access to the original message (``getOriginalMessage()``) and to some +debug information (``getDebug()``) such as the HTTP calls done by the HTTP +transports, which is useful to debug errors. + +The exceptions related to mailer transports (those which implement +:class:`Symfony\\Component\\Mailer\\Exception\\TransportException`) also provide +this debug information via the ``getDebug()`` method. + +.. versionadded:: 4.4 + + The ``getDebug()`` methods were introduced in Symfony 4.4. + .. _mailer-twig: Twig: HTML & CSS @@ -495,8 +516,8 @@ the extension in your application: $ composer require twig/extra-bundle twig/inky-extra -The extension adds an ``inky_to_html`` filter, which can be used to convert parts -or the entire email contents from Inky to HTML: +The extension adds an ``inky_to_html`` filter, which can be used to convert +parts or the entire email contents from Inky to HTML: .. code-block:: html+twig @@ -525,6 +546,102 @@ This makes use of the :ref:`css Twig namespace ` we create earlier. You could, for example, `download the foundation-emails.css file`_ directly from GitHub and save it in ``assets/css``. +Signing and Encrypting Messages +------------------------------- + +.. versionadded:: 4.4 + + The option to sign and/or encrypt messages was introduced in Symfony 4.4. + +It's possible to sign and/or encrypt email messages applying the `S/MIME`_ +standard to increase their integrity/security. Both options can be combined to +encrypt a signed message and/or to sign an encrypted message. + +Before signing/encrypting messages, make sure to have: + +* The `OpenSSL PHP extension`_ properly installed and configured; +* A valid S/MIME security certificate. + +Signing Messages +~~~~~~~~~~~~~~~~ + +When signing a message, a cryptographic hash is generated for the entire content +of the message (including attachments). This hash is added as an attachment so +the recipient can validate the integrity of the received message. However, the +contents of the original message are still readable for mailing agents not +supporting signed messages, so you must also encrypt the message if you want to +hide its contents:: + + use Symfony\Component\Mime\Crypto\SMimeSigner; + use Symfony\Component\Mime\Email; + + $email = (new Email()) + ->from('hello@example.com') + // ... + ->html('...'); + + $signer = new SMimeSigner('/path/to/certificate.crt', '/path/to/certificate-private-key.key'); + // if the private key has a passphrase, pass it as the third argument + // new SMimeSigner('/path/to/certificate.crt', '/path/to/certificate-private-key.key', 'the-passphrase'); + + $signedEmail = $signer->sign($email); + // now use the Mailer component to send this $signedEmail instead of the original email + +The certificate and private key must be `PEM encoded`_, and can be either +created using for example OpenSSL or obtained at an official Certificate +Authority (CA). The email recipient must have the CA certificate in the list of +trusted issuers in order to verify the signature. + +.. tip:: + + When using OpenSSL to generate certificates, make sure to add the + ``-addtrust emailProtection`` command option. + +.. tip:: + + The ``SMimeSigner`` class defines other optional arguments to pass + intermediate certificates and to configure the signing process using a + bitwise operator options for :phpfunction:`openssl_pkcs7_sign` PHP function. + +Encrypting Messages +~~~~~~~~~~~~~~~~~~~ + +When encrypting a message, the entire message (including attachments) is +encrypted using a certificate. Therefore, only the recipients that have the +corresponding private key can read the original message contents:: + + use Symfony\Component\Mime\Crypto\SMimeEncrypter; + use Symfony\Component\Mime\Email; + + $email = (new Email()) + ->from('hello@example.com') + // ... + ->html('...'); + + $encrypter = new SMimeEncrypter('/path/to/certificate.crt'); + $encryptedEmail = $encrypter->encrypt($email); + // now use the Mailer component to send this $encryptedEmail instead of the original email + +You can pass more than one certificate to the ``SMimeEncrypter()`` constructor +and it will select the appropriate certificate depending on the ``To`` option:: + + $firstEmail = (new Email()) + // ... + ->to('jane@example.com'); + + $secondEmail = (new Email()) + // ... + ->to('john@example.com'); + + $encrypter = new SMimeEncrypter([ + // key = email recipient; value = path to the certificate file + 'jane@example.com' => '/path/to/first-certificate.crt', + 'john@example.com' => '/path/to/second-certificate.crt', + ]); + + $firstEncryptedEmail = $encrypter->encrypt($firstEmail); + $secondEncryptedEmail = $encrypter->encrypt($secondEmail); + Sending Messages Async ---------------------- @@ -586,6 +703,36 @@ you have a transport called ``async``, you can route the message there: Thanks to this, instead of being delivered immediately, messages will be sent to the transport to be handled later (see :ref:`messenger-worker`). +Multiple Email Transports +------------------------- + +.. versionadded:: 4.4 + + The option to define multiple email transports was introduced in Symfony 4.4. + +You may want to use more than one mailer transport for delivery of your messages. +This can be configured by replacing the ``dsn`` configuration entry with a +``transports`` entry, like: + +.. code-block:: yaml + + # config/packages/mailer.yaml + framework: + mailer: + transports: + main: '%env(MAILER_DSN)%' + important: '%env(MAILER_DSN_IMPORTANT)%' + +By default the first transport is used. The other transports can be used by +adding a text header ``X-Transport`` to an email:: + + // Send using first "main" transport ... + $mailer->send($email); + + // ... or use the "important" one + $email->getHeaders()->addTextHeader('X-Transport', 'important'); + $mailer->send($email); + Development & Debugging ----------------------- @@ -601,7 +748,7 @@ environment: # config/packages/dev/mailer.yaml framework: mailer: - dsn: 'smtp://null' + dsn: 'null://null' .. note:: @@ -631,3 +778,6 @@ environment: .. _`league/html-to-markdown`: https://github.com/thephpleague/html-to-markdown .. _`Markdown syntax`: https://commonmark.org/ .. _`Inky`: https://foundation.zurb.com/emails.html +.. _`S/MIME`: https://en.wikipedia.org/wiki/S/MIME +.. _`OpenSSL PHP extension`: https://php.net/manual/en/book.openssl.php +.. _`PEM encoded`: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail diff --git a/mercure.rst b/mercure.rst index 63865613a72..bc2c471570a 100644 --- a/mercure.rst +++ b/mercure.rst @@ -283,10 +283,10 @@ by using the ``AbstractController::addLink`` helper method:: // src/Controller/DiscoverController.php namespace App\Controller; - use Fig\Link\Link; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\WebLink\LInk; class DiscoverController extends AbstractController { @@ -396,12 +396,12 @@ And here is the controller:: // src/Controller/DiscoverController.php namespace App\Controller; - use Fig\Link\Link; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Hmac\Sha256; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\WebLink\Link; class DiscoverController extends AbstractController { diff --git a/messenger.rst b/messenger.rst index 5cc5bbdd4e1..b0abd46d831 100644 --- a/messenger.rst +++ b/messenger.rst @@ -896,21 +896,21 @@ The Redis transport uses `streams`_ to queue messages. # .env MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages # Full DSN Example - MESSENGER_TRANSPORT_DSN=redis://password@localhost:6379/messages/symfony/consumer?auto_setup=true&serializer=1 + MESSENGER_TRANSPORT_DSN=redis://password@localhost:6379/messages/symfony/consumer?auto_setup=true&serializer=1&stream_max_entries=0&dbindex=0 -To use the Redis transport, you will need the Redis PHP extension (^4.3) and -a running Redis server (^5.0). +.. versionadded:: 4.4 -.. caution:: + The ``dbindex`` query parameter in Redis DSN was introduced in Symfony 4.4. - The Redis transport does not support "delayed" messages. +To use the Redis transport, you will need the Redis PHP extension (>=4.3) and +a running Redis server (^5.0). A number of options can be configured via the DSN or via the ``options`` key under the transport in ``messenger.yaml``: -================== ===================================== ======= +================== ===================================== ========================= Option Description Default -================== ===================================== ======= +================== ===================================== ========================= stream The Redis stream name messages group The Redis consumer group name symfony consumer Consumer name used in Redis consumer @@ -919,7 +919,15 @@ auth The Redis password serializer How to serialize the final payload ``Redis::SERIALIZER_PHP`` in Redis (the ``Redis::OPT_SERIALIZER`` option) -================== ===================================== ======= +stream_max_entries The maximum number of entries which ``0`` (which means "no trimming") + the stream will be trimmed to. Set + it to a large enough number to + avoid losing pending messages +================== ===================================== ========================= + +.. versionadded:: 4.4 + + The ``stream_max_entries`` option was introduced in Symfony 4.4. In Memory Transport ~~~~~~~~~~~~~~~~~~~ @@ -962,7 +970,7 @@ during a request:: /* @var InMemoryTransport $transport */ $transport = self::$container->get('messenger.transport.async_priority_normal'); - $this->assertCount(1, $transport->get()); + $this->assertCount(1, $transport->getSent()); } } @@ -1040,8 +1048,6 @@ by tagging the handler service with ``messenger.message_handler`` # only needed if can't be guessed by type-hint handles: App\Message\SmsNotification - # options returned by getHandledMessages() are supported here - .. code-block:: xml @@ -1053,7 +1059,9 @@ by tagging the handler service with ``messenger.message_handler`` - + + @@ -1061,10 +1069,27 @@ by tagging the handler service with ``messenger.message_handler`` .. code-block:: php // config/services.php + use App\Message\SmsNotification; use App\MessageHandler\SmsNotificationHandler; $container->register(SmsNotificationHandler::class) - ->addTag('messenger.message_handler'); + ->addTag('messenger.message_handler', [ + // only needed if can't be guessed by type-hint + 'handles' => SmsNotification::class, + ]); + + +Possible options to configure with tags are: + +* ``bus`` +* ``from_transport`` +* ``handles`` +* ``method`` +* ``priority`` + +.. versionadded:: 4.4 + + The ability to specify ``from_transport`` on the tag, was added in Symfony 4.4. Handler Subscriber & Options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1455,10 +1480,12 @@ In addition to middleware, Messenger also dispatches several events. You can :doc:`create an event listener ` to hook into various parts of the process. For each, the event class is the event name: +* :class:`Symfony\\Component\\Messenger\\Event\\WorkerStartedEvent` +* :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent` * :class:`Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent` * :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent` * :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent` -* :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent` +* :class:`Symfony\\Component\\Messenger\\Event\\WorkerRunningEvent` * :class:`Symfony\\Component\\Messenger\\Event\\WorkerStoppedEvent` Multiple Buses, Command & Event Buses diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 57cac38bf17..d0cfd613590 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -108,6 +108,7 @@ Configuration * `proxy`_ * `resolve`_ * `timeout`_ + * `max_duration`_ * `verify_host`_ * `verify_peer`_ @@ -117,6 +118,7 @@ Configuration * `scope`_ * `auth_basic`_ * `auth_bearer`_ + * `auth_ntlm`_ * `base_uri`_ * `bindto`_ * `buffer`_ @@ -135,6 +137,7 @@ Configuration * `query`_ * `resolve`_ * `timeout`_ + * `max_duration`_ * `verify_host`_ * `verify_peer`_ @@ -230,6 +233,7 @@ Configuration * `test`_ * `translator`_ + * `cache_dir`_ * :ref:`default_path ` * :ref:`enabled ` * `fallbacks`_ @@ -759,6 +763,20 @@ auth_bearer The token used to create the ``Authorization`` HTTP header used in HTTP Bearer authentication (also called token authentication). +auth_ntlm +......... + +**type**: ``string`` + +.. versionadded:: 4.4 + + The ``auth_ntlm`` option was introduced in Symfony 4.4. + +The username and password used to create the ``Authorization`` HTTP header used +in the `Microsoft NTLM authentication protocol`_. The value of this option must +follow the format ``username:password``. This authentication mechanism requires +using the cURL-based transport. + base_uri ........ @@ -793,11 +811,20 @@ outgoing network interface. buffer ...... -**type**: ``bool`` **default**: ``false`` +**type**: ``bool`` | ``Closure`` Buffering the response means that you can access its content multiple times -without performing the request again. Pass ``true`` as the value of this option -to enable buffering. +without performing the request again. Buffering is enabled by default when the +content type of the response is ``text/*``, ``application/json`` or ``application/xml``. + +If this option is a boolean value, the response is buffered when the value is +``true``. If this option is a closure, the response is buffered when the +returned value is ``true`` (the closure receives as argument an array with the +response headers). + +.. versionadded:: 4.4 + + The support of ``Closure`` in the ``buffer`` option was introduced in Symfony 4.4. cafile ...... @@ -951,6 +978,18 @@ Time, in seconds, to wait for a response. If the response stales for longer, a Its default value is the same as the value of PHP's `default_socket_timeout`_ config option. +max_duration +............ + +**type**: ``float`` **default**: 0 + +The maximum execution time, in seconds, that the request and the response are +allowed to take. A value lower than or equal to 0 means it is unlimited. + +.. versionadded:: 4.4 + + The ``max_duration`` option was introduced in Symfony 4.4. + verify_host ........... @@ -2068,6 +2107,18 @@ implement :class:`Symfony\\Component\\Templating\\Loader\\LoaderInterface`. translator ~~~~~~~~~~ +cache_dir +......... + +**type**: ``string`` | ``null`` **default**: ``%kernel.cache_dir%/translations/`` + +.. versionadded:: 4.4 + + The ``cache_dir`` option was introduced in Symfony 4.4. + +Defines the directory where the translation cache is stored. Use ``null`` to +disable this cache. + .. _reference-translator-enabled: enabled @@ -3042,4 +3093,5 @@ to know their differences. .. _`PEM formatted`: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail .. _`haveibeenpwned.com`: https://haveibeenpwned.com/ .. _`session.cache-limiter`: https://www.php.net/manual/en/session.configuration.php#ini.session.cache-limiter +.. _`Microsoft NTLM authentication protocol`: https://docs.microsoft.com/en-us/windows/desktop/secauthn/microsoft-ntlm .. _`utf-8 modifier`: https://www.php.net/reference.pcre.pattern.modifiers diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst index 8a61619a9fd..5ee1126110a 100644 --- a/reference/constraints/File.rst +++ b/reference/constraints/File.rst @@ -242,6 +242,20 @@ of given mime types (if an array). You can find a list of existing mime types on the `IANA website`_. +.. note:: + + When using this constraint on a :doc:`FileType field `, + the value of the ``mimeTypes`` option is also used in the ``accept`` + attribute of the related ```` HTML element. + + This behavior is applied only when using :ref:`form type guessing ` + (i.e. the form type is not defined explicitly in the ``->add()`` method of + the form builder) and when the field doesn't define its own ``accept`` value. + + .. versionadded:: 4.4 + + This feature was introduced in Symfony 4.4. + mimeTypesMessage ~~~~~~~~~~~~~~~~ diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst index 9b73066b700..b27f43ffbf1 100644 --- a/reference/constraints/Length.rst +++ b/reference/constraints/Length.rst @@ -3,15 +3,10 @@ Length Validates that a given string length is *between* some minimum and maximum value. -.. caution:: - - ``null`` and empty strings are not handled by this constraint. You need to - also add the :doc:`/reference/constraints/NotBlank` or :doc:`/reference/constraints/NotNull` - constraints to validate against these. - ========== =================================================================== Applies to :ref:`property or method ` -Options - `charset`_ +Options - `allowEmptyString`_ + - `charset`_ - `charsetMessage`_ - `exactMessage`_ - `groups`_ @@ -115,6 +110,20 @@ and "50", you might add the following: Options ------- +allowEmptyString +~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +.. versionadded:: 4.4 + + The ``allowEmptyString`` option was introduced in Symfony 4.4. + +When using the ``min`` option, it's mandatory to also define this option. If +set to ``true``, empty strings are considered valid (which is the same behavior +as previous Symfony versions). Set it to ``false`` to consider empty strings not +valid. + charset ~~~~~~~ diff --git a/reference/constraints/Range.rst b/reference/constraints/Range.rst index 9b8fab33d69..1d2ee4d077f 100644 --- a/reference/constraints/Range.rst +++ b/reference/constraints/Range.rst @@ -9,8 +9,11 @@ Options - `groups`_ - `invalidMessage`_ - `max`_ - `maxMessage`_ + - `maxPropertyPath`_ - `min`_ - `minMessage`_ + - `minPropertyPath`_ + - `notInRangeMessage`_ - `payload`_ Class :class:`Symfony\\Component\\Validator\\Constraints\\Range` Validator :class:`Symfony\\Component\\Validator\\Constraints\\RangeValidator` @@ -358,6 +361,28 @@ Parameter Description ``{{ value }}`` The current (invalid) value =============== ============================================================== +maxPropertyPath +~~~~~~~~~~~~~~~ + +**type**: ``string`` + +.. versionadded:: 4.4 + + The ``maxPropertyPath`` option was introduced in Symfony 4.4. + +It defines the object property whose value is used as `max`_ option. + +For example, if you want to compare the ``$submittedDate`` property of some object +with regard to the ``$deadline`` property of the same object, use +``maxPropertyPath="deadline"`` in the range constraint of ``$submittedDate``. + +.. tip:: + + When using this option, its value is available in error messages as the + ``{{ max_limit_path }}`` placeholder. Although it's not intended to + include it in the error messages displayed to end users, it's useful when + using APIs for doing any mapping logic on client-side. + min ~~~ @@ -383,6 +408,50 @@ Parameter Description ``{{ value }}`` The current (invalid) value =============== ============================================================== +minPropertyPath +~~~~~~~~~~~~~~~ + +**type**: ``string`` + +.. versionadded:: 4.4 + + The ``minPropertyPath`` option was introduced in Symfony 4.4. + +It defines the object property whose value is used as `min`_ option. + +For example, if you want to compare the ``$endDate`` property of some object +with regard to the ``$startDate`` property of the same object, use +``minPropertyPath="startDate"`` in the range constraint of ``$endDate``. + +.. tip:: + + When using this option, its value is available in error messages as the + ``{{ min_limit_path }}`` placeholder. Although it's not intended to + include it in the error messages displayed to end users, it's useful when + using APIs for doing any mapping logic on client-side. + +notInRangeMessage +~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be between {{ min }} and {{ max }}.`` + +.. versionadded:: 4.4 + + The ``notInRangeMessage`` option was introduced in Symfony 4.4. + +The message that will be shown if the underlying value is less than the +`min`_ option or greater than the `max`_ option. + +You can use the following parameters in this message: + +=============== ============================================================== +Parameter Description +=============== ============================================================== +``{{ max }}`` The upper limit +``{{ min }}`` The lower limit +``{{ value }}`` The current (invalid) value +=============== ============================================================== + .. include:: /reference/constraints/_payload-option.rst.inc .. _`is_numeric`: https://php.net/manual/en/function.is-numeric.php diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst index 0b9f8f7e715..dfe96f8655e 100644 --- a/reference/constraints/Type.rst +++ b/reference/constraints/Type.rst @@ -18,8 +18,11 @@ Validator :class:`Symfony\\Component\\Validator\\Constraints\\TypeValidator` Basic Usage ----------- -This will check that ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, -``firstName`` is of type ``string`` and ``age`` is an ``integer``. +This will check if ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, +``firstName`` is of type ``string`` (using :phpfunction:`is_string` PHP function), +``age`` is an ``integer`` (using :phpfunction:`is_int` PHP function) and +``accessCode`` contains either only letters or only digits (using +:phpfunction:`ctype_alpha` and :phpfunction:`ctype_digit` PHP functions). .. configuration-block:: @@ -49,6 +52,11 @@ This will check that ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, * ) */ protected $age; + + /** + * @Assert\Type(type={"alpha", "digit"}) + */ + protected $accessCode; } .. code-block:: yaml @@ -67,6 +75,10 @@ This will check that ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, type: integer message: The value {{ value }} is not a valid {{ type }}. + accessCode: + - Type: + type: [alpha, digit] + .. code-block:: xml @@ -92,6 +104,14 @@ This will check that ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, + + + + + @@ -116,9 +136,18 @@ This will check that ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, 'type' => 'integer', 'message' => 'The value {{ value }} is not a valid {{ type }}.', ])); + + $metadata->addPropertyConstraint('accessCode', new Assert\Type([ + 'type' => ['alpha', 'digit'], + ])); } } +.. versionadded:: 4.4 + + The feature to define multiple types in the ``type`` option was introduced + in Symfony 4.4. + Options ------- @@ -147,10 +176,16 @@ Parameter Description type ~~~~ -**type**: ``string`` [:ref:`default option `] +**type**: ``string`` or ``array`` [:ref:`default option `] + +.. versionadded:: 4.4 + + The feature to define multiple types in the ``type`` option was introduced + in Symfony 4.4. -This required option is either the FQCN (fully qualified class name) of some PHP -class/interface or a valid PHP datatype (checked by PHP's ``is_()`` functions): +This required option defines the type or collection of types allowed for the +given value. Each type is either the FQCN (fully qualified class name) of some +PHP class/interface or a valid PHP datatype (checked by PHP's ``is_()`` functions): * :phpfunction:`array ` * :phpfunction:`bool ` @@ -169,7 +204,7 @@ class/interface or a valid PHP datatype (checked by PHP's ``is_()`` functions): * :phpfunction:`scalar ` * :phpfunction:`string ` -Also, you can use ``ctype_()`` functions from corresponding +Also, you can use ``ctype_*()`` functions from corresponding `built-in PHP extension`_. Consider `a list of ctype functions`_: * :phpfunction:`alnum ` diff --git a/reference/constraints/_comparison-propertypath-option.rst.inc b/reference/constraints/_comparison-propertypath-option.rst.inc index a2acc52ef5c..66932a0e983 100644 --- a/reference/constraints/_comparison-propertypath-option.rst.inc +++ b/reference/constraints/_comparison-propertypath-option.rst.inc @@ -8,3 +8,14 @@ It defines the object property whose value is used to make the comparison. For example, if you want to compare the ``$endDate`` property of some object with regard to the ``$startDate`` property of the same object, use ``propertyPath="startDate"`` in the comparison constraint of ``$endDate``. + +.. tip:: + + When using this option, its value is available in error messages as the + ``{{ compared_value_path }}`` placeholder. Although it's not intended to + include it in the error messages displayed to end users, it's useful when + using APIs for doing any mapping logic on client-side. + + .. versionadded:: 4.4 + + The ``{{ compared_value_path }}`` placeholder was introduced in Symfony 4.4. diff --git a/reference/events.rst b/reference/events.rst index d6d7b669573..b7eec4d8dbd 100644 --- a/reference/events.rst +++ b/reference/events.rst @@ -239,14 +239,14 @@ sent as response:: public function onKernelException(ExceptionEvent $event) { - $exception = $event->getException(); + $exception = $event->getThrowable(); $response = new Response(); // setup the Response object based on the caught exception $event->setResponse($response); // you can alternatively set a new Exception // $exception = new \Exception('Some special exception'); - // $event->setException($exception); + // $event->setThrowable($exception); } .. note:: diff --git a/reference/forms/types.rst b/reference/forms/types.rst index 54cb8656707..49d769c8967 100644 --- a/reference/forms/types.rst +++ b/reference/forms/types.rst @@ -35,6 +35,7 @@ Form Types Reference types/datetime types/time types/birthday + types/week types/checkbox types/file diff --git a/reference/forms/types/country.rst b/reference/forms/types/country.rst index da066decbb9..9b32db9f985 100644 --- a/reference/forms/types/country.rst +++ b/reference/forms/types/country.rst @@ -21,7 +21,8 @@ the option manually, but then you should just use the ``ChoiceType`` directly. +-------------+-----------------------------------------------------------------------+ | Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | +-------------+-----------------------------------------------------------------------+ -| Options | - `choice_translation_locale`_ | +| Options | - `alpha3`_ | +| | - `choice_translation_locale`_ | +-------------+-----------------------------------------------------------------------+ | Overridden | - `choices`_ | | options | | @@ -62,6 +63,19 @@ the option manually, but then you should just use the ``ChoiceType`` directly. Field Options ------------- +alpha3 +~~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +.. versionadded:: 4.4 + + The ``alpha3`` option was introduced in Symfony 4.4. + +If this option is ``true``, the choice values use the `ISO 3166-1 alpha-3`_ +three-letter codes (e.g. New Zealand = ``NZL``) instead of the default +`ISO 3166-1 alpha-2`_ two-letter codes (e.g. New Zealand = ``NZ``). + .. include:: /reference/forms/types/options/choice_translation_locale.rst.inc Overridden Options @@ -136,3 +150,6 @@ The actual default value of this option depends on other field options: .. include:: /reference/forms/types/options/required.rst.inc .. include:: /reference/forms/types/options/row_attr.rst.inc + +.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 +.. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 diff --git a/reference/forms/types/language.rst b/reference/forms/types/language.rst index 9a751ecc4af..cebc36a183e 100644 --- a/reference/forms/types/language.rst +++ b/reference/forms/types/language.rst @@ -23,7 +23,8 @@ manually, but then you should just use the ``ChoiceType`` directly. +-------------+------------------------------------------------------------------------+ | Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | +-------------+------------------------------------------------------------------------+ -| Options | - `choice_translation_locale`_ | +| Options | - `alpha3`_ | +| | - `choice_translation_locale`_ | +-------------+------------------------------------------------------------------------+ | Overridden | - `choices`_ | | options | | @@ -64,6 +65,19 @@ manually, but then you should just use the ``ChoiceType`` directly. Field Options ------------- +alpha3 +~~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +.. versionadded:: 4.4 + + The ``alpha3`` option was introduced in Symfony 4.4. + +If this option is ``true``, the choice values use the `ISO 639-2 alpha-3`_ +three-letter codes (e.g. French = ``fra``) instead of the default +`ISO 639-1 alpha-2`_ two-letter codes (e.g. French = ``fr``). + .. include:: /reference/forms/types/options/choice_translation_locale.rst.inc Overridden Options @@ -139,4 +153,6 @@ The actual default value of this option depends on other field options: .. include:: /reference/forms/types/options/row_attr.rst.inc +.. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1 +.. _`ISO 639-2 alpha-3`: https://en.wikipedia.org/wiki/ISO_639-2 .. _`International Components for Unicode`: http://site.icu-project.org diff --git a/reference/forms/types/map.rst.inc b/reference/forms/types/map.rst.inc index b6ef2728ff9..4036f2f7dce 100644 --- a/reference/forms/types/map.rst.inc +++ b/reference/forms/types/map.rst.inc @@ -34,6 +34,7 @@ Date and Time Fields * :doc:`DateTimeType ` * :doc:`TimeType ` * :doc:`BirthdayType ` +* :doc:`WeekType ` Other Fields ~~~~~~~~~~~~ diff --git a/reference/forms/types/options/preferred_choices.rst.inc b/reference/forms/types/options/preferred_choices.rst.inc index 3d5d8567543..48adabbb78a 100644 --- a/reference/forms/types/options/preferred_choices.rst.inc +++ b/reference/forms/types/options/preferred_choices.rst.inc @@ -3,9 +3,9 @@ preferred_choices **type**: ``array``, ``callable`` or ``string`` **default**: ``[]`` -This option allows you to move certain choices to the top of your list with a visual -separator between them and the rest of the options. If you have a form of languages, -you can list the most popular on top, like Bork Bork and Pirate:: +This option allows you to display certain choices at the top of your list with a +visual separator between them and the complete list of options. If you have a +form of languages, you can list the most popular on top, like Bork Bork and Pirate:: use Symfony\Component\Form\Extension\Core\Type\ChoiceType; // ... @@ -20,6 +20,12 @@ you can list the most popular on top, like Bork Bork and Pirate:: 'preferred_choices' => ['muppets', 'arr'], ]); +.. versionadded:: 4.4 + + Starting from Symfony 4.4, the preferred choices are displayed both at the + top of the list and at their original locations on the list. In prior + Symfony versions, they were only displayed at the top of the list. + This options can also be a callback function to give you more flexibility. This might be especially useful if your values are objects:: diff --git a/reference/forms/types/options/weeks.rst.inc b/reference/forms/types/options/weeks.rst.inc new file mode 100644 index 00000000000..fb9e2d8d9b5 --- /dev/null +++ b/reference/forms/types/options/weeks.rst.inc @@ -0,0 +1,7 @@ +weeks +~~~~~ + +**type**: ``array`` **default**: 1 to 53 + +List of weeks available to the week field type. This option is only relevant +when the ``widget`` option is set to ``choice``. diff --git a/reference/forms/types/submit.rst b/reference/forms/types/submit.rst index d3897a84f9c..7c6c5d69b1c 100644 --- a/reference/forms/types/submit.rst +++ b/reference/forms/types/submit.rst @@ -9,6 +9,8 @@ A submit button. +----------------------+----------------------------------------------------------------------+ | Rendered as | ``button`` ``submit`` tag | +----------------------+----------------------------------------------------------------------+ +| Options | - `validate`_ | ++----------------------+----------------------------------------------------------------------+ | Inherited | - `attr`_ | | options | - `attr_translation_parameters`_ | | | - `disabled`_ | @@ -35,6 +37,21 @@ useful when :doc:`a form has multiple submit buttons `:: // ... } +Options +------- + +validate +~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +.. versionadded:: 4.4 + + The ``validate`` option was introduced in Symfony 4.4. + +Set this option to ``false`` to disable the client-side validation of the form +performed by the browser. + Inherited Options ----------------- diff --git a/reference/forms/types/week.rst b/reference/forms/types/week.rst new file mode 100644 index 00000000000..754139c9cd6 --- /dev/null +++ b/reference/forms/types/week.rst @@ -0,0 +1,184 @@ +.. index:: + single: Forms; Fields; WeekType + +WeekType Field +============== + +.. versionadded:: 4.4 + + The ``WeekType`` type was introduced in Symfony 4.4. + +This field type allows the user to modify data that represents a specific +`ISO 8601`_ week number (e.g. ``1984-W05``). + +Can be rendered as a text input or select tags. The underlying format of +the data can be a string or an array. + ++----------------------+-----------------------------------------------------------------------------+ +| Underlying Data Type | can be a string, or array (see the ``input`` option) | ++----------------------+-----------------------------------------------------------------------------+ +| Rendered as | single text box, two text boxes or two select fields | ++----------------------+-----------------------------------------------------------------------------+ +| Options | - `choice_translation_domain`_ | +| | - `placeholder`_ | +| | - `html5`_ | +| | - `input`_ | +| | - `widget`_ | +| | - `weeks`_ | +| | - `years`_ | ++----------------------+-----------------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | ++----------------------+-----------------------------------------------------------------------------+ +| Inherited | - `attr`_ | +| options | - `data`_ | +| | - `disabled`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `invalid_message`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++----------------------+-----------------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++----------------------+-----------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\WeekType` | ++----------------------+-----------------------------------------------------------------------------+ + +.. include:: /reference/forms/types/options/_debug_form.rst.inc + +Field Options +------------- + +.. include:: /reference/forms/types/options/choice_translation_domain.rst.inc + +placeholder +~~~~~~~~~~~ + +**type**: ``string`` | ``array`` + +If your widget option is set to ``choice``, then this field will be represented +as a series of ``select`` boxes. When the placeholder value is a string, +it will be used as the **blank value** of all select boxes:: + + use Symfony\Component\Form\Extension\Core\Type\WeekType; + + $builder->add('startWeek', WeekType::class, [ + 'placeholder' => 'Select a value', + ]); + +Alternatively, you can use an array that configures different placeholder +values for the year and week fields:: + + use Symfony\Component\Form\Extension\Core\Type\WeekType; + + $builder->add('startDateTime', WeekType::class, [ + 'placeholder' => [ + 'year' => 'Year', + 'week' => 'Week', + ] + ]); + +.. include:: /reference/forms/types/options/html5.rst.inc + +input +~~~~~ + +**type**: ``string`` **default**: ``array`` + +The format of the *input* data - i.e. the format that the date is stored +on your underlying object. Valid values are: + +* ``string`` (e.g. ``"2011-W17"``) +* ``array`` (e.g. ``[2011, 17]``) + +The value that comes back from the form will also be normalized back into +this format. + +widget +~~~~~~ + +**type**: ``string`` **default**: ``choice`` + +The basic way in which this field should be rendered. Can be one of the +following: + +* ``choice``: renders two select inputs; +* ``text``: renders a two field input of type ``text`` (year and week); +* ``single_text``: renders a single input of type ``week``. + +years +~~~~~ + +**type**: ``array`` **default**: ten years before to ten years after the +current year + +List of years available to the year field type. This option is only relevant +when the ``widget`` option is set to ``choice``. + +.. include:: /reference/forms/types/options/weeks.rst.inc + +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/compound_type.rst.inc + +.. include:: /reference/forms/types/options/empty_data.rst.inc + :end-before: DEFAULT_PLACEHOLDER + +The actual default value of this option depends on other field options: + +* If ``widget`` is ``single_text``, then ``''`` (empty string); +* Otherwise ``[]`` (empty array). + +.. include:: /reference/forms/types/options/empty_data.rst.inc + :start-after: DEFAULT_PLACEHOLDER + +error_bubbling +~~~~~~~~~~~~~~ + +**default**: ``false`` + +Inherited Options +----------------- + +These options inherit from the :doc:`FormType `: + +.. include:: /reference/forms/types/options/attr.rst.inc + +.. include:: /reference/forms/types/options/data.rst.inc + +.. include:: /reference/forms/types/options/disabled.rst.inc + +.. include:: /reference/forms/types/options/help.rst.inc + +.. include:: /reference/forms/types/options/help_attr.rst.inc + +.. include:: /reference/forms/types/options/help_html.rst.inc + +.. include:: /reference/forms/types/options/inherit_data.rst.inc + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + +.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc + +.. include:: /reference/forms/types/options/mapped.rst.inc + +.. include:: /reference/forms/types/options/row_attr.rst.inc + +Field Variables +--------------- + ++----------+------------+----------------------------------------------------------------------+ +| Variable | Type | Usage | ++==========+============+======================================================================+ +| widget | ``mixed`` | The value of the `widget`_ option. | ++----------+------------+----------------------------------------------------------------------+ +| type | ``string`` | Only present when widget is ``single_text`` and HTML5 is activated, | +| | | contains the input type to use (``datetime``, ``date`` or ``time``). | ++----------+------------+----------------------------------------------------------------------+ + +.. _`ISO 8601`: https://en.wikipedia.org/wiki/ISO_8601 diff --git a/routing.rst b/routing.rst index f5aebf15337..d7397f16978 100644 --- a/routing.rst +++ b/routing.rst @@ -1100,10 +1100,10 @@ A possible solution is to change the parameter requirements to be more permissiv .. note:: If the route defines several parameter and you apply this permissive - regular expression to all of them, the results won't be the expected. For + regular expression to all of them, you might get unexpected results. For example, if the route definition is ``/share/{path}/{token}`` and both - ``path`` and ``token`` accept ``/``, then ``path`` will contain its contents - and the token, and ``token`` will be empty. + ``path`` and ``token`` accept ``/``. The ``token`` only get the last path + and the rest of the match is matched by the first argument (``path``). .. note:: @@ -1295,8 +1295,7 @@ in the main article about Symfony templates. Redirecting to URLs and Routes Directly from a Route ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use the ``RedirectController`` to redirect to other routes (``redirectAction``) -and URLs (``urlRedirectAction``): +Use the ``RedirectController`` to redirect to other routes and URLs: .. configuration-block:: @@ -1305,7 +1304,7 @@ and URLs (``urlRedirectAction``): # config/routes.yaml doc_shortcut: path: /doc - controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction + controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController defaults: route: 'doc_page' # optionally you can define some arguments passed to the route @@ -1322,7 +1321,7 @@ and URLs (``urlRedirectAction``): legacy_doc: path: /legacy/doc - controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction + controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController defaults: # this value can be an absolute path or an absolute URL path: 'https://legacy.example.com/doc' @@ -1338,7 +1337,7 @@ and URLs (``urlRedirectAction``): https://symfony.com/schema/routing/routing-1.0.xsd"> + controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController"> doc_page index @@ -1354,7 +1353,7 @@ and URLs (``urlRedirectAction``): + controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController"> https://legacy.example.com/doc @@ -1371,7 +1370,7 @@ and URLs (``urlRedirectAction``): return function (RoutingConfigurator $routes) { $routes->add('doc_shortcut', '/doc') - ->controller([RedirectController::class, 'redirectAction']) + ->controller(RedirectController::class) ->defaults([ 'route' => 'doc_page', // optionally you can define some arguments passed to the template @@ -1389,7 +1388,7 @@ and URLs (``urlRedirectAction``): ; $routes->add('legacy_doc', '/legacy/doc') - ->controller([RedirectController::class, 'urlRedirectAction']) + ->controller(RedirectController::class) ->defaults([ // this value can be an absolute path or an absolute URL 'path' => 'https://legacy.example.com/doc', @@ -1404,6 +1403,13 @@ and URLs (``urlRedirectAction``): Symfony also provides some utilities to :ref:`redirect inside controllers ` +.. versionadded:: 4.4 + + In Symfony versions prior to 4.4, you needed to define the specific + ``RedirectController`` method to use (either ``redirectAction`` or + ``urlRedirectAction``). Starting from Symfony 4.4 this is no longer needed + because Symfony detects if the redirection is to a route or an URL. + .. _routing-trailing-slash-redirection: Redirecting URLs with Trailing Slashes diff --git a/routing/custom_route_loader.rst b/routing/custom_route_loader.rst index 60202690f17..8f9be5dee5c 100644 --- a/routing/custom_route_loader.rst +++ b/routing/custom_route_loader.rst @@ -211,6 +211,15 @@ of the service whose ID is ``admin_route_loader``. Your service doesn't have to extend or implement any special class, but the called method must return a :class:`Symfony\\Component\\Routing\\RouteCollection` object. +If you're using :ref:`autoconfigure `, your class should +implement the :class:`Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInterface` +interface to be tagged automatically. If you're **not using autoconfigure**, +tag it manually with ``routing.route_loader``. + +.. deprecated:: 4.4 + + Not tagging or implementing your route loader was deprecated in Symfony 4.4. + .. note:: The routes defined using service route loaders will be automatically diff --git a/security.rst b/security.rst index c91f7babc2f..3bfb384121d 100644 --- a/security.rst +++ b/security.rst @@ -1000,6 +1000,7 @@ Authentication (Identifying/Logging in the User) security/form_login_setup security/json_login_setup security/guard_authentication + security/password_migration security/auth_providers security/user_provider security/ldap diff --git a/security/custom_authentication_provider.rst b/security/custom_authentication_provider.rst index 1a0a78bf226..8c34aaf800f 100644 --- a/security/custom_authentication_provider.rst +++ b/security/custom_authentication_provider.rst @@ -97,8 +97,7 @@ The Listener Next, you need a listener to listen on the firewall. The listener is responsible for fielding requests to the firewall and calling the authentication -provider. A listener must be an instance of -:class:`Symfony\\Component\\Security\\Http\\Firewall\\ListenerInterface`. +provider. Since Symfony 4.3, Listener is a callable, so you have to implement __invoke() method. A security listener should handle the :class:`Symfony\\Component\\HttpKernel\\Event\\RequestEvent` event, and set an authenticated token in the token storage if successful:: @@ -112,9 +111,8 @@ set an authenticated token in the token storage if successful:: use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; - use Symfony\Component\Security\Http\Firewall\ListenerInterface; - class WsseListener implements ListenerInterface + class WsseListener { protected $tokenStorage; protected $authenticationManager; @@ -125,7 +123,7 @@ set an authenticated token in the token storage if successful:: $this->authenticationManager = $authenticationManager; } - public function handle(RequestEvent $event) + public function __invoke(RequestEvent $event) { $request = $event->getRequest(); diff --git a/security/guard_authentication.rst b/security/guard_authentication.rst index 77e948aaabd..a3027dd7e4b 100644 --- a/security/guard_authentication.rst +++ b/security/guard_authentication.rst @@ -296,7 +296,7 @@ Each authenticator needs the following methods: If ``getUser()`` returns a User object, this method is called. Your job is to verify if the credentials are correct. For a login form, this is where you would check that the password is correct for the user. To pass authentication, return - ``true``. If you return *anything* else + ``true``. If you return ``false`` (or throw an :ref:`AuthenticationException `), authentication will fail. diff --git a/security/ldap.rst b/security/ldap.rst index 2bb3eee95bd..180879a16cd 100644 --- a/security/ldap.rst +++ b/security/ldap.rst @@ -9,7 +9,7 @@ Symfony provides different means to work with an LDAP server. The Security component offers: * The ``ldap`` :doc:`user provider`, using the - :class:`Symfony\\Component\\Security\\Core\\User\\LdapUserProvider` + :class:`Symfony\\Component\\Ldap\\Security\\LdapUserProvider` class. Like all other user providers, it can be used with any authentication provider. @@ -34,6 +34,12 @@ This means that the following scenarios will work: * Loading user information from an LDAP server, while using another authentication strategy (token-based pre-authentication, for example). +.. deprecated:: 4.4 + + The class ``Symfony\Component\Security\Core\User\LdapUserProvider`` + has been deprecated in Symfony 4.4. Use the class + ``Symfony\Component\Ldap\Security\LdapUserProvider`` instead. + Installation ------------ @@ -149,6 +155,7 @@ use the ``ldap`` user provider. search_password: password default_roles: ROLE_USER uid_key: uid + extra_fields: ['email'] .. code-block:: xml @@ -189,6 +196,7 @@ use the ``ldap`` user provider. 'search_password' => 'password', 'default_roles' => 'ROLE_USER', 'uid_key' => 'uid', + 'extra_fields' => ['email'], ], ], ], @@ -268,6 +276,18 @@ implementation. Commonly used values are: If you pass ``null`` as the value of this option, the default UID key is used ``sAMAccountName``. +extra_fields +............ + +**type**: ``array`` **default**: ``null`` + +.. versionadded:: 4.4 + + The ``extra_fields`` option was introduced in Symfony 4.4. + +Defines the custom fields to pull from the LDAP server. If any field does not +exist, an ``\InvalidArgumentException`` will be thrown. + filter ...... @@ -472,6 +492,8 @@ Configuration example for form login and query_string service: Symfony\Component\Ldap\Ldap dn_string: 'dc=example,dc=com' query_string: '(&(uid={username})(memberOf=cn=users,ou=Services,dc=example,dc=com))' + search_dn: '...' + search_password: 'the-raw-password' .. code-block:: xml @@ -488,7 +510,9 @@ Configuration example for form login and query_string + query-string="(&(uid={username})(memberOf=cn=users,ou=Services,dc=example,dc=com))" + search-dn="..." + search-password="the-raw-password"/> @@ -505,12 +529,19 @@ Configuration example for form login and query_string 'service' => Ldap::class, 'dn_string' => 'dc=example,dc=com', 'query_string' => '(&(uid={username})(memberOf=cn=users,ou=Services,dc=example,dc=com))', + 'search_dn' => '...', + 'search_password' => 'the-raw-password', // ... ], ], ] ]); +.. deprecated:: 4.4 + + Using the ``query_string`` config option without defining ``search_dn`` and + ``search_password`` is deprecated since Symfony 4.4. + .. _`LDAP PHP extension`: http://www.php.net/manual/en/intro.ldap.php .. _`RFC4515`: http://www.faqs.org/rfcs/rfc4515.html .. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection diff --git a/security/password_migration.rst b/security/password_migration.rst new file mode 100644 index 00000000000..7ce2d08d153 --- /dev/null +++ b/security/password_migration.rst @@ -0,0 +1,210 @@ +.. index:: + single: Security; How to Migrate a Password Hash + +How to Migrate a Password Hash +============================== + +.. versionadded:: 4.4 + + Password migration was introduced in Symfony 4.4. + +In order to protect passwords, it is recommended to store them using the latest +hash algorithms. This means that if a better hash algorithm is supported on the +system, the user's password should be rehashed and stored. Symfony provides this +functionality when a user is successfully authenticated. + +To enable this, make sure you apply the following steps to your application: + +#. `Configure a new Encoder Using "migrate_from"`_ +#. `Upgrade the Password`_ +#. Optionally, `Trigger Password Migration From a Custom Encoder`_ + +Configure a new Encoder Using "migrate_from" +-------------------------------------------- + +When configuring a new encoder, you can specify a list of legacy encoders by +using the ``migrate_from`` option: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + # ... + + encoders: + legacy: + algorithm: sha256 + encode_as_base64: false + iterations: 1 + + App\Entity\User: + # the new encoder, along with its options + algorithm: sodium + migrate_from: + - bcrypt # uses the "bcrypt" encoder with the default options + - legacy # uses the "legacy" encoder configured above + + .. code-block:: xml + + + + + + + + + + + + + + bcrypt + + + legacy + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + // ... + + 'encoders' => [ + 'legacy' => [ + 'algorithm' => 'sha256', + 'encode_as_base64' => false, + 'iterations' => 1, + ], + + 'App\Entity\User' => [ + // the new encoder, along with its options + 'algorithm' => 'sodium', + 'migrate_from' => [ + 'bcrypt', // uses the "bcrypt" encoder with the default options + 'legacy', // uses the "legacy" encoder configured above + ], + ], + ], + ]); + +.. tip:: + + The *auto*, *native*, *bcrypt* and *argon* encoders automatically enable + password migration using the following list of ``migrate_from`` algorithms: + + #. :ref:`PBKDF2 ` (which uses :phpfunction:`hash_pbkdf2`); + #. Message digest (which uses :phpfunction:`hash`) + + Both use the ``hash_algorithm`` setting as algorithm. It is recommended to + use ``migrate_from`` instead of ``hash_algorithm``, unless the *auto* + encoder is used. + +Upgrade the Password +-------------------- + +Upon successful login, the Security system checks whether a better algorithm +is available to hash the user's password. If it is, it'll hash the correct +password using the new hash. You can enable this behavior by implementing how +this newly hashed password should be stored: + +* `When using Doctrine's entity user provider `_ +* `When using a custom user provider `_ + +After this, you're done and passwords are always hashed as secure as possible! + +Upgrade the Password when using Doctrine +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the :ref:`entity user provider `, implement +:class:`Symfony\\Component\\Security\\Core\\User\\PasswordUpgraderInterface` in +the ``UserRepository`` (see `the Doctrine docs for information`_ on how to +create this class if it's not already created). This interface implements +storing the newly created password hash:: + + // src/Repository/UserRepository.php + namespace App\Repository; + + // ... + use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; + + class UserRepository extends EntityRepository implements PasswordUpgraderInterface + { + // ... + + public function upgradePassword(UserInterface $user, string $newEncodedPassword): void + { + // set the new encoded password on the User object + $user->setPassword($newEncodedPassword); + + // execute the queries on the database + $this->getEntityManager()->flush($user); + } + } + +Upgrade the Password when using a Custom User Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're using a :ref:`custom user provider `, implement the +:class:`Symfony\\Component\\Security\\Core\\User\\PasswordUpgraderInterface` in +the user provider:: + + // src/Security/UserProvider.php + namespace App\Security; + + // ... + use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; + + class UserProvider implements UserProviderInterface, PasswordUpgraderInterface + { + // ... + + public function upgradePassword(UserInterface $user, string $newEncodedPassword): void + { + // set the new encoded password on the User object + $user->setPassword($newEncodedPassword); + + // ... store the new password + } + } + +Trigger Password Migration From a Custom Encoder +------------------------------------------------ + +If you're using a custom password encoder, you can trigger the password +migration by returning ``true`` in the ``needsRehash()`` method:: + + // src/Security/UserProvider.php + namespace App\Security; + + // ... + use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; + + class CustomPasswordEncoder implements PasswordEncoderInterface + { + // ... + + public function needsRehash(string $encoded): bool + { + // check whether the current password is hash using an outdated encoder + $hashIsOutdated = ...; + + return $hashIsOutdated; + } + } + +.. _`the Doctrine docs for information`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/working-with-objects.html#custom-repositories diff --git a/service_container.rst b/service_container.rst index 44b2d1ee4db..dded8bad51d 100644 --- a/service_container.rst +++ b/service_container.rst @@ -190,8 +190,8 @@ each time you ask for it. ->defaults() ->autowire() // Automatically injects dependencies in your services. ->autoconfigure() // Automatically registers your services as commands, event subscribers, etc. - ; - + ; + // makes classes in src/ available to be used as services // this creates a service per class whose id is the fully-qualified class name $services->load('App\\', '../src/*') @@ -665,6 +665,7 @@ You can also use the ``bind`` keyword to bind specific arguments by name or type # optionally you can define both the name and type of the argument to match string $adminEmail: 'manager@example.com' Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request' + iterable $rules: !tagged_iterator app.foo.rule # ... @@ -695,6 +696,10 @@ You can also use the ``bind`` keyword to bind specific arguments by name or type type="service" id="monolog.logger.request" /> + @@ -728,11 +733,16 @@ You can also use the ``bind`` keyword to bind specific arguments by name or type // optionally you can define both the name and type of the argument to match ->bind('string $adminEmail', 'manager@example.com') ->bind(LoggerInterface::class.' $requestLogger', ref('monolog.logger.request')) + ->bind('iterable $rules', tagged_iterator('app.foo.rule')) ; // ... }; +.. versionadded:: 4.4 + + The feature to bind tagged services was introduced in Symfony 4.4. + By putting the ``bind`` key under ``_defaults``, you can specify the value of *any* argument for *any* service defined in this file! You can bind arguments by name (e.g. ``$adminEmail``), by type (e.g. ``Psr\Log\LoggerInterface``) or both diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index 5cf0dedfcb6..7a23745b728 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -368,7 +368,7 @@ dealing with the ``TransformerInterface``. When using a `service definition prototype`_, if only one service is discovered that implements an interface, and that interface is also - discovered at the same time, configuring the alias is not mandatory + discovered in the same file, configuring the alias is not mandatory and Symfony will automatically create one. Dealing with Multiple Implementations of the Same Type diff --git a/service_container/tags.rst b/service_container/tags.rst index ca548f9e219..ffb297c52cb 100644 --- a/service_container/tags.rst +++ b/service_container/tags.rst @@ -508,7 +508,7 @@ first constructor argument to the ``App\HandlerCollection`` service: App\HandlerCollection: # inject all services tagged with app.handler as first argument arguments: - - !tagged app.handler + - !tagged_iterator app.handler .. code-block:: xml @@ -530,7 +530,7 @@ first constructor argument to the ``App\HandlerCollection`` service: - + @@ -553,7 +553,7 @@ first constructor argument to the ``App\HandlerCollection`` service: $services->set(App\HandlerCollection::class) // inject all services tagged with app.handler as first argument - ->args([tagged('app.handler')]) + ->args([tagged_iterator('app.handler')]) ; }; diff --git a/setup/built_in_web_server.rst b/setup/built_in_web_server.rst index 3f13a4c712c..bb0f563f0ee 100644 --- a/setup/built_in_web_server.rst +++ b/setup/built_in_web_server.rst @@ -4,11 +4,14 @@ How to Use PHP's built-in Web Server ==================================== -.. caution:: +.. deprecated:: 4.4 + + This article explains how to use the WebServerBundle to run Symfony + applications on your local computer. However, that bundle is deprecated + since Symfony 4.4 and will be removed in Symfony 5.0. - This article explains how to use the web server based on the WebServerBundle. - This is no longer recommended in new Symfony applications. Instead, use the - :doc:`Symfony Local Web Server `. + Instead of using WebServerBundle, the preferred way to run your Symfony + applications locally is to use the :doc:`Symfony Local Web Server `. The PHP CLI SAPI comes with a `built-in web server`_. It can be used to run your PHP applications locally during development, for testing or for application diff --git a/setup/upgrade_major.rst b/setup/upgrade_major.rst index 56b25062646..1c13102ea26 100644 --- a/setup/upgrade_major.rst +++ b/setup/upgrade_major.rst @@ -1,7 +1,7 @@ .. index:: single: Upgrading; Major Version -Upgrading a Major Version (e.g. 3.4.0 to 4.1.0) +Upgrading a Major Version (e.g. 4.4.0 to 5.0.0) =============================================== Every two years, Symfony releases a new major version release (the first number @@ -30,10 +30,10 @@ backwards incompatible changes. To accomplish this, the "old" (e.g. functions, classes, etc) code still works, but is marked as *deprecated*, indicating that it will be removed/changed in the future and that you should stop using it. -When the major version is released (e.g. 4.1.0), all deprecated features and +When the major version is released (e.g. 5.0.0), all deprecated features and functionality are removed. So, as long as you've updated your code to stop using these deprecated features in the last version before the major (e.g. -3.4.*), you should be able to upgrade without a problem. +4.4.*), you should be able to upgrade without a problem. To help you with this, deprecation notices are triggered whenever you end up using a deprecated feature. When visiting your application in the @@ -82,7 +82,7 @@ Now, you can start fixing the notices: OK (10 tests, 20 assertions) Remaining deprecation notices (6) - + The "request" service is deprecated and will be removed in 3.0. Add a type-hint for Symfony\Component\HttpFoundation\Request to your controller parameters to retrieve the request instead: 6x @@ -96,7 +96,7 @@ done! .. sidebar:: Using the Weak Deprecations Mode Sometimes, you can't fix all deprecations (e.g. something was deprecated - in 3.4 and you still need to support 3.3). In these cases, you can still + in 4.4 and you still need to support 4.3). In these cases, you can still use the bridge to fix as many deprecations as possible and then allow more of them to make your tests pass again. You can do this by using the ``SYMFONY_DEPRECATIONS_HELPER`` env variable: @@ -123,8 +123,9 @@ done! 2) Update to the New Major Version via Composer ----------------------------------------------- -Once your code is deprecation free, you can update the Symfony library via -Composer by modifying your ``composer.json`` file: +Once your code is deprecation free, you can update all the Symfony packages via +Composer by modifying your ``composer.json`` file. In this file update all +mentions of ``4.*`` to ``5.0.*``: .. code-block:: json @@ -132,16 +133,34 @@ Composer by modifying your ``composer.json`` file: "...": "...", "require": { - "symfony/symfony": "^4.1", + "symfony/console": "^5.0", + "symfony/dotenv": "^5.0", + "symfony/flex": "^1.3.1", + "symfony/framework-bundle": "^5.0", + "symfony/validator": "^5.0", + "symfony/yaml": "^5.0" }, "...": "..." } +At the bottom of your ``composer.json`` file, in the ``extra`` block you can +find a data setting for the Symfony version. Make sure to also upgrade +this one. For instance, update it to ``5.0.*`` to upgrade to Symfony 5.0: + +.. code-block:: json + + "extra": { + "symfony": { + "allow-contrib": false, + "require": "5.0.*" + } + } + Next, use Composer to download new versions of the libraries: .. code-block:: terminal - $ composer update symfony/symfony + $ composer update "symfony/*" .. include:: /setup/_update_dep_errors.rst.inc @@ -156,10 +175,3 @@ The next major version *may* also contain new BC breaks as a BC layer is not alw a possibility. Make sure you read the ``UPGRADE-X.0.md`` (where X is the new major version) included in the Symfony repository for any BC break that you need to be aware of. - -4) Updating to the Symfony 4 Flex Directory Structure ------------------------------------------------------ - -When upgrading to Symfony 4, you will probably also want to upgrade to the new -Symfony 4 directory structure so that you can take advantage of Symfony Flex. -This takes some work, but is optional. For details, see :doc:`/setup/flex`. diff --git a/templates.rst b/templates.rst index 20c4819f507..e31c6539d89 100644 --- a/templates.rst +++ b/templates.rst @@ -423,7 +423,7 @@ the :class:`Twig\\Environment` class:: // src/Service/SomeService.php namespace App\Service; - + use Twig\Environment; class SomeService @@ -566,12 +566,22 @@ errors. It's useful to run it before deploying your application to production .. code-block:: terminal - # check all the templates stored in a directory - $ php bin/console lint:twig templates/ + # check all the application templates + $ php bin/console lint:twig - # you can also check individual templates + # you can also check directories and individual templates + $ php bin/console lint:twig templates/email/ $ php bin/console lint:twig templates/article/recent_list.html.twig + # you can also show the deprecated features used in your templates + $ php bin/console lint:twig --show-deprecations templates/email/ + +.. versionadded:: 4.4 + + The feature that checks all the application templates when not passing any + arguments to ``lint:twig`` and the ``--show-deprecations`` option were + introduced in Symfony 4.4. + Inspecting Twig Information ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/testing.rst b/testing.rst index 334d5b74c22..4fd211cee26 100644 --- a/testing.rst +++ b/testing.rst @@ -693,6 +693,13 @@ The Crawler can extract information from the nodes:: // returns the node value for the first node $crawler->text(); + // returns the default text if the node does not exist + $crawler->text('Default text content'); + + // pass TRUE as the second argument of text() to remove all extra white spaces, including + // the internal ones (e.g. " foo\n bar baz \n " is returned as "foo bar baz") + $crawler->text(null, true); + // extracts an array of attributes for all nodes // (_text returns the node value) // returns an array for each element in crawler, @@ -704,6 +711,10 @@ The Crawler can extract information from the nodes:: return $node->attr('href'); }); +.. versionadded:: 4.4 + + The option to trim white spaces in ``text()`` was introduced in Symfony 4.4. + Links ~~~~~ @@ -802,6 +813,17 @@ their type:: $form['my_form[field][O]']->upload('/path/to/lucas.jpg'); $form['my_form[field][1]']->upload('/path/to/lisa.jpg'); +.. tip:: + + Instead of hardcoding the form name as part of the field names (e.g. + ``my_form[...]`` in previous examples), you can use the + :method:`Symfony\\Component\\DomCrawler\\Form::getName` method to get the + form name. + + .. versionadded:: 4.4 + + The ``getName()`` method was introduced in Symfony 4.4. + .. tip:: If you purposefully want to select "invalid" select/radio values, see diff --git a/testing/functional_tests_assertions.rst b/testing/functional_tests_assertions.rst index 63482dd046b..6cff835e1f0 100644 --- a/testing/functional_tests_assertions.rst +++ b/testing/functional_tests_assertions.rst @@ -32,6 +32,12 @@ Now here is the example with the assertions specific to Symfony:: Assertions Reference --------------------- +.. versionadded:: 4.4 + + Starting from Symfony 4.4, when using `symfony/panther`_ for end-to-end + testing, you can use all the following assertions except the ones related to + the :doc:`Crawler `. + Response ~~~~~~~~ @@ -72,12 +78,26 @@ Crawler - ``assertInputValueSame()`` - ``assertInputValueNotSame()`` -Troubleshooting ---------------- - -These assertions will not work with `symfony/panther`_ as they use the -``Request`` and ``Response`` objects from the ``HttpFoundation`` -component, and the ``KernelBrowser`` from the ``FrameworkBundle``. -Panther only uses the ``BrowserKit`` component. +Mailer +~~~~~~ + +- ``assertEmailCount()`` +- ``assertQueuedEmailCount()`` +- ``assertEmailIsQueued()`` +- ``assertEmailIsNotQueued()`` +- ``assertEmailAttachementCount()`` +- ``assertEmailTextBodyContains()`` +- ``assertEmailTextBodyNotContains()`` +- ``assertEmailHtmlBodyContains()`` +- ``assertEmailHtmlBodyNotContains()`` +- ``assertEmailHasHeader()`` +- ``assertEmailNotHasHeader()`` +- ``assertEmailHeaderSame()`` +- ``assertEmailHeaderNotSame()`` +- ``assertEmailAddressContains()`` + +.. versionadded:: 4.4 + + The mailer assert methods were introduced in Symfony 4.4. .. _`symfony/panther`: https://github.com/symfony/panther diff --git a/translation.rst b/translation.rst index 5515227d4d4..c9ef4e0ba4a 100644 --- a/translation.rst +++ b/translation.rst @@ -59,8 +59,8 @@ Configuration ------------- The previous command creates an initial config file where you can define the -default locale of the app and the :ref:`fallback locales ` -that will be used if Symfony can't find some translation: +default locale of the application and the directory where the translation files +are located: .. configuration-block:: @@ -70,8 +70,7 @@ that will be used if Symfony can't find some translation: framework: default_locale: 'en' translator: - fallbacks: ['en'] - # ... + default_path: '%kernel.project_dir%/translations' .. code-block:: xml @@ -87,7 +86,7 @@ that will be used if Symfony can't find some translation: - en + '%kernel.project_dir%/translations' @@ -98,7 +97,7 @@ that will be used if Symfony can't find some translation: // config/packages/translation.php $container->loadFromExtension('framework', [ 'default_locale' => 'en', - 'translator' => ['fallbacks' => ['en']], + 'translator' => ['default_path' => '%kernel.project_dir%/translations'], // ... ]); @@ -253,7 +252,7 @@ with these tasks: # updates the French translation files with the missing strings for that locale $ php bin/console translation:update --force fr - # check out the command help to see its options (prefix, output format, domain, etc.) + # check out the command help to see its options (prefix, output format, domain, sorting, etc.) $ php bin/console translation:update --help The ``translation:update`` command looks for missing translations in: @@ -397,8 +396,52 @@ checks translation resources for several locales: #. If it wasn't found, Symfony looks for the translation in a ``fr`` translation resource (e.g. ``messages.fr.xlf``); -#. If the translation still isn't found, Symfony uses the ``fallbacks`` configuration - parameter, which defaults to ``en`` (see `Configuration`_). +#. If the translation still isn't found, Symfony uses the ``fallbacks`` option, + which can be configured as follows: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/translation.yaml + framework: + translator: + fallbacks: ['en'] + # ... + + .. code-block:: xml + + + + + + + + en + + + + + + .. code-block:: php + + // config/packages/translation.php + $container->loadFromExtension('framework', [ + 'translator' => ['fallbacks' => ['en']], + // ... + ]); + +.. deprecated:: 4.4 + + In Symfony versions before 4.4, the ``fallbacks`` option was initialized to + ``en`` (English) when not configured explicitly. Starting from Symfony 4.4, + this option is initialized to the same value as the ``default_locale`` option. .. note:: diff --git a/web_link.rst b/web_link.rst index fa5cc5758eb..c9182a9dc9a 100644 --- a/web_link.rst +++ b/web_link.rst @@ -156,10 +156,10 @@ You can also add links to the HTTP response directly from controllers and servic // src/Controller/BlogController.php namespace App\Controller; - use Fig\Link\GenericLinkProvider; - use Fig\Link\Link; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\WebLink\GenericLinkProvider; + use Symfony\Component\WebLink\Link; class BlogController extends AbstractController {