diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index e71ca735f09..a568b014eab 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -50,18 +50,18 @@ rules: yarn_dev_option_at_the_end: ~ # no_app_bundle: ~ - # 4.x + # master versionadded_directive_major_version: - major_version: 4 + major_version: 5 versionadded_directive_min_version: - min_version: '4.0' + min_version: '5.0' deprecated_directive_major_version: - major_version: 4 + major_version: 5 deprecated_directive_min_version: - min_version: '4.0' + min_version: '5.0' # do not report as violation whitelist: @@ -90,8 +90,13 @@ whitelist: - '.. versionadded:: 1.6' # Flex in setup/upgrade_minor.rst - '0 => 123' # assertion for var_dumper - components/var_dumper.rst - '1 => "foo"' # assertion for var_dumper - components/var_dumper.rst + - '123,' # assertion for var_dumper - components/var_dumper.rst + - '"foo",' # assertion for var_dumper - components/var_dumper.rst - '$var .= "Because of this `\xE9` octet (\\xE9),\n";' - "`Deploying Symfony 4 Apps on Heroku`_." - ".. _`Deploying Symfony 4 Apps on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4" + - "// 224, 165, 141, 224, 164, 164, 224, 165, 135])" - '.. versionadded:: 0.2' # MercureBundle + - 'provides a ``loginUser()`` method to simulate logging in in your functional' - '.. code-block:: twig' + - '.. versionadded:: 3.6' # MonologBundle diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0586345396f..ddeb73add51 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ + + + + + + + + + env(base64:CACHE_DECRYPTION_KEY) + + + + + + + + + .. code-block:: php + + // config/packages/cache.php + use Symfony\Component\Cache\Marshaller\SodiumMarshaller; + use Symfony\Component\DependencyInjection\ChildDefinition; + use Symfony\Component\DependencyInjection\Reference; + + // ... + $container->setDefinition(SodiumMarshaller::class, new ChildDefinition('cache.default_marshaller')) + ->addArgument(['env(base64:CACHE_DECRYPTION_KEY)']) + // use multiple keys in order to rotate them + //->addArgument(['env(base64:CACHE_DECRYPTION_KEY)', 'env(base64:OLD_CACHE_DECRYPTION_KEY)']) + ->addArgument(new Reference(SodiumMarshaller::class.'.inner')); + +.. caution:: + + This will encrypt the values of the cache items, but not the cache keys. Be + careful not the leak sensitive data in the keys. + +When configuring multiple keys, the first key will be used for reading and +writing, and the additional key(s) will only be used for reading. Once all +cache items encrypted with the old key have expired, you can remove +``OLD_CACHE_DECRYPTION_KEY`` completely. diff --git a/components/asset.rst b/components/asset.rst index 6aab732333f..48e51754449 100644 --- a/components/asset.rst +++ b/components/asset.rst @@ -165,6 +165,22 @@ In those cases, use the echo $package->getUrl('css/app.css'); // result: build/css/app.b916426ea1d10021f3f17ce8031f93c2.css +If your JSON file is not on your local filesystem but is accessible over HTTP, +use the :class:`Symfony\\Component\\Asset\\VersionStrategy\\RemoteJsonManifestVersionStrategy` +with the :doc:`HttpClient component `:: + + use Symfony\Component\Asset\Package; + use Symfony\Component\Asset\VersionStrategy\RemoteJsonManifestVersionStrategy; + use Symfony\Component\HttpClient\HttpClient; + + $httpClient = HttpClient::create(); + $manifestUrl = 'https://cdn.example.com/rev-manifest.json'; + $package = new Package(new RemoteJsonManifestVersionStrategy($manifestUrl, $httpClient)); + +.. versionadded:: 5.1 + + The ``RemoteJsonManifestVersionStrategy`` was introduced in Symfony 5.1. + Custom Version Strategies ......................... @@ -184,12 +200,12 @@ every day:: $this->version = date('Ymd'); } - public function getVersion($path) + public function getVersion(string $path) { return $this->version; } - public function applyVersion($path) + public function applyVersion(string $path) { return sprintf('%s?v=%s', $path, $this->getVersion($path)); } diff --git a/components/browser_kit.rst b/components/browser_kit.rst index 76c0e33d5e1..475c84b9365 100644 --- a/components/browser_kit.rst +++ b/components/browser_kit.rst @@ -40,13 +40,7 @@ The component only provides an abstract client and does not provide any backend ready to use for the HTTP layer. To create your own client, you must extend the ``AbstractBrowser`` class and implement the :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::doRequest` method. - -.. deprecated:: 4.3 - - In Symfony 4.3 and earlier versions, the ``AbstractBrowser`` class was called - ``Client`` (which is now deprecated). - -The ``doRequest()`` method accepts a request and should return a response:: +This method accepts a request and should return a response:: namespace Acme; @@ -86,6 +80,20 @@ The value returned by the ``request()`` method is an instance of the :doc:`DomCrawler component `, which allows accessing and traversing HTML elements programmatically. +The :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::jsonRequest` method, +which defines the same arguments as the ``request()`` method, is a shortcut to +convert the request parameters into a JSON string and set the needed HTTP headers:: + + use Acme\Client; + + $client = new Client(); + // this encodes parameters as JSON and sets the required CONTENT_TYPE and HTTP_ACCEPT headers + $crawler = $client->jsonRequest('GET', '/', ['some_parameter' => 'some_value']); + +.. versionadded:: 5.3 + + The ``jsonRequest()`` method was introduced in Symfony 5.3. + The :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::xmlHttpRequest` method, which defines the same arguments as the ``request()`` method, is a shortcut to make AJAX requests:: @@ -167,6 +175,32 @@ provides access to the form properties (e.g. ``$form->getUri()``, // submit that form $crawler = $client->submit($form); +Custom Header Handling +~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The ``getHeaders()`` method was introduced in Symfony 5.2. + +The optional HTTP headers passed to the ``request()`` method follows the FastCGI +request format (uppercase, underscores instead of dashes and prefixed with ``HTTP_``). +Before saving those headers to the request, they are lower-cased, with ``HTTP_`` +stripped, and underscores turned to dashes. + +If you're making a request to an application that has special rules about header +capitalization or punctuation, override the ``getHeaders()`` method, which must +return an associative array of headers:: + + protected function getHeaders(Request $request): array + { + $headers = parent::getHeaders($request); + if (isset($request->getServer()['api_key'])) { + $headers['api_key'] = $request->getServer()['api_key']; + } + + return $headers; + } + Cookies ------- @@ -317,10 +351,6 @@ dedicated web crawler or scraper such as `Goutte`_:: '.table-list-header-toggle a:nth-child(1)' )->text()); -.. versionadded:: 4.3 - - The feature to make external HTTP requests was introduced in Symfony 4.3. - Learn more ---------- diff --git a/components/cache/adapters/array_cache_adapter.rst b/components/cache/adapters/array_cache_adapter.rst index ffdc1d60dd0..c7b06f40753 100644 --- a/components/cache/adapters/array_cache_adapter.rst +++ b/components/cache/adapters/array_cache_adapter.rst @@ -8,10 +8,7 @@ Array Cache Adapter Generally, this adapter is useful for testing purposes, as its contents are stored in memory and not persisted outside the running PHP process in any way. It can also be useful while warming up caches, due to the :method:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter::getValues` -method. - -This adapter can be passed a default cache lifetime as its first parameter, and a boolean that -toggles serialization as its second parameter:: +method:: use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -23,5 +20,17 @@ toggles serialization as its second parameter:: $defaultLifetime = 0, // if ``true``, the values saved in the cache are serialized before storing them - $storeSerialized = true + $storeSerialized = true, + + // the maximum lifetime (in seconds) of the entire cache (after this time, the + // entire cache is deleted to avoid stale data from consuming memory) + $maxLifetime = 0, + + // the maximum number of items that can be stored in the cache. When the limit + // is reached, cache follows the LRU model (least recently used items are deleted) + $maxItems = 0 ); + +.. versionadded:: 5.1 + + The ``maxLifetime`` and ``maxItems`` options were introduced in Symfony 5.1. diff --git a/components/cache/adapters/couchbasebucket_adapter.rst b/components/cache/adapters/couchbasebucket_adapter.rst new file mode 100644 index 00000000000..7043a7c3e95 --- /dev/null +++ b/components/cache/adapters/couchbasebucket_adapter.rst @@ -0,0 +1,150 @@ +.. index:: + single: Cache Pool + single: Couchabase Cache + +.. _couchbase-adapter: + +Couchbase Cache Adapter +======================= + +.. versionadded:: 5.1 + + The CouchbaseBucketAdapter was introduced in Symfony 5.1. + +This adapter stores the values in-memory using one (or more) `Couchbase server`_ +instances. Unlike the :ref:`APCu adapter `, and similarly to the +:ref:`Memcached adapter `, it is not limited to the current server's +shared memory; you can store contents independent of your PHP environment. +The ability to utilize a cluster of servers to provide redundancy and/or fail-over +is also available. + +.. caution:: + + **Requirements:** The `Couchbase PHP extension`_ as well as a `Couchbase server`_ + must be installed, active, and running to use this adapter. Version ``2.6`` or + greater of the `Couchbase PHP extension`_ is required for this adapter. + +This adapter expects a `Couchbase Bucket`_ instance to be passed as the first +parameter. A namespace and default cache lifetime can optionally be passed as +the second and third parameters:: + + use Symfony\Component\Cache\Adapter\CouchbaseBucketAdapter; + + $cache = new CouchbaseBucketAdapter( + // the client object that sets options and adds the server instance(s) + \CouchbaseBucket $client, + + // the name of bucket + string $bucket, + + // a string prefixed to the keys of the items stored in this cache + $namespace = '', + + // the default lifetime (in seconds) for cache items that do not define their + // own lifetime, with a value 0 causing items to be stored indefinitely + $defaultLifetime = 0, + ); + + +Configure the Connection +------------------------ + +The :method:`Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter::createConnection` +helper method allows creating and configuring a `Couchbase Bucket`_ class instance using a +`Data Source Name (DSN)`_ or an array of DSNs:: + + use Symfony\Component\Cache\Adapter\CouchbaseBucketAdapter; + + // pass a single DSN string to register a single server with the client + $client = CouchbaseBucketAdapter::createConnection( + 'couchbase://localhost' + // the DSN can include config options (pass them as a query string): + // 'couchbase://localhost:11210?operationTimeout=10' + // 'couchbase://localhost:11210?operationTimeout=10&configTimout=20' + ); + + // pass an array of DSN strings to register multiple servers with the client + $client = CouchbaseBucketAdapter::createConnection([ + 'couchbase://10.0.0.100', + 'couchbase://10.0.0.101', + 'couchbase://10.0.0.102', + // etc... + ]); + + // a single DSN can define multiple servers using the following syntax: + // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':' + $client = CouchbaseBucketAdapter::createConnection( + 'couchbase:?host[localhost]&host[localhost:12345]' + ); + + +Configure the Options +--------------------- + +The :method:`Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter::createConnection` +helper method also accepts an array of options as its second argument. The +expected format is an associative array of ``key => value`` pairs representing +option names and their respective values:: + + use Symfony\Component\Cache\Adapter\CouchbaseBucketAdapter; + + $client = CouchbaseBucketAdapter::createConnection( + // a DSN string or an array of DSN strings + [], + + // associative array of configuration options + [ + 'username' => 'xxxxxx', + 'password' => 'yyyyyy', + 'configTimeout' => '100', + ] + ); + +Available Options +~~~~~~~~~~~~~~~~~ + +``username`` (type: ``string``) + Username for connection ``CouchbaseCluster``. + +``password`` (type: ``string``) + Password of connection ``CouchbaseCluster``. + +``operationTimeout`` (type: ``int``, default: ``2500000``) + The operation timeout (in microseconds) is the maximum amount of time the library will + wait for an operation to receive a response before invoking its callback with a failure status. + +``configTimeout`` (type: ``int``, default: ``5000000``) + How long (in microseconds) the client will wait to obtain the initial configuration. + +``configNodeTimeout`` (type: ``int``, default: ``2000000``) + Per-node configuration timeout (in microseconds). + +``viewTimeout`` (type: ``int``, default: ``75000000``) + The I/O timeout (in microseconds) for HTTP requests to Couchbase Views API. + +``httpTimeout`` (type: ``int``, default: ``75000000``) + The I/O timeout (in microseconds) for HTTP queries (management API). + +``configDelay`` (type: ``int``, default: ``10000``) + Config refresh throttling + Modify the amount of time (in microseconds) before the configuration error threshold will forcefully be set to its maximum number forcing a configuration refresh. + +``htconfigIdleTimeout`` (type: ``int``, default: ``4294967295``) + Idling/Persistence for HTTP bootstrap (in microseconds). + +``durabilityInterval`` (type: ``int``, default: ``100000``) + The time (in microseconds) the client will wait between repeated probes to a given server. + +``durabilityTimeout`` (type: ``int``, default: ``5000000``) + The time (in microseconds) the client will spend sending repeated probes to a given key's vBucket masters and replicas before they are deemed not to have satisfied the durability requirements. + +.. tip:: + + Reference the `Couchbase Bucket`_ extension's `predefined constants`_ documentation + for additional information about the available options. + +.. _`Couchbase PHP extension`: https://docs.couchbase.com/sdk-api/couchbase-php-client-2.6.0/files/couchbase.html +.. _`predefined constants`: https://docs.couchbase.com/sdk-api/couchbase-php-client-2.6.0/classes/Couchbase.Bucket.html +.. _`Couchbase server`: https://couchbase.com/ +.. _`Couchbase Bucket`: https://docs.couchbase.com/sdk-api/couchbase-php-client-2.6.0/classes/Couchbase.Bucket.html +.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst index 9cd8c8cdd25..95161637b25 100644 --- a/components/cache/adapters/memcached_adapter.rst +++ b/components/cache/adapters/memcached_adapter.rst @@ -70,10 +70,6 @@ helper method allows creating and configuring a `Memcached`_ class instance usin 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3' ); -.. versionadded:: 4.2 - - The option to define multiple servers in a single DSN was introduced in Symfony 4.2. - The `Data Source Name (DSN)`_ for this adapter must use the following format: .. code-block:: text diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index 935afcd9f92..a81455d8e28 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -95,21 +95,13 @@ Below are common examples of valid DSNs showing a combination of available value ); `Redis Sentinel`_, which provides high availability for Redis, is also supported -when using the Predis library. Use the ``redis_sentinel`` parameter to set the -name of your service group:: +when using the PHP Redis Extension v5.2+ or the Predis library. Use the ``redis_sentinel`` +parameter to set the name of your service group:: RedisAdapter::createConnection( 'redis:?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster' ); -.. versionadded:: 4.2 - - 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/cache/psr6_psr16_adapters.rst b/components/cache/psr6_psr16_adapters.rst index 28a41fca0e7..6b98d26744b 100644 --- a/components/cache/psr6_psr16_adapters.rst +++ b/components/cache/psr6_psr16_adapters.rst @@ -46,10 +46,6 @@ this use-case:: // now use this wherever you want $githubApiClient = new GitHubApiClient($psr6Cache); -.. versionadded:: 4.3 - - The ``Psr16Adapter`` class was introduced in Symfony 4.3. - Using a PSR-6 Cache Object as a PSR-16 Cache -------------------------------------------- @@ -87,8 +83,4 @@ this use-case:: // now use this wherever you want $githubApiClient = new GitHubApiClient($psr16Cache); -.. versionadded:: 4.3 - - The ``Psr16Cache`` class was introduced in Symfony 4.3. - .. _`PSR-16`: https://www.php-fig.org/psr/psr-16/ diff --git a/components/config/definition.rst b/components/config/definition.rst index 67c7392eb33..dfe9f8e166a 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -68,10 +68,6 @@ implements the :class:`Symfony\\Component\\Config\\Definition\\ConfigurationInte } } -.. deprecated:: 4.2 - - Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2. - Adding Node Definitions to the Tree ----------------------------------- @@ -439,6 +435,13 @@ The following example shows these methods in practice:: Deprecating the Option ---------------------- +.. versionadded:: 5.1 + + The signature of the ``setDeprecated()`` method changed from + ``setDeprecated(?string $message)`` to + ``setDeprecated(string $package, string $version, ?string $message)`` + in Symfony 5.1. + You can deprecate options using the :method:`Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::setDeprecated` method:: @@ -447,11 +450,15 @@ method:: ->children() ->integerNode('old_option') // this outputs the following generic deprecation message: - // The child node "old_option" at path "..." is deprecated. - ->setDeprecated() + // Since acme/package 1.2: The child node "old_option" at path "..." is deprecated. + ->setDeprecated('acme/package', '1.2') // you can also pass a custom deprecation message (%node% and %path% placeholders are available): - ->setDeprecated('The "%node%" option is deprecated. Use "new_config_option" instead.') + ->setDeprecated( + 'acme/package', + '1.2', + 'The "%node%" option is deprecated. Use "new_config_option" instead.' + ) ->end() ->end() ; diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst index c8ef2f25a1f..ebdcaba1d64 100644 --- a/components/console/helpers/progressbar.rst +++ b/components/console/helpers/progressbar.rst @@ -56,11 +56,6 @@ you can also set the current progress by calling the 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:: @@ -130,10 +125,6 @@ The previous code will output: 1/2 [==============>-------------] 50% 2/2 [============================] 100% -.. versionadded:: 4.3 - - The ``iterate()`` method was introduced in Symfony 4.3. - Customizing the Progress Bar ---------------------------- @@ -322,11 +313,6 @@ 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 2174550a0cd..57239a8d6e4 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -40,7 +40,7 @@ the following to your command:: $question = new ConfirmationQuestion('Continue with this action?', false); if (!$helper->ask($input, $output, $question)) { - return 0; + return Command::SUCCESS; } } } @@ -105,6 +105,7 @@ from a predefined list:: $helper = $this->getHelper('question'); $question = new ChoiceQuestion( 'Please select your favorite color (defaults to red)', + // choices can also be PHP objects that implement __toString() method ['red', 'blue', 'yellow'], 0 ); @@ -116,6 +117,10 @@ from a predefined list:: // ... do something with the color } +.. versionadded:: 5.2 + + Support for using PHP objects as choice values was introduced in Symfony 5.2. + The option which should be selected by default is provided with the third argument of the constructor. The default is ``null``, which means that no option is the default one. @@ -214,10 +219,6 @@ provide a callback function to dynamically generate suggestions:: $filePath = $helper->ask($input, $output, $question); } -.. versionadded:: 4.3 - - The ``setAutocompleterCallback()`` method was introduced in Symfony 4.3. - Do not Trim the Answer ~~~~~~~~~~~~~~~~~~~~~~ @@ -238,9 +239,35 @@ You can also specify if you want to not trim the answer by setting it directly w $name = $helper->ask($input, $output, $question); } -.. versionadded:: 4.4 +Accept Multiline Answers +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The ``setMultiline()`` and ``isMultiline()`` methods were introduced in + Symfony 5.2. + +By default, the question helper stops reading user input when it receives a newline +character (i.e., when the user hits ``ENTER`` once). However, you may specify that +the response to a question should allow multiline answers by passing ``true`` to +:method:`Symfony\\Component\\Console\\Question\\Question::setMultiline`:: + + use Symfony\Component\Console\Question\Question; + + // ... + public function execute(InputInterface $input, OutputInterface $output) + { + // ... + $helper = $this->getHelper('question'); + + $question = new Question('How do you solve world peace?'); + $question->setMultiline(true); + + $answer = $helper->ask($input, $output, $question); + } - The ``setTrimmable()`` method was introduced in Symfony 4.4. +Multiline questions stop reading user input after receiving an end-of-transmission +control character (``Ctrl-D`` on Unix systems or ``Ctrl-Z`` on Windows). Hiding the User's Response ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst index 0e8df0d1031..7944c795684 100644 --- a/components/console/helpers/table.rst +++ b/components/console/helpers/table.rst @@ -265,6 +265,39 @@ Here is a full list of things you can customize: This method can also be used to override a built-in style. +.. versionadded:: 5.2 + + The option to style table cells was introduced in Symfony 5.2. + +In addition to the built-in table styles, you can also apply different styles +to each table cell via :class:`Symfony\\Component\\Console\\Helper\\TableCellStyle`:: + + use Symfony\Component\Console\Helper\Table; + use Symfony\Component\Console\Helper\TableCellStyle; + + $table = new Table($output); + + $table->setRows([ + [ + '978-0804169127', + new TableCell( + 'Divine Comedy', + [ + 'style' => new TableCellStyle([ + 'align' => 'center', + 'fg' => 'red', + 'bg' => 'green', + + // or + 'cellFormat' => '%s', + ]) + ] + ) + ], + ]); + + $table->render(); + Spanning Multiple Columns and Rows ---------------------------------- diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst index 457efb48dae..500d679d1e1 100644 --- a/components/console/single_command_tool.rst +++ b/components/console/single_command_tool.rst @@ -5,34 +5,36 @@ Building a single Command Application ===================================== When building a command line tool, you may not need to provide several commands. -In such case, having to pass the command name each time is tedious. Fortunately, -it is possible to remove this need by declaring a single command application:: +In such case, having to pass the command name each time is tedious. + +.. versionadded:: 5.1 + + The :class:`Symfony\\Component\\Console\\SingleCommandApplication` class was + introduced in Symfony 5.1. + +Fortunately, it is possible to remove this need by declaring a single command +application:: #!/usr/bin/env php register('echo') - ->addArgument('foo', InputArgument::OPTIONAL, 'The directory') - ->addOption('bar', null, InputOption::VALUE_REQUIRED) - ->setCode(function(InputInterface $input, OutputInterface $output) { - // output arguments and options - }) - ->getApplication() - ->setDefaultCommand('echo', true) // Single command application + use Symfony\Component\Console\SingleCommandApplication; + + (new SingleCommandApplication()) + ->setName('My Super Command') // Optional + ->setVersion('1.0.0') // Optional + ->addArgument('foo', InputArgument::OPTIONAL, 'The directory') + ->addOption('bar', null, InputOption::VALUE_REQUIRED) + ->setCode(function (InputInterface $input, OutputInterface $output) { + // output arguments and options + }) ->run(); -The :method:`Symfony\\Component\\Console\\Application::setDefaultCommand` method -accepts a boolean as second parameter. If true, the command ``echo`` will then -always be used, without having to pass its name. - You can still register a command as usual:: #!/usr/bin/env php @@ -49,3 +51,7 @@ You can still register a command as usual:: $application->setDefaultCommand($command->getName(), true); $application->run(); + +The :method:`Symfony\\Component\\Console\\Application::setDefaultCommand` method +accepts a boolean as second parameter. If true, the command ``echo`` will then +always be used, without having to pass its name. diff --git a/components/css_selector.rst b/components/css_selector.rst index c8100793ab4..df9ddd84487 100644 --- a/components/css_selector.rst +++ b/components/css_selector.rst @@ -98,10 +98,6 @@ Pseudo-classes are partially supported: ``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/dependency_injection.rst b/components/dependency_injection.rst index 486f89e599d..b303e96d484 100644 --- a/components/dependency_injection.rst +++ b/components/dependency_injection.rst @@ -299,15 +299,16 @@ config files: $services = $configurator->services(); $services->set('mailer', 'Mailer') - ->args(['%mailer.transport%']) + // the param() method was introduced in Symfony 5.2. + ->args([param('mailer.transport')]) ; $services->set('newsletter_manager', 'NewsletterManager') - ->call('setMailer', [ref('mailer')]) + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->call('setMailer', [service('mailer')]) ; }; - Learn More ---------- diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst index 1363d977a3a..7f2b28ad582 100644 --- a/components/dom_crawler.rst +++ b/components/dom_crawler.rst @@ -77,10 +77,6 @@ tree. The DomCrawler component will use it automatically when the content has an HTML5 doctype. - .. versionadded:: 4.3 - - The automatic support of the html5-php library was introduced in Symfony 4.3. - Node Filtering ~~~~~~~~~~~~~~ @@ -170,10 +166,6 @@ 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,10 +187,14 @@ Get the same level nodes after or before the current selection:: $crawler->filter('body > p')->nextAll(); $crawler->filter('body > p')->previousAll(); -Get all the child or parent nodes:: +Get all the child or ancestor nodes:: $crawler->filter('body')->children(); - $crawler->filter('body > p')->parents(); + $crawler->filter('body > p')->ancestors(); + +.. versionadded:: 5.3 + + The ``ancestors()`` method was introduced in Symfony 5.3. Get all the direct child nodes matching a CSS selector:: @@ -208,10 +204,6 @@ Get the first parent (heading toward the document root) of the element that matc $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` @@ -233,17 +225,10 @@ 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. + // by default, text() trims white spaces, including the internal ones + // (e.g. " foo\n bar baz \n " is returned as "foo bar baz") + // pass FALSE as the second argument to return the original text unchanged + $crawler->filterXPath('//body/p')->text('Default text content', false); Access the attribute value of the first node of the current selection:: @@ -261,10 +246,6 @@ Extract attribute and/or node values from the list of nodes:: Special attribute ``_text`` represents a node value, while ``_name`` represents the element name (the HTML tag name). - .. versionadded:: 4.3 - - The special attribute ``_name`` was introduced in Symfony 4.3. - Call an anonymous function on each node of the list:: use Symfony\Component\DomCrawler\Crawler; @@ -358,19 +339,11 @@ and :phpclass:`DOMNode` objects:: // avoid the exception passing an argument that html() returns when node does not exist $html = $crawler->html('Default HTML content'); - .. versionadded:: 4.3 - - 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 ~~~~~~~~~~~~~~~~~~~~~ @@ -522,10 +495,6 @@ useful methods for working with forms:: $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 is GET, then it mimics the browser's behavior and returns the ``action`` @@ -657,6 +626,23 @@ the whole form or specific field(s):: $form->disableValidation(); $form['country']->select('Invalid value'); +Resolving a URI +~~~~~~~~~~~~~~~ + +.. versionadded:: 5.1 + + The :class:`Symfony\\Component\\DomCrawler\\UriResolver` helper class was added in Symfony 5.1. + +The :class:`Symfony\\Component\\DomCrawler\\UriResolver` class takes an URI +(relative, absolute, fragment, etc.) and turns it into an absolute URI against +another given base URI:: + + use Symfony\Component\DomCrawler\UriResolver; + + UriResolver::resolve('/foo', 'http://localhost/bar/foo/'); // http://localhost/foo + UriResolver::resolve('?a=b', 'http://localhost/bar#foo'); // http://localhost/bar?a=b + UriResolver::resolve('../../', 'http://localhost/'); // http://localhost/ + Learn more ---------- diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst index ef0392e6dfc..abbaa3a4ad9 100644 --- a/components/event_dispatcher.rst +++ b/components/event_dispatcher.rst @@ -262,14 +262,6 @@ determine which instance is passed. default values by passing custom values to the constructors of ``RegisterListenersPass`` and ``AddEventAliasesPass``. -.. versionadded:: 4.3 - - Aliasing event names is possible since Symfony 4.3. - -.. versionadded:: 4.4 - - The ``AddEventAliasesPass`` class was introduced in Symfony 4.4. - .. _event_dispatcher-closures-as-listeners: .. index:: diff --git a/components/event_dispatcher/traceable_dispatcher.rst b/components/event_dispatcher/traceable_dispatcher.rst index 87d58023445..33a98a2336b 100644 --- a/components/event_dispatcher/traceable_dispatcher.rst +++ b/components/event_dispatcher/traceable_dispatcher.rst @@ -41,10 +41,10 @@ to register event listeners and dispatch events:: $traceableEventDispatcher->dispatch($event, 'event.the_name'); After your application has been processed, you can use the -:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface::getCalledListeners` +:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher::getCalledListeners` method to retrieve an array of event listeners that have been called in your application. Similarly, the -:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface::getNotCalledListeners` +:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher::getNotCalledListeners` method returns an array of event listeners that have not been called:: // ... diff --git a/components/expression_language/syntax.rst b/components/expression_language/syntax.rst index 5b7ee72ec7a..045451491f5 100644 --- a/components/expression_language/syntax.rst +++ b/components/expression_language/syntax.rst @@ -21,10 +21,6 @@ The component supports: * **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:: A backslash (``\``) must be escaped by 4 backslashes (``\\\\``) in a string diff --git a/components/filesystem.rst b/components/filesystem.rst index c0cad3dc9b9..6a9282bfe23 100644 --- a/components/filesystem.rst +++ b/components/filesystem.rst @@ -288,6 +288,12 @@ exception on failure:: // returns a path like : /tmp/prefix_wyjgtF $filesystem->tempnam('/tmp', 'prefix_'); + // returns a path like : /tmp/prefix_wyjgtF.png + $filesystem->tempnam('/tmp', 'prefix_', '.png'); + +.. versionadded:: 5.1 + + The option to set a suffix in ``tempnam()`` was introduced in Symfony 5.1. ``dumpFile`` ~~~~~~~~~~~~ diff --git a/components/finder.rst b/components/finder.rst index f799208558e..c0c5682d19a 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -148,10 +148,6 @@ rules to exclude files and directories from the results with the // excludes files/directories matching the .gitignore patterns $finder->ignoreVCSIgnored(true); -.. versionadded:: 4.3 - - The ``ignoreVCSIgnored()`` method was introduced in Symfony 4.3. - File Name ~~~~~~~~~ @@ -248,11 +244,6 @@ Multiple paths can be excluded by chaining calls or passing an array:: // same as above $finder->notPath(['first/dir', 'other/dir']); -.. versionadded:: 4.2 - - Support for passing arrays to ``notPath()`` was introduced in Symfony - 4.2 - File Size ~~~~~~~~~ diff --git a/components/http_foundation.rst b/components/http_foundation.rst index 980eb597c3b..e00f9dabb7b 100644 --- a/components/http_foundation.rst +++ b/components/http_foundation.rst @@ -83,17 +83,19 @@ can be accessed via several public properties: Each property is a :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` instance (or a sub-class of), which is a data holder class: -* ``request``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; +* ``request``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` or + :class:`Symfony\\Component\\HttpFoundation\\InputBag` if the data is + coming from ``$_POST`` parameters; -* ``query``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; +* ``query``: :class:`Symfony\\Component\\HttpFoundation\\InputBag`; -* ``cookies``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; +* ``cookies``: :class:`Symfony\\Component\\HttpFoundation\\InputBag`; * ``attributes``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; -* ``files``: :class:`Symfony\\Component\\HttpFoundation\\FileBag`; +* ``files``: :class:`Symfony\\Component\\HttpFoundation\\FileBag`; -* ``server``: :class:`Symfony\\Component\\HttpFoundation\\ServerBag`; +* ``server``: :class:`Symfony\\Component\\HttpFoundation\\ServerBag`; * ``headers``: :class:`Symfony\\Component\\HttpFoundation\\HeaderBag`. @@ -188,9 +190,18 @@ Finally, the raw data sent with the request body can be accessed using $content = $request->getContent(); -For instance, this may be useful to process a JSON string sent to the +For instance, this may be useful to process a XML string sent to the application by a remote service using the HTTP POST method. +If the request body is a JSON string, it can be accessed using +:method:`Symfony\\Component\\HttpFoundation\\Request::toArray`:: + + $data = $request->toArray(); + +.. versionadded:: 5.2 + + The ``toArray()`` method was introduced in Symfony 5.2. + Identifying a Request ~~~~~~~~~~~~~~~~~~~~~ @@ -236,7 +247,8 @@ Accessing the Session ~~~~~~~~~~~~~~~~~~~~~ If you have a session attached to the request, you can access it via the -:method:`Symfony\\Component\\HttpFoundation\\Request::getSession` method; +:method:`Symfony\\Component\\HttpFoundation\\Request::getSession` method or the +:method:`Symfony\\Component\\HttpFoundation\\RequestStack::getSession` method; the :method:`Symfony\\Component\\HttpFoundation\\Request::hasPreviousSession` method tells you if the request contains a session which was started in one of @@ -272,6 +284,14 @@ this complexity and defines some methods for the most common tasks:: HeaderUtils::unquote('"foo \"bar\""'); // => 'foo "bar"' + // Parses a query string but maintains dots (PHP parse_str() replaces '.' by '_') + HeaderUtils::parseQuery('foo[bar.baz]=qux'); + // => ['foo' => ['bar.baz' => 'qux']] + +.. versionadded:: 5.2 + + The ``parseQuery()`` method was introduced in Symfony 5.2. + Accessing ``Accept-*`` Headers Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -319,10 +339,6 @@ are also supported:: 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 @@ -447,9 +463,21 @@ method takes an instance of You can clear a cookie via the :method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::clearCookie` method. -Note you can create a -:class:`Symfony\\Component\\HttpFoundation\\Cookie` object from a raw header -value using :method:`Symfony\\Component\\HttpFoundation\\Cookie::fromString`. +In addition to the ``Cookie::create()`` method, you can create a ``Cookie`` +object from a raw header value using :method:`Symfony\\Component\\HttpFoundation\\Cookie::fromString` +method. You can also use the ``with*()`` methods to change some Cookie property (or +to build the entire Cookie using a fluent interface). Each ``with*()`` method returns +a new object with the modified property:: + + $cookie = Cookie::create('foo') + ->withValue('bar') + ->withExpires(strtotime('Fri, 20-May-2011 15:25:52 GMT')) + ->withDomain('.example.com') + ->withSecure(true); + +.. versionadded:: 5.1 + + The ``with*()`` methods were introduced in Symfony 5.1. Managing the HTTP Cache ~~~~~~~~~~~~~~~~~~~~~~~ @@ -481,15 +509,25 @@ can be used to set the most commonly used cache information in one method call:: $response->setCache([ - 'etag' => 'abcdef', - 'last_modified' => new \DateTime(), - 'max_age' => 600, - 's_maxage' => 600, - 'private' => false, - 'public' => true, - 'immutable' => true, + 'must_revalidate' => false, + 'no_cache' => false, + 'no_store' => false, + 'no_transform' => false, + 'public' => true, + 'private' => false, + 'proxy_revalidate' => false, + 'max_age' => 600, + 's_maxage' => 600, + 'immutable' => true, + 'last_modified' => new \DateTime(), + 'etag' => 'abcdef' ]); +.. versionadded:: 5.1 + + The ``must_revalidate``, ``no_cache``, ``no_store``, ``no_transform`` and + ``proxy_revalidate`` directives were introduced in Symfony 5.1. + To check if the Response validators (``ETag``, ``Last-Modified``) match a conditional value specified in the client Request, use the :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified` @@ -710,6 +748,37 @@ Session The session information is in its own document: :doc:`/components/http_foundation/sessions`. +Safe Content Preference +----------------------- + +Some web sites have a "safe" mode to assist those who don't want to be exposed +to content to which they might object. The `RFC 8674`_ specification defines a +way for user agents to ask for safe content to a server. + +The specification does not define what content might be considered objectionable, +so the concept of "safe" is not precisely defined. Rather, the term is interpreted +by the server and within the scope of each web site that chooses to act upon this information. + +Symfony offers two methods to interact with this preference: + +* :method:`Symfony\\Component\\HttpFoundation\\Request::preferSafeContent`; +* :method:`Symfony\\Component\\HttpFoundation\\Response::setContentSafe`; + +.. versionadded:: 5.1 + + The ``preferSafeContent()`` and ``setContentSafe()`` methods were introduced + in Symfony 5.1. + +The following example shows how to detect if the user agent prefers "safe" content:: + + if ($request->preferSafeContent()) { + $response = new Response($alternativeContent); + // this informs the user we respected their preferences + $response->setContentSafe(); + + return $response; + } + Learn More ---------- @@ -727,3 +796,4 @@ Learn More .. _Apache: https://tn123.org/mod_xsendfile/ .. _`JSON Hijacking`: https://haacked.com/archive/2009/06/25/json-hijacking.aspx/ .. _OWASP guidelines: https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside +.. _RFC 8674: https://tools.ietf.org/html/rfc8674 diff --git a/components/http_foundation/sessions.rst b/components/http_foundation/sessions.rst index 9c9479e3e5e..5756a38fc58 100644 --- a/components/http_foundation/sessions.rst +++ b/components/http_foundation/sessions.rst @@ -169,6 +169,11 @@ and "Remember Me" login settings or other user based state information. :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag` This implementation allows for attributes to be stored in a structured namespace. + .. deprecated:: 5.3 + + The ``NamespacedAttributeBag`` class is deprecated since Symfony 5.3. + If you need this feature, you will have to implement the class yourself. + :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface` has the API @@ -237,6 +242,11 @@ So any processing of this might quickly get ugly, even adding a token to the arr $tokens['c'] = $value; $session->set('tokens', $tokens); +.. deprecated:: 5.3 + + The ``NamespacedAttributeBag`` class is deprecated since Symfony 5.3. + If you need this feature, you will have to implement the class yourself. + With structured namespacing, the key can be translated to the array structure like this using a namespace character (which defaults to ``/``):: diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 9a1d5a31049..c0da0fd6cfa 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -65,8 +65,8 @@ that system:: */ public function handle( Request $request, - $type = self::MASTER_REQUEST, - $catch = true + int $type = self::MASTER_REQUEST, + bool $catch = true ); } @@ -530,22 +530,10 @@ object, which you can use to access the original exception via the 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 -comes with an :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`, +comes with an :class:`Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener`, which if you choose to use, will do this and more by default (see the sidebar below for more details). @@ -559,10 +547,10 @@ below for more details). There are two main listeners to ``kernel.exception`` when using the Symfony Framework. - **ExceptionListener in the HttpKernel Component** + **ErrorListener in the HttpKernel Component** The first comes core to the HttpKernel component - and is called :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`. + and is called :class:`Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener`. The listener has several goals: 1) The thrown exception is converted into a @@ -627,19 +615,6 @@ kernel.terminate ``KernelEvents::TERMINATE`` :class:`Sym kernel.exception ``KernelEvents::EXCEPTION`` :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` =========================== ====================================== ======================================================================== -.. deprecated:: 4.3 - - Since Symfony 4.3, most of the event classes were renamed. - The following old classes were deprecated: - - * `GetResponseEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\RequestEvent` - * `FilterControllerEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerEvent` - * `FilterControllerArgumentsEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent` - * `GetResponseForControllerResultEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ViewEvent` - * `FilterResponseEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ResponseEvent` - * `PostResponseEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\TerminateEvent` - * `GetResponseForExceptionEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` - .. _http-kernel-working-example: A full Working Example diff --git a/components/inflector.rst b/components/inflector.rst index 5e9f4325884..c42d6ebaeaa 100644 --- a/components/inflector.rst +++ b/components/inflector.rst @@ -5,60 +5,8 @@ The Inflector Component ======================= - The Inflector component converts English words between their singular and - plural forms. +.. deprecated:: 5.1 -Installation ------------- - -.. code-block:: terminal - - $ composer require symfony/inflector - -.. include:: /components/require_autoload.rst.inc - -When you May Need an Inflector ------------------------------- - -In some scenarios such as code generation and code introspection, it's usually -required to convert words from/to singular/plural. For example, if you need to -know which property is associated with an *adder* method, you must convert from -plural to singular (``addStories()`` method -> ``$story`` property). - -Although most human languages define simple pluralization rules, they also -define lots of exceptions. For example, the general rule in English is to add an -``s`` at the end of the word (``book`` -> ``books``) but there are lots of -exceptions even for common words (``woman`` -> ``women``, ``life`` -> ``lives``, -``news`` -> ``news``, ``radius`` -> ``radii``, etc.) - -This component abstracts all those pluralization rules so you can convert -from/to singular/plural with confidence. However, due to the complexity of the -human languages, this component only provides support for the English language. - -Usage ------ - -The Inflector component provides two static methods to convert from/to -singular/plural:: - - use Symfony\Component\Inflector\Inflector; - - Inflector::singularize('alumni'); // 'alumnus' - Inflector::singularize('knives'); // 'knife' - Inflector::singularize('mice'); // 'mouse' - - Inflector::pluralize('grandchild'); // 'grandchildren' - Inflector::pluralize('news'); // 'news' - Inflector::pluralize('bacterium'); // 'bacteria' - -Sometimes it's not possible to determine a unique singular/plural form for the -given word. In those cases, the methods return an array with all the possible -forms:: - - use Symfony\Component\Inflector\Inflector; - - Inflector::singularize('indices'); // ['index', 'indix', 'indice'] - Inflector::singularize('leaves'); // ['leaf', 'leave', 'leaff'] - - Inflector::pluralize('matrix'); // ['matricies', 'matrixes'] - Inflector::pluralize('person'); // ['persons', 'people'] + The Inflector component was deprecated in Symfony 5.1 and its code was moved + into the :doc:`String ` component. + :ref:`Read the new Inflector docs `. diff --git a/components/intl.rst b/components/intl.rst index 6417489b14e..cb120034615 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -120,14 +120,6 @@ You may convert codes between two-letter alpha2 and three-letter alpha3 codes:: $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`` @@ -159,10 +151,6 @@ to catching the exception, you can also check if a given script code is valid:: $isValidScript = Scripts::exists($scriptCode); -.. versionadded:: 4.3 - - The ``Scripts`` class was introduced in Symfony 4.3. - Country Names ~~~~~~~~~~~~~ @@ -219,14 +207,6 @@ You may convert codes between two-letter alpha2 and three-letter alpha3 codes:: $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 ~~~~~~~ @@ -262,10 +242,6 @@ to catching the exception, you can also check if a given locale code is valid:: $isValidLocale = Locales::exists($localeCode); -.. versionadded:: 4.3 - - The ``Locales`` class was introduced in Symfony 4.3. - Currencies ~~~~~~~~~~ @@ -308,10 +284,6 @@ to catching the exception, you can also check if a given currency code is valid: $isValidCurrency = Currencies::exists($currencyCode); -.. versionadded:: 4.3 - - The ``Currencies`` class was introduced in Symfony 4.3. - .. _component-intl-timezones: Timezones @@ -390,10 +362,6 @@ to catching the exception, you can also check if a given timezone ID is valid:: $isValidTimezone = Timezones::exists($timezoneId); -.. versionadded:: 4.3 - - The ``Timezones`` class was introduced in Symfony 4.3. - Learn more ---------- diff --git a/components/ldap.rst b/components/ldap.rst index d7cb6ed17cd..89fb39cb8e8 100644 --- a/components/ldap.rst +++ b/components/ldap.rst @@ -142,6 +142,9 @@ delete existing ones:: $phoneNumber = $entry->getAttribute('phoneNumber'); $isContractor = $entry->hasAttribute('contractorCompany'); + // attribute names in getAttribute() and hasAttribute() methods are case-sensitive + // pass FALSE as the second method argument to make them case-insensitive + $isContractor = $entry->hasAttribute('contractorCompany', false); $entry->setAttribute('email', ['fabpot@symfony.com']); $entryManager->update($entry); @@ -153,6 +156,11 @@ delete existing ones:: // Removing an existing entry $entryManager->remove(new Entry('cn=Test User,dc=symfony,dc=com')); +.. versionadded:: 5.3 + + The option to make attribute names case-insensitive in ``getAttribute()`` + and ``hasAttribute()`` was introduce in Symfony 5.3. + Batch Updating ______________ diff --git a/components/lock.rst b/components/lock.rst index 16049101e10..f5087d12e85 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -36,11 +36,6 @@ which in turn requires another class to manage the storage of locks:: $store = new SemaphoreStore(); $factory = new LockFactory($store); -.. 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` @@ -124,6 +119,18 @@ they can be decorated with the ``RetryTillSaveStore`` class:: $lock = $factory->createLock('notification-flush'); $lock->acquire(true); +When the provided store does not implement the +:class:`Symfony\\Component\\Lock\\BlockingStoreInterface` interface, the +``Lock`` class will retry to acquire the lock in a non-blocking way until the +lock is acquired. + +.. deprecated:: 5.2 + + As of Symfony 5.2, you don't need to use the ``RetryTillSaveStore`` class + anymore. The ``Lock`` class now provides the default logic to acquire locks + in blocking mode when the store does not implement the + ``BlockingStoreInterface`` interface. + Expiring Locks -------------- @@ -199,6 +206,63 @@ This component also provides two useful methods related to expiring locks: ``getRemainingLifetime()`` (which returns ``null`` or a ``float`` as seconds) and ``isExpired()`` (which returns a boolean). +Shared Locks +------------ + +.. versionadded:: 5.2 + + Shared locks (and the associated ``acquireRead()`` method and + ``SharedLockStoreInterface``) were introduced in Symfony 5.2. + +A shared or `readers–writer lock`_ is a synchronization primitive that allows +concurrent access for read-only operations, while write operations require +exclusive access. This means that multiple threads can read the data in parallel +but an exclusive lock is needed for writing or modifying data. They are used for +example for data structures that cannot be updated atomically and are invalid +until the update is complete. + +Use the :method:`Symfony\\Component\\Lock\\SharedLockInterface::acquireRead` method +to acquire a read-only lock, and the existing +:method:`Symfony\\Component\\Lock\\LockInterface::acquire` method to acquire a +write lock:: + + $lock = $factory->createLock('user'.$user->id); + if (!$lock->acquireRead()) { + return; + } + +Similar to the ``acquire()`` method, pass ``true`` as the argument of ``acquireRead()`` +to acquire the lock in a blocking mode:: + + $lock = $factory->createLock('user'.$user->id); + $lock->acquireRead(true); + +.. note:: + + The `priority policy`_ of Symfony's shared locks depends on the underlying + store (e.g. Redis store prioritizes readers vs writers). + +When a read-only lock is acquired with the method ``acquireRead()``, it's +possible to **promote** the lock, and change it to write lock, by calling the +``acquire()`` method:: + + $lock = $factory->createLock('user'.$userId); + $lock->acquireRead(true); + + if (!$this->shouldUpdate($userId)) { + return; + } + + $lock->acquire(true); // Promote the lock to write lock + $this->update($userId); + +In the same way, it's possible to **demote** a write lock, and change it to a +read-only lock by calling the ``acquireRead()`` method. + +When the provided store does not implement the +:class:`Symfony\\Component\\Lock\\SharedLockStoreInterface` interface, the +``Lock`` class will fallback to a write lock by calling the ``acquire()`` method. + The Owner of The Lock --------------------- @@ -253,22 +317,18 @@ Locks are created and managed in ``Stores``, which are classes that implement The component includes the following built-in store types: -============================================ ====== ======== ======== -Store Scope Blocking Expiring -============================================ ====== ======== ======== -:ref:`FlockStore ` local yes no -:ref:`MemcachedStore ` remote no yes -:ref:`PdoStore ` remote no yes -:ref:`RedisStore ` remote no yes -:ref:`SemaphoreStore ` local yes no -: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``. +============================================ ====== ======== ======== ======= +Store Scope Blocking Expiring Sharing +============================================ ====== ======== ======== ======= +:ref:`FlockStore ` local yes no yes +:ref:`MemcachedStore ` remote no yes no +:ref:`MongoDbStore ` remote no yes no +:ref:`PdoStore ` remote no yes no +:ref:`PostgreSqlStore ` remote yes yes yes +:ref:`RedisStore ` remote no yes yes +:ref:`SemaphoreStore ` local yes no no +:ref:`ZookeeperStore ` remote no no no +============================================ ====== ======== ======== ======= .. _lock-store-flock: @@ -312,6 +372,67 @@ support blocking, and expects a TTL to avoid stalled locks:: Memcached does not support TTL lower than 1 second. +.. _lock-store-mongodb: + +MongoDbStore +~~~~~~~~~~~~ + +.. versionadded:: 5.1 + + The ``MongoDbStore`` was introduced in Symfony 5.1. + +The MongoDbStore saves locks on a MongoDB server ``>=2.2``, it requires a +``\MongoDB\Collection`` or ``\MongoDB\Client`` from `mongodb/mongodb`_ or a +`MongoDB Connection String`_. +This store does not support blocking and expects a TTL to +avoid stalled locks:: + + use Symfony\Component\Lock\Store\MongoDbStore; + + $mongo = 'mongodb://localhost/database?collection=lock'; + $options = [ + 'gcProbablity' => 0.001, + 'database' => 'myapp', + 'collection' => 'lock', + 'uriOptions' => [], + 'driverOptions' => [], + ]; + $store = new MongoDbStore($mongo, $options); + +The ``MongoDbStore`` takes the following ``$options`` (depending on the first parameter type): + +============= ================================================================================================ +Option Description +============= ================================================================================================ +gcProbablity Should a TTL Index be created expressed as a probability from 0.0 to 1.0 (Defaults to ``0.001``) +database The name of the database +collection The name of the collection +uriOptions Array of uri options for `MongoDBClient::__construct`_ +driverOptions Array of driver options for `MongoDBClient::__construct`_ +============= ================================================================================================ + +When the first parameter is a: + +``MongoDB\Collection``: + +- ``$options['database']`` is ignored +- ``$options['collection']`` is ignored + +``MongoDB\Client``: + +- ``$options['database']`` is mandatory +- ``$options['collection']`` is mandatory + +MongoDB Connection String: + +- ``$options['database']`` is used otherwise ``/path`` from the DSN, at least one is mandatory +- ``$options['collection']`` is used otherwise ``?collection=`` from the DSN, at least one is mandatory + +.. note:: + + The ``collection`` querystring parameter is not part of the `MongoDB Connection String`_ definition. + It is used to allow constructing a ``MongoDbStore`` using a `Data Source Name (DSN)`_ without ``$options``. + .. _lock-store-pdo: PdoStore @@ -324,32 +445,41 @@ support blocking, and expects a TTL to avoid stalled locks:: use Symfony\Component\Lock\Store\PdoStore; // a PDO, a Doctrine DBAL connection or DSN for lazy connecting through PDO - $databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=lock'; + $databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=app'; $store = new PdoStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']); .. note:: This store does not support TTL lower than 1 second. -Before storing locks in the database, you must create the table that stores -the information. The store provides a method called -:method:`Symfony\\Component\\Lock\\Store\\PdoStore::createTable` -to set up this table for you according to the database engine used:: +The table where values are stored is created automatically on the first call to +the :method:`Symfony\\Component\\Lock\\Store\\PdoStore::save` method. +You can also create this table explicitly by calling the +:method:`Symfony\\Component\\Lock\\Store\\PdoStore::createTable` method in +your code. - try { - $store->createTable(); - } catch (\PDOException $exception) { - // the table could not be created for some reason - } +.. _lock-store-pgsql: -A great way to set up the table in production is to call the ``createTable()`` -method in your local computer and then generate a -:ref:`database migration `: +PostgreSqlStore +~~~~~~~~~~~~~~~ -.. code-block:: terminal +The PostgreSqlStore uses `Advisory Locks`_ provided by PostgreSQL. It requires a +`PDO`_ connection, a `Doctrine DBAL Connection`_, or a +`Data Source Name (DSN)`_. It supports native blocking, as well as sharing +locks:: - $ php bin/console doctrine:migrations:diff - $ php bin/console doctrine:migrations:migrate + use Symfony\Component\Lock\Store\PostgreSqlStore; + + // a PDO, a Doctrine DBAL connection or DSN for lazy connecting through PDO + $databaseConnectionOrDSN = 'postgresql://myuser:mypassword@localhost:5634/lock'; + $store = new PostgreSqlStore($databaseConnectionOrDSN); + +In opposite to the ``PdoStore``, the ``PostgreSqlStore`` does not need a table to +store locks and does not expire. + +.. versionadded:: 5.2 + + The ``PostgreSqlStore`` was introduced in Symfony 5.2. .. _lock-store-redis: @@ -448,7 +578,9 @@ Remote Stores ~~~~~~~~~~~~~ Remote stores (:ref:`MemcachedStore `, +:ref:`MongoDbStore `, :ref:`PdoStore `, +:ref:`PostgreSqlStore `, :ref:`RedisStore ` and :ref:`ZookeeperStore `) use a unique token to recognize the true owner of the lock. This token is stored in the @@ -473,6 +605,7 @@ Expiring Stores ~~~~~~~~~~~~~~~ Expiring stores (:ref:`MemcachedStore `, +:ref:`MongoDbStore `, :ref:`PdoStore ` and :ref:`RedisStore `) guarantee that the lock is acquired only for the defined duration of time. If @@ -593,6 +726,47 @@ method uses the Memcached's ``flush()`` method which purges and removes everythi The method ``flush()`` must not be called, or locks should be stored in a dedicated Memcached service away from Cache. +MongoDbStore +~~~~~~~~~~~~ + +.. caution:: + + The locked resource name is indexed in the ``_id`` field of the lock + collection. Beware that in MongoDB an indexed field's value can be + `a maximum of 1024 bytes in length`_ inclusive of structural overhead. + +A TTL index must be used to automatically clean up expired locks. +Such an index can be created manually: + +.. code-block:: javascript + + db.lock.ensureIndex( + { "expires_at": 1 }, + { "expireAfterSeconds": 0 } + ) + +Alternatively, the method ``MongoDbStore::createTtlIndex(int $expireAfterSeconds = 0)`` +can be called once to create the TTL index during database setup. Read more +about `Expire Data from Collections by Setting TTL`_ in MongoDB. + +.. tip:: + + ``MongoDbStore`` will attempt to automatically create a TTL index. + It's recommended to set constructor option ``gcProbablity = 0.0`` to + disable this behavior if you have manually dealt with TTL index creation. + +.. caution:: + + This store relies on all PHP application and database nodes to have + synchronized clocks for lock expiry to occur at the correct time. To ensure + locks don't expire prematurely; the lock TTL should be set with enough extra + time in ``expireAfterSeconds`` to account for any clock drift between nodes. + +``writeConcern`` and ``readConcern`` are not specified by MongoDbStore meaning +the collection's settings will take effect. +``readPreference`` is ``primary`` for all queries. +Read more about `Replica Set Read and Write Semantics`_ in MongoDB. + PdoStore ~~~~~~~~~~ @@ -617,6 +791,20 @@ have synchronized clocks. To ensure locks don't expire prematurely; the TTLs should be set with enough extra time to account for any clock drift between nodes. +PostgreSqlStore +~~~~~~~~~~~~~~~ + +The PdoStore relies on the `Advisory Locks`_ properties of the PostgreSQL +database. That means that by using :ref:`PostgreSqlStore ` +the locks will be automatically released at the end of the session in case the +client cannot unlock for any reason. + +If the PostgreSQL service or the machine hosting it restarts, every lock would +be lost without notifying the running processes. + +If the TCP connection is lost, the PostgreSQL may release locks without +notifying the application. + RedisStore ~~~~~~~~~~ @@ -719,10 +907,19 @@ instance, during the deployment of a new version. Processes with new configuration must not be started while old processes with old configuration are still running. +.. _`a maximum of 1024 bytes in length`: https://docs.mongodb.com/manual/reference/limits/#Index-Key-Limit .. _`ACID`: https://en.wikipedia.org/wiki/ACID +.. _`Advisory Locks`: https://www.postgresql.org/docs/current/explicit-locking.html +.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name +.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/src/Connection.php +.. _`Expire Data from Collections by Setting TTL`: https://docs.mongodb.com/manual/tutorial/expire-data/ .. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science) -.. _`PHP semaphore functions`: https://www.php.net/manual/en/book.sem.php +.. _`MongoDB Connection String`: https://docs.mongodb.com/manual/reference/connection-string/ +.. _`mongodb/mongodb`: https://packagist.org/packages/mongodb/mongodb +.. _`MongoDBClient::__construct`: https://docs.mongodb.com/php-library/current/reference/method/MongoDBClient__construct/ .. _`PDO`: https://www.php.net/pdo -.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Connection.php -.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name +.. _`PHP semaphore functions`: https://www.php.net/manual/en/book.sem.php +.. _`Replica Set Read and Write Semantics`: https://docs.mongodb.com/manual/applications/replication/ .. _`ZooKeeper`: https://zookeeper.apache.org/ +.. _`readers–writer lock`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock +.. _`priority policy`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Priority_policies diff --git a/components/messenger.rst b/components/messenger.rst index 0772293eab1..7e1af990db1 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -77,15 +77,9 @@ middleware stack. The component comes with a set of middleware that you can use. When using the message bus with Symfony's FrameworkBundle, the following middleware are configured for you: -#. :class:`Symfony\\Component\\Messenger\\Middleware\\LoggingMiddleware` (logs the processing of your messages) -#. :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` (enables asynchronous processing) +#. :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` (enables asynchronous processing, logs the processing of your messages if you pass a logger) #. :class:`Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware` (calls the registered handler(s)) -.. deprecated:: 4.3 - - The ``LoggingMiddleware`` is deprecated since Symfony 4.3 and will be - removed in 5.0. Pass a logger to ``SendMessageMiddleware`` instead. - Example:: use App\Message\MyMessage; @@ -332,12 +326,6 @@ do is to write your own CSV receiver:: } } -.. versionadded:: 4.3 - - In Symfony 4.3, the ``ReceiverInterface`` has changed its methods as shown - in the example above. You may need to update your code if you used this - interface in previous Symfony versions. - Receiver and Sender on the same Bus ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/mime.rst b/components/mime.rst index 95206fa22e3..0ceb3007bc5 100644 --- a/components/mime.rst +++ b/components/mime.rst @@ -9,10 +9,6 @@ The Mime Component The Mime component allows manipulating the MIME messages used to send emails and provides utilities related to MIME types. -.. versionadded:: 4.3 - - The Mime component was introduced in Symfony 4.3. - Installation ------------ diff --git a/components/options_resolver.rst b/components/options_resolver.rst index a88c9f95d31..941d61de6c7 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -440,10 +440,6 @@ This way, the ``$value`` argument will receive the previously normalized value, otherwise you can prepend the new normalizer by passing ``true`` as third argument. -.. versionadded:: 4.3 - - The ``addNormalizer()`` method was introduced in Symfony 4.3. - Default Values that Depend on another Option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -724,18 +720,31 @@ In same way, parent options can access to the nested options as normal arrays:: Deprecating the Option ~~~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 5.1 + + The signature of the ``setDeprecated()`` method changed from + ``setDeprecated(string $option, ?string $message)`` to + ``setDeprecated(string $option, string $package, string $version, $message)`` + in Symfony 5.1. + Once an option is outdated or you decided not to maintain it anymore, you can deprecate it using the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDeprecated` method:: $resolver ->setDefined(['hostname', 'host']) - // this outputs the following generic deprecation message: - // The option "hostname" is deprecated. - ->setDeprecated('hostname') - // you can also pass a custom deprecation message - ->setDeprecated('hostname', 'The option "hostname" is deprecated, use "host" instead.') + // this outputs the following generic deprecation message: + // Since acme/package 1.2: The option "hostname" is deprecated. + ->setDeprecated('hostname', 'acme/package', '1.2') + + // you can also pass a custom deprecation message (%name% placeholder is available) + ->setDeprecated( + 'hostname', + 'acme/package', + '1.2', + 'The option "hostname" is deprecated, use "host" instead.' + ) ; .. note:: @@ -760,7 +769,7 @@ the option:: ->setDefault('encryption', null) ->setDefault('port', null) ->setAllowedTypes('port', ['null', 'int']) - ->setDeprecated('port', function (Options $options, $value) { + ->setDeprecated('port', 'acme/package', '1.2', function (Options $options, $value) { if (null === $value) { return 'Passing "null" to option "port" is deprecated, pass an integer instead.'; } @@ -782,6 +791,40 @@ the option:: This closure receives as argument the value of the option after validating it and before normalizing it when the option is being resolved. +Chaining Option Configurations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In many cases you may need to define multiple configurations for each option. +For example, suppose the ``InvoiceMailer`` class has an ``host`` option that is required +and a ``transport`` option which can be one of ``sendmail``, ``mail`` and ``smtp``. +You can improve the readability of the code avoiding to duplicate option name for +each configuration using the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::define` +method:: + + // ... + class InvoiceMailer + { + // ... + public function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->define('host') + ->required() + ->default('smtp.example.org') + ->allowedTypes('string') + ->info('The IP address or hostname'); + + $resolver->define('transport') + ->required() + ->default('transport') + ->allowedValues(['sendmail', 'mail', 'smtp']); + } + } + +.. versionadded:: 5.1 + + The ``define()`` and ``info()`` methods were introduced in Symfony 5.1. + Performance Tweaks ~~~~~~~~~~~~~~~~~~ diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index 284dae4d816..dedb70c20d3 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -161,18 +161,14 @@ each test suite's results in their own section. Trigger Deprecation Notices --------------------------- -Deprecation notices can be triggered by using:: +Deprecation notices can be triggered by using ``trigger_deprecation`` from +the ``symfony/deprecation-contracts`` package:: - @trigger_error('Your deprecation message', E_USER_DEPRECATED); + // indicates something is deprecated since version 1.3 of vendor-name/packagename + trigger_deprecation('vendor-name/package-name', '1.3', 'Your deprecation message'); -You can also require the ``symfony/deprecation-contracts`` package that provides -a global ``trigger_deprecation()`` function for this usage. - -Without the `@-silencing operator`_, users would need to opt-out from deprecation -notices. Silencing by default swaps this behavior and allows users to opt-in -when they are ready to cope with them (by adding a custom error handler like the -one provided by this bridge). When not silenced, deprecation notices will appear -in the **Unsilenced** section of the deprecation report. + // you can also use printf format (all arguments after the message will be used) + trigger_deprecation('...', '1.3', 'Value "%s" is deprecated, use ... instead.', $value); Mark Tests as Legacy -------------------- @@ -293,6 +289,35 @@ Here is a summary that should help you pick the right configuration: | | cannot afford to use one of the modes above. | +------------------------+-----------------------------------------------------+ +Baseline Deprecations +..................... + +If your application has some deprecations that you can't fix for some reasons, +you can tell Symfony to ignore them. The trick is to create a file with the +allowed deprecations and define it as the "deprecation baseline". Deprecations +inside that file are ignore but the rest of deprecations are still reported. + +First, generate the file with the allowed deprecations (run the same command +whenever you want to update the existing file): + +.. code-block:: terminal + + $ SYMFONY_DEPRECATIONS_HELPER='generateBaseline=true&baselineFile=./tests/allowed.json' ./vendor/bin/simple-phpunit + +This command stores all the deprecations reported while running tests in the +given file path and encoded in JSON. + +Then, you can run the following command to use that file and ignore those deprecations: + +.. code-block:: terminal + + $ SYMFONY_DEPRECATIONS_HELPER='baselineFile=./tests/allowed.json' ./vendor/bin/simple-phpunit + +.. versionadded:: 5.2 + + The ``baselineFile`` and ``generateBaseline`` options were introduced in + Symfony 5.2. + Disabling the Verbose Output ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -300,6 +325,14 @@ By default, the bridge will display a detailed output with the number of deprecations and where they arise. If this is too much for you, you can use ``SYMFONY_DEPRECATIONS_HELPER=verbose=0`` to turn the verbose output off. +It's also possible to change verbosity per deprecation type. For example, using +``quiet[]=indirect&quiet[]=other`` will hide details for deprecations of types +"indirect" and "other". + +.. versionadded:: 5.1 + + The ``quiet`` option was introduced in Symfony 5.1. + Disabling the Deprecation Helper ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -332,32 +365,70 @@ time. This can be disabled with the ``debug-class-loader`` option. -.. versionadded:: 4.2 +Compile-time Deprecations +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``debug:container`` command to list the deprecations generated during +the compiling and warming up of the container: + +.. code-block:: terminal + + $ php bin/console debug:container --deprecations + +.. versionadded:: 5.1 + + The ``--deprecations`` option was introduced in Symfony 5.1. + +Log Deprecations +~~~~~~~~~~~~~~~~ + +For turning the verbose output off and write it to a log file instead you can use +``SYMFONY_DEPRECATIONS_HELPER='logFile=/path/deprecations.log'``. - The ``DebugClassLoader`` integration was introduced in Symfony 4.2. +.. versionadded:: 5.3 + + The ``logFile`` option was introduced in Symfony 5.3. Write Assertions about Deprecations ----------------------------------- When adding deprecations to your code, you might like writing tests that verify that they are triggered as required. To do so, the bridge provides the -``@expectedDeprecation`` annotation that you can use on your test methods. +``expectDeprecation()`` method that you can use on your test methods. It requires you to pass the expected message, given in the same format as for the `PHPUnit's assertStringMatchesFormat()`_ method. If you expect more than one -deprecation message for a given test method, you can use the annotation several +deprecation message for a given test method, you can use the method several times (order matters):: - /** - * @group legacy - * @expectedDeprecation This "%s" method is deprecated. - * @expectedDeprecation The second argument of the "%s" method is deprecated. - */ - public function testDeprecatedCode() + use PHPUnit\Framework\TestCase; + use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; + + class MyTest extends TestCase { - @trigger_error('This "Foo" method is deprecated.', E_USER_DEPRECATED); - @trigger_error('The second argument of the "Bar" method is deprecated.', E_USER_DEPRECATED); + use ExpectDeprecationTrait; + + /** + * @group legacy + */ + public function testDeprecatedCode() + { + // test some code that triggers the following deprecation: + // trigger_deprecation('vendor-name/package-name', '5.1', 'This "Foo" method is deprecated.'); + $this->expectDeprecation('Since vendor-name/package-name 5.1: This "%s" method is deprecated'); + + // ... + + // test some code that triggers the following deprecation: + // trigger_deprecation('vendor-name/package-name', '4.4', 'The second argument of the "Bar" method is deprecated.'); + $this->expectDeprecation('Since vendor-name/package-name 4.4: The second argument of the "%s" method is deprecated.'); + } } +.. deprecated:: 5.1 + + Symfony versions previous to 5.1 also included a ``@expectedDeprecation`` + annotation to test deprecations, but it was deprecated in favor of the method. + Display the Full Stack Trace ---------------------------- @@ -394,10 +465,6 @@ the test suite cannot use the latest versions of PHPUnit because: 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 @@ -407,10 +474,6 @@ 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()``, @@ -446,10 +509,6 @@ call to the ``doSetUp()``, ``doTearDown()``, ``doSetUpBeforeClass()`` and 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 @@ -619,20 +678,19 @@ functions: Use Case ~~~~~~~~ -Consider the following example that uses the ``checkMX`` option of the ``Email`` -constraint to test the validity of the email domain:: +Consider the following example that tests a custom class called ``DomainValidator`` +which defines a ``checkDnsRecord`` option to also validate that a domain is +associated to a valid host:: + use App\Validator\DomainValidator; use PHPUnit\Framework\TestCase; - use Symfony\Component\Validator\Constraints\Email; class MyTest extends TestCase { public function testEmail() { - $validator = ... - $constraint = new Email(['checkMX' => true]); - - $result = $validator->validate('foo@example.com', $constraint); + $validator = new DomainValidator(['checkDnsRecord' => true]); + $isValid = $validator->validate('example.com'); // ... } @@ -642,22 +700,23 @@ In order to avoid making a real network connection, add the ``@dns-sensitive`` annotation to the class and use the ``DnsMock::withMockedHosts()`` to configure the data you expect to get for the given hosts:: + use App\Validator\DomainValidator; use PHPUnit\Framework\TestCase; - use Symfony\Component\Validator\Constraints\Email; + use Symfony\Bridge\PhpUnit\DnsMock; /** * @group dns-sensitive */ - class MyTest extends TestCase + class DomainValidatorTest extends TestCase { public function testEmails() { - DnsMock::withMockedHosts(['example.com' => [['type' => 'MX']]]); - - $validator = ... - $constraint = new Email(['checkMX' => true]); + DnsMock::withMockedHosts([ + 'example.com' => [['type' => 'A', 'ip' => '1.2.3.4']], + ]); - $result = $validator->validate('foo@example.com', $constraint); + $validator = new DomainValidator(['checkDnsRecord' => true]); + $isValid = $validator->validate('example.com'); // ... } @@ -856,6 +915,15 @@ If you have installed the bridge through Composer, you can run it by calling e.g It's also possible to set ``SYMFONY_PHPUNIT_VERSION`` as a real env var (not defined in a :ref:`dotenv file `). + In the same way, ``SYMFONY_MAX_PHPUNIT_VERSION`` will set the maximum version + of PHPUnit to be considered. This is useful when testing a framework that does + not support the latest version(s) of PHPUnit. + +.. versionadded:: 5.2 + + The ``SYMFONY_MAX_PHPUNIT_VERSION`` env variable was introduced in + Symfony 5.2. + .. tip:: If you still need to use ``prophecy`` (but not ``symfony/yaml``), diff --git a/components/process.rst b/components/process.rst index b7845145ca3..2cd131be46d 100644 --- a/components/process.rst +++ b/components/process.rst @@ -102,10 +102,20 @@ with a non-zero code):: :method:`Symfony\\Component\\Process\\Process::getLastOutputTime` method. This method returns ``null`` if the process wasn't started! - .. versionadded:: 4.4 +Configuring Process Options +--------------------------- - The :method:`Symfony\\Component\\Process\\Process::getLastOutputTime` - method was introduced in Symfony 4.4. +.. versionadded:: 5.2 + + The feature to configure process options was introduced in Symfony 5.2. + +Symfony uses the PHP :phpfunction:`proc_open` function to run the processes. +You can configure the options passed to the ``other_options`` argument of +``proc_open()`` using the ``setOptions()`` method:: + + $process = new Process(['...', '...', '...']); + // this option allows a subprocess to continue running after the main script exited + $process->setOptions(['create_new_console' => true]); Using Features From the OS Shell -------------------------------- @@ -150,10 +160,6 @@ enclosing a variable name into ``"${:`` and ``}"`` exactly, the process object will replace it with its escaped value, or will fail if the variable is not found in the list of environment variables attached to the command. -.. versionadded:: 4.4 - - Portable command lines were introduced in Symfony 4.4. - Setting Environment Variables for Processes ------------------------------------------- @@ -383,6 +389,22 @@ instead:: ); $process->run(); +Using a Prepared Command Line +----------------------------- + +You can run a process by using a prepared command line with double quote +variable notation. This allows you to use placeholders so that only the +parameterized values can be changed, but not the rest of the script:: + + 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! + Process Timeout --------------- @@ -413,6 +435,14 @@ check regularly:: usleep(200000); } +.. tip:: + + You can get the process start time using the ``getStartTime()`` method. + + .. versionadded:: 5.1 + + The ``getStartTime()`` method was introduced in Symfony 5.1. + .. _reference-process-signal: Process Idle Timeout diff --git a/components/property_access.rst b/components/property_access.rst index 7b9053b835f..3495cef07c5 100644 --- a/components/property_access.rst +++ b/components/property_access.rst @@ -169,11 +169,6 @@ This will produce: ``This person is an author`` Accessing a non Existing Property Path ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.3 - - The ``disableExceptionOnInvalidPropertyPath()`` method was introduced in - Symfony 4.3. - By default a :class:`Symfony\\Component\\PropertyAccess\\Exception\\NoSuchPropertyException` is thrown if the property path passed to :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::getValue` does not exist. You can change this behavior using the @@ -196,6 +191,8 @@ method:: $value = $propertyAccessor->getValue($person, 'birthday'); +.. _components-property-access-magic-get: + Magic ``__get()`` Method ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -218,6 +215,11 @@ The ``getValue()`` method can also use the magic ``__get()`` method:: var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...] +.. versionadded:: 5.2 + + The magic ``__get()`` method can be disabled since in Symfony 5.2. + see `Enable other Features`_. + .. _components-property-access-magic-call: Magic ``__call()`` Method @@ -278,6 +280,8 @@ also write to an array. This can be achieved using the // or // var_dump($person['first_name']); // 'Wouter' +.. _components-property-access-writing-to-objects: + Writing to Objects ------------------ @@ -356,6 +360,11 @@ see `Enable other Features`_:: var_dump($person->getWouter()); // [...] +.. versionadded:: 5.2 + + The magic ``__set()`` method can be disabled since in Symfony 5.2. + see `Enable other Features`_. + Writing to Array Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -399,6 +408,45 @@ and ``removeChild()`` methods to access to the ``children`` property. If available, *adder* and *remover* methods have priority over a *setter* method. +Using non-standard adder/remover methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes, adder and remover methods don't use the standard ``add`` or ``remove`` prefix, like in this example:: + + // ... + class PeopleList + { + // ... + + public function joinPeople(string $people): void + { + $this->peoples[] = $people; + } + + public function leavePeople(string $people): void + { + foreach ($this->peoples as $id => $item) { + if ($people === $item) { + unset($this->peoples[$id]); + break; + } + } + } + } + + use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; + use Symfony\Component\PropertyAccess\PropertyAccessor; + + $list = new PeopleList(); + $reflectionExtractor = new ReflectionExtractor(null, null, ['join', 'leave']); + $propertyAccessor = new PropertyAccessor(false, false, null, true, $reflectionExtractor, $reflectionExtractor); + $propertyAccessor->setValue($person, 'peoples', ['kevin', 'wouter']); + + var_dump($person->getPeoples()); // ['kevin', 'wouter'] + +Instead of calling ``add()`` and ``remove()``, the PropertyAccess +component will call ``join()`` and ``leave()`` methods. + Checking Property Paths ----------------------- @@ -466,14 +514,20 @@ configured to enable extra features. To do that you could use the // ... $propertyAccessorBuilder = PropertyAccess::createPropertyAccessorBuilder(); - // enables magic __call - $propertyAccessorBuilder->enableMagicCall(); + $propertyAccessorBuilder->enableMagicCall(); // enables magic __call + $propertyAccessorBuilder->enableMagicGet(); // enables magic __get + $propertyAccessorBuilder->enableMagicSet(); // enables magic __set + $propertyAccessorBuilder->enableMagicMethods(); // enables magic __get, __set and __call - // disables magic __call - $propertyAccessorBuilder->disableMagicCall(); + $propertyAccessorBuilder->disableMagicCall(); // disables magic __call + $propertyAccessorBuilder->disableMagicGet(); // disables magic __get + $propertyAccessorBuilder->disableMagicSet(); // disables magic __set + $propertyAccessorBuilder->disableMagicMethods(); // disables magic __get, __set and __call - // checks if magic __call handling is enabled + // checks if magic __call, __get or __set handling are enabled $propertyAccessorBuilder->isMagicCallEnabled(); // true or false + $propertyAccessorBuilder->isMagicGetEnabled(); // true or false + $propertyAccessorBuilder->isMagicSetEnabled(); // true or false // At the end get the configured property accessor $propertyAccessor = $propertyAccessorBuilder->getPropertyAccessor(); @@ -485,7 +539,7 @@ configured to enable extra features. To do that you could use the Or you can pass parameters directly to the constructor (not the recommended way):: - // ... - $propertyAccessor = new PropertyAccessor(true); // this enables handling of magic __call + // enable handling of magic __call, __set but not __get: + $propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL | PropertyAccessor::MAGIC_SET); .. _The Inflector component: https://github.com/symfony/inflector diff --git a/components/property_info.rst b/components/property_info.rst index d8e3a693b12..b6684d948d8 100644 --- a/components/property_info.rst +++ b/components/property_info.rst @@ -293,10 +293,6 @@ string values: ``array``, ``bool``, ``callable``, ``float``, ``int``, Constants inside the :class:`Symfony\\Component\\PropertyInfo\\Type` class, in the form ``Type::BUILTIN_TYPE_*``, are provided for convenience. -.. versionadded:: 4.4 - - Support for typed properties (added in PHP 7.4) was introduced in Symfony 4.4. - ``Type::isNullable()`` ~~~~~~~~~~~~~~~~~~~~~~ @@ -327,10 +323,6 @@ this returns ``true`` if: ``@var SomeClass``, ``@var SomeClass``, ``@var Doctrine\Common\Collections\Collection``, etc.) -.. versionadded:: 4.2 - - The support of phpDocumentor collection types was introduced in Symfony 4.2. - ``Type::getCollectionKeyType()`` & ``Type::getCollectionValueType()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -384,11 +376,6 @@ return and scalar types for PHP 7:: // Initializable information $reflectionExtractor->isInitializable($class, $property); -.. versionadded:: 4.1 - - The feature to extract the property types from constructor arguments was - introduced in Symfony 4.1. - .. note:: When using the Symfony framework, this service is automatically registered @@ -447,8 +434,16 @@ with the ``property_info`` service in the Symfony Framework:: ); $serializerExtractor = new SerializerExtractor($serializerClassMetadataFactory); - // List information. - $serializerExtractor->getProperties($class); + // the `serializer_groups` option must be configured (may be set to null) + $serializerExtractor->getProperties($class, ['serializer_groups' => ['mygroup']]); + +If ``serializer_groups`` is set to ``null``, serializer groups metadata won't be +checked but you will get only the properties considered by the Serializer +Component (notably the ``@Ignore`` annotation is taken into account). + +.. versionadded:: 5.2 + + Support for the ``null`` value in ``serializer_groups`` was introduced in Symfony 5.2. DoctrineExtractor ~~~~~~~~~~~~~~~~~ @@ -504,10 +499,6 @@ service by defining it as a service with one or more of the following * ``property_info.initializable_extractor`` if it provides initializable information (it checks if a property can be initialized through the constructor). -.. versionadded:: 4.2 - - The ``property_info.initializable_extractor`` was introduced in Symfony 4.2. - .. _`phpDocumentor Reflection`: https://github.com/phpDocumentor/ReflectionDocBlock .. _`phpdocumentor/reflection-docblock`: https://packagist.org/packages/phpdocumentor/reflection-docblock .. _`Doctrine ORM`: https://www.doctrine-project.org/projects/orm.html diff --git a/components/security.rst b/components/security.rst index ee6c1580472..9985b611c63 100644 --- a/components/security.rst +++ b/components/security.rst @@ -14,12 +14,6 @@ The Security Component Installation ------------ -.. code-block:: terminal - - $ composer require symfony/security - -.. include:: /components/require_autoload.rst.inc - The Security component is divided into several smaller sub-components which can be used separately: @@ -38,6 +32,17 @@ be used separately: It brings many layers of authentication together, allowing the creation of complex authentication systems. +You can install each of them separately in your project: + +.. code-block:: terminal + + $ composer require symfony/security-core + $ composer require symfony/security-http + $ composer require symfony/security-csrf + $ composer require symfony/security-guard + +.. include:: /components/require_autoload.rst.inc + .. seealso:: This article explains how to use the Security features as an independent diff --git a/components/security/authentication.rst b/components/security/authentication.rst index 1f75b433c6c..8761e87915a 100644 --- a/components/security/authentication.rst +++ b/components/security/authentication.rst @@ -319,10 +319,6 @@ the ``switch_user`` firewall listener. The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event is triggered when a token has been deauthenticated because of a user change, it can help you doing some clean-up task. -.. versionadded:: 4.3 - - The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event was introduced in Symfony 4.3. - .. seealso:: For more information on switching users, see diff --git a/components/security/authorization.rst b/components/security/authorization.rst index f6a8776aa7b..b884ce97ac0 100644 --- a/components/security/authorization.rst +++ b/components/security/authorization.rst @@ -19,7 +19,7 @@ by an instance of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Acc An authorization decision will always be based on a few things: * The current token - For instance, the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles` + For instance, the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames` method may be used to retrieve the roles of the current user (e.g. ``ROLE_SUPER_ADMIN``), or a decision may be based on the class of the token. * A set of attributes @@ -51,6 +51,13 @@ recognizes several strategies: abstained from voting, the decision is based on the ``allow_if_all_abstain`` config option (which defaults to ``false``). +``priority`` + grants or denies access by the first voter that does not abstain; + + .. versionadded:: 5.1 + + The ``priority`` version strategy was introduced in Symfony 5.1. + Usage of the available options in detail:: use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; @@ -58,7 +65,7 @@ Usage of the available options in detail:: // instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface $voters = [...]; - // one of "affirmative", "consensus", "unanimous" + // one of "affirmative", "consensus", "unanimous", "priority" $strategy = ...; // whether or not to grant access when all voters abstain @@ -100,10 +107,22 @@ AuthenticatedVoter ~~~~~~~~~~~~~~~~~~ The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter` -voter supports the attributes ``IS_AUTHENTICATED_FULLY``, ``IS_AUTHENTICATED_REMEMBERED``, -and ``IS_AUTHENTICATED_ANONYMOUSLY`` and grants access based on the current -level of authentication, i.e. is the user fully authenticated, or only based -on a "remember-me" cookie, or even authenticated anonymously?:: +voter supports the attributes ``IS_AUTHENTICATED_FULLY``, +``IS_AUTHENTICATED_REMEMBERED``, ``IS_AUTHENTICATED_ANONYMOUSLY``, +to grant access based on the current level of authentication, i.e. is the +user fully authenticated, or only based on a "remember-me" cookie, or even +authenticated anonymously? + +It also supports the attributes ``IS_ANONYMOUS``, ``IS_REMEMBERED``, +``IS_IMPERSONATOR`` to grant access based on a specific state of +authentication. + +.. versionadded:: 5.1 + + The ``IS_ANONYMOUS``, ``IS_REMEMBERED`` and ``IS_IMPERSONATOR`` + attributes were introduced in Symfony 5.1. + +:: use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; @@ -125,7 +144,7 @@ RoleVoter The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` supports attributes starting with ``ROLE_`` and grants access to the user when at least one required ``ROLE_*`` attribute can be found in the array of -roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles` +roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames` method:: use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; @@ -187,7 +206,7 @@ expressions have access to a number of $object = ...; $expression = new Expression( - '"ROLE_ADMIN" in roles or (not is_anonymous() and user.isSuperAdmin())' + '"ROLE_ADMIN" in role_names or (not is_anonymous() and user.isSuperAdmin())' ) $vote = $expressionVoter->vote($token, $object, [$expression]); @@ -260,4 +279,3 @@ decision manager:: if (!$authorizationChecker->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); } - diff --git a/components/semaphore.rst b/components/semaphore.rst new file mode 100644 index 00000000000..ebae3df89e8 --- /dev/null +++ b/components/semaphore.rst @@ -0,0 +1,81 @@ +.. index:: + single: Semaphore + single: Components; Semaphore + +The Semaphore Component +======================= + + The Semaphore Component manages `semaphores`_, a mechanism to provide + exclusive access to a shared resource. + +.. versionadded:: 5.2 + + The Semaphore Component was introduced in Symfony 5.2. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/semaphore + +.. include:: /components/require_autoload.rst.inc + +Usage +----- + +In computer science, a semaphore is a variable or abstract data type used to +control access to a common resource by multiple processes in a concurrent +system such as a multitasking operating system. The main difference +with :doc:`locks ` is that semaphores allow more than one process to +access a resource, whereas locks only allow one process. + +Create semaphores with the :class:`Symfony\\Component\\Semaphore\\SemaphoreFactory` +class, which in turn requires another class to manage the storage:: + + use Symfony\Component\Semaphore\SemaphoreFactory; + use Symfony\Component\Semaphore\Store\RedisStore; + + $redis = new Redis(); + $redis->connect('172.17.0.2'); + + $store = new RedisStore($redis); + $factory = new SemaphoreFactory($store); + +The semaphore is created by calling the +:method:`Symfony\\Component\\Semaphore\\SemaphoreFactory::createSemaphore` +method. Its first argument is an arbitrary string that represents the locked +resource. Its second argument is the maximum number of process allowed. Then, a +call to the :method:`Symfony\\Component\\Semaphore\\SemaphoreInterface::acquire` +method will try to acquire the semaphore:: + + // ... + $semaphore = $factory->createSemaphore('pdf-invoice-generation', 2); + + if ($semaphore->acquire()) { + // The resource "pdf-invoice-generation" is locked. + // You can compute and generate invoice safely here. + + $semaphore->release(); + } + +If the semaphore can not be acquired, the method returns ``false``. The +``acquire()`` method can be safely called repeatedly, even if the semaphore is +already acquired. + +.. note:: + + Unlike other implementations, the Semaphore component distinguishes + semaphores instances even when they are created for the same resource. If a + semaphore has to be used by several services, they should share the same + ``Semaphore`` instance returned by the ``SemaphoreFactory::createSemaphore`` + method. + +.. tip:: + + If you don't release the semaphore explicitly, it will be released + automatically on instance destruction. In some cases, it can be useful to + lock a resource across several requests. To disable the automatic release + behavior, set the fifth argument of the ``createSemaphore()`` method to ``false``. + +.. _`semaphores`: https://en.wikipedia.org/wiki/Semaphore_(programming) diff --git a/components/serializer.rst b/components/serializer.rst index f554ed7cf76..400aebee771 100644 --- a/components/serializer.rst +++ b/components/serializer.rst @@ -227,11 +227,6 @@ normalized data, instead of the denormalizer re-creating them. Note that arrays of objects. Those will still be replaced when present in the normalized data. -.. versionadded:: 4.3 - - The ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option was - introduced in Symfony 4.3. - .. _component-serializer-attributes-groups: Attributes Groups @@ -418,9 +413,74 @@ As for groups, attributes can be selected during both the serialization and dese Ignoring Attributes ------------------- -As an option, there's a way to ignore attributes from the origin object. -To remove those attributes provide an array via the ``AbstractNormalizer::IGNORED_ATTRIBUTES`` -key in the ``context`` parameter of the desired serializer method:: +All attributes are included by default when serializing objects. There are two +options to ignore some of those attributes. + +Option 1: Using ``@Ignore`` Annotation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. configuration-block:: + + .. code-block:: php-annotations + + namespace App\Model; + + use Symfony\Component\Serializer\Annotation\Ignore; + + class MyClass + { + public $foo; + + /** + * @Ignore() + */ + public $bar; + } + + .. code-block:: yaml + + App\Model\MyClass: + attributes: + bar: + ignore: true + + .. code-block:: xml + + + + + + true + + + + +You can now ignore specific attributes during serialization:: + + use App\Model\MyClass; + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; + + $obj = new MyClass(); + $obj->foo = 'foo'; + $obj->bar = 'bar'; + + $normalizer = new ObjectNormalizer($classMetadataFactory); + $serializer = new Serializer([$normalizer]); + + $data = $serializer->normalize($obj); + // $data = ['foo' => 'foo']; + +Option 2: Using the Context +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pass an array with the names of the attributes to ignore using the +``AbstractNormalizer::IGNORED_ATTRIBUTES`` key in the ``context`` of the +serializer method:: use Acme\Person; use Symfony\Component\Serializer\Encoder\JsonEncoder; @@ -438,12 +498,6 @@ key in the ``context`` parameter of the desired serializer method:: $serializer = new Serializer([$normalizer], [$encoder]); $serializer->serialize($person, 'json', [AbstractNormalizer::IGNORED_ATTRIBUTES => ['age']]); // Output: {"name":"foo"} -.. deprecated:: 4.2 - - The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes` - method that was used as an alternative to the ``AbstractNormalizer::IGNORED_ATTRIBUTES`` option - was deprecated in Symfony 4.2. - .. _component-serializer-converting-property-names-when-serializing-and-deserializing: Converting Property Names when Serializing and Deserializing @@ -474,12 +528,12 @@ A custom name converter can handle such cases:: class OrgPrefixNameConverter implements NameConverterInterface { - public function normalize($propertyName) + public function normalize(string $propertyName) { return 'org_'.$propertyName; } - public function denormalize($propertyName) + public function denormalize(string $propertyName) { // removes 'org_' prefix return 'org_' === substr($propertyName, 0, 4) ? substr($propertyName, 4) : $propertyName; @@ -643,12 +697,6 @@ and ``remove``. Using Callbacks to Serialize Properties with Object Instances ------------------------------------------------------------- -.. deprecated:: 4.2 - - The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setCallbacks` - method is deprecated since Symfony 4.2. Use the ``callbacks`` - key of the context instead. - When serializing, you can set a callback to format a specific object property:: use App\Model\Person; @@ -681,11 +729,6 @@ When serializing, you can set a callback to format a specific object property:: $serializer->serialize($person, 'json'); // Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"} -.. deprecated:: 4.2 - - The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setCallbacks` is deprecated since - Symfony 4.2, use the "callbacks" key of the context instead. - .. _component-serializer-normalizers: Normalizers @@ -762,10 +805,6 @@ The Serializer component provides several built-in normalizers: This normalizer converts :phpclass:`DateTimeZone` objects into strings that represent the name of the timezone according to the `list of PHP timezones`_. - .. versionadded:: 4.3 - - The ``DateTimeZoneNormalizer`` was introduced in Symfony 4.3. - :class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` This normalizer converts :phpclass:`SplFileInfo` objects into a data URI string (``data:...``) such that files can be embedded into serialized data. @@ -774,6 +813,12 @@ The Serializer component provides several built-in normalizers: This normalizer converts :phpclass:`DateInterval` objects into strings. By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format. +:class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` + This normalizer works with classes that implement + :class:`Symfony\\Component\\Form\\FormInterface`. + + It will get errors from the form and normalize them into an normalized array. + :class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer` This normalizer converts objects that implement :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface` @@ -782,13 +827,27 @@ The Serializer component provides several built-in normalizers: :class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` Normalizes errors according to the API Problem spec `RFC 7807`_. -.. note:: +:class:`Symfony\\Component\\Serializer\\Normalizer\\UidNormalizer` + This normalizer converts objects that implement + :class:`Symfony\\Component\\Uid\\AbstractUid` into strings. + The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Uuid` + is the `RFC 4122`_ format (example: ``d9e7a184-5d5b-11ea-a62a-3499710062d0``). + The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Ulid` + is the Base 32 format (example: ``01E439TP9XJZ9RPFH3T1PYBCR8``). + You can change the string format by setting the serializer context option + ``UidNormalizer::NORMALIZATION_FORMAT_KEY`` to ``UidNormalizer::NORMALIZATION_FORMAT_BASE_58``, + ``UidNormalizer::NORMALIZATION_FORMAT_BASE_32`` or ``UidNormalizer::NORMALIZATION_FORMAT_RFC_4122``. - You can also create your own Normalizer to use another structure. Read more at - :doc:`/serializer/custom_normalizer`. + Also it can denormalize ``uuid`` or ``ulid`` strings to :class:`Symfony\\Component\\Uid\\Uuid` + or :class:`Symfony\\Component\\Uid\\Ulid`. The format does not matter. -All these normalizers are enabled by default when using the Serializer component -in a Symfony application. +.. versionadded:: 5.2 + + The ``UidNormalizer`` was introduced in Symfony 5.2. + +.. versionadded:: 5.3 + + The ``UidNormalizer`` normalization formats were introduced in Symfony 5.3. .. _component-serializer-encoders: @@ -885,10 +944,6 @@ Option Description D ``output_utf8_bom`` Outputs special `UTF-8 BOM`_ along with encoded data ``false`` ======================= ===================================================== ========================== -.. versionadded:: 4.4 - - The ``output_utf8_bom`` option was introduced in Symfony 4.4. - The ``XmlEncoder`` ~~~~~~~~~~~~~~~~~~ @@ -940,8 +995,7 @@ always as a collection. .. tip:: XML comments are ignored by default when decoding contents, but this - behavior can be changed with the optional ``$decoderIgnoredNodeTypes`` argument of - the ``XmlEncoder`` class constructor. + behavior can be changed with the optional context key ``XmlEncoder::DECODER_IGNORED_NODE_TYPES``. Data with ``#comment`` keys are encoded to XML comments by default. This can be changed with the optional ``$encoderIgnoredNodeTypes`` argument of the @@ -977,11 +1031,6 @@ Option Description generated XML ============================== ================================================= ========================== -.. versionadded:: 4.2 - - The ``decoder_ignored_node_types`` and ``encoder_ignored_node_types`` - options were introduced in Symfony 4.2. - The ``YamlEncoder`` ~~~~~~~~~~~~~~~~~~~ @@ -1118,12 +1167,6 @@ having unique identifiers:: var_dump($serializer->serialize($org, 'json')); // {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]} -.. deprecated:: 4.2 - - The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setCircularReferenceHandler` - method is deprecated since Symfony 4.2. Use the ``AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER`` - key of the context instead. - Handling Serialization Depth ---------------------------- @@ -1276,12 +1319,6 @@ having unique identifiers:: ]; */ -.. deprecated:: 4.2 - - The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setMaxDepthHandler` - method is deprecated since Symfony 4.2. Use the ``max_depth_handler`` - key of the context instead. - Handling Arrays --------------- @@ -1557,3 +1594,4 @@ Learn more .. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object .. _`API Platform`: https://api-platform.com .. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php +.. _`RFC 4122`: https://tools.ietf.org/html/rfc4122 diff --git a/components/string.rst b/components/string.rst new file mode 100644 index 00000000000..48f17f0b3e9 --- /dev/null +++ b/components/string.rst @@ -0,0 +1,584 @@ +.. index:: + single: String + single: Components; String + +The String Component +==================== + + The String component provides a single object-oriented API to work with + three "unit systems" of strings: bytes, code points and grapheme clusters. + +.. versionadded:: 5.0 + + The String component was introduced in Symfony 5.0. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/string + +.. include:: /components/require_autoload.rst.inc + +What is a String? +----------------- + +You can skip this section if you already know what a *"code point"* or a +*"grapheme cluster"* are in the context of handling strings. Otherwise, read +this section to learn about the terminology used by this component. + +Languages like English require a very limited set of characters and symbols to +display any content. Each string is a series of characters (letters or symbols) +and they can be encoded even with the most limited standards (e.g. `ASCII`_). + +However, other languages require thousands of symbols to display their contents. +They need complex encoding standards such as `Unicode`_ and concepts like +"character" no longer make sense. Instead, you have to deal with these terms: + +* `Code points`_: they are the atomic unit of information. A string is a series + of code points. Each code point is a number whose meaning is given by the + `Unicode`_ standard. For example, the English letter ``A`` is the ``U+0041`` + code point and the Japanese *kana* ``の`` is the ``U+306E`` code point. +* `Grapheme clusters`_: they are a sequence of one or more code points which are + displayed as a single graphical unit. For example, the Spanish letter ``ñ`` is + a grapheme cluster that contains two code points: ``U+006E`` = ``n`` (*"latin + small letter N"*) + ``U+0303`` = ``◌̃`` (*"combining tilde"*). +* Bytes: they are the actual information stored for the string contents. Each + code point can require one or more bytes of storage depending on the standard + being used (UTF-8, UTF-16, etc.). + +The following image displays the bytes, code points and grapheme clusters for +the same word written in English (``hello``) and Hindi (``नमस्ते``): + +.. image:: /_images/components/string/bytes-points-graphemes.png + :align: center + +Usage +----- + +Create a new object of type :class:`Symfony\\Component\\String\\ByteString`, +:class:`Symfony\\Component\\String\\CodePointString` or +:class:`Symfony\\Component\\String\\UnicodeString`, pass the string contents as +their arguments and then use the object-oriented API to work with those strings:: + + use Symfony\Component\String\UnicodeString; + + $text = (new UnicodeString('This is a déjà-vu situation.')) + ->trimEnd('.') + ->replace('déjà-vu', 'jamais-vu') + ->append('!'); + // $text = 'This is a jamais-vu situation!' + + $content = new UnicodeString('नमस्ते दुनिया'); + if ($content->ignoreCase()->startsWith('नमस्ते')) { + // ... + } + +Method Reference +---------------- + +Methods to Create String Objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First, you can create objects prepared to store strings as bytes, code points +and grapheme clusters with the following classes:: + + use Symfony\Component\String\ByteString; + use Symfony\Component\String\CodePointString; + use Symfony\Component\String\UnicodeString; + + $foo = new ByteString('hello'); + $bar = new CodePointString('hello'); + // UnicodeString is the most commonly used class + $baz = new UnicodeString('hello'); + +Use the ``wrap()`` static method to instantiate more than one string object:: + + $contents = ByteString::wrap(['hello', 'world']); // $contents = ByteString[] + $contents = UnicodeString::wrap(['I', '❤️', 'Symfony']); // $contents = UnicodeString[] + + // use the unwrap method to make the inverse conversion + $contents = UnicodeString::unwrap([ + new UnicodeString('hello'), new UnicodeString('world'), + ]); // $contents = ['hello', 'world'] + +If you work with lots of String objects, consider using the shortcut functions +to make your code more concise:: + + // the b() function creates byte strings + use function Symfony\Component\String\b; + + // both lines are equivalent + $foo = new ByteString('hello'); + $foo = b('hello'); + + // the u() function creates Unicode strings + use function Symfony\Component\String\u; + + // both lines are equivalent + $foo = new UnicodeString('hello'); + $foo = u('hello'); + + // the s() function creates a byte string or Unicode string + // depending on the given contents + use function Symfony\Component\String\s; + + // creates a ByteString object + $foo = s("\xfe\xff"); + // creates a UnicodeString object + $foo = s('अनुच्छेद'); + +.. versionadded:: 5.1 + + The ``s()`` function was introduced in Symfony 5.1. + +There are also some specialized constructors:: + + // ByteString can create a random string of the given length + $foo = ByteString::fromRandom(12); + // by default, random strings use A-Za-z0-9 characters; you can restrict + // the characters to use with the second optional argument + $foo = ByteString::fromRandom(6, 'AEIOU0123456789'); + $foo = ByteString::fromRandom(10, 'qwertyuiop'); + + // CodePointString and UnicodeString can create a string from code points + $foo = UnicodeString::fromCodePoints(0x928, 0x92E, 0x938, 0x94D, 0x924, 0x947); + // equivalent to: $foo = new UnicodeString('नमस्ते'); + +.. versionadded:: 5.1 + + The second argument of ``ByteString::fromRandom()`` was introduced in Symfony 5.1. + +Methods to Transform String Objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each string object can be transformed into the other two types of objects:: + + $foo = ByteString::fromRandom(12)->toCodePointString(); + $foo = (new CodePointString('hello'))->toUnicodeString(); + $foo = UnicodeString::fromCodePoints(0x68, 0x65, 0x6C, 0x6C, 0x6F)->toByteString(); + + // the optional $toEncoding argument defines the encoding of the target string + $foo = (new CodePointString('hello'))->toByteString('Windows-1252'); + // the optional $fromEncoding argument defines the encoding of the original string + $foo = (new ByteString('さよなら'))->toCodePointString('ISO-2022-JP'); + +If the conversion is not possible for any reason, you'll get an +:class:`Symfony\\Component\\String\\Exception\\InvalidArgumentException`. + +There is also a method to get the bytes stored at some position:: + + // ('नमस्ते' bytes = [224, 164, 168, 224, 164, 174, 224, 164, 184, + // 224, 165, 141, 224, 164, 164, 224, 165, 135]) + b('नमस्ते')->bytesAt(0); // [224] + u('नमस्ते')->bytesAt(0); // [224, 164, 168] + + b('नमस्ते')->bytesAt(1); // [164] + u('नमस्ते')->bytesAt(1); // [224, 164, 174] + +Methods Related to Length and White Spaces +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + // returns the number of graphemes, code points or bytes of the given string + $word = 'नमस्ते'; + (new ByteString($word))->length(); // 18 (bytes) + (new CodePointString($word))->length(); // 6 (code points) + (new UnicodeString($word))->length(); // 4 (graphemes) + + // some symbols require double the width of others to represent them when using + // a monospaced font (e.g. in a console). This method returns the total width + // needed to represent the entire word + $word = 'नमस्ते'; + (new ByteString($word))->width(); // 18 + (new CodePointString($word))->width(); // 4 + (new UnicodeString($word))->width(); // 4 + // if the text contains multiple lines, it returns the max width of all lines + $text = "<<width(); // 14 + + // only returns TRUE if the string is exactly an empty string (not even white spaces) + u('hello world')->isEmpty(); // false + u(' ')->isEmpty(); // false + u('')->isEmpty(); // true + + // removes all white spaces from the start and end of the string and replaces two + // or more consecutive white spaces inside contents by a single white space + u(" \n\n hello world \n \n")->collapseWhitespace(); // 'hello world' + +Methods to Change Case +~~~~~~~~~~~~~~~~~~~~~~ + +:: + + // changes all graphemes/code points to lower case + u('FOO Bar')->lower(); // 'foo bar' + + // when dealing with different languages, uppercase/lowercase is not enough + // there are three cases (lower, upper, title), some characters have no case, + // case is context-sensitive and locale-sensitive, etc. + // this method returns a string that you can use in case-insensitive comparisons + u('FOO Bar')->folded(); // 'foo bar' + u('Die O\'Brian Straße')->folded(); // "die o'brian strasse" + + // changes all graphemes/code points to upper case + u('foo BAR')->upper(); // 'FOO BAR' + + // changes all graphemes/code points to "title case" + u('foo bar')->title(); // 'Foo bar' + u('foo bar')->title(true); // 'Foo Bar' + + // changes all graphemes/code points to camelCase + u('Foo: Bar-baz.')->camel(); // 'fooBarBaz' + // changes all graphemes/code points to snake_case + u('Foo: Bar-baz.')->snake(); // 'foo_bar_baz' + // other cases can be achieved by chaining methods. E.g. PascalCase: + u('Foo: Bar-baz.')->camel()->title(); // 'FooBarBaz' + +The methods of all string classes are case-sensitive by default. You can perform +case-insensitive operations with the ``ignoreCase()`` method:: + + u('abc')->indexOf('B'); // null + u('abc')->ignoreCase()->indexOf('B'); // 1 + +Methods to Append and Prepend +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + // adds the given content (one or more strings) at the beginning/end of the string + u('world')->prepend('hello'); // 'helloworld' + u('world')->prepend('hello', ' '); // 'hello world' + + u('hello')->append('world'); // 'helloworld' + u('hello')->append(' ', 'world'); // 'hello world' + + // adds the given content at the beginning of the string (or removes it) to + // make sure that the content starts exactly with that content + u('Name')->ensureStart('get'); // 'getName' + u('getName')->ensureStart('get'); // 'getName' + u('getgetName')->ensureStart('get'); // 'getName' + // this method is similar, but works on the end of the content instead of on the beginning + u('User')->ensureEnd('Controller'); // 'UserController' + u('UserController')->ensureEnd('Controller'); // 'UserController' + u('UserControllerController')->ensureEnd('Controller'); // 'UserController' + + // returns the contents found before/after the first occurrence of the given string + u('hello world')->before('world'); // 'hello ' + u('hello world')->before('o'); // 'hell' + u('hello world')->before('o', true); // 'hello' + + u('hello world')->after('hello'); // ' world' + u('hello world')->after('o'); // ' world' + u('hello world')->after('o', true); // 'o world' + + // returns the contents found before/after the last occurrence of the given string + u('hello world')->beforeLast('o'); // 'hello w' + u('hello world')->beforeLast('o', true); // 'hello wo' + + u('hello world')->afterLast('o'); // 'rld' + u('hello world')->afterLast('o', true); // 'orld' + +Methods to Pad and Trim +~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + // makes a string as long as the first argument by adding the given + // string at the beginning, end or both sides of the string + u(' Lorem Ipsum ')->padBoth(20, '-'); // '--- Lorem Ipsum ----' + u(' Lorem Ipsum')->padStart(20, '-'); // '-------- Lorem Ipsum' + u('Lorem Ipsum ')->padEnd(20, '-'); // 'Lorem Ipsum --------' + + // repeats the given string the number of times passed as argument + u('_.')->repeat(10); // '_._._._._._._._._._.' + + // removes the given characters (by default, white spaces) from the string + u(' Lorem Ipsum ')->trim(); // 'Lorem Ipsum' + u('Lorem Ipsum ')->trim('m'); // 'Lorem Ipsum ' + u('Lorem Ipsum')->trim('m'); // 'Lorem Ipsu' + + u(' Lorem Ipsum ')->trimStart(); // 'Lorem Ipsum ' + u(' Lorem Ipsum ')->trimEnd(); // ' Lorem Ipsum' + +Methods to Search and Replace +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + // checks if the string starts/ends with the given string + u('https://symfony.com')->startsWith('https'); // true + u('report-1234.pdf')->endsWith('.pdf'); // true + + // checks if the string contents are exactly the same as the given contents + u('foo')->equalsTo('foo'); // true + + // checks if the string content match the given regular expression + u('avatar-73647.png')->match('/avatar-(\d+)\.png/'); + // result = ['avatar-73647.png', '73647'] + + // checks if the string contains any of the other given strings + u('aeiou')->containsAny('a'); // true + u('aeiou')->containsAny(['ab', 'efg']); // false + u('aeiou')->containsAny(['eio', 'foo', 'z']); // true + + // finds the position of the first occurrence of the given string + // (the second argument is the position where the search starts and negative + // values have the same meaning as in PHP functions) + u('abcdeabcde')->indexOf('c'); // 2 + u('abcdeabcde')->indexOf('c', 2); // 2 + u('abcdeabcde')->indexOf('c', -4); // 7 + u('abcdeabcde')->indexOf('eab'); // 4 + u('abcdeabcde')->indexOf('k'); // null + + // finds the position of the last occurrence of the given string + // (the second argument is the position where the search starts and negative + // values have the same meaning as in PHP functions) + u('abcdeabcde')->indexOfLast('c'); // 7 + u('abcdeabcde')->indexOfLast('c', 2); // 7 + u('abcdeabcde')->indexOfLast('c', -4); // 2 + u('abcdeabcde')->indexOfLast('eab'); // 4 + u('abcdeabcde')->indexOfLast('k'); // null + + // replaces all occurrences of the given string + u('http://symfony.com')->replace('http://', 'https://'); // 'https://symfony.com' + // replaces all occurrences of the given regular expression + u('(+1) 206-555-0100')->replaceMatches('/[^A-Za-z0-9]++/', ''); // '12065550100' + // you can pass a callable as the second argument to perform advanced replacements + u('123')->replaceMatches('/\d/', function ($match) { + return '['.$match[0].']'; + }); // result = '[1][2][3]' + +.. versionadded:: 5.1 + + The ``containsAny()`` method was introduced in Symfony 5.1. + +Methods to Join, Split, Truncate and Reverse +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + // uses the string as the "glue" to merge all the given strings + u(', ')->join(['foo', 'bar']); // 'foo, bar' + + // breaks the string into pieces using the given delimiter + u('template_name.html.twig')->split('.'); // ['template_name', 'html', 'twig'] + // you can set the maximum number of pieces as the second argument + u('template_name.html.twig')->split('.', 2); // ['template_name', 'html.twig'] + + // returns a substring which starts at the first argument and has the length of the + // second optional argument (negative values have the same meaning as in PHP functions) + u('Symfony is great')->slice(0, 7); // 'Symfony' + u('Symfony is great')->slice(0, -6); // 'Symfony is' + u('Symfony is great')->slice(11); // 'great' + u('Symfony is great')->slice(-5); // 'great' + + // reduces the string to the length given as argument (if it's longer) + u('Lorem Ipsum')->truncate(3); // 'Lor' + u('Lorem Ipsum')->truncate(80); // 'Lorem Ipsum' + // the second argument is the character(s) added when a string is cut + // (the total length includes the length of this character(s)) + u('Lorem Ipsum')->truncate(8, '…'); // 'Lorem I…' + // if the third argument is false, the last word before the cut is kept + // even if that generates a string longer than the desired length + u('Lorem Ipsum')->truncate(8, '…', false); // 'Lorem Ipsum' + +.. versionadded:: 5.1 + + The third argument of ``truncate()`` was introduced in Symfony 5.1. + +:: + + // breaks the string into lines of the given length + u('Lorem Ipsum')->wordwrap(4); // 'Lorem\nIpsum' + // by default it breaks by white space; pass TRUE to break unconditionally + u('Lorem Ipsum')->wordwrap(4, "\n", true); // 'Lore\nm\nIpsu\nm' + + // replaces a portion of the string with the given contents: + // the second argument is the position where the replacement starts; + // the third argument is the number of graphemes/code points removed from the string + u('0123456789')->splice('xxx'); // 'xxx' + u('0123456789')->splice('xxx', 0, 2); // 'xxx23456789' + u('0123456789')->splice('xxx', 0, 6); // 'xxx6789' + u('0123456789')->splice('xxx', 6); // '012345xxx' + + // breaks the string into pieces of the length given as argument + u('0123456789')->chunk(3); // ['012', '345', '678', '9'] + + // reverses the order of the string contents + u('foo bar')->reverse(); // 'rab oof' + u('さよなら')->reverse(); // 'らなよさ' + +.. versionadded:: 5.1 + + The ``reverse()`` method was introduced in Symfony 5.1. + +Methods Added by ByteString +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These methods are only available for ``ByteString`` objects:: + + // returns TRUE if the string contents are valid UTF-8 contents + b('Lorem Ipsum')->isUtf8(); // true + b("\xc3\x28")->isUtf8(); // false + +Methods Added by CodePointString and UnicodeString +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These methods are only available for ``CodePointString`` and ``UnicodeString`` +objects:: + + // transliterates any string into the latin alphabet defined by the ASCII encoding + // (don't use this method to build a slugger because this component already provides + // a slugger, as explained later in this article) + u('नमस्ते')->ascii(); // 'namaste' + u('さよなら')->ascii(); // 'sayonara' + u('спасибо')->ascii(); // 'spasibo' + + // returns an array with the code point or points stored at the given position + // (code points of 'नमस्ते' graphemes = [2344, 2350, 2360, 2340] + u('नमस्ते')->codePointsAt(0); // [2344] + u('नमस्ते')->codePointsAt(2); // [2360] + +`Unicode equivalence`_ is the specification by the Unicode standard that +different sequences of code points represent the same character. For example, +the Swedish letter ``å`` can be a single code point (``U+00E5`` = *"latin small +letter A with ring above"*) or a sequence of two code points (``U+0061`` = +*"latin small letter A"* + ``U+030A`` = *"combining ring above"*). The +``normalize()`` method allows to pick the normalization mode:: + + // these encode the letter as a single code point: U+00E5 + u('å')->normalize(UnicodeString::NFC); + u('å')->normalize(UnicodeString::NFKC); + // these encode the letter as two code points: U+0061 + U+030A + u('å')->normalize(UnicodeString::NFD); + u('å')->normalize(UnicodeString::NFKD); + +Slugger +------- + +In some contexts, such as URLs and file/directory names, it's not safe to use +any Unicode character. A *slugger* transforms a given string into another string +that only includes safe ASCII characters:: + + use Symfony\Component\String\Slugger\AsciiSlugger; + + $slugger = new AsciiSlugger(); + $slug = $slugger->slug('Wôrķšƥáçè ~~sèťtïñğš~~'); + // $slug = 'Workspace-settings' + + // you can also pass an array with additional character substitutions + $slugger = new AsciiSlugger('en', ['en' => ['%' => 'percent', '€' => 'euro']]); + $slug = $slugger->slug('10% or 5€'); + // $slug = '10-percent-or-5-euro' + + // if there is no symbols map for your locale (e.g. 'en_GB') then the parent locale's symbols map + // will be used instead (i.e. 'en') + $slugger = new AsciiSlugger('en_GB', ['en' => ['%' => 'percent', '€' => 'euro']]); + $slug = $slugger->slug('10% or 5€'); + // $slug = '10-percent-or-5-euro' + + // for more dynamic substitutions, pass a PHP closure instead of an array + $slugger = new AsciiSlugger('en', function ($string, $locale) { + return str_replace('❤️', 'love', $string); + }); + +.. versionadded:: 5.1 + + The feature to define additional substitutions was introduced in Symfony 5.1. + +.. versionadded:: 5.2 + + The feature to use a PHP closure to define substitutions was introduced in Symfony 5.2. + +.. versionadded:: 5.3 + + The feature to fallback to the parent locale's symbols map was introduced in Symfony 5.3. + +The separator between words is a dash (``-``) by default, but you can define +another separator as the second argument:: + + $slug = $slugger->slug('Wôrķšƥáçè ~~sèťtïñğš~~', '/'); + // $slug = 'Workspace/settings' + +The slugger transliterates the original string into the Latin script before +applying the other transformations. The locale of the original string is +detected automatically, but you can define it explicitly:: + + // this tells the slugger to transliterate from Korean language + $slugger = new AsciiSlugger('ko'); + + // you can override the locale as the third optional parameter of slug() + $slug = $slugger->slug('...', '-', 'fa'); + +In a Symfony application, you don't need to create the slugger yourself. Thanks +to :doc:`service autowiring `, you can inject a +slugger by type-hinting a service constructor argument with the +:class:`Symfony\\Component\\String\\Slugger\\SluggerInterface`. The locale of +the injected slugger is the same as the request locale:: + + use Symfony\Component\String\Slugger\SluggerInterface; + + class MyService + { + private $slugger; + + public function __construct(SluggerInterface $slugger) + { + $this->slugger = $slugger; + } + + public function someMethod() + { + $slug = $this->slugger->slug('...'); + } + } + +.. _string-inflector: + +Inflector +--------- + +.. versionadded:: 5.1 + + The inflector feature was introduced in Symfony 5.1. + +In some scenarios such as code generation and code introspection, you need to +convert words from/to singular/plural. For example, to know the property +associated with an *adder* method, you must convert from plural +(``addStories()`` method) to singular (``$story`` property). + +Most human languages have simple pluralization rules, but at the same time they +define lots of exceptions. For example, the general rule in English is to add an +``s`` at the end of the word (``book`` -> ``books``) but there are lots of +exceptions even for common words (``woman`` -> ``women``, ``life`` -> ``lives``, +``news`` -> ``news``, ``radius`` -> ``radii``, etc.) + +This component provides an :class:`Symfony\\Component\\String\\Inflector\\EnglishInflector` +class to convert English words from/to singular/plural with confidence:: + + use Symfony\Component\String\Inflector\EnglishInflector; + + $inflector = new EnglishInflector(); + + $result = $inflector->singularize('teeth'); // ['tooth'] + $result = $inflector->singularize('radii'); // ['radius'] + $result = $inflector->singularize('leaves'); // ['leaf', 'leave', 'leaff'] + + $result = $inflector->pluralize('bacterium'); // ['bacteria'] + $result = $inflector->pluralize('news'); // ['news'] + $result = $inflector->pluralize('person'); // ['persons', 'people'] + +The value returned by both methods is always an array because sometimes it's not +possible to determine a unique singular/plural form for the given word. + +.. _`ASCII`: https://en.wikipedia.org/wiki/ASCII +.. _`Unicode`: https://en.wikipedia.org/wiki/Unicode +.. _`Code points`: https://en.wikipedia.org/wiki/Code_point +.. _`Grapheme clusters`: https://en.wikipedia.org/wiki/Grapheme +.. _`Unicode equivalence`: https://en.wikipedia.org/wiki/Unicode_equivalence diff --git a/components/uid.rst b/components/uid.rst new file mode 100644 index 00000000000..e84b7296fad --- /dev/null +++ b/components/uid.rst @@ -0,0 +1,371 @@ +.. index:: + single: UID + single: Components; UID + +The UID Component +================= + + The UID component provides utilities to work with `unique identifiers`_ (UIDs) + such as UUIDs and ULIDs. + +.. versionadded:: 5.1 + + The UID component was introduced in Symfony 5.1. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/uid + +.. include:: /components/require_autoload.rst.inc + +UUIDs +----- + +`UUIDs`_ (*universally unique identifiers*) are one of the most popular UIDs in +the software industry. UUIDs are 128-bit numbers usually represented as five +groups of hexadecimal characters: ``xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx`` +(the ``M`` digit is the UUID version and the ``N`` digit is the UUID variant). + +Generating UUIDs +~~~~~~~~~~~~~~~~ + +Use the named constructors of the ``Uuid`` class or any of the specific classes +to create each type of UUID:: + + use Symfony\Component\Uid\Uuid; + + // UUID type 1 generates the UUID using the MAC address of your device and a timestamp. + // Both are obtained automatically, so you don't have to pass any constructor argument. + $uuid = Uuid::v1(); // $uuid is an instance of Symfony\Component\Uid\UuidV1 + + // UUID type 4 generates a random UUID, so you don't have to pass any constructor argument. + $uuid = Uuid::v4(); // $uuid is an instance of Symfony\Component\Uid\UuidV4 + + // UUID type 3 and 5 generate a UUID hashing the given namespace and name. Type 3 uses + // MD5 hashes and Type 5 uses SHA-1. The namespace is another UUID (e.g. a Type 4 UUID) + // and the name is an arbitrary string (e.g. a product name; if it's unique). + $namespace = Uuid::v4(); + $name = $product->getUniqueName(); + + $uuid = Uuid::v3($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV3 + $uuid = Uuid::v5($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV5 + + // the namespaces defined by RFC 4122 are available as constants + // (see https://tools.ietf.org/html/rfc4122#appendix-C) + $uuid = Uuid::v3(Uuid::NAMESPACE_DNS, $name); + $uuid = Uuid::v3(Uuid::NAMESPACE_URL, $name); + $uuid = Uuid::v3(Uuid::NAMESPACE_OID, $name); + $uuid = Uuid::v3(Uuid::NAMESPACE_X500, $name); + + // UUID type 6 is not part of the UUID standard. It's lexicographically sortable + // (like ULIDs) and contains a 60-bit timestamp and 63 extra unique bits. + // It's defined in http://gh.peabody.io/uuidv6/ + $uuid = Uuid::v6(); // $uuid is an instance of Symfony\Component\Uid\UuidV6 + +.. versionadded:: 5.3 + + The ``Uuid::NAMESPACE_*`` constants were introduced in Symfony 5.3. + +If your UUID is generated by another system, use the ``fromString()`` method to +create an object and make use of the utilities available for Symfony UUIDs:: + + // this value is generated somewhere else (can also be in binary format) + $uuidValue = 'd9e7a184-5d5b-11ea-a62a-3499710062d0'; + $uuid = Uuid::fromString($uuidValue); + +Converting UUIDs +~~~~~~~~~~~~~~~~ + +Use these methods to transform the UUID object into different bases:: + + $uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0'); + + $uuid->toBinary(); // string(16) "..." (binary contents can't be printed) + $uuid->toBase32(); // string(26) "6SWYGR8QAV27NACAHMK5RG0RPG" + $uuid->toBase58(); // string(22) "TuetYWNHhmuSQ3xPoVLv9M" + $uuid->toRfc4122(); // string(36) "d9e7a184-5d5b-11ea-a62a-3499710062d0" + +Working with UUIDs +~~~~~~~~~~~~~~~~~~ + +UUID objects created with the ``Uuid`` class can use the following methods +(which are equivalent to the ``uuid_*()`` method of the PHP extension):: + + use Symfony\Component\Uid\NilUuid; + use Symfony\Component\Uid\Uuid; + + // checking if the UUID is null (note that the class is called + // NilUuid instead of NullUuid to follow the UUID standard notation) + $uuid = Uuid::v4(); + $uuid instanceof NilUuid; // false + + // checking the type of UUID + use Symfony\Component\Uid\UuidV4; + $uuid = Uuid::v4(); + $uuid instanceof UuidV4; // true + + // getting the UUID datetime (it's only available in certain UUID types) + $uuid = Uuid::v1(); + $uuid->getDateTime(); // returns a \DateTimeImmutable instance + + // comparing UUIDs and checking for equality + $uuid1 = Uuid::v1(); + $uuid4 = Uuid::v4(); + $uuid1->equals($uuid4); // false + + // this method returns: + // * int(0) if $uuid1 and $uuid4 are equal + // * int > 0 if $uuid1 is greater than $uuid4 + // * int < 0 if $uuid1 is less than $uuid4 + $uuid1->compare($uuid4); // e.g. int(4) + +.. versionadded:: 5.3 + + The ``getDateTime()`` method was introduced in Symfony 5.3. In previous + versions it was called ``getTime()``. + +Storing UUIDs in Databases +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you :doc:`use Doctrine `, consider using the ``uuid`` Doctrine +type, which converts to/from UUID objects automatically:: + + // src/Entity/Product.php + namespace App\Entity; + + use Doctrine\ORM\Mapping as ORM; + + /** + * @ORM\Entity(repositoryClass="App\Repository\ProductRepository") + */ + class Product + { + /** + * @ORM\Column(type="uuid") + */ + private $someProperty; + + // ... + } + +There's also a Doctrine generator to help autogenerate UUID values for the +entity primary keys:: + + // there are generators for UUID V1 and V6 too + use Symfony\Bridge\Doctrine\IdGenerator\UuidV4Generator; + use Symfony\Component\Uid\Uuid; + + /** + * @ORM\Entity(repositoryClass="App\Repository\ProductRepository") + */ + class Product + { + /** + * @ORM\Id + * @ORM\Column(type="uuid", unique=true) + * @ORM\GeneratedValue(strategy="CUSTOM") + * @ORM\CustomIdGenerator(class=UuidV4Generator::class) + */ + private $id; + + // ... + + public function getId(): ?Uuid + { + return $this->id; + } + + // ... + } + +.. versionadded:: 5.2 + + The UUID type and generators were introduced in Symfony 5.2. + +When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine +knows how to convert these UUID types to build the SQL query +(e.g. ``->findOneBy(['user' => $user->getUuid()])``). However, when using DQL +queries or building the query yourself, you'll need to set ``uuid`` as the type +of the UUID parameters:: + + // src/Repository/ProductRepository.php + + // ... + class ProductRepository extends ServiceEntityRepository + { + // ... + + public function findUserProducts(User $user): array + { + $qb = $this->createQueryBuilder('p') + // ... + // add 'uuid' as the third argument to tell Doctrine that this is an UUID + ->setParameter('user', $user->getUuid(), 'uuid') + + // alternatively, you can convert it to a value compatible with + // the type inferred by Doctrine + ->setParameter('user', $user->getUuid()->toBinary()) + ; + + // ... + } + } + +ULIDs +----- + +`ULIDs`_ (*Universally Unique Lexicographically Sortable Identifier*) are 128-bit +numbers usually represented as a 26-character string: ``TTTTTTTTTTRRRRRRRRRRRRRRRR`` +(where ``T`` represents a timestamp and ``R`` represents the random bits). + +ULIDs are an alternative to UUIDs when using those is impractical. They provide +128-bit compatibility with UUID, they are lexicographically sortable and they +are encoded as 26-character strings (vs 36-character UUIDs). + +Generating ULIDs +~~~~~~~~~~~~~~~~ + +Instantiate the ``Ulid`` class to generate a random ULID value:: + + use Symfony\Component\Uid\Ulid; + + $ulid = new Ulid(); // e.g. 01AN4Z07BY79KA1307SR9X4MV3 + +If your ULID is generated by another system, use the ``fromString()`` method to +create an object and make use of the utilities available for Symfony ULIDs:: + + // this value is generated somewhere else (can also be in binary format) + $ulidValue = '01E439TP9XJZ9RPFH3T1PYBCR8'; + $ulid = Ulid::fromString($ulidValue); + +Converting ULIDs +~~~~~~~~~~~~~~~~ + +Use these methods to transform the ULID object into different bases:: + + $ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8'); + + $ulid->toBinary(); // string(16) "..." (binary contents can't be printed) + $ulid->toBase32(); // string(26) "01E439TP9XJZ9RPFH3T1PYBCR8" + $ulid->toBase58(); // string(22) "1BKocMc5BnrVcuq2ti4Eqm" + $ulid->toRfc4122(); // string(36) "0171069d-593d-97d3-8b3e-23d06de5b308" + +Working with ULIDs +~~~~~~~~~~~~~~~~~~ + +ULID objects created with the ``Ulid`` class can use the following methods:: + + use Symfony\Component\Uid\Ulid; + + $ulid1 = new Ulid(); + $ulid2 = new Ulid(); + + // checking if a given value is valid as ULID + $isValid = Ulid::isValid($ulidValue); // true or false + + // getting the ULID datetime + $ulid1->getDateTime(); // returns a \DateTimeImmutable instance + + // comparing ULIDs and checking for equality + $ulid1->equals($ulid2); // false + // this method returns $ulid1 <=> $ulid2 + $ulid1->compare($ulid2); // e.g. int(-1) + +.. versionadded:: 5.3 + + The ``getDateTime()`` method was introduced in Symfony 5.3. In previous + versions it was called ``getTime()``. + +Storing ULIDs in Databases +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you :doc:`use Doctrine `, consider using the ``ulid`` Doctrine +type, which converts to/from ULID objects automatically:: + + // src/Entity/Product.php + namespace App\Entity; + + use Doctrine\ORM\Mapping as ORM; + + /** + * @ORM\Entity(repositoryClass="App\Repository\ProductRepository") + */ + class Product + { + /** + * @ORM\Column(type="ulid") + */ + private $someProperty; + + // ... + } + +There's also a Doctrine generator to help autogenerate ULID values for the +entity primary keys:: + + use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; + use Symfony\Component\Uid\Ulid; + + /** + * @ORM\Entity(repositoryClass="App\Repository\ProductRepository") + */ + class Product + { + /** + * @ORM\Id + * @ORM\Column(type="ulid", unique=true) + * @ORM\GeneratedValue(strategy="CUSTOM") + * @ORM\CustomIdGenerator(class=UlidGenerator::class) + */ + private $id; + + // ... + + public function getId(): ?Ulid + { + return $this->id; + } + + // ... + + } + +.. versionadded:: 5.2 + + The ULID type and generator were introduced in Symfony 5.2. + +When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine +knows how to convert these ULID types to build the SQL query +(e.g. ``->findOneBy(['user' => $user->getUlid()])``). However, when using DQL +queries or building the query yourself, you'll need to set ``ulid`` as the type +of the ULID parameters:: + + // src/Repository/ProductRepository.php + + // ... + class ProductRepository extends ServiceEntityRepository + { + // ... + + public function findUserProducts(User $user): array + { + $qb = $this->createQueryBuilder('p') + // ... + // add 'ulid' as the third argument to tell Doctrine that this is an ULID + ->setParameter('user', $user->getUlid(), 'ulid') + + // alternatively, you can convert it to a value compatible with + // the type inferred by Doctrine + ->setParameter('user', $user->getUlid()->toBinary()) + ; + + // ... + } + } + +.. _`unique identifiers`: https://en.wikipedia.org/wiki/UID +.. _`UUIDs`: https://en.wikipedia.org/wiki/Universally_unique_identifier +.. _`ULIDs`: https://github.com/ulid/spec diff --git a/components/validator/resources.rst b/components/validator/resources.rst index 1455c2518bc..7f9b02fb544 100644 --- a/components/validator/resources.rst +++ b/components/validator/resources.rst @@ -158,10 +158,6 @@ implement the PSR-6 interface :class:`Psr\\Cache\\CacheItemPoolInterface`):: ->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 a607ddeb59b..b661bd7a44a 100644 --- a/components/var_dumper.rst +++ b/components/var_dumper.rst @@ -66,7 +66,7 @@ current PHP SAPI: You can also select the output format explicitly defining the ``VAR_DUMPER_FORMAT`` environment variable and setting its value to either - ``html`` or ``cli``. + ``html``, ``cli`` or :ref:`server `. .. note:: @@ -186,6 +186,39 @@ Then you can use the following command to start a server out-of-the-box: $ ./vendor/bin/var-dump-server [OK] Server listening on tcp://127.0.0.1:9912 +.. _var-dumper-dump-server-format: + +Configuring the Dump Server with Environment Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The ``VAR_DUMPER_FORMAT=server`` feature was introduced in Symfony 5.2. + +If you prefer to not modify the application configuration (e.g. to quickly debug +a project given to you) use the ``VAR_DUMPER_FORMAT`` env var. + +First, start the server as usual: + +.. code-block:: terminal + + $ ./vendor/bin/var-dump-server + +Then, run your code with the ``VAR_DUMPER_FORMAT=server`` env var by configuring +this value in the :ref:`.env file of your application `. For +console commands, you can also define this env var as follows: + +.. code-block:: terminal + + $ VAR_DUMPER_FORMAT=server [your-cli-command] + +.. note:: + + The host used by the ``server`` format is the one configured in the + ``VAR_DUMPER_SERVER`` env var or ``127.0.0.1:9912`` if none is defined. + If you prefer, you can also configure the host in the ``VAR_DUMPER_FORMAT`` + env var like this: ``VAR_DUMPER_FORMAT=tcp://127.0.0.1:1234``. + DebugBundle and Twig Integration -------------------------------- @@ -227,10 +260,6 @@ option. Read more about this and other options in 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 -------------------------------------------------------- @@ -258,11 +287,6 @@ The ``VarDumperTestTrait`` also includes these other methods: 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; diff --git a/components/yaml.rst b/components/yaml.rst index 763051ad6d1..29b8114ff53 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -390,10 +390,6 @@ you can dump them as ``~`` with the ``DUMP_NULL_AS_TILDE`` flag:: $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/components/yaml/yaml_format.rst b/components/yaml/yaml_format.rst index 46d43a797be..0cca9901836 100644 --- a/components/yaml/yaml_format.rst +++ b/components/yaml/yaml_format.rst @@ -122,7 +122,13 @@ Numbers .. code-block:: yaml # an octal - 014 + 0o14 + +.. deprecated:: 5.1 + + In YAML 1.1, octal numbers use the notation ``0...``, whereas in YAML 1.2 + the notation changes to ``0o...``. Symfony 5.1 added support for YAML 1.2 + notation and deprecated support for YAML 1.1 notation. .. code-block:: yaml diff --git a/configuration.rst b/configuration.rst index ecc34b99ddc..1b028499eb1 100644 --- a/configuration.rst +++ b/configuration.rst @@ -144,10 +144,6 @@ configuration files, even if they use a different format: // ... -.. versionadded:: 4.4 - - The ``not_found`` option value for ``ignore_errors`` was introduced in Symfony 4.4. - .. _config-parameter-intro: .. _config-parameters-yml: .. _configuration-parameters: @@ -616,10 +612,6 @@ Define a default value in case the environment variable is not set: 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:: bash @@ -740,10 +732,6 @@ their values by running: # run this command to show all the details for a specific env var: $ php bin/console debug:container --env-var=FOO -.. versionadded:: 4.3 - - The option to debug environment variables was introduced in Symfony 4.3. - .. _configuration-accessing-parameters: Accessing Configuration Parameters diff --git a/configuration/dot-env-changes.rst b/configuration/dot-env-changes.rst index df418e6ea75..89844d991b1 100644 --- a/configuration/dot-env-changes.rst +++ b/configuration/dot-env-changes.rst @@ -45,16 +45,12 @@ If you created your application after November 15th 2018, you don't need to make any changes! Otherwise, here is the list of changes you'll need to make - these changes can be made to any Symfony 3.4 or higher app: -#. Create a new `config/bootstrap.php`_ file in your project. This file loads Composer's - autoloader and loads all the ``.env`` files as needed (note: in an earlier recipe, - this file was called ``src/.bootstrap.php``; if you are upgrading from Symfony 3.3 - or 4.1, use the `3.3/config/bootstrap.php`_ file instead). +#. Update your ``public/index.php`` file to add the code of the `public/index.php`_ + file provided by Symfony. If you've customized this file, make sure to keep + those changes (but add the rest of the changes made by Symfony). -#. Update your `public/index.php`_ (`index.php diff`_) file to load the new ``config/bootstrap.php`` - file. If you've customized this file, make sure to keep those changes (but use - the rest of the changes). - -#. Update your `bin/console`_ file to load the new ``config/bootstrap.php`` file. +#. Update your ``bin/console`` file to add the code of the `bin/console`_ file + provided by Symfony. #. Update ``.gitignore``: @@ -86,14 +82,11 @@ changes can be made to any Symfony 3.4 or higher app: You can also update the `comment on the top of .env`_ to reflect the new changes. #. If you're using PHPUnit, you will also need to `create a new .env.test`_ file - and update your `phpunit.xml.dist file`_ so it loads the ``config/bootstrap.php`` + and update your `phpunit.xml.dist file`_ so it loads the ``tests/bootstrap.php`` file. -.. _`config/bootstrap.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/config/bootstrap.php -.. _`3.3/config/bootstrap.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/config/bootstrap.php -.. _`public/index.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/public/index.php -.. _`index.php diff`: https://github.com/symfony/recipes/compare/8a4e5555e30d5dff64275e2788a901f31a214e79...86e2b6795c455f026e5ab0cba2aff2c7a18511f7#diff-7d73eabd1e5eb7d969ddf9a7ce94f954 -.. _`bin/console`: https://github.com/symfony/recipes/blob/master/symfony/console/3.3/bin/console +.. _`public/index.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/5.1/public/index.php +.. _`bin/console`: https://github.com/symfony/recipes/blob/master/symfony/console/5.1/bin/console .. _`comment on the top of .env`: https://github.com/symfony/recipes/blob/master/symfony/flex/1.0/.env .. _`create a new .env.test`: https://github.com/symfony/recipes/blob/master/symfony/phpunit-bridge/3.3/.env.test .. _`phpunit.xml.dist file`: https://github.com/symfony/recipes/blob/master/symfony/phpunit-bridge/3.3/phpunit.xml.dist diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst index 7b134067bef..407b5137fbc 100644 --- a/configuration/env_var_processors.rst +++ b/configuration/env_var_processors.rst @@ -164,7 +164,9 @@ Symfony provides the following env var processors: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://symfony.com/schema/dic/security" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> Symfony\Component\HttpFoundation\Request::METHOD_HEAD @@ -397,10 +399,6 @@ Symfony provides the following env var processors: 'auth' => '%env(require:PHP_FILE)%', ]); - .. versionadded:: 4.3 - - The ``require`` processor was introduced in Symfony 4.3. - ``env(trim:FOO)`` Trims the content of ``FOO`` env var, removing whitespaces from the beginning and end of the string. This is especially useful in combination with the @@ -443,10 +441,6 @@ Symfony provides the following env var processors: 'auth' => '%env(trim:file:AUTH_FILE)%', ]); - .. versionadded:: 4.3 - - The ``trim`` processor was introduced in Symfony 4.3. - ``env(key:FOO:BAR)`` Retrieves the value associated with the key ``FOO`` from the array whose contents are stored in the ``BAR`` env var: @@ -528,10 +522,6 @@ Symfony provides the following env var processors: When the fallback parameter is omitted (e.g. ``env(default::API_KEY)``), the value returned is ``null``. - .. versionadded:: 4.3 - - The ``default`` processor was introduced in Symfony 4.3. - ``env(url:FOO)`` Parses an absolute URL and returns its components as an associative array. @@ -601,10 +591,6 @@ Symfony provides the following env var processors: In order to ease extraction of the resource from the URL, the leading ``/`` is trimmed from the ``path`` component. - .. versionadded:: 4.3 - - The ``url`` processor was introduced in Symfony 4.3. - ``env(query_string:FOO)`` Parses the query string part of the given URL and returns its components as an associative array. @@ -651,10 +637,6 @@ Symfony provides the following env var processors: ], ]); - .. versionadded:: 4.3 - - The ``query_string`` processor was introduced in Symfony 4.3. - It is also possible to combine any number of processors: .. configuration-block:: @@ -717,7 +699,7 @@ create a class that implements class LowercasingEnvVarProcessor implements EnvVarProcessorInterface { - public function getEnv($prefix, $name, \Closure $getEnv) + public function getEnv(string $prefix, string $name, \Closure $getEnv) { $env = $getEnv($name); diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index 16402bd5a54..890f60d1ca8 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -24,12 +24,11 @@ Next, create an ``index.php`` file that defines the kernel class and runs it:: // index.php use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; - use Symfony\Component\Config\Loader\LoaderInterface; - use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Kernel as BaseKernel; - use Symfony\Component\Routing\RouteCollectionBuilder; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; require __DIR__.'/vendor/autoload.php'; @@ -37,29 +36,27 @@ Next, create an ``index.php`` file that defines the kernel class and runs it:: { use MicroKernelTrait; - public function registerBundles() + public function registerBundles(): array { return [ - new Symfony\Bundle\FrameworkBundle\FrameworkBundle() + new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), ]; } - protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) + protected function configureContainer(ContainerConfigurator $c): void { // PHP equivalent of config/packages/framework.yaml - $c->loadFromExtension('framework', [ + $c->extension('framework', [ 'secret' => 'S0ME_SECRET' ]); } - protected function configureRoutes(RouteCollectionBuilder $routes) + protected function configureRoutes(RoutingConfigurator $routes): void { - // kernel is a service that points to this class - // optional 3rd argument is the route name - $routes->add('/random/{limit}', 'kernel::randomNumber'); + $routes->add('random_number', '/random/{limit}')->controller([$this, 'randomNumber']); } - public function randomNumber($limit) + public function randomNumber(int $limit): JsonResponse { return new JsonResponse([ 'number' => random_int(0, $limit), @@ -91,15 +88,15 @@ that define your bundles, your services and your routes: **registerBundles()** This is the same ``registerBundles()`` that you see in a normal kernel. -**configureContainer(ContainerBuilder $c, LoaderInterface $loader)** +**configureContainer(ContainerConfigurator $c)** This method builds and configures the container. In practice, you will use - ``loadFromExtension`` to configure different bundles (this is the equivalent + ``extension()`` to configure different bundles (this is the equivalent of what you see in a normal ``config/packages/*`` file). You can also register services directly in PHP or load external configuration files (shown below). -**configureRoutes(RouteCollectionBuilder $routes)** +**configureRoutes(RoutingConfigurator $routes)** Your job in this method is to add routes to the application. The - ``RouteCollectionBuilder`` has methods that make adding routes in PHP more + ``RoutingConfigurator`` has methods that make adding routes in PHP more fun. You can also load external routing files (shown below). Advanced Example: Twig, Annotations and the Web Debug Toolbar @@ -134,16 +131,15 @@ hold the kernel. Now it looks like this:: namespace App; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; - use Symfony\Component\Config\Loader\LoaderInterface; - use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Kernel as BaseKernel; - use Symfony\Component\Routing\RouteCollectionBuilder; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; class Kernel extends BaseKernel { use MicroKernelTrait; - public function registerBundles() + public function registerBundles(): array { $bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), @@ -151,45 +147,52 @@ hold the kernel. Now it looks like this:: ]; if ($this->getEnvironment() == 'dev') { - $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); + $bundles[] = new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); } return $bundles; } - protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) + protected function configureContainer(ContainerConfigurator $c): void { - $loader->load(__DIR__.'/../config/framework.yaml'); + $c->import(__DIR__.'/../config/framework.yaml'); + + // register all classes in /src/ as service + $c->services() + ->load('App\\', __DIR__.'/*') + ->autowire() + ->autoconfigure() + ; // configure WebProfilerBundle only if the bundle is enabled if (isset($this->bundles['WebProfilerBundle'])) { - $c->loadFromExtension('web_profiler', [ + $c->extension('web_profiler', [ 'toolbar' => true, 'intercept_redirects' => false, ]); } } - protected function configureRoutes(RouteCollectionBuilder $routes) + protected function configureRoutes(RoutingConfigurator $routes): void { // import the WebProfilerRoutes, only if the bundle is enabled if (isset($this->bundles['WebProfilerBundle'])) { - $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml', '/_wdt'); - $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml', '/_profiler'); + $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml')->prefix('/_wdt'); + $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml')->prefix('/_profiler'); } // load the annotation routes - $routes->import(__DIR__.'/../src/Controller/', '/', 'annotation'); + $routes->import(__DIR__.'/Controller/', 'annotation'); } // optional, to use the standard Symfony cache directory - public function getCacheDir() + public function getCacheDir(): string { return __DIR__.'/../var/cache/'.$this->getEnvironment(); } // optional, to use the standard Symfony logs directory - public function getLogDir() + public function getLogDir(): string { return __DIR__.'/../var/log'; } @@ -245,6 +248,7 @@ has one file in it:: namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class MicroController extends AbstractController @@ -252,7 +256,7 @@ has one file in it:: /** * @Route("/random/{limit}") */ - public function randomNumber($limit) + public function randomNumber(int $limit): Response { $number = random_int(0, $limit); @@ -327,7 +331,6 @@ As before you can use the :doc:`Symfony Local Web Server .. code-block:: terminal - cd public/ $ symfony server:start Then visit the page in your browser: http://localhost:8000/random/10 diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst index e0bd417886d..46a17e71351 100644 --- a/configuration/override_dir_structure.rst +++ b/configuration/override_dir_structure.rst @@ -41,8 +41,8 @@ at your project root directory. Override the Cache Directory ---------------------------- -You can change the default cache directory by overriding the ``getCacheDir()`` -method in the ``Kernel`` class of your application:: +Changing the cache directory can be achieved by overriding the +``getCacheDir()`` method in the ``Kernel`` class of your application:: // src/Kernel.php @@ -61,6 +61,9 @@ In this code, ``$this->environment`` is the current environment (i.e. ``dev``). In this case you have changed the location of the cache directory to ``var/{environment}/cache/``. +You can also change the cache directory defining an environment variable named +``APP_CACHE_DIR`` whose value is the full path of the cache folder. + .. caution:: You should keep the cache directory different for each environment, @@ -73,9 +76,11 @@ In this case you have changed the location of the cache directory to Override the Log Directory -------------------------- -Overriding the ``var/log/`` directory is the same as overriding the ``var/cache/`` -directory. The only difference is that you need to override the ``getLogDir()`` -method:: +Overriding the ``var/log/`` directory is almost the same as overriding the +``var/cache/`` directory. + +You can do it overriding the ``getLogDir()`` method in the ``Kernel`` class of +your application:: // src/Kernel.php @@ -92,6 +97,9 @@ method:: Here you have changed the location of the directory to ``var/{environment}/log/``. +You can also change the log directory defining an environment variable named +``APP_LOG_DIR`` whose value is the full path of the log folder. + .. _override-templates-dir: Override the Templates Directory diff --git a/configuration/secrets.rst b/configuration/secrets.rst index bb89c67258f..696ce519682 100644 --- a/configuration/secrets.rst +++ b/configuration/secrets.rst @@ -4,10 +4,6 @@ How to Keep Sensitive Information Secret ======================================== -.. versionadded:: 4.4 - - The Secrets management was introduced in Symfony 4.4. - :ref:`Environment variables ` are the best way to store configuration that depends on where the application is run - for example, some API key that might be set to one value while developing locally and another value on production. diff --git a/console.rst b/console.rst index e980cea046a..63287faac82 100644 --- a/console.rst +++ b/console.rst @@ -48,16 +48,31 @@ want a command to create a user:: // ... put here the code to create the user // this method must return an integer number with the "exit status code" - // of the command. + // of the command. You can also use these constants to make code more readable // return this if there was no problem running the command - return 0; + // (it's equivalent to returning int(0)) + return Command::SUCCESS; // or return this if some error happened during the execution - // return 1; + // (it's equivalent to returning int(1)) + // return Command::FAILURE; + + // or return this to indicate incorrect command usage; e.g. invalid options + // or missing arguments (it's equivalent to returning int(2)) + // return Command::INVALID } } +.. versionadded:: 5.1 + + The ``Command::SUCCESS`` and ``Command::FAILURE`` constants were introduced + in Symfony 5.1. + +.. versionadded:: 5.3 + + The ``Command::INVALID`` constant was introduced in Symfony 5.3 + Configuring the Command ----------------------- @@ -156,7 +171,7 @@ the console:: $output->write('You are about to '); $output->write('create a user.'); - return 0; + return Command::SUCCESS; } Now, try executing the command: @@ -215,7 +230,7 @@ method, which returns an instance of $section1->clear(2); // Output is now completely empty! - return 0; + return Command::SUCCESS; } } @@ -257,7 +272,7 @@ Use input options or arguments to pass information to the command:: // retrieve the argument value using getArgument() $output->writeln('Username: '.$input->getArgument('username')); - return 0; + return Command::SUCCESS; } Now, you can pass the username to the command: @@ -308,7 +323,7 @@ as a service, you can use normal dependency injection. Imagine you have a $output->writeln('User successfully generated!'); - return 0; + return Command::SUCCESS; } } @@ -332,14 +347,9 @@ command: :method:`Symfony\\Component\\Console\\Command\\Command::execute` *(required)* This method is executed after ``interact()`` and ``initialize()``. - It contains the logic you want the command to execute and it should + It contains the logic you want the command to execute and it must return an integer which will be used as the command `exit status`_. - .. deprecated:: 4.4 - - Not returning an integer with the exit status as the result of - ``execute()`` is deprecated since Symfony 4.4. - .. _console-testing-commands: Testing Commands @@ -376,12 +386,20 @@ console:: // the output of the command in the console $output = $commandTester->getDisplay(); - $this->assertContains('Username: Wouter', $output); + $this->assertStringContainsString('Username: Wouter', $output); // ... } } +If you are using a :doc:`single-command application `, +call ``setAutoExit(false)`` on it to get the command result in ``CommandTester``. + +.. versionadded:: 5.2 + + The ``setAutoExit()`` method for single-command applications was introduced + in Symfony 5.2. + .. tip:: You can also test a whole console application by using diff --git a/console/coloring.rst b/console/coloring.rst index 774a2ab96fa..7e77a090b25 100644 --- a/console/coloring.rst +++ b/console/coloring.rst @@ -40,13 +40,28 @@ It is possible to define your own styles using the use Symfony\Component\Console\Formatter\OutputFormatterStyle; // ... - $outputStyle = new OutputFormatterStyle('red', 'yellow', ['bold', 'blink']); + $outputStyle = new OutputFormatterStyle('red', '#ff0', ['bold', 'blink']); $output->getFormatter()->setStyle('fire', $outputStyle); $output->writeln('foo'); -Available foreground and background colors are: ``black``, ``red``, ``green``, -``yellow``, ``blue``, ``magenta``, ``cyan`` and ``white``. +Any hex color is supported for foreground and background colors. Besides that, these named colors are supported: +``black``, ``red``, ``green``, ``yellow``, ``blue``, ``magenta``, ``cyan``, ``white``, +``gray``, ``bright-red``, ``bright-green``, ``bright-yellow``, ``bright-blue``, +``bright-magenta``, ``bright-cyan`` and ``bright-white``. + +.. versionadded:: 5.2 + + True (hex) color support was introduced in Symfony 5.2 + +.. versionadded:: 5.3 + + Support for bright colors was introduced in Symfony 5.3. + +.. note:: + + If the terminal doesn't support true colors, the nearest named color is used. + E.g. ``#c0392b`` is degraded to ``red`` or ``#f1c40f`` is degraded to ``yellow``. And available options are: ``bold``, ``underscore``, ``blink``, ``reverse`` (enables the "reverse video" mode where the background and foreground colors @@ -59,6 +74,9 @@ You can also set these colors and options directly inside the tag name:: // green text $output->writeln('foo'); + // red text + $output->writeln('foo'); + // black text on a cyan background $output->writeln('foo'); @@ -77,10 +95,6 @@ You can also set these colors and options directly inside the tag name:: Displaying Clickable Links ~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.3 - - The feature to display clickable links was introduced in Symfony 4.3. - Commands can use the special ```` tag to display links similar to the ```` elements of web pages:: diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst index fb5e7ff70eb..af4f275e221 100644 --- a/console/commands_as_services.rst +++ b/console/commands_as_services.rst @@ -45,8 +45,8 @@ For example, suppose you want to log something from within your command:: { $this->logger->info('Waking up the sun'); // ... - - return 0; + + return Command::SUCCESS; } } @@ -63,13 +63,6 @@ command and start logging. work (e.g. making database queries), as that code will be run, even if you're using the console to execute a different command. -.. note:: - - In previous Symfony versions, you could make the command class extend from - :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand` to - get services via ``$this->getContainer()->get('SERVICE_ID')``. This is - deprecated in Symfony 4.2 and it won't work in future Symfony versions. - .. _console-command-service-lazy-loading: Lazy Loading diff --git a/console/input.rst b/console/input.rst index 0813bad58c5..8bd42ae6c85 100644 --- a/console/input.rst +++ b/console/input.rst @@ -52,6 +52,8 @@ You now have access to a ``last_name`` argument in your command:: } $output->writeln($text.'!'); + + return Command::SUCCESS; } } @@ -197,7 +199,7 @@ separation at all (e.g. ``-i 5`` or ``-i5``). this situation, always place options after the command name, or avoid using a space to separate the option name from its value. -There are four option variants you can use: +There are five option variants you can use: ``InputOption::VALUE_IS_ARRAY`` This option accepts multiple values (e.g. ``--dir=/foo --dir=/bar``); @@ -214,6 +216,14 @@ There are four option variants you can use: This option may or may not have a value (e.g. ``--yell`` or ``--yell=loud``). +``InputOption::VALUE_NEGATABLE`` + Accept either the flag (e.g. ``--yell``) or its negation (e.g. + ``--no-yell``). + +.. versionadded:: 5.3 + + The ``InputOption::VALUE_NEGATABLE`` constant was introduced in Symfony 5.3. + You can combine ``VALUE_IS_ARRAY`` with ``VALUE_REQUIRED`` or ``VALUE_OPTIONAL`` like this:: diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst index 36cd393907c..54f6e4b051d 100644 --- a/console/lockable_trait.rst +++ b/console/lockable_trait.rst @@ -27,7 +27,7 @@ that adds two convenient methods to lock and release commands:: if (!$this->lock()) { $output->writeln('The command is already running in another process.'); - return 0; + return Command::SUCCESS; } // If you prefer to wait until the lock is released, use this: @@ -39,8 +39,12 @@ that adds two convenient methods to lock and release commands:: // automatically when the execution of the command ends $this->release(); - return 0; + return Command::SUCCESS; } } +.. versionadded:: 5.1 + + The ``Command::SUCCESS`` constant was introduced in Symfony 5.1. + .. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science) diff --git a/console/style.rst b/console/style.rst index dd981436e50..66db35011b1 100644 --- a/console/style.rst +++ b/console/style.rst @@ -152,10 +152,6 @@ Content Methods ] ); - .. 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:: @@ -169,10 +165,6 @@ Content Methods ['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 @@ -333,6 +325,27 @@ Result Methods 'Consectetur adipiscing elit', ]); +:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::info` + It's similar to the ``success()`` method (the given string or array of strings + are displayed with a green background) but the ``[OK]`` label is not prefixed. + It's meant to be used once to display the final result of executing the given + command, without showing the result as a successful or failed one:: + + // use simple strings for short info messages + $io->info('Lorem ipsum dolor sit amet'); + + // ... + + // consider using arrays when displaying long info messages + $io->info([ + 'Lorem ipsum dolor sit amet', + 'Consectetur adipiscing elit', + ]); + +.. versionadded:: 5.2 + + The ``info()`` method was introduced in Symfony 5.2. + :method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::warning` It displays the given string or array of strings highlighted as a warning message (with a red background and the ``[WARNING]`` label). It's meant to be diff --git a/contributing/code/conventions.rst b/contributing/code/conventions.rst index 3174ac93660..18ea420b841 100644 --- a/contributing/code/conventions.rst +++ b/contributing/code/conventions.rst @@ -146,40 +146,35 @@ A feature is marked as deprecated by adding a ``@deprecated`` PHPDoc to relevant classes, methods, properties, ...:: /** - * @deprecated since Symfony 2.8. + * @deprecated since Symfony 5.1. */ The deprecation message must indicate the version in which the feature was deprecated, and whenever possible, how it was replaced:: /** - * @deprecated since Symfony 2.8, use Replacement instead. + * @deprecated since Symfony 5.1, use Replacement instead. */ When the replacement is in another namespace than the deprecated class, its FQCN must be used:: /** - * @deprecated since Symfony 2.8, use A\B\Replacement instead. + * @deprecated since Symfony 5.1, use A\B\Replacement instead. */ -A PHP ``E_USER_DEPRECATED`` error must also be triggered to help people with the migration:: +A deprecation must also be triggered to help people with the migration +(requires the ``symfony/deprecation-contracts`` package):: - @trigger_error(sprintf('The "%s" class is deprecated since Symfony 2.8, use "%s" instead.', Deprecated::class, Replacement::class), E_USER_DEPRECATED); + trigger_deprecation('symfony/package-name', '5.1', 'The "%s" class is deprecated, use "%s" instead.', Deprecated::class, Replacement::class); -Without the `@-silencing operator`_, users would need to opt-out from deprecation -notices. Silencing swaps this behavior and allows users to opt-in when they are -ready to cope with them (by adding a custom error handler like the one used by -the Web Debug Toolbar or by the PHPUnit bridge). - -When deprecating a whole class the ``trigger_error()`` call should be placed -after the use declarations, like in this example from -`ServiceRouterLoader`_:: +When deprecating a whole class the ``trigger_deprecation()`` call should be placed +after the use declarations, like in this example from `ServiceRouterLoader`_:: namespace Symfony\Component\Routing\Loader\DependencyInjection; use Symfony\Component\Routing\Loader\ContainerLoader; - @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ServiceRouterLoader::class, ContainerLoader::class), E_USER_DEPRECATED); + trigger_deprecation('symfony/routing', '4.4', 'The "%s" class is deprecated, use "%s" instead.', ServiceRouterLoader::class, ContainerLoader::class); /** * @deprecated since Symfony 4.4, use Symfony\Component\Routing\Loader\ContainerLoader instead. @@ -232,5 +227,3 @@ of the impacted component: * Remove the `Deprecated` class, use `Replacement` instead This task is mandatory and must be done in the same pull request. - -.. _`@-silencing operator`: https://www.php.net/manual/en/language.operators.errorcontrol.php diff --git a/contributing/code/pull_requests.rst b/contributing/code/pull_requests.rst index e69371e9d5e..ed765eb9e52 100644 --- a/contributing/code/pull_requests.rst +++ b/contributing/code/pull_requests.rst @@ -25,7 +25,7 @@ Before working on Symfony, setup a friendly environment with the following software: * Git; -* PHP version 7.1.3 or above. +* PHP version 7.2.5 or above. Configure Git ~~~~~~~~~~~~~ diff --git a/contributing/code/reproducer.rst b/contributing/code/reproducer.rst index 771bd69eeac..6efae2a8ee8 100644 --- a/contributing/code/reproducer.rst +++ b/contributing/code/reproducer.rst @@ -65,8 +65,9 @@ to a route definition. Then, after creating your project: of controllers, actions, etc. as in your original application. #. Create a small controller and add your routing definition that shows the bug. #. Don't create or modify any other file. -#. Execute ``composer require symfony/web-server-bundle`` and use the ``server:run`` - command to browse to the new route and see if the bug appears or not. +#. Install the :doc:`local web server ` provided by Symfony + and use the ``symfony server:start`` command to browse to the new route and + see if the bug appears or not. #. If you can see the bug, you're done and you can already share the code with us. #. If you can't see the bug, you must keep making small changes. For example, if your original route was defined using XML, forget about the previous route diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst index 586a868aae2..88e015dc961 100644 --- a/contributing/code/standards.rst +++ b/contributing/code/standards.rst @@ -72,7 +72,7 @@ short example containing most features described below:: */ public function someDeprecatedMethod() { - @trigger_error(sprintf('The %s() method is deprecated since vendor-name/package-name 2.8 and will be removed in 3.0. Use Acme\Baz::someMethod() instead.', __METHOD__), E_USER_DEPRECATED); + trigger_deprecation('symfony/package-name', '5.1', 'The %s() method is deprecated, use Acme\Baz::someMethod() instead.', __METHOD__); return Baz::someMethod(); } @@ -187,10 +187,6 @@ Structure * Exception and error message strings must be concatenated using :phpfunction:`sprintf`; -* Calls to :phpfunction:`trigger_error` with type ``E_USER_DEPRECATED`` must be - switched to opt-in via ``@`` operator. - Read more at :ref:`contributing-code-conventions-deprecations`; - * Do not use ``else``, ``elseif``, ``break`` after ``if`` and ``case`` conditions which return or throw something; diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst index 80d5033accd..008ebab81b7 100644 --- a/contributing/community/releases.rst +++ b/contributing/community/releases.rst @@ -9,10 +9,10 @@ published through a *time-based model*: * A new **Symfony patch version** (e.g. 4.4.12, 5.1.9) comes out roughly every month. It only contains bug fixes, so you can safely upgrade your applications; -* A new **Symfony minor version** (e.g. 4.4, 5.0, 5.1) comes out every *six months*: +* A new **Symfony minor version** (e.g. 4.4, 5.1) comes out every *six months*: one in *May* and one in *November*. It contains bug fixes and new features, but it doesn't include any breaking change, so you can safely upgrade your applications; -* A new **Symfony major version** (e.g. 4.0, 5.0) comes out every *two years*. +* A new **Symfony major version** (e.g. 4.0, 5.0, 6.0) comes out every *two years*. It can contain breaking changes, so you may need to do some changes in your applications before upgrading. diff --git a/contributing/community/reviews.rst b/contributing/community/reviews.rst index 14d8b71a28d..342ba431201 100644 --- a/contributing/community/reviews.rst +++ b/contributing/community/reviews.rst @@ -150,7 +150,7 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps: * Does the code break backward compatibility? If yes, does the PR header say so? * Does the PR contain deprecations? If yes, does the PR header say so? Does - the code contain ``trigger_error()`` statements for all deprecated + the code contain ``trigger_deprecation()`` statements for all deprecated features? * Are all deprecations and backward compatibility breaks documented in the latest UPGRADE-X.X.md file? Do those explanations contain "Before"/"After" diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst index 1f6f1787918..2c465096f0b 100644 --- a/contributing/documentation/format.rst +++ b/contributing/documentation/format.rst @@ -104,6 +104,7 @@ Markup Format Use It to Display ``html+php`` PHP code blended with HTML ``ini`` INI ``php-annotations`` PHP Annotations +``php-attributes`` PHP Attributes =================== ====================================== Adding Links @@ -173,39 +174,39 @@ If you are documenting a brand new feature, a change or a deprecation that's been made in Symfony, you should precede your description of the change with the corresponding directive and a short description: -For a new feature or a behavior change use the ``.. versionadded:: 4.x`` +For a new feature or a behavior change use the ``.. versionadded:: 5.x`` directive: .. code-block:: rst - .. versionadded:: 4.2 + .. versionadded:: 5.2 - Named autowiring aliases have been introduced in Symfony 4.2. + ... ... ... was introduced in Symfony 5.2. If you are documenting a behavior change, it may be helpful to *briefly* describe how the behavior has changed: .. code-block:: rst - .. versionadded:: 4.2 + .. versionadded:: 5.2 - Support for ICU MessageFormat was introduced in Symfony 4.2. Prior to this, - pluralization was managed by the ``transChoice`` method. + ... ... ... was introduced in Symfony 5.2. Prior to this, + ... ... ... ... ... ... ... ... . -For a deprecation use the ``.. deprecated:: 4.x`` directive: +For a deprecation use the ``.. deprecated:: 5.x`` directive: .. code-block:: rst - .. deprecated:: 4.2 + .. deprecated:: 5.2 - Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2. + ... ... ... was deprecated in Symfony 5.2. -Whenever a new major version of Symfony is released (e.g. 5.0, 6.0, etc), +Whenever a new major version of Symfony is released (e.g. 6.0, 7.0, etc), a new branch of the documentation is created from the ``master`` branch. At this point, all the ``versionadded`` and ``deprecated`` tags for Symfony versions that have a lower major version will be removed. For example, if -Symfony 5.0 were released today, 4.0 to 4.4 ``versionadded`` and ``deprecated`` -tags would be removed from the new ``5.0`` branch. +Symfony 6.0 were released today, 5.0 to 5.4 ``versionadded`` and ``deprecated`` +tags would be removed from the new ``6.0`` branch. .. _reStructuredText: https://docutils.sourceforge.io/rst.html .. _Sphinx: https://www.sphinx-doc.org/ diff --git a/controller.rst b/controller.rst index 212d0a2b509..a5017452832 100644 --- a/controller.rst +++ b/controller.rst @@ -394,7 +394,7 @@ Request object. Managing the Session -------------------- -Symfony provides a session service that you can use to store information +Symfony provides a session object that you can use to store information about the user between requests. Session is enabled by default, but will only be started if you read or write from it. diff --git a/controller/argument_value_resolver.rst b/controller/argument_value_resolver.rst index 90763c591c0..c9693bbaf9b 100644 --- a/controller/argument_value_resolver.rst +++ b/controller/argument_value_resolver.rst @@ -53,17 +53,6 @@ In addition, some components and official bundles provide other value resolvers: the controller can be accessed by anonymous users. It requires installing the :doc:`Security component `. -:class:`Symfony\\Bundle\\SecurityBundle\\SecurityUserValueResolver` - Injects the object that represents the current logged in user if type-hinted - with ``UserInterface``. Default value can be set to ``null`` in case - the controller can be accessed by anonymous users. It requires installing - the `SecurityBundle`_. - -.. deprecated:: 4.1 - - The ``SecurityUserValueResolver`` was deprecated in Symfony 4.1 in favor of - :class:`Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver`. - ``Psr7ServerRequestResolver`` Injects a `PSR-7`_ compliant version of the current request if type-hinted with ``RequestInterface``, ``MessageInterface`` or ``ServerRequestInterface``. @@ -238,11 +227,17 @@ and adding a priority. .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\ArgumentResolver\UserValueResolver; - $container->autowire(UserValueResolver::class) - ->addTag('controller.argument_value_resolver', ['priority' => 50]) - ; + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(UserValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 50]) + ; + }; While adding a priority is optional, it's recommended to add one to make sure the expected value is injected. The built-in ``RequestAttributeValueResolver``, @@ -265,6 +260,5 @@ passing the user along sub-requests). .. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html .. _`yield`: https://www.php.net/manual/en/language.generators.syntax.php -.. _`SecurityBundle`: https://github.com/symfony/security-bundle .. _`PSR-7`: https://www.php-fig.org/psr/psr-7/ .. _`SensioFrameworkExtraBundle`: https://github.com/sensiolabs/SensioFrameworkExtraBundle diff --git a/controller/error_pages.rst b/controller/error_pages.rst index f3c8256e453..337723d8605 100644 --- a/controller/error_pages.rst +++ b/controller/error_pages.rst @@ -317,7 +317,7 @@ error pages. .. note:: - If your listener calls ``setResponse()`` on the + If your listener calls ``setThrowable()`` on the :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`, event, propagation will be stopped and the response will be sent to the client. diff --git a/controller/service.rst b/controller/service.rst index ca2f09b5d70..f8048e09def 100644 --- a/controller/service.rst +++ b/controller/service.rst @@ -182,12 +182,12 @@ Base Controller Methods and Their Service Replacements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The best way to see how to replace base ``Controller`` convenience methods is to -look at the `ControllerTrait`_ that holds its logic. +look at the `AbstractController`_ class 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.4/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php -.. _`ControllerTrait`: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php +.. _`Controller class source code`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +.. _`AbstractController`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.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/controller/upload_file.rst b/controller/upload_file.rst index dad80ee957d..edd17ed50dc 100644 --- a/controller/upload_file.rst +++ b/controller/upload_file.rst @@ -129,13 +129,14 @@ Finally, you need to update the code of the controller that handles the form:: use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; + use Symfony\Component\String\Slugger\SluggerInterface; class ProductController extends AbstractController { /** * @Route("/product/new", name="app_product_new") */ - public function new(Request $request) + public function new(Request $request, SluggerInterface $slugger) { $product = new Product(); $form = $this->createForm(ProductType::class, $product); @@ -150,7 +151,7 @@ Finally, you need to update the code of the controller that handles the form:: if ($brochureFile) { $originalFilename = pathinfo($brochureFile->getClientOriginalName(), PATHINFO_FILENAME); // this is needed to safely include the file name as part of the URL - $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename); + $safeFilename = $slugger->slug($originalFilename); $newFilename = $safeFilename.'-'.uniqid().'.'.$brochureFile->guessExtension(); // Move the file to the directory where brochures are stored @@ -199,19 +200,13 @@ There are some important things to consider in the code of the above controller: users. This also applies to the files uploaded by your visitors. The ``UploadedFile`` class provides methods to get the original file extension (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getExtension`), - the original file size (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getClientSize`) + the original file size (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getSize`) and the original file name (:method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getClientOriginalName`). However, they are considered *not safe* because a malicious user could tamper that information. That's why it's always better to generate a unique name and use the :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::guessExtension` method to let Symfony guess the right extension according to the file MIME type; -.. deprecated:: 4.1 - - The :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getClientSize` - method was deprecated in Symfony 4.1 and will be removed in Symfony 5.0. - Use ``getSize()`` instead. - You can use the following code to link to the PDF brochure of a product: .. code-block:: html+twig @@ -244,20 +239,23 @@ logic to a separate service:: use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\UploadedFile; + use Symfony\Component\String\Slugger\SluggerInterface; class FileUploader { private $targetDirectory; + private $slugger; - public function __construct($targetDirectory) + public function __construct($targetDirectory, SluggerInterface $slugger) { $this->targetDirectory = $targetDirectory; + $this->slugger = $slugger; } public function upload(UploadedFile $file) { $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); - $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename); + $safeFilename = $this->slugger->slug($originalFilename); $fileName = $safeFilename.'-'.uniqid().'.'.$file->guessExtension(); try { @@ -319,10 +317,17 @@ Then, define a service for this class: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\Service\FileUploader; - $container->autowire(FileUploader::class) - ->setArgument('$targetDirectory', '%brochures_directory%'); + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(FileUploader::class) + ->arg('$targetDirectory', '%brochures_directory%') + ; + }; Now you're ready to use this service in the controller:: diff --git a/create_framework/http_kernel_httpkernelinterface.rst b/create_framework/http_kernel_httpkernelinterface.rst index 9bda9e5c731..a5c46c8daaa 100644 --- a/create_framework/http_kernel_httpkernelinterface.rst +++ b/create_framework/http_kernel_httpkernelinterface.rst @@ -118,22 +118,28 @@ The Response class contains methods that let you configure the HTTP cache. One of the most powerful is ``setCache()`` as it abstracts the most frequently used caching strategies into a single array:: - $date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00'); - $response->setCache([ - 'public' => true, - 'etag' => 'abcde', - 'last_modified' => $date, - 'max_age' => 10, - 's_maxage' => 10, + 'must_revalidate' => false, + 'no_cache' => false, + 'no_store' => false, + 'no_transform' => false, + 'public' => true, + 'private' => false, + 'proxy_revalidate' => false, + 'max_age' => 600, + 's_maxage' => 600, + 'immutable' => true, + 'last_modified' => new \DateTime(), + 'etag' => 'abcdef' ]); // it is equivalent to the following code $response->setPublic(); + $response->setMaxAge(600); + $response->setSharedMaxAge(600); + $response->setImmutable(); + $response->setLastModified(new \DateTime()); $response->setEtag('abcde'); - $response->setLastModified($date); - $response->setMaxAge(10); - $response->setSharedMaxAge(10); When using the validation model, the ``isNotModified()`` method allows you to cut on the response time by short-circuiting the response generation as early as diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst index 099ada7e704..a4d6d401c33 100644 --- a/create_framework/unit_testing.rst +++ b/create_framework/unit_testing.rst @@ -49,7 +49,7 @@ resolver. Modify the framework to make use of them:: namespace Simplex; // ... - + use Calendar\Controller\LeapYearController; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; @@ -183,7 +183,7 @@ Response:: $response = $framework->handle(new Request()); $this->assertEquals(200, $response->getStatusCode()); - $this->assertContains('Yep, this is a leap year!', $response->getContent()); + $this->assertStringContainsString('Yep, this is a leap year!', $response->getContent()); } In this test, we simulate a route that matches and returns a simple diff --git a/deployment/proxies.rst b/deployment/proxies.rst index 285dd221b11..95d1ddfd0c9 100644 --- a/deployment/proxies.rst +++ b/deployment/proxies.rst @@ -22,32 +22,93 @@ Solution: ``setTrustedProxies()`` --------------------------------- To fix this, you need to tell Symfony which reverse proxy IP addresses to trust -and what headers your reverse proxy uses to send information:: +and what headers your reverse proxy uses to send information: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + // the IP address (or range) of your proxy + trusted_proxies: '192.0.0.1,10.0.0.0/8' + // trust *all* "X-Forwarded-*" headers (the ! prefix means to not trust those headers) + trusted_headers: ['x-forwarded-all', '!x-forwarded-host', '!x-forwarded-prefix'] + // or, if your proxy instead uses the "Forwarded" header + trusted_headers: ['forwarded', '!x-forwarded-host', '!x-forwarded-prefix'] + // or, if you're using a wellknown proxy + trusted_headers: [!php/const Symfony\\Component\\HttpFoundation\\Request::HEADER_X_FORWARDED_AWS_ELB, '!x-forwarded-host', '!x-forwarded-prefix'] + trusted_headers: [!php/const Symfony\\Component\\HttpFoundation\\Request::HEADER_X_FORWARDED_TRAEFIK, '!x-forwarded-host', '!x-forwarded-prefix'] + + .. code-block:: xml + + + + + + + + 192.0.0.1,10.0.0.0/8 + + + x-forwarded-all + !x-forwarded-host + !x-forwarded-prefix + + + forwarded + !x-forwarded-host + !x-forwarded-prefix + + + + .. code-block:: php + + // config/packages/framework.php + use Symfony\Component\HttpFoundation\Request; + + $container->loadFromExtension('framework', [ + // the IP address (or range) of your proxy + 'trusted_proxies' => '192.0.0.1,10.0.0.0/8', + // trust *all* "X-Forwarded-*" headers (the ! prefix means to not trust those headers) + 'trusted_headers' => ['x-forwarded-all', '!x-forwarded-host', '!x-forwarded-prefix'], + // or, if your proxy instead uses the "Forwarded" header + 'trusted_headers' => ['forwarded', '!x-forwarded-host', '!x-forwarded-prefix'], + // or, if you're using a wellknown proxy + 'trusted_headers' => [Request::HEADER_X_FORWARDED_AWS_ELB, '!x-forwarded-host', '!x-forwarded-prefix'], + 'trusted_headers' => [Request::HEADER_X_FORWARDED_TRAEFIK, '!x-forwarded-host', '!x-forwarded-prefix'], + ]); + +.. deprecated:: 5.2 + + In previous Symfony versions, the above example used ``HEADER_X_FORWARDED_ALL`` + to trust all "X-Forwarded-" headers, but that constant is deprecated since + Symfony 5.2 in favor of the individual ``HEADER_X_FORWARDED_*`` constants. - // public/index.php - - // ... - $request = Request::createFromGlobals(); - - // tell Symfony about your reverse proxy - Request::setTrustedProxies( - // the IP address (or range) of your proxy - ['192.0.0.1', '10.0.0.0/8'], - - // trust *all* "X-Forwarded-*" headers - Request::HEADER_X_FORWARDED_ALL - - // or, if your proxy instead uses the "Forwarded" header - // Request::HEADER_FORWARDED +.. caution:: - // or, if you're using AWS ELB - // Request::HEADER_X_FORWARDED_AWS_ELB - ); + Enabling the ``Request::HEADER_X_FORWARDED_HOST`` option exposes the + application to `HTTP Host header attacks`_. Make sure the proxy really + sends an ``x-forwarded-host`` header. The Request object has several ``Request::HEADER_*`` constants that control exactly *which* headers from your reverse proxy are trusted. The argument is a bit field, so you can also pass your own value (e.g. ``0b00110``). +.. versionadded:: 5.2 + + The feature to configure trusted proxies and headers with ``trusted_proxies`` + and ``trusted_headers`` options was introduced in Symfony 5.2. In earlier + Symfony versions you needed to use the ``Request::setTrustedProxies()`` + method in the ``public/index.php`` file. + .. caution:: The "trusted proxies" feature does not work as expected when using the @@ -64,23 +125,19 @@ In this case, you'll need to - *very carefully* - trust *all* proxies. other than your load balancers. For AWS, this can be done with `security groups`_. #. Once you've guaranteed that traffic will only come from your trusted reverse - proxies, configure Symfony to *always* trust incoming request:: + proxies, configure Symfony to *always* trust incoming request: - // public/index.php + .. code-block:: yaml - // ... - Request::setTrustedProxies( - // trust *all* requests (the 'REMOTE_ADDR' string is replaced at - // run time by $_SERVER['REMOTE_ADDR']) - ['127.0.0.1', 'REMOTE_ADDR'], + # config/packages/framework.yaml + framework: + # ... + // trust *all* requests (the 'REMOTE_ADDR' string is replaced at + // run time by $_SERVER['REMOTE_ADDR']) + trusted_proxies: '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. + // if you're using ELB, otherwise use another Request::HEADER-* constant + trusted_headers: [!php/const Symfony\\Component\\HttpFoundation\\Request::HEADER_X_FORWARDED_AWS_ELB, '!x-forwarded-host', '!x-forwarded-prefix'] 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 @@ -96,6 +153,12 @@ other information. # .env TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + trusted_proxies: '%env(TRUSTED_PROXIES)%' If you are also using a reverse proxy on top of your load balancer (e.g. `CloudFront`_), calling ``$request->server->get('REMOTE_ADDR')`` won't be @@ -107,11 +170,13 @@ trusted proxies. Custom Headers When Using a Reverse Proxy ----------------------------------------- -Some reverse proxies (like `CloudFront`_ with ``CloudFront-Forwarded-Proto``) may force you to use a custom header. -For instance you have ``Custom-Forwarded-Proto`` instead of ``X-Forwarded-Proto``. +Some reverse proxies (like `CloudFront`_ with ``CloudFront-Forwarded-Proto``) +may force you to use a custom header. For instance you have +``Custom-Forwarded-Proto`` instead of ``X-Forwarded-Proto``. -In this case, you'll need to set the header ``X-Forwarded-Proto`` with the value of -``Custom-Forwarded-Proto`` early enough in your application, i.e. before handling the request:: +In this case, you'll need to set the header ``X-Forwarded-Proto`` with the value +of ``Custom-Forwarded-Proto`` early enough in your application, i.e. before +handling the request:: // public/index.php @@ -123,4 +188,5 @@ In this case, you'll need to set the header ``X-Forwarded-Proto`` with the value .. _`security groups`: https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-groups.html .. _`CloudFront`: https://en.wikipedia.org/wiki/Amazon_CloudFront .. _`CloudFront IP ranges`: https://ip-ranges.amazonaws.com/ip-ranges.json +.. _`HTTP Host header attacks`: https://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html .. _`nginx realip module`: http://nginx.org/en/docs/http/ngx_http_realip_module.html diff --git a/doctrine.rst b/doctrine.rst index 8226bf22700..f44a1c36ff1 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -232,7 +232,7 @@ If everything worked, you should see something like this: SUCCESS! - Next: Review the new migration "src/Migrations/Version20180207231217.php" + Next: Review the new migration "migrations/Version20180207231217.php" Then: Run the migration with php bin/console doctrine:migrations:migrate If you open this file, it contains the SQL needed to update your database! To run @@ -495,10 +495,6 @@ doesn't replace the validation configuration entirely. You still need to add some :doc:`validation constraints ` to ensure that data provided by the user is correct. -.. versionadded:: 4.3 - - The automatic validation has been added in Symfony 4.3. - Fetching Objects from the Database ---------------------------------- diff --git a/doctrine/events.rst b/doctrine/events.rst index c98be25d736..3eea84aff4f 100644 --- a/doctrine/events.rst +++ b/doctrine/events.rst @@ -200,22 +200,28 @@ with the ``doctrine.event_listener`` tag: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\EventListener\SearchIndexer; - // listeners are applied by default to all Doctrine connections - $container->autowire(SearchIndexer::class) - ->addTag('doctrine.event_listener', [ - // this is the only required option for the lifecycle listener tag - 'event' => 'postPersist', + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + // listeners are applied by default to all Doctrine connections + $services->set(SearchIndexer::class) + ->tag('doctrine.event_listener', [ + // this is the only required option for the lifecycle listener tag + 'event' => 'postPersist', - // listeners can define their priority in case multiple listeners are associated - // to the same event (default priority = 0; higher numbers = listener is run earlier) - 'priority' => 500, + // listeners can define their priority in case multiple listeners are associated + // to the same event (default priority = 0; higher numbers = listener is run earlier) + 'priority' => 500, - # you can also restrict listeners to a specific Doctrine connection - 'connection' => 'default', - ]) - ; + # you can also restrict listeners to a specific Doctrine connection + 'connection' => 'default', + ]) + ; + }; .. tip:: @@ -316,33 +322,35 @@ with the ``doctrine.orm.entity_listener`` tag: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\Entity\User; use App\EventListener\UserChangedNotifier; - $container->autowire(UserChangedNotifier::class) - ->addTag('doctrine.orm.entity_listener', [ - // These are the options required to define the entity listener: - 'event' => 'postUpdate', - 'entity' => User::class, - - // These are other options that you may define if needed: + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); - // set the 'lazy' option to TRUE to only instantiate listeners when they are used - // 'lazy' => true, + $services->set(UserChangedNotifier::class) + ->tag('doctrine.orm.entity_listener', [ + // These are the options required to define the entity listener: + 'event' => 'postUpdate', + 'entity' => User::class, - // set the 'entity_manager' option if the listener is not associated to the default manager - // 'entity_manager' => 'custom', + // These are other options that you may define if needed: - // 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', - ]) - ; + // set the 'lazy' option to TRUE to only instantiate listeners when they are used + // 'lazy' => true, -.. versionadded:: 4.4 + // set the 'entity_manager' option if the listener is not associated to the default manager + // 'entity_manager' => 'custom', - Support for invokable listeners (using the ``__invoke()`` method) was introduced in Symfony 4.4. + // 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', + ]) + ; + }; Doctrine Lifecycle Subscribers ------------------------------ @@ -440,11 +448,17 @@ with the ``doctrine.event_subscriber`` tag: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\EventListener\DatabaseActivitySubscriber; - $container->autowire(DatabaseActivitySubscriber::class) - ->addTag('doctrine.event_subscriber') - ; + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(DatabaseActivitySubscriber::class) + ->tag('doctrine.event_subscriber') + ; + }; If you need to associate the subscriber with a specific Doctrine connection, you can do it in the service configuration: @@ -479,11 +493,17 @@ can do it in the service configuration: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\EventListener\DatabaseActivitySubscriber; - $container->autowire(DatabaseActivitySubscriber::class) - ->addTag('doctrine.event_subscriber', ['connection' => 'default']) - ; + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(DatabaseActivitySubscriber::class) + ->tag('doctrine.event_subscriber', ['connection' => 'default']) + ; + }; .. tip:: diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst index ba3475dbfbc..d7a546783f5 100644 --- a/doctrine/multiple_entity_managers.rst +++ b/doctrine/multiple_entity_managers.rst @@ -193,8 +193,8 @@ for each entity manager, but you are free to define the same connection for both the connection or entity manager, the default (i.e. ``default``) is used. If you use a different name than ``default`` for the default entity manager, - you will need to redefine the default entity manager in ``prod`` environment - configuration too: + you will need to redefine the default entity manager in the ``prod`` environment + configuration and in the Doctrine migrations configuration (if you use that): .. code-block:: yaml @@ -205,6 +205,13 @@ for each entity manager, but you are free to define the same connection for both # ... + .. code-block:: yaml + + # config/packages/doctrine_migrations.yaml + doctrine_migrations: + # ... + em: 'your default entity manager name' + When working with multiple connections to create your databases: .. code-block:: terminal diff --git a/event_dispatcher.rst b/event_dispatcher.rst index 2b95a637e28..038a405b10b 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -67,12 +67,6 @@ The most common way to listen to an event is to register an **event listener**:: Check out the :doc:`Symfony events reference ` to see what type of object each event provides. -.. versionadded:: 4.3 - - The :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` class was - introduced in Symfony 4.3. In previous versions it was called - ``Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent``. - Now that the class is created, you need to register it as a service and notify Symfony that it is a "listener" on the ``kernel.exception`` event by using a special "tag": @@ -106,11 +100,17 @@ using a special "tag": .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\EventListener\ExceptionListener; - $container->register(ExceptionListener::class) - ->tag('kernel.event_listener', ['event' => 'kernel.exception']) - ; + return function(ContainerConfigurator $configurator) { + $services = $configurator->services(); + + $services->set(ExceptionListener::class) + ->tag('kernel.event_listener', ['event' => 'kernel.exception']) + ; + }; Symfony follows this logic to decide which method to call inside the event listener class: @@ -275,11 +275,6 @@ name (FQCN) of the corresponding event class:: } } -.. versionadded:: 4.3 - - Referring Symfony's core events via the FQCN of the event class is possible - since Symfony 4.3. - Internally, the event FQCN are treated as aliases for the original event names. Since the mapping already happens when compiling the service container, event listeners and subscribers using FQCN instead of event names will appear under @@ -310,10 +305,6 @@ The compiler pass will always extend the existing list of aliases. Because of that, it is safe to register multiple instances of the pass with different configurations. -.. versionadded:: 4.4 - - The ``AddEventAliasesPass`` class was introduced in Symfony 4.4. - Debugging Event Listeners ------------------------- @@ -331,6 +322,29 @@ its name: $ php bin/console debug:event-dispatcher kernel.exception +or can get everything which partial matches the event name: + +.. code-block:: terminal + + $ php bin/console debug:event-dispatcher kernel // matches "kernel.exception", "kernel.response" etc. + $ php bin/console debug:event-dispatcher Security // matches "Symfony\Component\Security\Http\Event\CheckPassportEvent" + +.. versionadded:: 5.3 + + The ability to match partial event names was introduced in Symfony 5.3. + +The :doc:`new experimental Security ` +system adds an event dispatcher per firewall. Use the ``--dispatcher`` option to +get the registered listeners for a particular event dispatcher: + +.. code-block:: terminal + + $ php bin/console debug:event-dispatcher --dispatcher=security.event_dispatcher.main + +.. versionadded:: 5.3 + + The ``dispatcher`` option was introduced in Symfony 5.3. + Learn more ---------- diff --git a/form/bootstrap4.rst b/form/bootstrap4.rst index ccf4659738c..6f3b878ed1b 100644 --- a/form/bootstrap4.rst +++ b/form/bootstrap4.rst @@ -115,10 +115,6 @@ 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 some classes to the label: diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst index 2d13673be33..248818aa6d3 100644 --- a/form/create_custom_field_type.rst +++ b/form/create_custom_field_type.rst @@ -430,12 +430,6 @@ libraries are used in your application: {% endfor %} {% endblock %} -.. note:: - - Symfony 4.2 deprecated calling ``FormRenderer::searchAndRenderBlock`` for - fields that have already been rendered. That's why the previous example - includes the ``... if not child.rendered`` statement. - The first part of the Twig block name (e.g. ``postal_address``) comes from the class name (``PostalAddressType`` -> ``postal_address``). This can be controlled by overriding the ``getBlockPrefix()`` method in ``PostalAddressType``. The diff --git a/form/data_mappers.rst b/form/data_mappers.rst index c14eabd7683..24ff0716f5f 100644 --- a/form/data_mappers.rst +++ b/form/data_mappers.rst @@ -98,7 +98,7 @@ in your form type:: /** * @param Color|null $viewData */ - public function mapDataToForms($viewData, $forms): void + public function mapDataToForms($viewData, \Traversable $forms): void { // there is no data yet, so nothing to prepopulate if (null === $viewData) { @@ -119,7 +119,7 @@ in your form type:: $forms['blue']->setData($viewData->getBlue()); } - public function mapFormsToData($forms, &$viewData): void + public function mapFormsToData(\Traversable $forms, &$viewData): void { /** @var FormInterface[] $forms */ $forms = iterator_to_array($forms); @@ -189,6 +189,45 @@ method:: Cool! When using the ``ColorType`` form, the custom data mapper methods will create a new ``Color`` object now. +Mapping Form Fields Using Callbacks +----------------------------------- + +Conveniently, you can also map data from and into a form field by using the +``getter`` and ``setter`` options. For example, suppose you have a form with some +fields and only one of them needs to be mapped in some special way or you only +need to change how it's written into the underlying object. In that case, register +a PHP callable that is able to write or read to/from that specific object:: + + public function buildForm(FormBuilderInterface $builder, array $options) + { + // ... + + $builder->add('state', ChoiceType::class, [ + 'choices' => [ + 'active' => true, + 'paused' => false, + ], + 'getter' => function (Task $task, FormInterface $form): bool { + return !$task->isCancelled() && !$task->isPaused(); + }, + 'setter' => function (Task &$task, bool $state, FormInterface $form): void { + if ($state) { + $task->activate(); + } else { + $task->pause(); + } + }, + ]); + } + +If available, these options have priority over the property path accessor and +the default data mapper will still use the :doc:`PropertyAccess component ` +for the other form fields. + +.. versionadded:: 5.2 + + The ``getter`` and ``setter`` options were introduced in Symfony 5.2. + .. caution:: When a form has the ``inherit_data`` option set to ``true``, it does not use the data mapper and diff --git a/form/data_transformers.rst b/form/data_transformers.rst index aa0e88789bf..3c93fd66012 100644 --- a/form/data_transformers.rst +++ b/form/data_transformers.rst @@ -324,10 +324,6 @@ end-user error message in the data transformer using the } } -.. versionadded:: 4.3 - - The ``setInvalidMessage()`` method was introduced in Symfony 4.3. - That's it! If you're using the :ref:`default services.yaml configuration `, Symfony will automatically know to pass your ``TaskType`` an instance of the diff --git a/form/form_customization.rst b/form/form_customization.rst index 81d4007ce9e..125b01e749a 100644 --- a/form/form_customization.rst +++ b/form/form_customization.rst @@ -320,10 +320,6 @@ spot (since it'll render the field for you). form_parent(form_view) ...................... -.. versionadded:: 4.3 - - The ``form_parent()`` function was introduced in Symfony 4.3. - Returns the parent form view or ``null`` if the form view already is the root form. Using this function should be preferred over accessing the parent form using ``form.parent``. The latter way will produce different results diff --git a/form/form_themes.rst b/form/form_themes.rst index 1b5a3594a2d..927da208176 100644 --- a/form/form_themes.rst +++ b/form/form_themes.rst @@ -35,7 +35,14 @@ in a single Twig template and they are enabled in the ``bootstrap_3_horizontal_layout.html.twig`` but updated for Bootstrap 4 styles. * `foundation_5_layout.html.twig`_, wraps each form field inside a ``
`` element with the appropriate CSS classes to apply the default styles of the - `Foundation CSS framework`_. + version 5 of `Foundation CSS framework`_. +* `foundation_6_layout.html.twig`_, wraps each form field inside a ``
`` + element with the appropriate CSS classes to apply the default styles of the + version 6 of `Foundation CSS framework`_. + +.. versionadded:: 5.1 + + The ``foundation_6_layout.html.twig`` was introduced in Symfony 5.1. .. tip:: @@ -297,10 +304,6 @@ field without having to :doc:`create a custom form type `, the fragment -of each collection item follows a predefined pattern. For example, consider the -following complex example where a ``TaskManagerType`` has a collection of -``TaskListType`` which in turn has a collection of ``TaskType``:: +When using a :doc:`collection of forms `, you have +several ways of customizing the collection and each of its entries. First, +use the following blocks to customize each part of all form collections: + +.. code-block:: twig + + {% block collection_row %} ... {% endblock %} + {% block collection_label %} ... {% endblock %} + {% block collection_widget %} ... {% endblock %} + {% block collection_help %} ... {% endblock %} + {% block collection_errors %} ... {% endblock %} + +You can also customize each entry of all collections with the following blocks: + +.. code-block:: twig + + {% block collection_entry_row %} ... {% endblock %} + {% block collection_entry_label %} ... {% endblock %} + {% block collection_entry_widget %} ... {% endblock %} + {% block collection_entry_help %} ... {% endblock %} + {% block collection_entry_errors %} ... {% endblock %} + +.. versionadded:: 5.1 + + The ``collection_entry_*`` blocks were introduced in Symfony 5.1. + +Finally, you can customize specific form collections instead of all of them. +For example, consider the following complex example where a ``TaskManagerType`` +has a collection of ``TaskListType`` which in turn has a collection of +``TaskType``:: class TaskManagerType extends AbstractType { @@ -603,6 +632,7 @@ is a collection of fields (e.g. a whole form), and not just an individual field: .. _`Bootstrap 3 CSS framework`: https://getbootstrap.com/docs/3.4/ .. _`Bootstrap 4 CSS framework`: https://getbootstrap.com/docs/4.4/ .. _`foundation_5_layout.html.twig`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig +.. _`foundation_6_layout.html.twig`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_6_layout.html.twig .. _`Foundation CSS framework`: https://get.foundation/ .. _`Twig "use" tag`: https://twig.symfony.com/doc/2.x/tags/use.html .. _`Twig parent() function`: https://twig.symfony.com/doc/2.x/functions/parent.html diff --git a/form/type_guesser.rst b/form/type_guesser.rst index 49488e524ba..f3148d9192d 100644 --- a/form/type_guesser.rst +++ b/form/type_guesser.rst @@ -45,19 +45,19 @@ Start by creating the class and these methods. Next, you'll learn how to fill ea class PHPDocTypeGuesser implements FormTypeGuesserInterface { - public function guessType($class, $property): ?TypeGuess + public function guessType(string $class, string $property): ?TypeGuess { } - public function guessRequired($class, $property): ?ValueGuess + public function guessRequired(string $class, string $property): ?ValueGuess { } - public function guessMaxLength($class, $property): ?ValueGuess + public function guessMaxLength(string $class, string $property): ?ValueGuess { } - public function guessPattern($class, $property): ?ValueGuess + public function guessPattern(string $class, string $property): ?ValueGuess { } } @@ -96,7 +96,7 @@ With this knowledge, you can implement the ``guessType()`` method of the class PHPDocTypeGuesser implements FormTypeGuesserInterface { - public function guessType($class, $property): ?TypeGuess + public function guessType(string $class, string $property): ?TypeGuess { $annotations = $this->readPhpDocAnnotations($class, $property); diff --git a/forms.rst b/forms.rst index 1654aa3deba..60e60ce2085 100644 --- a/forms.rst +++ b/forms.rst @@ -354,7 +354,7 @@ can set this option to generate forms compatible with the Bootstrap 4 CSS framew ]); The :ref:`built-in Symfony form themes ` include -Bootstrap 3 and 4 and Foundation 5. You can also +Bootstrap 3 and 4 as well as Foundation 5 and 6. You can also :ref:`create your own Symfony form theme `. In addition to form themes, Symfony allows you to @@ -570,6 +570,52 @@ To see the second approach - adding constraints to the form - and to learn more about the validation constraints, please refer to the :doc:`Symfony validation documentation `. +Form Validation Messages +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The ``legacy_error_messages`` option was introduced in Symfony 5.2 + +The form types have default error messages that are more clear and +user-friendly than the ones provided by the validation constraints. To enable +these new messages set the ``legacy_error_messages`` option to ``false``: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + form: + legacy_error_messages: false + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/packages/framework.php + $container->loadFromExtension('framework', [ + 'form' => [ + 'legacy_error_messages' => false, + ], + ]); + Other Common Form Features -------------------------- @@ -689,7 +735,7 @@ Set the ``label`` option on fields to define their labels explicitly:: ->add('dueDate', DateType::class, [ // set it to FALSE to not display the label for this field - 'label' => 'To Be Completed Before', + 'label' => 'To Be Completed Before', ]) .. tip:: @@ -901,10 +947,11 @@ option in the options field array:: ->add('task', null, ['attr' => ['maxlength' => 4]]) -.. versionadded:: 4.3 +.. seealso:: - Starting from Symfony 4.3, :doc:`Doctrine ` metadata is introspected - to add :ref:`automatic validation constraints `. + Besides guessing the form type, Symfony also guesses :ref:`validation constraints ` + if you're using a Doctrine entity. Read :ref:`automatic_object_validation` + guide for more information. Unmapped Fields ~~~~~~~~~~~~~~~ diff --git a/frontend/custom_version_strategy.rst b/frontend/custom_version_strategy.rst index 6361ba632c0..0fe3f5ff987 100644 --- a/frontend/custom_version_strategy.rst +++ b/frontend/custom_version_strategy.rst @@ -71,13 +71,13 @@ version string:: * @param string $manifestPath * @param string|null $format */ - public function __construct($manifestPath, $format = null) + public function __construct(string $manifestPath, string $format = null) { $this->manifestPath = $manifestPath; $this->format = $format ?: '%s?%s'; } - public function getVersion($path) + public function getVersion(string $path) { if (!is_array($this->hashes)) { $this->hashes = $this->loadManifest(); @@ -86,7 +86,7 @@ version string:: return isset($this->hashes[$path]) ? $this->hashes[$path] : ''; } - public function applyVersion($path) + public function applyVersion(string $path) { $version = $this->getVersion($path); diff --git a/frontend/encore/advanced-config.rst b/frontend/encore/advanced-config.rst index eb77baef504..86bdb812b94 100644 --- a/frontend/encore/advanced-config.rst +++ b/frontend/encore/advanced-config.rst @@ -65,8 +65,8 @@ state of the current configuration to build a new one: Encore .setOutputPath('public/build/first_build/') .setPublicPath('/build/first_build') - .addEntry('app', './assets/js/app.js') - .addStyleEntry('global', './assets/css/global.scss') + .addEntry('app', './assets/app.js') + .addStyleEntry('global', './assets/styles/global.scss') .enableSassLoader() .autoProvidejQuery() .enableSourceMaps(!Encore.isProduction()) @@ -85,8 +85,8 @@ state of the current configuration to build a new one: Encore .setOutputPath('public/build/second_build/') .setPublicPath('/build/second_build') - .addEntry('mobile', './assets/js/mobile.js') - .addStyleEntry('mobile', './assets/css/mobile.less') + .addEntry('mobile', './assets/mobile.js') + .addStyleEntry('mobile', './assets/styles/mobile.less') .enableLessLoader() .enableSourceMaps(!Encore.isProduction()) ; diff --git a/frontend/encore/bootstrap.rst b/frontend/encore/bootstrap.rst index fedb5153cac..b56475cb12a 100644 --- a/frontend/encore/bootstrap.rst +++ b/frontend/encore/bootstrap.rst @@ -18,7 +18,7 @@ a ``global.scss`` file, import it from there: .. code-block:: scss - // assets/css/global.scss + // assets/styles/global.scss // customize some Bootstrap variables $primary: darken(#428bca, 20%); diff --git a/frontend/encore/code-splitting.rst b/frontend/encore/code-splitting.rst index ffbfe8b4d28..759987e5f0a 100644 --- a/frontend/encore/code-splitting.rst +++ b/frontend/encore/code-splitting.rst @@ -9,7 +9,7 @@ clicked a link: .. code-block:: javascript - // assets/js/app.js + // assets/app.js import $ from 'jquery'; // a fictional "large" module (e.g. it imports video.js internally) @@ -27,13 +27,13 @@ the code via AJAX when it's needed: .. code-block:: javascript - // assets/js/app.js + // assets/app.js import $ from 'jquery'; $('.js-open-video').on('click', function() { // you could start a loading animation here - + // use import() as a function - it returns a Promise import('./components/VideoPlayer').then(({ default: VideoPlayer }) => { // you could stop a loading animation here diff --git a/frontend/encore/copy-files.rst b/frontend/encore/copy-files.rst index bc263ef056a..7ea5a541622 100644 --- a/frontend/encore/copy-files.rst +++ b/frontend/encore/copy-files.rst @@ -12,7 +12,7 @@ To reference an image tag from inside a JavaScript file, *require* the file: .. code-block:: javascript - // assets/js/app.js + // assets/app.js // returns the final, public path to this file // path is relative to this file - e.g. assets/images/logo.png diff --git a/frontend/encore/dev-server.rst b/frontend/encore/dev-server.rst index 3a602f89b19..4d0904125cb 100644 --- a/frontend/encore/dev-server.rst +++ b/frontend/encore/dev-server.rst @@ -18,6 +18,24 @@ As a consequence, the ``link`` and ``script`` tags need to point to the new serv :ref:`processing your assets through entrypoints.json ` in some other way), you're done: the paths in your templates will automatically point to the dev server. +Enabling HTTPS using the Symfony Web Server +------------------------------------------- + +If you're using the :doc:`Symfony web server ` locally with HTTPS, +you'll need to also tell the dev-server to use HTTPS. To do this, you can reuse the Symfony web +server SSL certificate: + +.. code-block:: terminal + + # Unix-based systems + $ yarn dev-server --https --pfx=$HOME/.symfony/certs/default.p12 + + # Windows + $ encore dev-server --https --pfx=%UserProfile%\.symfony\certs\default.p12 + +dev-server Options +------------------ + The ``dev-server`` command supports all the options defined by `webpack-dev-server`_. You can set these options via command line options: diff --git a/frontend/encore/faq.rst b/frontend/encore/faq.rst index 3c621c3b8d0..c6c6d86c257 100644 --- a/frontend/encore/faq.rst +++ b/frontend/encore/faq.rst @@ -116,7 +116,7 @@ But, instead of working, you see an error: This dependency was not found: - * respond.js in ./assets/js/app.js + * respond.js in ./assets/app.js Typically, a package will "advertise" its "main" file by adding a ``main`` key to its ``package.json``. But sometimes, old libraries won't have this. Instead, you'll diff --git a/frontend/encore/installation.rst b/frontend/encore/installation.rst index 7cf878a1637..bbd6469a1c3 100644 --- a/frontend/encore/installation.rst +++ b/frontend/encore/installation.rst @@ -79,9 +79,9 @@ is the main config file for both Webpack and Webpack Encore: * Each entry will result in one JavaScript file (e.g. app.js) * and one CSS file (e.g. app.css) if your JavaScript imports CSS. */ - .addEntry('app', './assets/js/app.js') - //.addEntry('page1', './assets/js/page1.js') - //.addEntry('page2', './assets/js/page2.js') + .addEntry('app', './assets/app.js') + //.addEntry('page1', './assets/page1.js') + //.addEntry('page2', './assets/page2.js') // When enabled, Webpack "splits" your files into smaller pieces for greater optimization. .splitEntryChunks() @@ -124,17 +124,17 @@ is the main config file for both Webpack and Webpack Encore: // uncomment if you use API Platform Admin (composer require api-admin) //.enableReactPreset() - //.addEntry('admin', './assets/js/admin.js') + //.addEntry('admin', './assets/admin.js') ; module.exports = Encore.getWebpackConfig(); -Next, open the new ``assets/js/app.js`` file which contains some JavaScript code +Next, open the new ``assets/app.js`` file which contains some JavaScript code *and* imports some CSS: .. code-block:: javascript - // assets/js/app.js + // assets/app.js /* * Welcome to your app's main JavaScript file! * @@ -143,18 +143,18 @@ Next, open the new ``assets/js/app.js`` file which contains some JavaScript code */ // any CSS you import will output into a single css file (app.css in this case) - import '../css/app.css'; + import './styles/app.css'; // Need jQuery? Install it with "yarn add jquery", then uncomment to import it. // import $ from 'jquery'; - console.log('Hello Webpack Encore! Edit me in assets/js/app.js'); + console.log('Hello Webpack Encore! Edit me in assets/app.js'); -And the new ``assets/css/app.css`` file: +And the new ``assets/styles/app.css`` file: .. code-block:: css - /* assets/css/app.css */ + /* assets/styles/app.css */ body { background-color: lightgray; } diff --git a/frontend/encore/shared-entry.rst b/frontend/encore/shared-entry.rst index 6693b649d8d..a2c2d08ea2a 100644 --- a/frontend/encore/shared-entry.rst +++ b/frontend/encore/shared-entry.rst @@ -18,11 +18,11 @@ Update your code to use ``createSharedEntry()``: Encore // ... - - .addEntry('app', './assets/js/app.js') - + .createSharedEntry('app', './assets/js/app.js') - .addEntry('homepage', './assets/js/homepage.js') - .addEntry('blog', './assets/js/blog.js') - .addEntry('store', './assets/js/store.js') + - .addEntry('app', './assets/app.js') + + .createSharedEntry('app', './assets/app.js') + .addEntry('homepage', './assets/homepage.js') + .addEntry('blog', './assets/blog.js') + .addEntry('store', './assets/store.js') Before making this change, if both ``app.js`` and ``store.js`` require ``jquery``, then ``jquery`` would be packaged into *both* files, which is wasteful. By making diff --git a/frontend/encore/simple-example.rst b/frontend/encore/simple-example.rst index 4373f9c3b66..dd93a31cf37 100644 --- a/frontend/encore/simple-example.rst +++ b/frontend/encore/simple-example.rst @@ -4,8 +4,8 @@ Encore: Setting up your Project After :doc:`installing Encore `, your app already has one CSS and one JS file, organized into an ``assets/`` directory: -* ``assets/js/app.js`` -* ``assets/css/app.css`` +* ``assets/app.js`` +* ``assets/styles/app.css`` With Encore, think of your ``app.js`` file like a standalone JavaScript application: it will *require* all of the dependencies it needs (e.g. jQuery or React), @@ -14,12 +14,12 @@ application: it will *require* all of the dependencies it needs (e.g. jQuery or .. code-block:: javascript - // assets/js/app.js + // assets/app.js // ... - import '../css/app.css'; + import './styles/app.css'; - // var $ = require('jquery'); + // import $ from 'jquery'; Encore's job (via Webpack) is simple: to read and follow *all* of the ``require()`` statements and create one final ``app.js`` (and ``app.css``) that contains *everything* @@ -43,14 +43,14 @@ of your project. It already holds the basic config you need: // public path used by the web server to access the output path .setPublicPath('/build') - .addEntry('app', './assets/js/app.js') + .addEntry('app', './assets/app.js') // ... ; // ... -The *key* part is ``addEntry()``: this tells Encore to load the ``assets/js/app.js`` +The *key* part is ``addEntry()``: this tells Encore to load the ``assets/app.js`` file and follow *all* of the ``require()`` statements. It will then package everything together and - thanks to the first ``app`` argument - output final ``app.js`` and ``app.css`` files into the ``public/build`` directory. @@ -121,7 +121,7 @@ can do most of the work for you: .. _encore-entrypointsjson-simple-description: That's it! When you refresh your page, all of the JavaScript from -``assets/js/app.js`` - as well as any other JavaScript files it included - will +``assets/app.js`` - as well as any other JavaScript files it included - will be executed. All the CSS files that were required will also be displayed. The ``encore_entry_link_tags()`` and ``encore_entry_script_tags()`` functions @@ -152,7 +152,7 @@ files. First, create a file that exports a function: .. code-block:: javascript - // assets/js/greet.js + // assets/greet.js module.exports = function(name) { return `Yo yo ${name} - welcome to Encore!`; }; @@ -167,7 +167,7 @@ Great! Use ``require()`` to import ``jquery`` and ``greet.js``: .. code-block:: diff - // assets/js/app.js + // assets/app.js // ... + // loads the jquery package from node_modules @@ -196,7 +196,7 @@ To export values using the alternate syntax, use ``export``: .. code-block:: diff - // assets/js/greet.js + // assets/greet.js - module.exports = function(name) { + export default function(name) { return `Yo yo ${name} - welcome to Encore!`; @@ -206,9 +206,9 @@ To import values, use ``import``: .. code-block:: diff - // assets/js/app.js - - require('../css/app.css'); - + import '../css/app.css'; + // assets/app.js + - require('../styles/app.css'); + + import './styles/app.css'; - var $ = require('jquery'); + import $ from 'jquery'; @@ -228,12 +228,12 @@ etc.). To handle this, create a new "entry" JavaScript file for each page: .. code-block:: javascript - // assets/js/checkout.js + // assets/checkout.js // custom code for your checkout page .. code-block:: javascript - // assets/js/account.js + // assets/account.js // custom code for your account page Next, use ``addEntry()`` to tell Webpack to read these two new files when it builds: @@ -243,9 +243,9 @@ Next, use ``addEntry()`` to tell Webpack to read these two new files when it bui // webpack.config.js Encore // ... - .addEntry('app', './assets/js/app.js') - + .addEntry('checkout', './assets/js/checkout.js') - + .addEntry('account', './assets/js/account.js') + .addEntry('app', './assets/app.js') + + .addEntry('checkout', './assets/checkout.js') + + .addEntry('account', './assets/account.js') // ... And because you just changed the ``webpack.config.js`` file, make sure to stop @@ -294,9 +294,9 @@ file to ``app.scss`` and update the ``import`` statement: .. code-block:: diff - // assets/js/app.js - - import '../css/app.css'; - + import '../css/app.scss'; + // assets/app.js + - import './styles/app.css'; + + import './styles/app.scss'; Then, tell Encore to enable the Sass pre-processor: @@ -345,7 +345,7 @@ If you want to only compile a CSS file, that's possible via ``addStyleEntry()``: Encore // ... - .addStyleEntry('some_page', './assets/css/some_page.css') + .addStyleEntry('some_page', './assets/styles/some_page.css') ; This will output a new ``some_page.css``. diff --git a/frontend/encore/split-chunks.rst b/frontend/encore/split-chunks.rst index ebaa4ee48ce..0205537b7d0 100644 --- a/frontend/encore/split-chunks.rst +++ b/frontend/encore/split-chunks.rst @@ -14,10 +14,10 @@ To enable this, call ``splitEntryChunks()``: // ... // multiple entry files, which probably import the same code - .addEntry('app', './assets/js/app.js') - .addEntry('homepage', './assets/js/homepage.js') - .addEntry('blog', './assets/js/blog.js') - .addEntry('store', './assets/js/store.js') + .addEntry('app', './assets/app.js') + .addEntry('homepage', './assets/homepage.js') + .addEntry('blog', './assets/blog.js') + .addEntry('store', './assets/store.js') + .splitEntryChunks() diff --git a/frontend/encore/vuejs.rst b/frontend/encore/vuejs.rst index dd1227c9447..3d10eedcd41 100644 --- a/frontend/encore/vuejs.rst +++ b/frontend/encore/vuejs.rst @@ -28,6 +28,38 @@ Any ``.vue`` files that you require will be processed correctly. You can also configure the `vue-loader options`_ by passing an options callback to ``enableVueLoader()``. See the `Encore's index.js file`_ for detailed documentation. +Runtime Compiler Build +---------------------- + +By default, Encore uses a Vue "build" that allows you to compile templates at +runtime. This means that you *can* do either of these: + +.. code-block:: javascript + + new Vue({ + template: '
{{ hi }}
' + }) + + new Vue({ + el: '#app', // where
in your DOM contains the Vue template + }); + +If you do *not* need this functionality (e.g. you use single file components), +then you can tell Encore to create a *smaller* and CSP-compliant build: + +.. code-block:: javascript + + // webpack.config.js + // ... + + Encore + // ... + + .enableVueLoader(() => {}, { runtimeCompilerBuild: false }) + ; + +You can also silence the recommendation by passing ``runtimeCompilerBuild: true``. + Hot Module Replacement (HMR) ---------------------------- diff --git a/http_cache.rst b/http_cache.rst index 0bd62b30c8e..ca0ccf85e9f 100644 --- a/http_cache.rst +++ b/http_cache.rst @@ -167,11 +167,6 @@ cache efficiency of your routes. You can change the name of the header used for the trace information using the ``trace_header`` config option. -.. versionadded:: 4.3 - - The ``trace_level`` and ``trace_header`` configuration options - were introduced in Symfony 4.3. - .. _http-cache-symfony-versus-varnish: .. sidebar:: Changing from one Reverse Proxy to another @@ -348,16 +343,28 @@ the most useful ones:: Additionally, most cache-related HTTP headers can be set via the single :method:`Symfony\\Component\\HttpFoundation\\Response::setCache` method:: - // sets cache settings in one call + // use this method to set several cache settings in one call + // (this example lists all the available cache settings) $response->setCache([ - 'etag' => $etag, - 'last_modified' => $date, - 'max_age' => 10, - 's_maxage' => 10, - 'public' => true, - // 'private' => true, + 'must_revalidate' => false, + 'no_cache' => false, + 'no_store' => false, + 'no_transform' => false, + 'public' => true, + 'private' => false, + 'proxy_revalidate' => false, + 'max_age' => 600, + 's_maxage' => 600, + 'immutable' => true, + 'last_modified' => new \DateTime(), + 'etag' => 'abcdef' ]); +.. versionadded:: 5.1 + + The ``must_revalidate``, ``no_cache``, ``no_store``, ``no_transform`` and + ``proxy_revalidate`` directives were introduced in Symfony 5.1. + Cache Invalidation ------------------ diff --git a/http_cache/cache_invalidation.rst b/http_cache/cache_invalidation.rst index 4e84cb00807..6c6dcf295a5 100644 --- a/http_cache/cache_invalidation.rst +++ b/http_cache/cache_invalidation.rst @@ -60,7 +60,7 @@ to support the ``PURGE`` HTTP method:: class CacheKernel extends HttpCache { - protected function invalidate(Request $request, $catch = false) + protected function invalidate(Request $request, bool $catch = false) { if ('PURGE' !== $request->getMethod()) { return parent::invalidate($request, $catch); diff --git a/http_client.rst b/http_client.rst index 91122c00961..e0eb9ba9e95 100644 --- a/http_client.rst +++ b/http_client.rst @@ -5,10 +5,6 @@ HTTP Client =========== -.. versionadded:: 4.3 - - The HttpClient component was introduced in Symfony 4.3. - Installation ------------ @@ -147,6 +143,7 @@ Some options are described in this guide: * `Query String Parameters`_ * `Headers`_ * `Redirects`_ +* `Retry Failed Requests`_ * `HTTP Proxies`_ Check out the full :ref:`http_client config reference ` @@ -480,10 +477,6 @@ each request (which overrides any global authentication): 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 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -626,6 +619,35 @@ according to the ``multipart/form-data`` content-type. The 'body' => $formData->bodyToIterable(), ]); +.. tip:: + + When using multidimensional arrays the :class:`Symfony\\Component\\Mime\\Part\\Multipart\\FormDataPart` + class automatically appends ``[key]`` to the name of the field:: + + $formData = new FormDataPart([ + 'array_field' => [ + 'some value', + 'other value', + ], + ]); + + $formData->getParts(); // Returns two instances of TextPart + // with the names "array_field[0]" and "array_field[1]" + + This behavior can be bypassed by using the following array structure:: + + $formData = new FormDataPart([ + ['array_field' => 'some value'], + ['array_field' => 'other value'], + ]); + + $formData->getParts(); // Returns two instances of TextPart both + // with the name "array_field" + + .. versionadded:: 5.2 + + The alternative array structure was introduced in Symfony 5.2. + By default, HttpClient streams the body contents when uploading them. This might not work with all servers, resulting in HTTP status code 411 ("Length Required") because there is no ``Content-Length`` header. The solution is to turn the body @@ -667,6 +689,39 @@ making a request. Use the ``max_redirects`` setting to configure this behavior 'max_redirects' => 0, ]); +Retry Failed Requests +~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The feature to retry failed HTTP requests was introduced in Symfony 5.2. + +Sometimes, requests fail because of network issues or temporary server errors. +Symfony's HttpClient allows to retry failed requests automatically using the +:ref:`retry_failed option `. + +By default, failed requests are retried up to 3 times, with an exponential delay +between retries (first retry = 1 second; third retry: 4 seconds) and only for +the following HTTP status codes: ``423``, ``425``, ``429``, ``502`` and ``503`` +when using any HTTP method and ``500``, ``504``, ``507`` and ``510`` when using +an HTTP `idempotent method`_. + +Check out the full list of configurable :ref:`retry_failed options ` +to learn how to tweak each of them to fit your application needs. + +When using the HttpClient outside of a Symfony application, use the +:class:`Symfony\\Component\\HttpClient\\RetryableHttpClient` class to wrap your +original HTTP client:: + + use Symfony\Component\HttpClient\RetryableHttpClient; + + $client = new RetryableHttpClient(HttpClient::create()); + +The ``RetryableHttpClient`` uses a +:class:`Symfony\\Component\\HttpClient\\Retry\\RetryStrategyInterface` to +decide if the request should be retried, and to define the waiting time between +each retry. + HTTP Proxies ~~~~~~~~~~~~ @@ -736,12 +791,55 @@ When using this component in a full-stack Symfony application, this behavior is not configurable and cURL will be used automatically if the cURL PHP extension is installed and enabled. Otherwise, the native PHP streams will be used. +Configuring CurlHttpClient Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The feature to configure extra cURL options was introduced in Symfony 5.2. + +PHP allows to configure lots of `cURL options`_ via the :phpfunction:`curl_setopt` +function. In order to make the component more portable when not using cURL, the +``CurlHttpClient`` only uses some of those options (and they are ignored in the +rest of clients). + +Add an ``extra.curl`` option in your configuration to pass those extra options:: + + use Symfony\Component\HttpClient\CurlHttpClient; + + $client = new CurlHttpClient(); + + $client->request('POST', 'https://...', [ + // ... + 'extra' => [ + 'curl' => [ + CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V6 + ] + ] + ]); + +.. note:: + + Some cURL options are impossible to override (e.g. because of thread safety) + and you'll get an exception when trying to override them. + HTTP/2 Support ~~~~~~~~~~~~~~ -When requesting an ``https`` URL, HTTP/2 is enabled by default if libcurl >= 7.36 -is used. To force HTTP/2 for ``http`` URLs, you need to enable it explicitly via -the ``http_version`` option: +When requesting an ``https`` URL, HTTP/2 is enabled by default if one of the +following tools is installed: + +* The `libcurl`_ package version 7.36 or higher; +* The `amphp/http-client`_ Packagist package version 4.2 or higher. + +.. versionadded:: 5.1 + + Integration with ``amphp/http-client`` was introduced in Symfony 5.1. + Prior to this version, HTTP/2 was only supported when ``libcurl`` was + installed. + +To force HTTP/2 for ``http`` URLs, you need to enable it explicitly via the +``http_version`` option: .. configuration-block:: @@ -822,10 +920,6 @@ 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 @@ -888,10 +982,6 @@ 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 ~~~~~~~~~~~~~~~~~~~ @@ -1122,6 +1212,57 @@ installed in your application:: ``CachingHttpClient`` accepts a third argument to set the options of the ``HttpCache``. +Consuming Server-Sent Events +---------------------------- + +.. versionadded:: 5.2 + + The feature to consume server-sent events was introduced in Symfony 5.2. + +`Server-sent events`_ is an Internet standard used to push data to web pages. +Its JavaScript API is built around an `EventSource`_ object, which listens to +the events sent from some URL. The events are a stream of data (served with the +``text/event-stream`` MIME type) with the following format: + +.. code-block:: text + + data: This is the first message. + + data: This is the second message, it + data: has two lines. + + data: This is the third message. + +Symfony's HTTP client provides an EventSource implementation to consume these +server-sent events. Use the :class:`Symfony\\Component\\HttpClient\\EventSourceHttpClient` +to wrap your HTTP client, open a connection to a server that responds with a +``text/event-stream`` content type and consume the stream as follows:: + + use Symfony\Component\HttpClient\EventSourceHttpClient; + + // the second optional argument is the reconnection time in seconds (default = 10) + $client = new EventSourceHttpClient($client, 10); + $source = $client->connect('https://localhost:8080/events'); + while ($source) { + foreach ($client->stream($source, 2) as $r => $chunk) { + if ($chunk->isTimeout()) { + // ... + continue; + } + + if ($chunk->isLast()) { + // ... + + return; + } + + // this is a special ServerSentEvent chunk holding the pushed message + if ($chunk instanceof ServerSentEvent) { + // do something with the server event ... + } + } + } + Interoperability ---------------- @@ -1226,17 +1367,9 @@ Now you can make HTTP requests with the PSR-18 client as follows: $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 @@ -1325,10 +1458,6 @@ Then you're ready to go:: 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`. @@ -1415,8 +1544,90 @@ However, using ``MockResponse`` allows simulating chunked responses and timeouts $mockResponse = new MockResponse($body()); +.. versionadded:: 5.2 + + The feature explained below was introduced in Symfony 5.2. + +Finally, you can also create an invokable or iterable class that generates the +responses and use it as a callback in functional tests:: + + namespace App\Tests; + + use Symfony\Component\HttpClient\Response\MockResponse; + use Symfony\Contracts\HttpClient\ResponseInterface; + + class MockClientCallback + { + public function __invoke(string $method, string $url, array $options = []): ResponseInterface + { + // load a fixture file or generate data + // ... + return new MockResponse($data); + } + } + +Then configure Symfony to use your callback: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services_test.yaml + services: + # ... + App\Tests\MockClientCallback: ~ + + # config/packages/test/framework.yaml + framework: + http_client: + mock_response_factory: App\Tests\MockClientCallback + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/framework.php + $container->loadFromExtension('framework', [ + 'http_client' => [ + 'mock_response_factory' => MockClientCallback::class, + ], + ]); + .. _`cURL PHP extension`: https://www.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 +.. _`libcurl`: https://curl.haxx.se/libcurl/ +.. _`amphp/http-client`: https://packagist.org/packages/amphp/http-client +.. _`cURL options`: https://www.php.net/manual/en/function.curl-setopt.php +.. _`Server-sent events`: https://html.spec.whatwg.org/multipage/server-sent-events.html +.. _`EventSource`: https://www.w3.org/TR/eventsource/#eventsource +.. _`idempotent method`: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods_and_web_applications diff --git a/index.rst b/index.rst index a1cd8e9f3c6..a4f512151f5 100644 --- a/index.rst +++ b/index.rst @@ -49,8 +49,10 @@ Topics mercure messenger migration + notifier performance profiler + rate_limiter routing security session diff --git a/lock.rst b/lock.rst index 3fde32ee190..6a62c558fd7 100644 --- a/lock.rst +++ b/lock.rst @@ -56,10 +56,12 @@ this behavior by using the ``lock`` key like: lock: 'zookeeper://z1.docker' lock: 'zookeeper://z1.docker,z2.docker' lock: 'sqlite:///%kernel.project_dir%/var/lock.db' - lock: 'mysql:host=127.0.0.1;dbname=lock' - lock: 'pgsql:host=127.0.0.1;dbname=lock' - lock: 'sqlsrv:server=localhost;Database=test' - lock: 'oci:host=localhost;dbname=test' + lock: 'mysql:host=127.0.0.1;dbname=app' + lock: 'pgsql:host=127.0.0.1;dbname=app' + lock: 'pgsql+advisory:host=127.0.0.1;dbname=lock' + lock: 'sqlsrv:server=127.0.0.1;Database=app' + lock: 'oci:host=127.0.0.1;dbname=app' + lock: 'mongodb://127.0.0.1/app?collection=lock' lock: '%env(LOCK_DSN)%' # named locks @@ -102,13 +104,17 @@ this behavior by using the ``lock`` key like: sqlite:///%kernel.project_dir%/var/lock.db - mysql:host=127.0.0.1;dbname=lock + mysql:host=127.0.0.1;dbname=app - pgsql:host=127.0.0.1;dbname=lock + pgsql:host=127.0.0.1;dbname=app - sqlsrv:server=localhost;Database=test + pgsql+advisory:host=127.0.0.1;dbname=lock - oci:host=localhost;dbname=test + sqlsrv:server=127.0.0.1;Database=app + + oci:host=127.0.0.1;dbname=app + + mongodb://127.0.0.1/app?collection=lock %env(LOCK_DSN)% @@ -135,10 +141,12 @@ this behavior by using the ``lock`` key like: 'lock' => 'zookeeper://z1.docker', 'lock' => 'zookeeper://z1.docker,z2.docker', 'lock' => 'sqlite:///%kernel.project_dir%/var/lock.db', - 'lock' => 'mysql:host=127.0.0.1;dbname=lock', - 'lock' => 'pgsql:host=127.0.0.1;dbname=lock', - 'lock' => 'sqlsrv:server=localhost;Database=test', - 'lock' => 'oci:host=localhost;dbname=test', + 'lock' => 'mysql:host=127.0.0.1;dbname=app', + 'lock' => 'pgsql:host=127.0.0.1;dbname=app', + 'lock' => 'pgsql+advisory:host=127.0.0.1;dbname=lock', + 'lock' => 'sqlsrv:server=127.0.0.1;Database=app', + 'lock' => 'oci:host=127.0.0.1;dbname=app', + 'lock' => 'mongodb://127.0.0.1/app?collection=lock', 'lock' => '%env(LOCK_DSN)%', // named locks @@ -151,22 +159,23 @@ this behavior by using the ``lock`` key like: Locking a Resource ------------------ -To lock the default resource, autowire the lock using -:class:`Symfony\\Component\\Lock\\LockInterface` (service id ``lock``):: +To lock the default resource, autowire the lock factory using +:class:`Symfony\\Component\\Lock\\LockFactory` (service id ``lock.factory``):: // src/Controller/PdfController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Lock\LockInterface; + use Symfony\Component\Lock\LockFactory; class PdfController extends AbstractController { /** * @Route("/download/terms-of-use.pdf") */ - public function downloadPdf(LockInterface $lock, MyPdfGeneratorService $pdf) + public function downloadPdf(LockFactory $factory, MyPdfGeneratorService $pdf) { + $lock = $factory->createLock('pdf-creation'); $lock->acquire(true); // heavy computation @@ -265,18 +274,12 @@ provides :ref:`named lock `: ], ]); -Each name becomes a service where the service id suffixed by the name of the -lock (e.g. ``lock.invoice``). An autowiring alias is also created for each lock -using the camel case version of its name suffixed by ``Lock`` - e.g. ``invoice`` -can be injected automatically by naming the argument ``$invoiceLock`` and -type-hinting it with :class:`Symfony\\Component\\Lock\\LockInterface`. - -Symfony also provide a corresponding factory and store following the same rules -(e.g. ``invoice`` generates a ``lock.invoice.factory`` and -``lock.invoice.store``, both can be injected automatically by naming -respectively ``$invoiceLockFactory`` and ``$invoiceLockStore`` and type-hinted -with :class:`Symfony\\Component\\Lock\\LockFactory` and -:class:`Symfony\\Component\\Lock\\PersistingStoreInterface`) +Each name becomes a service where the service id is part of the name of the +lock (e.g. ``lock.invoice.factory``). An autowiring alias is also created for +each lock using the camel case version of its name suffixed by ``LockFactory`` +- e.g. ``invoice`` can be injected automatically by naming the argument +``$invoiceLockFactory`` and type-hinting it with +:class:`Symfony\\Component\\Lock\\LockFactory`. Blocking Store -------------- @@ -289,4 +292,4 @@ you can do it by :doc:`decorating the store ` or use the ``monolog.logger`` tag with the ``channel`` property as explained in the diff --git a/logging/handlers.rst b/logging/handlers.rst index 1682040e504..9f5b903cfa9 100644 --- a/logging/handlers.rst +++ b/logging/handlers.rst @@ -4,10 +4,6 @@ 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. diff --git a/logging/monolog_email.rst b/logging/monolog_email.rst index 7822de7c58c..22ed4d08928 100644 --- a/logging/monolog_email.rst +++ b/logging/monolog_email.rst @@ -4,10 +4,10 @@ How to Configure Monolog to Email Errors ======================================== -.. caution:: +.. versionadded:: 3.6 - This feature is compatible with the new :doc:`Symfony mailer ` - starting from Symfony 5.1. All previous versions require using SwiftMailer. + Support for emailing errors using :doc:`Symfony mailer ` was added + in MonologBundle 3.6. `Monolog`_ can be configured to send an email when an error occurs within an application. The configuration for this requires a few nested handlers @@ -33,9 +33,9 @@ it is broken down. handler: deduplicated deduplicated: type: deduplication - handler: swift - swift: - type: swift_mailer + handler: symfony_mailer + symfony_mailer: + type: symfony_mailer from_email: 'error@example.com' to_email: 'error@example.com' # or list of recipients @@ -73,11 +73,11 @@ it is broken down. [ 'type' => 'deduplication', - 'handler' => 'swift', + 'handler' => 'symfony_mailer', ], - 'swift' => [ - 'type' => 'swift_mailer', + 'symfony_mailer' => [ + 'type' => 'symfony_mailer', 'from_email' => 'error@example.com', 'to_email' => 'error@example.com', // or a list of recipients @@ -162,7 +162,7 @@ You can adjust the time period using the ``time`` option: type: deduplication # the time in seconds during which duplicate entries are discarded (default: 60) time: 10 - handler: swift + handler: symfony_mailer .. code-block:: xml @@ -172,7 +172,7 @@ You can adjust the time period using the ``time`` option: + handler="symfony_mailer"/> .. code-block:: php @@ -184,12 +184,12 @@ You can adjust the time period using the ``time`` option: 'type' => 'deduplication', // the time in seconds during which duplicate entries are discarded (default: 60) 'time' => 10, - 'handler' => 'swift', + 'handler' => 'symfony_mailer', ], ], ]); -The messages are then passed to the ``swift`` handler. This is the handler that +The messages are then passed to the ``symfony_mailer`` handler. This is the handler that actually deals with emailing you the error. The settings for this are straightforward, the to and from addresses, the formatter, the content type and the subject. @@ -217,9 +217,9 @@ get logged on the server as well as the emails being sent: level: debug deduplicated: type: deduplication - handler: swift - swift: - type: swift_mailer + handler: symfony_mailer + symfony_mailer: + type: symfony_mailer from_email: 'error@example.com' to_email: 'error@example.com' subject: 'An Error Occurred! %%message%%' @@ -259,11 +259,11 @@ get logged on the server as well as the emails being sent: [ 'type' => 'deduplication', - 'handler' => 'swift', + 'handler' => 'symfony_mailer', ], - 'swift' => [ - 'type' => 'swift_mailer', + 'symfony_mailer' => [ + 'type' => 'symfony_mailer', 'from_email' => 'error@example.com', 'to_email' => 'error@example.com', // or a list of recipients diff --git a/logging/processors.rst b/logging/processors.rst index 5f1e27834b5..c0a3eb33bbf 100644 --- a/logging/processors.rst +++ b/logging/processors.rst @@ -19,30 +19,33 @@ using a processor:: // src/Logger/SessionRequestProcessor.php namespace App\Logger; - use Symfony\Component\HttpFoundation\Session\SessionInterface; + use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; + use Symfony\Component\HttpFoundation\RequestStack; class SessionRequestProcessor { - private $session; - private $sessionId; + private $requestStack; - public function __construct(SessionInterface $session) + public function __construct(RequestStack $requestStack) { - $this->session = $session; + $this->requestStack = $requestStack; } // this method is called for each log record; optimize it to not hurt performance public function __invoke(array $record) { - if (!$this->session->isStarted()) { + try { + $session = $requestStack->getSession(); + } catch (SessionNotFoundException $e) { + return; + } + if (!$session->isStarted()) { return $record; } - if (!$this->sessionId) { - $this->sessionId = substr($this->session->getId(), 0, 8) ?: '????????'; - } + $sessionId = substr($session->getId(), 0, 8) ?: '????????'; - $record['extra']['token'] = $this->sessionId.'-'.substr(uniqid('', true), -8); + $record['extra']['token'] = $sessionId.'-'.substr(uniqid('', true), -8); return $record; } @@ -171,6 +174,14 @@ Symfony's MonologBridge provides processors that can be registered inside your a Adds information from the current user's token to the record namely username, roles and whether the user is authenticated. +:class:`Symfony\\Bridge\\Monolog\\Processor\\SwitchUserTokenProcessor` + Adds information about the user who is impersonating the logged in user, + namely username, roles and whether the user is authenticated. + + .. versionadded:: 5.2 + + The ``SwitchUserTokenProcessor`` was introduced in Symfony 5.2. + :class:`Symfony\\Bridge\\Monolog\\Processor\\WebProcessor` Overrides data from the request using the data inside Symfony's request object. @@ -181,11 +192,6 @@ Symfony's MonologBridge provides processors that can be registered inside your a :class:`Symfony\\Bridge\\Monolog\\Processor\\ConsoleCommandProcessor` Adds information about current console command. -.. versionadded:: 4.3 - - The ``RouteProcessor`` and the ``ConsoleCommandProcessor`` were introduced - in Symfony 4.3. - .. seealso:: Check out the `built-in Monolog processors`_ to learn more about how to diff --git a/mailer.rst b/mailer.rst index 58531bf85f3..9c9c1451add 100644 --- a/mailer.rst +++ b/mailer.rst @@ -1,11 +1,6 @@ Sending Emails with Mailer ========================== -.. versionadded:: 4.3 - - The Mailer component was introduced in Symfony 4.3. The previous solution, - called Swift Mailer, is still valid: :doc:`Swift Mailer `. - Installation ------------ @@ -17,6 +12,9 @@ integration, CSS inlining, file attachments and a lot more. Get them installed w $ composer require symfony/mailer + +.. _mailer-transport-setup: + Transport Setup --------------- @@ -48,6 +46,7 @@ over SMTP by configuring the DSN in your ``.env`` file (the ``user``, xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> + @@ -57,6 +56,7 @@ over SMTP by configuring the DSN in your ``.env`` file (the ``user``, // config/packages/mailer.php use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->extension('framework', [ 'mailer' => [ @@ -80,31 +80,43 @@ over SMTP by configuring the DSN in your ``.env`` file (the ``user``, Using Built-in Transports ~~~~~~~~~~~~~~~~~~~~~~~~~ -============ ======================================== ============================== +.. versionadded:: 5.2 + + The native protocol was introduced in Symfony 5.2. + +============ ======================================== ============================================================== DSN protocol Example Description -============ ======================================== ============================== -smtp ``smtp://user:pass@smtp.example.com:25`` Mailer uses an SMTP server to - send emails -sendmail ``sendmail://default`` Mailer uses the local sendmail - binary to send emails -============ ======================================== ============================== +============ ======================================== ============================================================== +smtp ``smtp://user:pass@smtp.example.com:25`` Mailer uses an SMTP server to send emails +sendmail ``sendmail://default`` Mailer uses the local sendmail binary to send emails +native ``native://default`` Mailer uses the sendmail binary and options configured + in the ``sendmail_path`` setting of ``php.ini``. On Windows + hosts, Mailer fallbacks to ``smtp`` and ``smtp_port`` + ``php.ini`` settings when ``sendmail_path`` is not configured. +============ ======================================== ============================================================== Using a 3rd Party Transport ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Instead of using your own SMTP server, you can send emails via a 3rd party +Instead of using your own SMTP server or sendmail binary, you can send emails via a 3rd party provider. Mailer supports several - install whichever you want: -================== ============================================= +================== ============================================== Service Install with -================== ============================================= +================== ============================================== Amazon SES ``composer require symfony/amazon-mailer`` Gmail ``composer require symfony/google-mailer`` MailChimp ``composer require symfony/mailchimp-mailer`` Mailgun ``composer require symfony/mailgun-mailer`` +Mailjet ``composer require symfony/mailjet-mailer`` Postmark ``composer require symfony/postmark-mailer`` SendGrid ``composer require symfony/sendgrid-mailer`` -================== ============================================= +Sendinblue ``composer require symfony/sendinblue-mailer`` +================== ============================================== + +.. versionadded:: 5.2 + + The Sendinblue integration was introduced in Symfony 5.2. Each library includes a :ref:`Symfony Flex recipe ` that will add a configuration example to your ``.env`` file. For example, suppose you want to @@ -143,16 +155,18 @@ transport, but you can force to use one: This table shows the full list of available DSN formats for each third party provider: -==================== ========================================== =========================================== ======================================== - 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 -==================== ========================================== =========================================== ======================================== +==================== ==================================================== =========================================== ======================================== + 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 + Mailjet mailjet+smtp://ACCESS_KEY:SECRET_KEY@default n/a mailjet+api://ACCESS_KEY:SECRET_KEY@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 + Sendinblue sendinblue+smtp://apikey:USERNAME:PASSWORD@default n/a sendinblue+api://KEY@default +==================== ==================================================== =========================================== ======================================== .. caution:: @@ -160,6 +174,16 @@ party provider: For example, the DSN ``ses+smtp://ABC1234:abc+12/345@default`` should be configured as ``ses+smtp://ABC1234:abc%2B12%2F345@default`` +.. note:: + + When using SMTP, the default timeout for sending a message before throwing an + exception is the value defined in the `default_socket_timeout`_ PHP.ini option. + + .. versionadded:: 5.1 + + The usage of ``default_socket_timeout`` as the default timeout was + introduced in Symfony 5.1. + .. tip:: If you want to override the default host for a provider (to debug an issue using @@ -203,9 +227,28 @@ A round-robin transport is configured with two or more transports and the MAILER_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 -all of them fail). +The mailer will start using a randomly selected transport and if it fails, it +will retry the same delivery with the next transports until one of them succeeds +(or until all of them fail). + +.. versionadded:: 5.1 + + The random selection of the first transport was introduced in Symfony 5.1. + In previous Symfony versions the first transport was always selected first. + +TLS Peer Verification +~~~~~~~~~~~~~~~~~~~~~ + +By default, SMTP transports perform TLS peer verification. This behavior is +configurable with the ``verify_peer`` option. Although it's not recommended to +disable this verification for security reasons, it can be useful while developing +the application or when using a self-signed certificate:: + + $dsn = 'smtp://user:pass@smtp.example.com?verify_peer=0' + +.. versionadded:: 5.1 + + The ``verify_peer`` option was introduced in Symfony 5.1. Creating & Sending Messages --------------------------- @@ -269,7 +312,7 @@ both strings or address objects:: // defining the email address and name as a string // (the format must match: 'Name ') - ->from(Address::fromString('Fabien Potencier ')) + ->from(Address::create('Fabien Potencier ')) // ... ; @@ -281,6 +324,16 @@ both strings or address objects:: :class:`Symfony\\Component\\Mailer\\Event\\MessageEvent` event to set the same ``From`` email to all messages. +.. note:: + + The local part of the address (what goes before the ``@``) can include UTF-8 + characters, except for the sender address (to avoid issues with bounced emails). + For example: ``föóbàr@example.com``, ``用户@example.com``, ``θσερ@example.com``, etc. + + .. versionadded:: 5.2 + + Support for UTF-8 characters in email addresses was introduced in Symfony 5.2. + Use ``addTo()``, ``addCc()``, or ``addBcc()`` methods to add more addresses:: $email = (new Email()) @@ -434,18 +487,10 @@ transports, which is useful to debug errors. ID of the message (being the original random ID generated by Symfony or the new ID generated by the mailer provider). - .. versionadded:: 4.4 - - The ``getMessageId()`` method was introduced in Symfony 4.4. - 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 @@ -642,14 +687,14 @@ arguments to the filter: .. code-block:: html+twig - {% apply inline_css(source('@css/email.css')) %} + {% apply inline_css(source('@styles/email.css')) %}

Welcome {{ username }}!

{# ... #} {% endapply %} You can pass unlimited number of arguments to ``inline_css()`` to load multiple CSS files. For this example to work, you also need to define a new Twig namespace -called ``css`` that points to the directory where ``email.css`` lives: +called ``styles`` that points to the directory where ``email.css`` lives: .. _mailer-css-namespace: @@ -663,7 +708,7 @@ called ``css`` that points to the directory where ``email.css`` lives: paths: # point this wherever your css files live - '%kernel.project_dir%/assets/css': css + '%kernel.project_dir%/assets/styles': styles .. code-block:: xml @@ -679,7 +724,7 @@ called ``css`` that points to the directory where ``email.css`` lives: - %kernel.project_dir%/assets/css + %kernel.project_dir%/assets/styles @@ -690,7 +735,7 @@ called ``css`` that points to the directory where ``email.css`` lives: // ... 'paths' => [ // point this wherever your css files live - '%kernel.project_dir%/assets/css' => 'css', + '%kernel.project_dir%/assets/styles' => 'styles', ], ]); @@ -772,30 +817,31 @@ You can combine all filters to create complex email messages: .. code-block:: twig - {% apply inky_to_html|inline_css(source('@css/foundation-emails.css')) %} + {% apply inky_to_html|inline_css(source('@styles/foundation-emails.css')) %} {# ... #} {% endapply %} -This makes use of the :ref:`css Twig namespace ` we created +This makes use of the :ref:`styles Twig namespace ` we created earlier. You could, for example, `download the foundation-emails.css file`_ -directly from GitHub and save it in ``assets/css``. +directly from GitHub and save it in ``assets/styles``. 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. +It's possible to sign and/or encrypt email messages 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. +.. tip:: + + When using OpenSSL to generate certificates, make sure to add the + ``-addtrust emailProtection`` command option. + Signing Messages ~~~~~~~~~~~~~~~~ @@ -804,7 +850,19 @@ 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:: +hide its contents. + +You can sign messages using either ``S/MIME`` or ``DKIM``. In both cases, 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. + +S/MIME Signer +............. + +`S/MIME`_ is a standard for public key encryption and signing of MIME data. It +requires using both a certificate and a private key:: use Symfony\Component\Mime\Crypto\SMimeSigner; use Symfony\Component\Mime\Email; @@ -821,22 +879,52 @@ hide its contents:: $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. +DKIM Signer +........... + +`DKIM`_ is an email authentication method that affixes a digital signature, +linked to a domain name, to each outgoing email messages. It requires a private +key but not a certificate:: + + use Symfony\Component\Mime\Crypto\DkimSigner; + use Symfony\Component\Mime\Email; + + $email = (new Email()) + ->from('hello@example.com') + // ... + ->html('...'); + + // first argument: same as openssl_pkey_get_private(), either a string with the + // contents of the private key or the absolute path to it (prefixed with 'file://') + // second and third arguments: the domain name and "selector" used to perform a DNS lookup + // (the selector is a string used to point to a specific DKIM public key record in your DNS) + $signer = new DkimSigner('file:///path/to/private-key.key', 'example.com', 'sf'); + // if the private key has a passphrase, pass it as the fifth argument + // new DkimSigner('file:///path/to/private-key.key', 'example.com', 'sf', [], 'the-passphrase'); + + $signedEmail = $signer->sign($email); + // now use the Mailer component to send this $signedEmail instead of the original email + + // DKIM signer provides many config options and a helper object to configure them + use Symfony\Component\Mime\Crypto\DkimOptions; + + $signedEmail = $signer->sign($email, (new DkimOptions()) + ->bodyCanon('relaxed') + ->headerCanon('relaxed') + ->headersToIgnore(['Message-ID']) + ->toArray() + ); + +.. versionadded:: 5.2 + + The DKIM signer was introduced in Symfony 5.2. + Encrypting Messages ~~~~~~~~~~~~~~~~~~~ @@ -883,10 +971,6 @@ and it will select the appropriate certificate depending on the ``To`` option:: 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: @@ -1010,6 +1094,42 @@ 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`). +Adding Tags and Metadata to Emails +---------------------------------- + +.. versionadded:: 5.1 + + The :class:`Symfony\\Component\\Mailer\\Header\\TagHeader` and + :class:`Symfony\\Component\\Mailer\\Header\\MetadataHeader` classes were + introduced in Symfony 5.1. + +Certain 3rd party transports support email *tags* and *metadata*, which can be used +for grouping, tracking and workflows. You can add those by using the +:class:`Symfony\\Component\\Mailer\\Header\\TagHeader` and +:class:`Symfony\\Component\\Mailer\\Header\\MetadataHeader` classes. If your transport +supports headers, it will convert them to their appropriate format:: + + use Symfony\Component\Mailer\Header\MetadataHeader; + use Symfony\Component\Mailer\Header\TagHeader; + + $email->getHeaders()->add(new TagHeader('password-reset')); + $email->getHeaders()->add(new MetadataHeader('Color', 'blue')); + $email->getHeaders()->add(new MetadataHeader('Client-ID', '12345')); + +If your transport does not support tags and metadata, they will be added as custom headers: + +.. code-block:: text + + X-Tag: password-reset + X-Metadata-Color: blue + X-Metadata-Client-ID: 12345 + +The following transports currently support tags and metadata: + +* Postmark +* Mailgun +* MailChimp + Development & Debugging ----------------------- @@ -1118,6 +1238,8 @@ a specific address, instead of the *real* address: .. _`Markdown syntax`: https://commonmark.org/ .. _`Inky`: https://get.foundation/emails/docs/inky.html .. _`S/MIME`: https://en.wikipedia.org/wiki/S/MIME +.. _`DKIM`: https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail .. _`OpenSSL PHP extension`: https://www.php.net/manual/en/book.openssl.php .. _`PEM encoded`: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail +.. _`default_socket_timeout`: https://www.php.net/manual/en/filesystem.configuration.php#ini.default-socket-timeout .. _`RFC 3986`: https://www.ietf.org/rfc/rfc3986.txt diff --git a/mercure.rst b/mercure.rst index 527833e2f7e..ff45874f671 100644 --- a/mercure.rst +++ b/mercure.rst @@ -421,6 +421,7 @@ And here is the controller:: use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Key; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\WebLink\Link; @@ -441,10 +442,14 @@ And here is the controller:: ->toString(); $response = $this->json(['@id' => '/demo/books/1', 'availability' => 'https://schema.org/InStock']); - $response->headers->set( - 'set-cookie', - sprintf('mercureAuthorization=%s; path=/.well-known/mercure; secure; httponly; SameSite=strict', $token) - ); + $cookie = Cookie::create('mercureAuthorization') + ->withValue($token) + ->withPath('/.well-known/mercure') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite('strict') + ; + $response->headers->setCookie($cookie); return $response; } diff --git a/messenger.rst b/messenger.rst index 2947f18cb68..edd43cd7881 100644 --- a/messenger.rst +++ b/messenger.rst @@ -118,13 +118,12 @@ is capable of sending messages (e.g. to a queueing system) and then .. note:: If you want to use a transport that's not supported, check out the - `Enqueue's transport`_, which supports things like Kafka, Amazon SQS and - Google Pub/Sub. + `Enqueue's transport`_, which supports things like Kafka and Google Pub/Sub. A transport is registered using a "DSN". Thanks to Messenger's Flex recipe, your ``.env`` file already has a few examples. -.. code-block:: bash +.. code-block:: env # MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages # MESSENGER_TRANSPORT_DSN=doctrine://default @@ -453,11 +452,6 @@ You can do this with the ``messenger:consume`` command: # use -vv to see details about what's happening $ php bin/console messenger:consume async -vv -.. versionadded:: 4.3 - - The ``messenger:consume`` command was renamed in Symfony 4.3 (previously it - was called ``messenger:consume-messages``). - The first argument is the receiver's name (or service id if you routed to a custom service). By default, the command will run forever: looking for new messages on your transport and handling them. This command is called your "worker". @@ -722,6 +716,15 @@ this is configurable for each transport: ], ]); +.. tip:: + + Symfony triggers a :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageRetriedEvent` + when a message is retried so you can run your own logic. + + .. versionadded:: 5.2 + + The ``WorkerMessageRetriedEvent`` class was introduced in Symfony 5.2. + Avoiding Retrying ~~~~~~~~~~~~~~~~~ @@ -730,6 +733,18 @@ and should not be retried. If you throw :class:`Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException`, the message will not be retried. +Forcing Retrying +~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.1 + + The ``RecoverableMessageHandlingException`` was introduced in Symfony 5.1. + +Sometimes handling a message must fail in a way that you *know* is temporary +and must be retried. If you throw +:class:`Symfony\\Component\\Messenger\\Exception\\RecoverableMessageHandlingException`, +the message will always be retried. + .. _messenger-failure-transport: Saving & Retrying Failed Messages @@ -816,6 +831,13 @@ to retry them: # remove a message without retrying it $ php bin/console messenger:failed:remove 20 + # remove messages without retrying them and show each message before removing it + $ php bin/console messenger:failed:remove 20 30 --show-messages + +.. versionadded:: 5.1 + + The ``--show-messages`` option was introduced in Symfony 5.1. + If the message fails again, it will be re-sent back to the failure transport due to the normal :ref:`retry rules `. Once the max retry has been hit, the message will be discarded permanently. @@ -826,19 +848,101 @@ Transport Configuration ----------------------- Messenger supports a number of different transport types, each with their own -options. +options. Options can be passed to the transport via a DSN string or configuration. + +.. code-block:: env + + # .env + MESSENGER_TRANSPORT_DSN=amqp://localhost/%2f/messages?auto_setup=false + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + transports: + my_transport: + dsn: "%env(MESSENGER_TRANSPORT_DSN)%" + options: + auto_setup: false + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', [ + 'messenger' => [ + 'transports' => [ + 'my_transport' => [ + 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', + 'options' => [ + 'auto_setup' => false, + ] + ], + ], + ], + ]); + +Options defined under ``options`` take precedence over ones defined in the DSN. AMQP Transport ~~~~~~~~~~~~~~ -The ``amqp`` transport configuration looks like this: +The AMQP transport uses the AMQP PHP extension to send messages to queues like +RabbitMQ. + +.. versionadded:: 5.1 + + Starting from Symfony 5.1, the AMQP transport has moved to a separate package. + Install it by running: + + .. code-block:: terminal + + $ composer require symfony/amqp-messenger -.. code-block:: bash +The AMQP transport DSN may looks like this: + +.. code-block:: env # .env MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages -To use Symfony's built-in AMQP transport, you need the AMQP PHP extension. + # or use the AMQPS protocol + MESSENGER_TRANSPORT_DSN=amqps://guest:guest@localhost/%2f/messages + +.. versionadded:: 5.2 + + The AMQPS protocol support was introduced in Symfony 5.2. + +If you want to use TLS/SSL encrypted AMQP, you must also provide a CA certificate. +Define the certificate path in the ``amqp.cacert`` PHP.ini setting +(e.g. ``amqp.cacert = /etc/ssl/certs``) or in the ``cacert`` parameter of the +DSN (e.g ``amqps://localhost?cacert=/etc/ssl/certs/``). + +The default port used by TLS/SSL encrypted AMQP is 5671, but you can overwrite +it in the ``port`` parameter of the DSN (e.g. ``amqps://localhost?cacert=/etc/ssl/certs/&port=12345``). .. note:: @@ -848,13 +952,85 @@ To use Symfony's built-in AMQP transport, you need the AMQP PHP extension. The transport has a number of other options, including ways to configure the exchange, queues binding keys and more. See the documentation on -:class:`Symfony\\Component\\Messenger\\Transport\\AmqpExt\\Connection`. +:class:`Symfony\\Component\\Messenger\\Bridge\\Amqp\\Transport\\Connection`. + +The transport has a number of options: + +============================================ ================================================= =================================== + Option Description Default +============================================ ================================================= =================================== +``auto_setup`` Whether the table should be created ``true`` + automatically during send / get. +``cacert`` Path to the CA cert file in PEM format. +``cert`` Path to the client certificate in PEM format. +``channel_max`` Specifies highest channel number that the server + permits. 0 means standard extension limit +``confirm_timeout`` Timeout in seconds for confirmation, if none + specified transport will not wait for message + confirmation. Note: 0 or greater seconds. May be + fractional. +``connect_timeout`` Connection timeout. Note: 0 or greater seconds. + May be fractional. +``confirm_timeout`` Number of seconds to wait for message sending + confirmation. If not specified, transport won't + wait for confirmation. May be fractional. +``frame_max`` The largest frame size that the server proposes + for the connection, including frame header and + end-byte. 0 means standard extension limit + (depends on librabbimq default frame size limit) +``heartbeat`` The delay, in seconds, of the connection + heartbeat that the server wants. 0 means the + server does not want a heartbeat. Note, + librabbitmq has limited heartbeat support, which + means heartbeats checked only during blocking + calls. +``host`` Hostname of the AMQP service +``key`` Path to the client key in PEM format. +``password`` Password to use to connect to the AMQP service +``persistent`` ``'false'`` +``port`` Port of the AMQP service +``prefetch_count`` +``read_timeout`` Timeout in for income activity. Note: 0 or + greater seconds. May be fractional. +``retry`` +``sasl_method`` +``user`` Username to use to connect the AMQP service +``verify`` Enable or disable peer verification. If peer + verification is enabled then the common name in + the server certificate must match the server + name. Peer verification is enabled by default. +``vhost`` Virtual Host to use with the AMQP service +``write_timeout`` Timeout in for outcome activity. Note: 0 or + greater seconds. May be fractional. +``delay[queue_name_pattern]`` Pattern to use to create the queues ``delay_%exchange_name%_%routing_key%_%delay%`` +``delay[exchange_name]`` Name of the exchange to be used for the ``delays`` + delayed/retried messages +``queues[name][arguments]`` Extra arguments +``queues[name][binding_arguments]`` Arguments to be used while binding the queue. +``queues[name][binding_keys]`` The binding keys (if any) to bind to this queue +``queues[name][flags]`` Queue flags ``AMQP_DURABLE`` +``exchange[arguments]`` +``exchange[default_publish_routing_key]`` Routing key to use when publishing, if none is + specified on the message +``exchange[flags]`` Exchange flags ``AMQP_DURABLE`` +``exchange[name]`` Name of the exchange +``exchange[type]`` Type of exchange ``fanout`` +============================================ ================================================= =================================== + +.. versionadded:: 5.2 + + The ``confirm_timeout`` option was introduced in Symfony 5.2. + +.. deprecated:: 5.3 + + The ``prefetch_count`` option was deprecated in Symfony 5.3 because it has + no effect on the AMQP Messenger transport. You can also configure AMQP-specific settings on your message by adding -:class:`Symfony\\Component\\Messenger\\Transport\\AmqpExt\\AmqpStamp` to +:class:`Symfony\\Component\\Messenger\\Bridge\\Amqp\\Transport\\AmqpStamp` to your Envelope:: - use Symfony\Component\Messenger\Transport\AmqpExt\AmqpStamp; + use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpStamp; // ... $attributes = []; @@ -876,114 +1052,38 @@ your Envelope:: Doctrine Transport ~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.3 - - The Doctrine transport was introduced in Symfony 4.3. - The Doctrine transport can be used to store messages in a database table. -.. code-block:: bash - - # .env - MESSENGER_TRANSPORT_DSN=doctrine://default +.. versionadded:: 5.1 -The format is ``doctrine://``, in case you have multiple connections -and want to use one other than the "default". The transport will automatically create -a table named ``messenger_messages`` (this is configurable) when the transport is -first used. You can disable that with the ``auto_setup`` option and set the table -up manually by calling the ``messenger:setup-transports`` command. + Starting from Symfony 5.1, the Doctrine transport has moved to a separate package. + Install it by running: -.. tip:: + .. code-block:: terminal - To avoid tools like Doctrine Migrations from trying to remove this table because - it's not part of your normal schema, you can set the ``schema_filter`` option: + $ composer require symfony/doctrine-messenger - .. configuration-block:: +The Doctrine transport DSN may looks like this: - .. code-block:: yaml +.. code-block:: env - # config/packages/doctrine.yaml - doctrine: - dbal: - schema_filter: '~^(?!messenger_messages)~' + # .env + MESSENGER_TRANSPORT_DSN=doctrine://default - .. code-block:: xml +The format is ``doctrine://``, in case you have multiple connections +and want to use one other than the "default". The transport will automatically create +a table named ``messenger_messages``. - # config/packages/doctrine.xml - +.. versionadded:: 5.1 - .. code-block:: php + The ability to automatically generate a migration for the ``messenger_messages`` + table was introduced in Symfony 5.1 and DoctrineBundle 2.1. - # config/packages/doctrine.php - $container->loadFromExtension('doctrine', [ - 'dbal' => [ - 'schema_filter' => '~^(?!messenger_messages)~', - // ... - ], - // ... - ]); +Or, to create the table yourself, set the ``auto_setup`` option to ``false`` and +:ref:`generate a migration `. The transport has a number of options: -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/messenger.yaml - framework: - messenger: - transports: - async_priority_high: "%env(MESSENGER_TRANSPORT_DSN)%?queue_name=high_priority" - async_normal: - dsn: "%env(MESSENGER_TRANSPORT_DSN)%" - options: - queue_name: normal_priority - - .. code-block:: xml - - - - - - - - - - - - normal_priority - - - - - - - - .. code-block:: php - - // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'async_priority_high' => '%env(MESSENGER_TRANSPORT_DSN)%?queue_name=high_priority', - 'async_priority_low' => [ - 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', - 'options' => [ - 'queue_name' => 'normal_priority' - ] - ], - ], - ], - ]); - -Options defined under ``options`` take precedence over ones defined in the DSN. - ================== ===================================== ====================== Option Description Default ================== ===================================== ====================== @@ -1001,25 +1101,76 @@ auto_setup Whether the table should be created automatically during send / get. true ================== ===================================== ====================== +Beanstalkd Transport +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The Beanstalkd transport was introduced in Symfony 5.2. + +The Beanstalkd transports sends messages directly to a Beanstalkd work queue. Install +it by running: + +.. code-block:: terminal + + $ composer require symfony/beanstalkd-messenger + +The Beanstalkd transport DSN may looks like this: + +.. code-block:: env + + # .env + MESSENGER_TRANSPORT_DSN=beanstalkd://localhost:11300?tube_name=foo&timeout=4&ttr=120 + + # If no port, it will default to 11300 + MESSENGER_TRANSPORT_DSN=beanstalkd://localhost + +The transport has a number of options: + +================== =================================== ====================== + Option Description Default +================== =================================== ====================== +tube_name Name of the queue default +timeout Message reservation timeout 0 (will cause the + - in seconds. server to immediately + return either a + response or a + TransportException + will be thrown) +ttr The message time to run before it + is put back in the ready queue + - in seconds. 90 +================== =================================== ====================== + Redis Transport ~~~~~~~~~~~~~~~ -.. versionadded:: 4.3 +The Redis transport uses `streams`_ to queue messages. This transport requires +the Redis PHP extension (>=4.3) and a running Redis server (^5.0). + +.. versionadded:: 5.1 - The Redis transport was introduced in Symfony 4.3. + Starting from Symfony 5.1, the Redis transport has moved to a separate package. + Install it by running: -The Redis transport uses `streams`_ to queue messages. + .. code-block:: terminal -.. code-block:: bash + $ composer require symfony/redis-messenger + +The Redis transport DSN may looks like this: + +.. code-block:: env # .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&stream_max_entries=0&dbindex=0 + # Unix Socket Example + MESSENGER_TRANSPORT_DSN=redis:///var/run/redis.sock -.. versionadded:: 4.4 +.. versionadded:: 5.1 - The ``dbindex`` query parameter in Redis DSN was introduced in Symfony 4.4. + The Unix socket DSN was introduced in Symfony 5.1. To use the Redis transport, you will need the Redis PHP extension (>=4.3) and a running Redis server (^5.0). @@ -1027,33 +1178,64 @@ 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 -auto_setup Create the Redis group automatically? true -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. +=================== ===================================== ================================= + Option Description Default +=================== ===================================== ================================= +stream The Redis stream name messages +group The Redis consumer group name symfony +consumer Consumer name used in Redis consumer +auto_setup Create the Redis group automatically? true +auth The Redis password +delete_after_ack If ``true``, messages are deleted false + automatically after processing them +delete_after_reject If ``true``, messages are deleted true + automatically if they are rejected +lazy Connect only when a connection is false + really needed +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 +tls Enable TLS support for the connection false +redeliver_timeout Timeout before retrying a pending ``3600`` + message which is owned by an + abandoned consumer (if a worker died + for some reason, this will occur, + eventually you should retry the + message) - in seconds. +claim_interval Interval on which pending/abandoned ``60000`` (1 Minute) + messages should be checked for to + claim - in milliseconds +=================== ===================================== ================================= -In Memory Transport -~~~~~~~~~~~~~~~~~~~ +.. caution:: + + There should never be more than one ``messenger:consume`` command running with the same + config (stream, group and consumer name) to avoid having a message handled more than once. + Using the ``HOSTNAME`` as the consumer might often be a good idea. In case you are using + Kubernetes to orchestrate your containers, consider using a ``StatefulSet``. + +.. tip:: + + Set ``delete_after_ack`` to ``true`` (if you use a single group) or define + ``stream_max_entries`` (if you can estimate how many max entries is acceptable + in your case) to avoid memory leaks. Otherwise, all messages will remain + forever in Redis. + +.. versionadded:: 5.1 -.. versionadded:: 4.3 + The ``delete_after_ack``, ``redeliver_timeout`` and ``claim_interval`` + options were introduced in Symfony 5.1. - The ``in-memory`` transport was introduced in Symfony 4.3. +.. versionadded:: 5.2 + + The ``delete_after_reject`` and ``lazy`` options were introduced in Symfony 5.2. + +In Memory Transport +~~~~~~~~~~~~~~~~~~~ The ``in-memory`` transport does not actually deliver messages. Instead, it holds them in memory during the request, which can be useful for testing. @@ -1106,8 +1288,8 @@ Then, while testing, messages will *not* be delivered to the real transport. Even better, in a test, you can check that exactly one message was sent during a request:: - // tests/DefaultControllerTest.php - namespace App\Tests; + // tests/Controller/DefaultControllerTest.php + namespace App\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Messenger\Transport\InMemoryTransport; @@ -1127,6 +1309,16 @@ during a request:: } } +The transport has a number of options: + +``serialize`` (boolean, default: ``false``) + Whether to serialize messages or not. This is useful to test an additional + layer, especially when you use your own message serializer. + +.. versionadded:: 5.3 + + The ``serialize`` option was introduced in Symfony 5.3. + .. note:: All ``in-memory`` transports will be reset automatically after each test **in** @@ -1134,15 +1326,91 @@ during a request:: :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase` or :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase`. -Serializing Messages -~~~~~~~~~~~~~~~~~~~~ +Amazon SQS +~~~~~~~~~~ + +.. versionadded:: 5.1 + + The Amazon SQS transport was introduced in Symfony 5.1. -.. versionadded:: 4.3 +The Amazon SQS transport is perfect for application hosted on AWS. Install it by +running: - The default serializer changed in 4.3 from the Symfony serializer to the - native PHP serializer. Existing applications should configure their transports - to use the Symfony serializer to avoid losing already-queued messages after - upgrading. +.. code-block:: terminal + + $ composer require symfony/amazon-sqs-messenger + +The SQS transport DSN may looks like this: + +.. code-block:: env + + # .env + MESSENGER_TRANSPORT_DSN=https://AKIAIOSFODNN7EXAMPLE:j17M97ffSVoKI0briFoo9a@sqs.eu-west-3.amazonaws.com/123456789012/messages + MESSENGER_TRANSPORT_DSN=sqs://localhost:9494/messages?sslmode=disable + +.. note:: + + The transport will automatically create queues that are needed. This + can be disabled setting the ``auto_setup`` option to ``false``. + +.. tip:: + + Before sending or receiving a message, Symfony needs to convert the queue + name into an AWS queue URL by calling the ``GetQueueUrl`` API in AWS. This + extra API call can be avoided by providing a DSN which is the queue URL. + + .. versionadded:: 5.2 + + The feature to provide the queue URL in the DSN was introduced in Symfony 5.2. + +The transport has a number of options: + +====================== ====================================== =================================== + Option Description Default +====================== ====================================== =================================== +``access_key`` AWS access key +``account`` Identifier of the AWS account The owner of the credentials +``auto_setup`` Whether the queue should be created ``true`` + automatically during send / get. +``buffer_size`` Number of messages to prefetch 9 +``debug`` If ``true`` it logs all HTTP requests ``false`` + and responses (it impacts performance) +``endpoint`` Absolute URL to the SQS service https://sqs.eu-west-1.amazonaws.com +``poll_timeout`` Wait for new message duration in 0.1 + seconds +``queue_name`` Name of the queue messages +``region`` Name of the AWS region eu-west-1 +``secret_key`` AWS secret key +``visibility_timeout`` Amount of seconds the message will Queue's configuration + not be visible (`Visibility Timeout`_) +``wait_time`` `Long polling`_ duration in seconds 20 +====================== ====================================== =================================== + +.. versionadded:: 5.3 + + The ``debug`` option was introduced in Symfony 5.3. + +.. note:: + + The ``wait_time`` parameter defines the maximum duration Amazon SQS should + wait until a message is available in a queue before sending a response. + It helps reducing the cost of using Amazon SQS by eliminating the number + of empty responses. + + The ``poll_timeout`` parameter defines the duration the receiver should wait + before returning null. It avoids blocking other receivers from being called. + +.. note:: + + If the queue name is suffixed by ``.fifo``, AWS will create a `FIFO queue`_. + Use the stamp :class:`Symfony\\Component\\Messenger\\Bridge\\AmazonSqs\\Transport\\AmazonSqsFifoStamp` + to define the ``Message group ID`` and the ``Message deduplication ID``. + + FIFO queues don't support setting a delay per message, a value of ``delay: 0`` + is required in the retry strategy settings. + +Serializing Messages +~~~~~~~~~~~~~~~~~~~~ When messages are sent to (and received from) a transport, they're serialized using PHP's native ``serialize()`` & ``unserialize()`` functions. You can change @@ -1284,7 +1552,6 @@ by tagging the handler service with ``messenger.message_handler`` 'handles' => SmsNotification::class, ]); - Possible options to configure with tags are: * ``bus`` @@ -1293,10 +1560,6 @@ Possible options to configure with tags are: * ``method`` * ``priority`` -.. versionadded:: 4.4 - - The ability to specify ``from_transport`` on the tag, was added in Symfony 4.4. - Handler Subscriber & Options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1598,7 +1861,6 @@ middleware and *only* include your own: ], ]); - .. note:: If a middleware service is abstract, a different instance of the service will @@ -1727,3 +1989,6 @@ Learn more .. _`streams`: https://redis.io/topics/streams-intro .. _`Supervisor docs`: http://supervisord.org/ .. _`SymfonyCasts' message serializer tutorial`: https://symfonycasts.com/screencast/messenger/transport-serializer +.. _`Long polling`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html +.. _`Visibility Timeout`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html +.. _`FIFO queue`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html diff --git a/messenger/custom-transport.rst b/messenger/custom-transport.rst index e0fbcb3ca23..be41d63a41e 100644 --- a/messenger/custom-transport.rst +++ b/messenger/custom-transport.rst @@ -35,12 +35,12 @@ The transport object needs to implement the and :class:`Symfony\\Component\\Messenger\\Transport\\Receiver\\ReceiverInterface`). Here is a simplified example of a database transport:: - use Ramsey\Uuid\Uuid; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Messenger\Transport\TransportInterface; + use Symfony\Component\Uid\Uuid; class YourTransport implements TransportInterface { @@ -108,8 +108,7 @@ Here is a simplified example of a database transport:: public function send(Envelope $envelope): Envelope { $encodedMessage = $this->serializer->encode($envelope); - $uuid = Uuid::uuid4()->toString(); - + $uuid = (string) Uuid::v4(); // Add a message to the "my_queue" table $this->db->createQuery( 'INSERT INTO my_queue (id, envelope, delivered_at, handled) diff --git a/messenger/multiple_buses.rst b/messenger/multiple_buses.rst index c79aeb7b2b7..8a6d59b91df 100644 --- a/messenger/multiple_buses.rst +++ b/messenger/multiple_buses.rst @@ -262,4 +262,9 @@ You can also restrict the list to a specific bus by providing its name as argume handled by App\MessageHandler\MultipleBusesMessageHandler --------------------------------------------------------------------------------------- +.. tip:: + + Since Symfony 5.1, the command will also show the PHPDoc description of + the message and handler classes. + .. _article about CQRS: https://martinfowler.com/bliki/CQRS.html diff --git a/migration.rst b/migration.rst index 8c6117de25c..80699e6a93f 100644 --- a/migration.rst +++ b/migration.rst @@ -238,10 +238,13 @@ could look something like this:: // public/index.php use App\Kernel; use App\LegacyBridge; - use Symfony\Component\Debug\Debug; + use Symfony\Component\Dotenv\Dotenv; + use Symfony\Component\ErrorHandler\Debug; use Symfony\Component\HttpFoundation\Request; - require dirname(__DIR__).'/config/bootstrap.php'; + require dirname(__DIR__).'/vendor/autoload.php'; + + (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); /* * The kernel will always be available globally, allowing you to @@ -260,7 +263,7 @@ could look something like this:: if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) { Request::setTrustedProxies( explode(',', $trustedProxies), - Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST + Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO ); } diff --git a/notifier.rst b/notifier.rst new file mode 100644 index 00000000000..0e55317c31f --- /dev/null +++ b/notifier.rst @@ -0,0 +1,654 @@ +.. index:: + single: Notifier + +Creating and Sending Notifications +================================== + +.. versionadded:: 5.0 + + The Notifier component was introduced in Symfony 5.0. + +Installation +------------ + +Current web applications use many different channels to send messages to +the users (e.g. SMS, Slack messages, emails, push notifications, etc.). The +Notifier component in Symfony is an abstraction on top of all these +channels. It provides a dynamic way to manage how the messages are sent. +Get the Notifier installed using: + +.. code-block:: terminal + + $ composer require symfony/notifier + +Channels: Chatters, Texters, Email and Browser +---------------------------------------------- + +The notifier component can send notifications to different channels. Each +channel can integrate with different providers (e.g. Slack or Twilio SMS) +by using transports. + +The notifier component supports the following channels: + +* :ref:`SMS channel ` sends notifications to phones via + SMS messages; +* :ref:`Chat channel ` sends notifications to chat + services like Slack and Telegram; +* :ref:`Email channel ` integrates the :doc:`Symfony Mailer `; +* Browser channel uses :ref:`flash messages `. + +.. tip:: + + Use :doc:`secrets ` to securily store your + API's tokens. + +.. _notifier-sms-channel: +.. _notifier-texter-dsn: + +SMS Channel +~~~~~~~~~~~ + +The SMS channel uses :class:`Symfony\\Component\\Notifier\\Texter` classes +to send SMS messages to mobile phones. This feature requires subscribing to +a third-party service that sends SMS messages. Symfony provides integration +with a couple popular SMS services: + +========== ================================ ==================================================== +Service Package DSN +========== ================================ ==================================================== +AllMySms ``symfony/allmysms-notifier`` ``allmysms://LOGIN:APIKEY@default?from=FROM`` +Clickatell ``symfony/clickatell-notifier`` ``clickatell://ACCESS_TOKEN@default?from=FROM`` +Esendex ``symfony/esendex-notifier`` ``esendex://USER_NAME:PASSWORD@default?accountreference=ACCOUNT_REFERENCE&from=FROM`` +FreeMobile ``symfony/free-mobile-notifier`` ``freemobile://LOGIN:PASSWORD@default?phone=PHONE`` +GatewayApi ``symfony/gatewayapi-notifier`` ``gatewayapi://TOKEN@default?from=FROM`` +Infobip ``symfony/infobip-notifier`` ``infobip://AUTH_TOKEN@HOST?from=FROM`` +Iqsms ``symfony/iqsms-notifier`` ``iqsms://LOGIN:PASSWORD@default?from=FROM`` +Mobyt ``symfony/mobyt-notifier`` ``mobyt://USER_KEY:ACCESS_TOKEN@default?from=FROM`` +Nexmo ``symfony/nexmo-notifier`` ``nexmo://KEY:SECRET@default?from=FROM`` +Octopush ``symfony/octopush-notifier`` ``octopush://USERLOGIN:APIKEY@default?from=FROM&type=TYPE`` +OvhCloud ``symfony/ovh-cloud-notifier`` ``ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME`` +Sendinblue ``symfony/sendinblue-notifier`` ``sendinblue://API_KEY@default?sender=PHONE`` +Sinch ``symfony/sinch-notifier`` ``sinch://ACCOUNT_ID:AUTH_TOKEN@default?from=FROM`` +Smsapi ``symfony/smsapi-notifier`` ``smsapi://TOKEN@default?from=FROM`` +SpotHit ``symfony/spothit-notifier`` ``spothit://TOKEN@default?from=FROM`` +Twilio ``symfony/twilio-notifier`` ``twilio://SID:TOKEN@default?from=FROM`` +========== ================================ ==================================================== + +.. versionadded:: 5.1 + + The OvhCloud, Sinch and FreeMobile integrations were introduced in Symfony 5.1. + +.. versionadded:: 5.2 + + The Smsapi, Infobip, Mobyt, Esendex and Sendinblue integrations were introduced in Symfony 5.2. + +.. versionadded:: 5.3 + + The Iqsms, GatewayApi, Octopush, AllMySms, Clickatell and SpotHit integrations + were introduced in Symfony 5.3. + + +To enable a texter, add the correct DSN in your ``.env`` file and +configure the ``texter_transports``: + +.. code-block:: bash + + # .env + TWILIO_DSN=twilio://SID:TOKEN@default?from=FROM + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/notifier.yaml + framework: + notifier: + texter_transports: + twilio: '%env(TWILIO_DSN)%' + + .. code-block:: xml + + + + + + + + + %env(TWILIO_DSN)% + + + + + + .. code-block:: php + + # config/packages/notifier.php + $container->loadFromExtension('framework', [ + 'notifier' => [ + 'texter_transports' => [ + 'twilio' => '%env(TWILIO_DSN)%', + ], + ], + ]); + +.. _notifier-chat-channel: +.. _notifier-chatter-dsn: + +Chat Channel +~~~~~~~~~~~~ + +The chat channel is used to send chat messages to users by using +:class:`Symfony\\Component\\Notifier\\Chatter` classes. Symfony provides +integration with these chat services: + +========== ================================ =========================================================================== +Service Package DSN +========== ================================ =========================================================================== +Discord ``symfony/discord-notifier`` ``discord://TOKEN@default?webhook_id=ID`` +Firebase ``symfony/firebase-notifier`` ``firebase://USERNAME:PASSWORD@default`` +Gitter ``symfony/gitter-notifier`` ``GITTER_DSN=gitter://TOKEN@default?room_id=ROOM_ID`` +GoogleChat ``symfony/google-chat-notifier`` ``googlechat://ACCESS_KEY:ACCESS_TOKEN@default/SPACE?thread_key=THREAD_KEY`` +LinkedIn ``symfony/linked-in-notifier`` ``linkedin://TOKEN:USER_ID@default`` +Mattermost ``symfony/mattermost-notifier`` ``mattermost://ACCESS_TOKEN@HOST/PATH?channel=CHANNEL`` +Mercure ``symfony/mercure-notifier`` ``mercure://PUBLISHER_SERVICE_ID?topic=TOPIC`` +RocketChat ``symfony/rocket-chat-notifier`` ``rocketchat://TOKEN@ENDPOINT?channel=CHANNEL`` +Slack ``symfony/slack-notifier`` ``slack://TOKEN@default?channel=CHANNEL`` +Telegram ``symfony/telegram-notifier`` ``telegram://TOKEN@default?channel=CHAT_ID`` +Zulip ``symfony/zulip-notifier`` ``zulip://EMAIL:TOKEN@HOST?channel=CHANNEL`` +========== ================================ =========================================================================== + +.. versionadded:: 5.1 + + The Firebase, Mattermost and RocketChat integrations were introduced in Symfony + 5.1. The Slack DSN changed in Symfony 5.1 to use Slack Incoming + Webhooks instead of legacy tokens. + +.. versionadded:: 5.2 + + The GoogleChat, LinkedIn, Zulip and Discord integrations were introduced in Symfony 5.2. + The Slack DSN changed in Symfony 5.2 to use Slack Web API again same as in 5.0. + +.. versionadded:: 5.3 + + The Gitter and Mercure integrations were introduced in Symfony 5.3. + +Chatters are configured using the ``chatter_transports`` setting: + +.. code-block:: bash + + # .env + SLACK_DSN=slack://TOKEN@default?channel=CHANNEL + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/notifier.yaml + framework: + notifier: + chatter_transports: + slack: '%env(SLACK_DSN)%' + + .. code-block:: xml + + + + + + + + + %env(SLACK_DSN)% + + + + + + .. code-block:: php + + # config/packages/notifier.php + $container->loadFromExtension('framework', [ + 'notifier' => [ + 'chatter_transports' => [ + 'slack' => '%env(SLACK_DSN)%', + ], + ], + ]); + +.. _notifier-email-channel: + +Email Channel +~~~~~~~~~~~~~ + +The email channel uses the :doc:`Symfony Mailer ` to send +notifications using the special +:class:`Symfony\\Bridge\\Twig\\Mime\\NotificationEmail`. It is +required to install the Twig bridge along with the Inky and CSS Inliner +Twig extensions: + +.. code-block:: terminal + + $ composer require symfony/twig-pack twig/cssinliner-extra twig/inky-extra + +After this, :ref:`configure the mailer `. You can +also set the default "from" email address that should be used to send the +notification emails: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/mailer.yaml + framework: + mailer: + dsn: '%env(MAILER_DSN)%' + envelope: + sender: 'notifications@example.com' + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + # config/packages/mailer.php + $container->loadFromExtension('framework', [ + 'mailer' => [ + 'dsn' => '%env(MAILER_DSN)%', + 'envelope' => [ + 'sender' => 'notifications@example.com', + ], + ], + ]); + +Configure to use Failover or Round-Robin Transports +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Besides configuring one or more separate transports, you can also use the +special ``||`` and ``&&`` characters to implement a failover or round-robin +transport: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/notifier.yaml + framework: + notifier: + chatter_transports: + # Send notifications to Slack and use Telegram if + # Slack errored + main: '%env(SLACK_DSN)% || %env(TELEGRAM_DSN)%' + + # Send notifications to the next scheduled transport calculated by round robin + roundrobin: '%env(SLACK_DSN)% && %env(TELEGRAM_DSN)%' + + .. code-block:: xml + + + + + + + + + + %env(SLACK_DSN)% || %env(TELEGRAM_DSN)% + + + + + + + + + .. code-block:: php + + # config/packages/notifier.php + $container->loadFromExtension('framework', [ + 'notifier' => [ + 'chatter_transports' => [ + // Send notifications to Slack and use Telegram if + // Slack errored + 'main' => '%env(SLACK_DSN)% || %env(TELEGRAM_DSN)%', + + // Send notifications to the next scheduled transport calculated by round robin + 'roundrobin' => '%env(SLACK_DSN)% && %env(TELEGRAM_DSN)%', + ], + ], + ]); + +Creating & Sending Notifications +-------------------------------- + +To send a notification, autowire the +:class:`Symfony\\Component\\Notifier\\NotifierInterface` (service ID +``notifier``). This class has a ``send()`` method that allows you to send a +:class:`Symfony\\Component\\Notifier\\Notification\\Notification` to a +:class:`Symfony\\Component\\Notifier\\Recipient\\Recipient`:: + + // src/Controller/InvoiceController.php + namespace App\Controller; + + use Symfony\Component\Notifier\Notification\Notification; + use Symfony\Component\Notifier\NotifierInterface; + use Symfony\Component\Notifier\Recipient\Recipient; + + class InvoiceController extends AbstractController + { + /** + * @Route("/invoice/create") + */ + public function create(NotifierInterface $notifier) + { + // ... + + // Create a Notification that has to be sent + // using the "email" channel + $notification = (new Notification('New Invoice', ['email'])) + ->content('You got a new invoice for 15 EUR.'); + + // The receiver of the Notification + $recipient = new Recipient( + $user->getEmail(), + $user->getPhonenumber() + ); + + // Send the notification to the recipient + $sentMessage = $notifier->send($notification, $recipient); + + // ... + } + } + +The ``Notification`` is created by using two arguments: the subject and +channels. The channels specify which channel (or transport) should be used +to send the notification. For instance, ``['email', 'sms']`` will send +both an email and sms notification to the user. + +The ``send()`` method used to send the notification returns a variable of type +:class:`Symfony\\Component\\Notifier\\Message\\SentMessage` which provides +information such as the message ID and the original message contents. + +.. versionadded:: 5.2 + + The ``SentMessage`` class was introduced in Symfony 5.2. + +The default notification also has a ``content()`` and ``emoji()`` method to +set the notification content and icon. + +Symfony provides the following recipients: + +:class:`Symfony\\Component\\Notifier\\Recipient\\NoRecipient` + This is the default and is useful when there is no need to have + information about the receiver. For example, the browser channel uses + the current requests's :ref:`session flashbag `; + +:class:`Symfony\\Component\\Notifier\\Recipient\\Recipient` + This can contain both email address and phonenumber of the user. This + recipient can be used for all channels (depending on whether they are + actually set). + +.. versionadded:: 5.2 + + The ``AdminRecipient`` class was removed in Symfony 5.2, you should use + ``Recipient`` instead. + +Configuring Channel Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Instead of specifying the target channels on creation, Symfony also allows +you to use notification importance levels. Update the configuration to +specify what channels should be used for specific levels (using +``channel_policy``): + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/notifier.yaml + framework: + notifier: + # ... + channel_policy: + # Use SMS, Slack and email for urgent notifications + urgent: ['sms', 'chat/slack', 'email'] + + # Use Slack for highly important notifications + high: ['chat/slack'] + + # Use browser for medium and low notifications + medium: ['browser'] + low: ['browser'] + + .. code-block:: xml + + + + + + + + + + + + sms + chat/slack + email + + + chat/slack + + + browser + browser + + + + + + .. code-block:: php + + # config/packages/notifier.php + $container->loadFromExtension('framework', [ + 'notifier' => [ + // ... + 'channel_policy' => [ + // Use SMS, Slack and email for urgent notifications + 'urgent' => ['sms', 'chat/slack', 'email'], + + // Use Slack for highly important notifications + 'high' => ['chat/slack'], + + // Use browser for medium and low notifications + 'medium' => ['browser'], + 'low' => ['browser'], + ], + ], + ]); + +Now, whenever the notification's importance is set to "high", it will be +sent using the Slack transport:: + + // ... + class InvoiceController extends AbstractController + { + /** + * @Route("/invoice/create") + */ + public function invoice(NotifierInterface $notifier) + { + // ... + + $notification = (new Notification('New Invoice')) + ->content('You got a new invoice for 15 EUR.') + ->importance(Notification::IMPORTANCE_HIGH); + + $notifier->send($notification, new Recipient('wouter@wouterj.nl')); + + // ... + } + } + +Customize Notifications +----------------------- + +You can extend the ``Notification`` or ``Recipient`` base classes to +customize their behavior. For instance, you can overwrite the +``getChannels()`` method to only return ``sms`` if the invoice price is +very high and the recipient has a phone number:: + + namespace App\Notifier; + + use Symfony\Component\Notifier\Notification\Notification; + use Symfony\Component\Notifier\Recipient\RecipientInterface; + use Symfony\Component\Notifier\Recipient\SmsRecipientInterface; + + class InvoiceNotification extends Notification + { + private $price; + + public function __construct(int $price) + { + $this->price = $price; + } + + public function getChannels(RecipientInterface $recipient) + { + if ( + $this->price > 10000 + && $recipient instanceof SmsRecipientInterface + ) { + return ['sms']; + } + + return ['email']; + } + } + +Customize Notification Messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each channel has its own notification interface that you can implement to +customize the notification message. For instance, if you want to modify the +message based on the chat service, implement +:class:`Symfony\\Component\\Notifier\\Notification\\ChatNotificationInterface` +and its ``asChatMessage()`` method:: + + // src/Notifier/InvoiceNotification.php + namespace App\Notifier; + + use Symfony\Component\Notifier\Message\ChatMessage; + use Symfony\Component\Notifier\Notification\ChatNotificationInterface; + use Symfony\Component\Notifier\Notification\Notification; + use Symfony\Component\Notifier\Recipient\SmsRecipientInterface; + + class InvoiceNotification extends Notification implements ChatNotificationInterface + { + private $price; + + public function __construct(int $price) + { + $this->price = $price; + } + + public function asChatMessage(RecipientInterface $recipient, string $transport = null): ?ChatMessage + { + // Add a custom emoji if the message is sent to Slack + if ('slack' === $transport) { + return (new ChatMessage('You\'re invoiced '.$this->price.' EUR.')) + ->emoji('money'); + } + + // If you return null, the Notifier will create the ChatMessage + // based on this notification as it would without this method. + return null; + } + } + +The +:class:`Symfony\\Component\\Notifier\\Notification\\SmsNotificationInterface` +and +:class:`Symfony\\Component\\Notifier\\Notification\\EmailNotificationInterface` +also exists to modify messages send to those channels. + +Disabling Delivery +------------------ + +While developing (or testing), you may want to disable delivery of notifications +entirely. You can do this by forcing Notifier to use the ``NullTransport`` for +all configured texter and chatter transports only in the ``dev`` (and/or +``test``) environment: + +.. code-block:: yaml + + # config/packages/dev/notifier.yaml + framework: + notifier: + texter_transports: + twilio: 'null://null' + chatter_transports: + slack: 'null://null' + +.. TODO + - Using the message bus for asynchronous notification + - Describe notifier monolog handler + - Describe notification_on_failed_messages integration + +Learn more +---------- + +.. toctree:: + :maxdepth: 1 + :glob: + + notifier/* diff --git a/notifier/chatters.rst b/notifier/chatters.rst new file mode 100644 index 00000000000..ffeb6e0dc5e --- /dev/null +++ b/notifier/chatters.rst @@ -0,0 +1,185 @@ +.. index:: + single: Notifier; Chatters + +How to send Chat Messages +========================= + +.. versionadded:: 5.0 + + The Notifier component was introduced in Symfony 5.0. + +The :class:`Symfony\\Component\\Notifier\\ChatterInterface` class allows +you to send messages to chat services like Slack or Telegram:: + + // src/Controller/CheckoutController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Notifier\ChatterInterface; + use Symfony\Component\Notifier\Message\ChatMessage; + use Symfony\Component\Routing\Annotation\Route; + + class CheckoutController extends AbstractController + { + /** + * @Route("/checkout/thankyou") + */ + public function thankyou(ChatterInterface $chatter) + { + $message = (new ChatMessage('You got a new invoice for 15 EUR.')) + // if not set explicitly, the message is send to the + // default transport (the first one configured) + ->transport('slack'); + + $sentMessage = $chatter->send($message); + + // ... + } + } + +The ``send()`` method returns a variable of type +:class:`Symfony\\Component\\Notifier\\Message\\SentMessage` which provides +information such as the message ID and the original message contents. + +.. versionadded:: 5.2 + + The ``SentMessage`` class was introduced in Symfony 5.2. + +.. seealso:: + + Read :ref:`the main Notifier guide ` to see how + to configure the different transports. + +Adding Interactions to a Slack Message +-------------------------------------- + +With a Slack message, you can use the +:class:`Symfony\\Component\\Notifier\\Bridge\\Slack\\SlackOptions` class +to add some interactive options called `Block elements`_:: + + use Symfony\Component\Notifier\Bridge\Slack\Block\SlackActionsBlock; + use Symfony\Component\Notifier\Bridge\Slack\Block\SlackDividerBlock; + use Symfony\Component\Notifier\Bridge\Slack\Block\SlackImageBlock; + use Symfony\Component\Notifier\Bridge\Slack\Block\SlackSectionBlock; + use Symfony\Component\Notifier\Bridge\Slack\SlackOptions; + use Symfony\Component\Notifier\Message\ChatMessage; + + $chatMessage = new ChatMessage('Contribute To Symfony'); + + // Create Slack Actions Block and add some buttons + $contributeToSymfonyBlocks = (new SlackActionsBlock()) + ->button( + 'Improve Documentation', + 'https://symfony.com/doc/current/contributing/documentation/standards.html', + 'primary' + ) + ->button( + 'Report bugs', + 'https://symfony.com/doc/current/contributing/code/bugs.html', + 'danger' + ); + + $slackOptions = (new SlackOptions()) + ->block((new SlackSectionBlock()) + ->text('The Symfony Community') + ->accessory( + new SlackImageBlockElement( + 'https://symfony.com/favicons/apple-touch-icon.png', + 'Symfony' + ) + ) + ) + ->block(new SlackDividerBlock()) + ->block($contributeToSymfonyBlocks); + + // Add the custom options to the chat message and send the message + $chatMessage->options($slackOptions); + + $chatter->send($chatMessage); + +Adding Interactions to a Discord Message +---------------------------------------- + +With a Discord message, you can use the +:class:`Symfony\\Component\\Notifier\\Bridge\\Discord\\DiscordOptions` class +to add some interactive options called `Embed elements`_:: + + use Symfony\Component\Notifier\Bridge\Discord\DiscordOptions; + use Symfony\Component\Notifier\Bridge\Discord\Embeds\DiscordEmbed; + use Symfony\Component\Notifier\Bridge\Discord\Embeds\DiscordFieldEmbedObject; + use Symfony\Component\Notifier\Bridge\Discord\Embeds\DiscordFooterEmbedObject; + use Symfony\Component\Notifier\Bridge\Discord\Embeds\DiscordMediaEmbedObject; + use Symfony\Component\Notifier\Message\ChatMessage; + + $chatMessage = new ChatMessage(''); + + // Create Discord Embed + $discordOptions = (new DiscordOptions()) + ->username('connor bot') + ->addEmbed((new DiscordEmbed()) + ->color(2021216) + ->title('New song added!') + ->thumbnail((new DiscordMediaEmbedObject()) + ->url('https://i.scdn.co/image/ab67616d0000b2735eb27502aa5cb1b4c9db426b')) + ->addField((new DiscordFieldEmbedObject()) + ->name('Track') + ->value('[Common Ground](https://open.spotify.com/track/36TYfGWUhIRlVjM8TxGUK6)') + ->inline(true) + ) + ->addField((new DiscordFieldEmbedObject()) + ->name('Artist') + ->value('Alasdair Fraser') + ->inline(true) + ) + ->addField((new DiscordFieldEmbedObject()) + ->name('Album') + ->value('Dawn Dance') + ->inline(true) + ) + ->footer((new DiscordFooterEmbedObject()) + ->text('Added ...') + ->iconUrl('https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/200px-Spotify_logo_without_text.svg.png') + ) + ) + ; + + // Add the custom options to the chat message and send the message + $chatMessage->options($discordOptions); + + $chatter->send($chatMessage); + +Adding Interactions to a Telegram Message +----------------------------------------- + +With a Telegram message, you can use the +:class:`Symfony\\Component\\Notifier\\Bridge\\Telegram\\TelegramOptions` class +to add `message options`_:: + + use Symfony\Component\Notifier\Bridge\Telegram\Reply\Markup\Button\InlineKeyboardButton; + use Symfony\Component\Notifier\Bridge\Telegram\Reply\Markup\InlineKeyboardMarkup; + use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions; + use Symfony\Component\Notifier\Message\ChatMessage; + + $chatMessage = new ChatMessage(''); + + // Create Telegram options + $telegramOptions = (new TelegramOptions()) + ->chatId('@symfonynotifierdev') + ->parseMode('MarkdownV2') + ->disableWebPagePreview(true) + ->disableNotification(true) + ->replyMarkup((new InlineKeyboardMarkup()) + ->inlineKeyboard([ + (new InlineKeyboardButton('Visit symfony.com')) + ->url('https://symfony.com/'), + ]) + ); + + // Add the custom options to the chat message and send the message + $chatMessage->options($telegramOptions); + + $chatter->send($chatMessage); + +.. _`Block elements`: https://api.slack.com/reference/block-kit/block-elements +.. _`Embed elements`: https://discord.com/developers/docs/resources/webhook +.. _`message options`: https://core.telegram.org/bots/api diff --git a/notifier/texters.rst b/notifier/texters.rst new file mode 100644 index 00000000000..4cf9b6f2de2 --- /dev/null +++ b/notifier/texters.rst @@ -0,0 +1,52 @@ +.. index:: + single: Notifier; Texters + +How to send SMS Messages +======================== + +.. versionadded:: 5.0 + + The Notifier component was introduced in Symfony 5.0. + +The :class:`Symfony\\Component\\Notifier\\TexterInterface` class allows +you to send SMS messages:: + + // src/Controller/SecurityController.php + namespace App\Controller; + + use Symfony\Component\Notifier\Message\SmsMessage; + use Symfony\Component\Notifier\TexterInterface; + use Symfony\Component\Routing\Annotation\Route; + + class SecurityController + { + /** + * @Route("/login/success") + */ + public function loginSuccess(TexterInterface $texter) + { + $sms = new SmsMessage( + // the phone number to send the SMS message to + '+1411111111', + // the message + 'A new login was detected!' + ); + + $sentMessage = $texter->send($sms); + + // ... + } + } + +The ``send()`` method returns a variable of type +:class:`Symfony\\Component\\Notifier\\Message\\SentMessage` which provides +information such as the message ID and the original message contents. + +.. versionadded:: 5.2 + + The ``SentMessage`` class was introduced in Symfony 5.2. + +.. seealso:: + + Read :ref:`the main Notifier guide ` to see how + to configure the different transports. diff --git a/page_creation.rst b/page_creation.rst index 90096beb4d4..0d7ff3e910b 100644 --- a/page_creation.rst +++ b/page_creation.rst @@ -52,7 +52,7 @@ random) number and prints it. To do that, create a "Controller" class and a class LuckyController { - public function number() + public function number(): Response { $number = random_int(0, 100); diff --git a/performance.rst b/performance.rst index afd5295a9e3..2541676ae03 100644 --- a/performance.rst +++ b/performance.rst @@ -17,6 +17,7 @@ for maximum performance: * **Symfony Application Checklist**: #. :ref:`Install APCu Polyfill if your server uses APC ` + #. :ref:`Restrict the number of locales enabled in the application ` * **Production Server Checklist**: @@ -37,16 +38,19 @@ OPcache, install the `APCu Polyfill component`_ in your application to enable compatibility with `APCu PHP functions`_ and unlock support for advanced Symfony features, such as the APCu Cache adapter. +.. _performance-enabled-locales: + +Restrict the Number of Locales Enabled in the Application +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the :ref:`framework.translator.enabled_locales ` +option to only generate the translation files actually used in your application. + .. _performance-service-container-single-file: Dump the Service Container into a Single File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.4 - - The ``container.dumper.inline_factories`` parameter was introduced in - Symfony 4.4. - Symfony compiles the :doc:`service container ` into multiple small files by default. Set this parameter to ``true`` to compile the entire container into a single file, which could improve performance when using @@ -97,10 +101,6 @@ used byte code cache is `APC`_. Use the OPcache class preloading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.4 - - The feature that generates the preloading file was introduced in Symfony 4.4. - Starting from PHP 7.4, OPcache can compile and load classes at start-up and make them available to all requests until the server is restarted, improving performance significantly. @@ -116,6 +116,10 @@ You can configure PHP to use this preload file: ; php.ini opcache.preload=/path/to/project/config/preload.php +Use the :ref:`container.preload ` and +:ref:`container.no_preload ` service tags to define +which classes should or should not be preloaded by PHP. + .. _performance-configure-opcache: Configure OPcache for Maximum Performance diff --git a/profiler/data_collector.rst b/profiler/data_collector.rst index 292047b5dc7..6e53fd5203d 100644 --- a/profiler/data_collector.rst +++ b/profiler/data_collector.rst @@ -14,9 +14,13 @@ Creating a custom Data Collector A data collector is a PHP class that implements the :class:`Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface`. For convenience, your data collectors can also extend from the -:class:`Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector` class, which -implements the interface and provides some utilities and the ``$this->data`` -property to store the collected information. +:class:`Symfony\\Bundle\\FrameworkBundle\\DataCollector\\AbstractDataCollector` +class, which implements the interface and provides some utilities and the +``$this->data`` property to store the collected information. + +.. versionadded:: 5.2 + + The ``AbstractDataCollector`` class was introduced in Symfony 5.2. The following example shows a custom collector that stores information about the request:: @@ -24,11 +28,12 @@ request:: // src/DataCollector/RequestCollector.php namespace App\DataCollector; + use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; - class RequestCollector extends DataCollector + class RequestCollector extends AbstractDataCollector { public function collect(Request $request, Response $response, \Throwable $exception = null) { @@ -37,25 +42,14 @@ request:: 'acceptable_content_types' => $request->getAcceptableContentTypes(), ]; } - - public function reset() - { - $this->data = []; - } - - public function getName() - { - return 'app.request_collector'; - } - - // ... } +These are the method that you can define in the data collector class: + :method:`Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface::collect` method: Stores the collected data in local properties (``$this->data`` if you extend - from :class:`Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector`). - If the data to collect cannot be obtained through the request or response, - inject the needed services in the data collector. + from ``AbstractDataCollector``). If you need some services to collect the + data, inject those services in the data collector constructor. .. caution:: @@ -70,14 +64,16 @@ request:: to provide your own ``serialize()`` method. :method:`Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface::reset` method: - It's called between requests to reset the state of the profiler. Use it to - remove all the information collected with the ``collect()`` method. + It's called between requests to reset the state of the profiler. By default + it only empties the ``$this->data`` contents, but you can override this method + to do additional cleaning. :method:`Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface::getName` method: Returns the collector identifier, which must be unique in the application. + By default it returns the FQCN of the data collector class, but you can + override this method to return a custom name (e.g. ``app.request_collector``). This value is used later to access the collector information (see - :doc:`/testing/profiling`) so it's recommended to return a string which is - short, lowercase and without white spaces. + :doc:`/testing/profiling`) so you may prefer using short strings instead of FQCN strings. The ``collect()`` method is called during the :ref:`kernel.response ` event. If you need to collect data that is only available later, implement @@ -85,17 +81,11 @@ event. If you need to collect data that is only available later, implement and define the ``lateCollect()`` method, which is invoked right before the profiler data serialization (during :ref:`kernel.terminate ` event). -.. _data_collector_tag: - -Enabling Custom Data Collectors -------------------------------- +.. note:: -If you're using the :ref:`default services.yaml configuration ` -with ``autoconfigure``, then Symfony will automatically see your new data collector! -Your ``collect()`` method should be called next time your refresh. - -If you're not using ``autoconfigure``, you can also :ref:`manually wire your service ` -and :doc:`tag ` it with ``data_collector``. + If you're using the :ref:`default services.yaml configuration ` + with ``autoconfigure``, then Symfony will start using your data collector after the + next page refresh. Otherwise, :ref:`enable the data collector by hand `. Adding Web Profiler Templates ----------------------------- @@ -104,18 +94,24 @@ The information collected by your data collector can be displayed both in the web debug toolbar and in the web profiler. To do so, you need to create a Twig template that includes some specific blocks. -However, first you must add some getters in the data collector class to give the +First, add the ``getTemplate()`` method in your data collector class to return +the path of the Twig template to use. Then, add some *getters* to give the template access to the collected information:: // src/DataCollector/RequestCollector.php namespace App\DataCollector; - use Symfony\Component\HttpKernel\DataCollector\DataCollector; + use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector; - class RequestCollector extends DataCollector + class RequestCollector extends AbstractDataCollector { // ... + public static function getTemplate(): ?string + { + return 'data_collector/template.html.twig'; + } + public function getMethod() { return $this->data['method']; @@ -133,6 +129,7 @@ block and set the value of two variables called ``icon`` and ``text``: .. code-block:: html+twig + {# templates/data_collector/template.html.twig #} {% extends '@WebProfiler/Profiler/layout.html.twig' %} {% block toolbar %} @@ -178,6 +175,7 @@ must also define additional blocks: .. code-block:: html+twig + {# templates/data_collector/template.html.twig #} {% extends '@WebProfiler/Profiler/layout.html.twig' %} {% block toolbar %} @@ -227,8 +225,25 @@ The ``menu`` and ``panel`` blocks are the only required blocks to define the contents displayed in the web profiler panel associated with this data collector. All blocks have access to the ``collector`` object. -Finally, to enable the data collector template, override your service configuration -to specify a tag that contains the template: +.. note:: + + The position of each panel in the toolbar is determined by the collector + priority, which can only be defined when :ref:`configuring the data collector by hand `. + +.. note:: + + If you're using the :ref:`default services.yaml configuration ` + with ``autoconfigure``, then Symfony will start displaying your collector data + in the toolbar after the next page refresh. Otherwise, :ref:`enable the data collector by hand `. + +.. _data_collector_tag: + +Enabling Custom Data Collectors +------------------------------- + +If you don't use Symfony's default configuration with +:ref:`autowire and autoconfigure ` +you'll need to configure the data collector explicitly: .. configuration-block:: @@ -239,11 +254,12 @@ to specify a tag that contains the template: App\DataCollector\RequestCollector: tags: - - name: data_collector - template: 'data_collector/template.html.twig' + name: data_collector # must match the value returned by the getName() method - id: 'app.request_collector' - # optional priority + id: 'App\DataCollector\RequestCollector' + # optional template (it has more priority than the value returned by getTemplate()) + template: 'data_collector/template.html.twig' + # optional priority (positive or negative integer; default = 0) # priority: 300 .. code-block:: xml @@ -257,11 +273,13 @@ to specify a tag that contains the template: - + + + @@ -277,15 +295,11 @@ to specify a tag that contains the template: $services = $configurator->services(); $services->set(RequestCollector::class) - ->autowire() ->tag('data_collector', [ + 'id' => RequestCollector::class, + // optional template (it has more priority than the value returned by getTemplate()) 'template' => 'data_collector/template.html.twig', - 'id' => 'app.request_collector', + // optional priority (positive or negative integer; default = 0) // 'priority' => 300, ]); }; - -The position of each panel in the toolbar is determined by the collector priority. -Priorities are defined as positive or negative integers and they default to ``0``. -Most built-in collectors use ``255`` as their priority. If you want your collector -to be displayed before them, use a higher value (like 300). diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index d88bb5d32ed..e3b388a0bc4 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -72,9 +72,6 @@ What other possible classes or interfaces could you use? Find out by running: Request stack that controls the lifecycle of requests. Symfony\Component\HttpFoundation\RequestStack (request_stack) - Interface for the session. - Symfony\Component\HttpFoundation\Session\SessionInterface (session) - RouterInterface is the interface that all Router classes must implement. Symfony\Component\Routing\RouterInterface (router.default) diff --git a/rate_limiter.rst b/rate_limiter.rst new file mode 100644 index 00000000000..4b6fc27d4ab --- /dev/null +++ b/rate_limiter.rst @@ -0,0 +1,318 @@ +Rate Limiter +============ + +.. versionadded:: 5.2 + + The RateLimiter component was introduced in Symfony 5.2 as an + :doc:`experimental feature `. + +A "rate limiter" controls how frequently some event (e.g. an HTTP request or a +login attempt) is allowed to happen. Rate limiting is commonly used as a +defensive measure to protect services from excessive use (intended or not) and +maintain their availability. It's also useful to control your internal or +outbound processes (e.g. limit the number of simultaneously processed messages). + +Symfony uses these rate limiters in built-in features like "login throttling", +which limits how many failed login attempts a user can make in a given period of +time, but you can use them for your own features too. + +.. _rate-limiter-policies: + +Rate Limiting Policies +---------------------- + +Symfony's rate limiter implements some of the most common policies to enforce +rate limits: **fixed window**, **sliding window**, **token bucket**. + +Fixed Window Rate Limiter +~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the simplest technique and it's based on setting a limit for a given +interval of time (e.g. 5,000 requests per hour or 3 login attempts every 15 +minutes). + +In the diagram below, the limit is set to "5 tokens per hour". Each window +starts at the first hit (i.e. 10:15, 11:30 and 12:30). As soon as there are +5 hits (the blue squares) in a window, all others will be rejected (red +squares). + +.. raw:: html + + + +Its main drawback is that resource usage is not evenly distributed in time and +it can overload the server at the window edges. In the previous example, +there are 6 accepted requests between 11:00 and 12:00. + +This is more significant with bigger limits. For instance, with 5,000 requests +per hour, a user could make the 4,999 requests in the last minute of some +hour and another 5,000 requests during the first minute of the next hour, +making 9,999 requests in total in two minutes and possibly overloading the +server. These periods of excessive usage are called "bursts". + +Sliding Window Rate Limiter +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The sliding window algorithm is an alternative to the fixed window algorithm +designed to reduce bursts. This is the same example as above, but then +using a 1 hour window that slides over the timeline: + +.. raw:: html + + + +As you can see, this removes the edges of the window and would prevent the +6th request at 11:45. + +To achieve this, the rate limit is approximated based on the current window and +the previous window. + +For example: the limit is 5,000 requests per hour; a user made 4,000 requests +the previous hour and 500 requests this hour. 15 minutes in to the current hour +(25% of the window) the hit count would be calculated as: 75% * 4,000 + 500 = 3,500. +At this point in time the user can only do 1,500 more requests. + +The math shows that the closer the last window is, the more will the hit count +of the last window effect the current limit. This will make sure that a user can +do 5,000 requests per hour but only if they are spread out evenly. + +Token Bucket Rate Limiter +~~~~~~~~~~~~~~~~~~~~~~~~~ + +This technique implements the `token bucket algorithm`_, which defines a +continuously updating budget of resource usage. It roughly works like this: + +* A bucket is created with an initial set of tokens; +* A new token is added to the bucket with a predefined frequency (e.g. every second); +* Allowing an event consumes one or more tokens; +* If the bucket still contains tokens, the event is allowed; otherwise, it's denied; +* If the bucket is at full capacity, new tokens are discarded. + +The below diagram shows a token bucket of size 4 that is filled with a rate +of 1 token per 15 minutes: + +.. raw:: html + + + +This algorithm handles more complex back-off algorithm to manage bursts. +For instance, it can allow a user to try a password 5 times and then only +allow 1 every 15 minutes (unless the user waits 75 minutes and they will be +allowed 5 tries again). + +Installation +------------ + +Before using a rate limiter for the first time, run the following command to +install the associated Symfony Component in your application: + +.. code-block:: terminal + + $ composer require symfony/rate-limiter + +Configuration +------------- + +The following example creates two different rate limiters for an API service, to +enforce different levels of service (free or paid): + +.. code-block:: yaml + + # config/packages/rate_limiter.yaml + framework: + rate_limiter: + anonymous_api: + # use 'sliding_window' if you prefer that policy + policy: 'fixed_window' + limit: 100 + interval: '60 minutes' + authenticated_api: + policy: 'token_bucket' + limit: 5000 + rate: { interval: '15 minutes', amount: 500 } + +.. note:: + + The value of the ``interval`` option must be a number followed by any of the + units accepted by the `PHP date relative formats`_ (e.g. ``3 seconds``, + ``10 hours``, ``1 day``, etc.) + +In the ``anonymous_api`` limiter, after making the first HTTP request, you can +make up to 100 requests in the next 60 minutes. After that time, the counter +resets and you have another 100 requests for the following 60 minutes. + +In the ``authenticated_api`` limiter, after making the first HTTP request you +are allowed to make up to 5,000 HTTP requests in total, and this number grows +at a rate of another 500 requests every 15 minutes. If you don't make that +number of requests, the unused ones don't accumulate (the ``limit`` option +prevents that number from being higher than 5,000). + +Rate Limiting in Action +----------------------- + +After having installed and configured the rate limiter, inject it in any service +or controller and call the ``consume()`` method to try to consume a given number +of tokens. For example, this controller uses the previous rate limiter to control +the number of requests to the API:: + + // src/Controller/ApiController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; + use Symfony\Component\RateLimiter\RateLimiterFactory; + + class ApiController extends AbstractController + { + // if you're using service autowiring, the variable name must be: + // "rate limiter name" (in camelCase) + "Limiter" suffix + public function index(RateLimiterFactory $anonymousApiLimiter) + { + // create a limiter based on a unique identifier of the client + // (e.g. the client's IP address, a username/email, an API key, etc.) + $limiter = $anonymousApiLimiter->create($request->getClientIp()); + + // the argument of consume() is the number of tokens to consume + // and returns an object of type Limit + if (false === $limiter->consume(1)->isAccepted()) { + throw new TooManyRequestsHttpException(); + } + + // you can also use the ensureAccepted() method - which throws a + // RateLimitExceededException if the limit has been reached + // $limiter->consume(1)->ensureAccepted(); + + // ... + } + + // ... + } + +.. note:: + + In a real application, instead of checking the rate limiter in all the API + controller methods, create an :doc:`event listener or subscriber ` + for the :ref:`kernel.request event ` + and check the rate limiter once for all requests. + +Wait until a Token is Available +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Instead of dropping a request or process when the limit has been reached, +you might want to wait until a new token is available. This can be achieved +using the ``reserve()`` method:: + + // src/Controller/ApiController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\RateLimiter\RateLimiterFactory; + + class ApiController extends AbstractController + { + public function registerUser(Request $request, RateLimiterFactory $authenticatedApiLimiter) + { + $apiKey = $request->headers->get('apikey'); + $limiter = $authenticatedApiLimiter->create($apiKey); + + // this blocks the application until the given number of tokens can be consumed + $limiter->reserve(1)->wait(); + + // optional, pass a maximum wait time (in seconds), a MaxWaitDurationExceededException + // is thrown if the process has to wait longer. E.g. to wait at most 20 seconds: + //$limiter->reserve(1, 20)->wait(); + + // ... + } + + // ... + } + +The ``reserve()`` method is able to reserve a token in the future. Only use +this method if you're planning to wait, otherwise you will block other +processes by reserving unused tokens. + +.. note:: + + Not all strategies allow reserving tokens in the future. These + strategies may throw a ``ReserveNotSupportedException`` when calling + ``reserve()``. + + In these cases, you can use ``consume()`` together with ``wait()``, but + there is no guarantee that a token is available after the wait:: + + // ... + do { + $limit = $limiter->consume(1); + $limit->wait(); + } while (!$limit->isAccepted()); + +Exposing the Rate Limiter Status +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using a rate limiter in APIs, it's common to include some standard HTTP +headers in the response to expose the limit status (e.g. remaining tokens, when +new tokens will be available, etc.) + +Use the :class:`Symfony\\Component\\RateLimiter\\RateLimit` object returned by +the ``consume()`` method (also available via the ``getRateLimit()`` method of +the :class:`Symfony\\Component\\RateLimiter\\Reservation` object returned by the +``reserve()`` method) to get the value of those HTTP headers:: + + // src/Controller/ApiController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\RateLimiter\RateLimiterFactory; + + class ApiController extends AbstractController + { + public function index(RateLimiterFactory $anonymousApiLimiter) + { + $limiter = $anonymousApiLimiter->create($request->getClientIp()); + $limit = $limiter->consume(); + $headers = [ + 'X-RateLimit-Remaining' => $limit->getRemainingTokens(), + 'X-RateLimit-Retry-After' => $limit->getRetryAfter()->getTimestamp(), + 'X-RateLimit-Limit' => $limit->getLimit(), + ]; + + if (false === $limit->isAccepted()) { + return new Response(null, Response::HTTP_TOO_MANY_REQUESTS, $headers); + } + + // ... + + $reponse = new Response('...'); + $response->headers->add($headers); + + return $response; + } + } + +Rate Limiter Storage and Locking +-------------------------------- + +Rate limiters use the default cache and locking mechanisms defined in your +Symfony application. If you prefer to change that, use the ``lock_factory`` and +``storage_service`` options: + +.. code-block:: yaml + + # config/packages/rate_limiter.yaml + framework: + rate_limiter: + anonymous_api_limiter: + # ... + # the value is the name of any cache pool defined in your application + cache_pool: 'app.redis_cache' + # or define a service implementing StorageInterface to use a different + # mechanism to store the limiter information + storage_service: 'App\RateLimiter\CustomRedisStorage' + # the value is the name of any lock defined in your application + lock_factory: 'app.rate_limiter_lock' + +.. _`token bucket algorithm`: https://en.wikipedia.org/wiki/Token_bucket +.. _`PHP date relative formats`: https://www.php.net/datetime.formats.relative diff --git a/reference/configuration/doctrine.rst b/reference/configuration/doctrine.rst index cf49aaaa2b9..05d096e10b4 100644 --- a/reference/configuration/doctrine.rst +++ b/reference/configuration/doctrine.rst @@ -222,33 +222,35 @@ Keep in mind that you can't use both syntaxes at the same time. Caching Drivers ~~~~~~~~~~~~~~~ -.. deprecated:: 4.4 - - All the Doctrine caching types are deprecated since Symfony 4.4 and won't - be available in Symfony 5.0 and higher. Replace them with either ``type: service`` - or ``type: pool`` and use any of the cache pools/services defined with - :doc:`Symfony Cache `. - -The built-in types of caching drivers are: ``array``, ``apc``, ``apcu``, -``memcache``, ``memcached``, ``redis``, ``wincache``, ``zenddata`` and ``xcache``. -There is a special type called ``service`` which lets you define the ID of your -own caching service. - -The following example shows an overview of the caching configurations: +Use any of the existing :doc:`Symfony Cache ` pools or define new pools +to cache each of Doctrine ORM elements (queries, results, etc.): .. code-block:: yaml + # config/packages/prod/doctrine.yaml + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system + doctrine: orm: - auto_mapping: true - # each caching driver type defines its own config options - metadata_cache_driver: apc + # ... + metadata_cache_driver: + type: pool + pool: doctrine.system_cache_pool + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool result_cache_driver: - type: memcache - host: localhost - port: 11211 - instance_class: Memcache - # the 'service' type requires to define the 'id' option too + type: pool + pool: doctrine.result_cache_pool + + # in addition to Symfony Cache pools, you can also use the + # 'type: service' option to use any service as the cache query_cache_driver: type: service id: App\ORM\MyCacheService diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 4515077d1d7..d3baf76260a 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -137,11 +137,34 @@ Configuration * `proxy`_ * `query`_ * `resolve`_ + + * :ref:`retry_failed ` + + * `retry_strategy`_ + * :ref:`enabled ` + * `delay`_ + * `http_codes`_ + * `max_delay`_ + * `max_retries`_ + * `multiplier`_ + * `jitter`_ + * `timeout`_ * `max_duration`_ * `verify_host`_ * `verify_peer`_ + * :ref:`retry_failed ` + + * `retry_strategy`_ + * :ref:`enabled ` + * `delay`_ + * `http_codes`_ + * `max_delay`_ + * `max_retries`_ + * `multiplier`_ + * `jitter`_ + * `http_method_override`_ * `ide`_ * :ref:`lock ` @@ -155,11 +178,14 @@ Configuration * :ref:`dsn ` * `transports`_ + * `message_bus`_ * `envelope`_ * `sender`_ * `recipients`_ + * :ref:`headers ` + * `php_errors`_ * `log`_ @@ -176,6 +202,8 @@ Configuration * `property_access`_ * `magic_call`_ + * `magic_get`_ + * `magic_set`_ * `throw_exception_on_invalid_index`_ * `throw_exception_on_invalid_property_path`_ @@ -183,12 +211,20 @@ Configuration * :ref:`enabled ` +* `rate_limiter`_: + + * :ref:`name ` + + * `lock_factory`_ + * `policy`_ + * `request`_: * `formats`_ * `router`_ + * `default_uri`_ * `http_port`_ * `https_port`_ * `resource`_ @@ -236,27 +272,19 @@ Configuration * `storage_id`_ * `use_cookies`_ -* `templating`_ - - * :ref:`cache ` - * `engines`_ - * :ref:`form ` - - * :ref:`resources ` - - * `loaders`_ - * `test`_ * `translator`_ * `cache_dir`_ * :ref:`default_path ` * :ref:`enabled ` + * :ref:`enabled_locales ` * `fallbacks`_ * `formatter`_ * `logging`_ * :ref:`paths ` +* `trusted_headers`_ * `trusted_hosts`_ * `trusted_proxies`_ * `validation`_ @@ -275,7 +303,6 @@ Configuration * `endpoint`_ * `static_method`_ - * `strict_email`_ * `translation_domain`_ * `web_link`_ @@ -354,12 +381,32 @@ named ``kernel.http_method_override``. $request = Request::createFromGlobals(); // ... +.. _reference-framework-trusted-headers: + +trusted_headers +~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The ``trusted_headers`` option was introduced in Symfony 5.2. + +The ``trusted_headers`` option is needed to configure which client information +should be trusted (e.g. their host) when running Symfony behind a load balancer +or a reverse proxy. See :doc:`/deployment/proxies`. + .. _reference-framework-trusted-proxies: trusted_proxies ~~~~~~~~~~~~~~~ -The ``trusted_proxies`` option was removed in Symfony 3.3. See :doc:`/deployment/proxies`. +.. versionadded:: 5.2 + + The ``trusted_proxies`` option was reintroduced in Symfony 5.2 (it had been + removed in Symfony 3.3). + +The ``trusted_proxies`` option is needed to get precise information about the +client (e.g. their IP address) when running Symfony behind a load balancer or a +reverse proxy. See :doc:`/deployment/proxies`. ide ~~~ @@ -480,10 +527,6 @@ disallow_search_engine_index **type**: ``boolean`` **default**: ``true`` when the debug mode is enabled, ``false`` otherwise. -.. versionadded:: 4.3 - - The ``disallow_search_engine_index`` option was introduced in Symfony 4.3. - If ``true``, Symfony adds a ``X-Robots-Tag: noindex`` HTTP tag to all responses (unless your own app adds that header, in which case it's not modified). This `X-Robots-Tag HTTP header`_ tells search engines to not index your web site. @@ -620,10 +663,6 @@ error_controller **type**: ``string`` **default**: ``error_controller`` -.. versionadded:: 4.4 - - The ``error_controller`` option was introduced in Symfony 4.4. - This is the controller that is called when an exception is thrown anywhere in your application. The default controller (:class:`Symfony\\Component\\HttpKernel\\Controller\\ErrorController`) @@ -705,12 +744,6 @@ hinclude_default_template **type**: ``string`` **default**: ``null`` -.. versionadded:: 4.3 - - The ``framework.fragments.hinclude_default_template`` option was introduced - in Symfony 4.3. In previous Symfony versions it was defined under - ``framework.templating.hinclude_default_template``. - Sets the content shown during the loading of the fragment or when JavaScript is disabled. This can be either a template name or the content itself. @@ -780,6 +813,40 @@ If you use for example as the type and name of an argument, autowiring will inject the ``my_api.client`` service into your autowired classes. +.. _reference-http-client-retry-failed: + +By enabling the optional ``retry_failed`` configuration, the HTTP client service +will automatically retry failed HTTP requests. + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + # ... + retry_failed: + # retry_strategy: app.custom_strategy + http_codes: + 0: ['GET', 'HEAD'] # retry network errors if request method is GET or HEAD + 429: true # retry all responses with 429 status code + 500: ['GET', 'HEAD'] + max_retries: 2 + delay: 1000 + multiplier: 3 + max_delay: 5000 + jitter: 0.3 + + scoped_clients: + my_api.client: + # ... + retry_failed: + max_retries: 4 + +.. versionadded:: 5.2 + + The ``retry_failed`` option was introduced in Symfony 5.2. + auth_basic .......... @@ -802,10 +869,6 @@ 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 @@ -856,10 +919,6 @@ If this option is a boolean value, the response is buffered when the value is 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 ...... @@ -883,6 +942,27 @@ ciphers A list of the names of the ciphers allowed for the SSL/TLS connections. They can be separated by colons, commas or spaces (e.g. ``'RC4-SHA:TLS13-AES-128-GCM-SHA256'``). +delay +..... + +**type**: ``integer`` **default**: ``1000`` + +.. versionadded:: 5.2 + + The ``delay`` option was introduced in Symfony 5.2. + +The initial delay in milliseconds used to compute the waiting time between retries. + +.. _reference-http-client-retry-enabled: + +enabled +....... + +**type**: ``boolean`` **default**: ``false`` + +Whether to enable the support for retry failed HTTP request or not. +This setting is automatically set to true when one of the child settings is configured. + .. _http-headers: headers @@ -893,6 +973,17 @@ headers An associative array of the HTTP headers added before making the request. This value must use the format ``['header-name' => 'value0, value1, ...']``. +http_codes +.......... + +**type**: ``array`` **default**: :method:`Symfony\\Component\\HttpClient\\Retry\\GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES` + +.. versionadded:: 5.2 + + The ``http_codes`` option was introduced in Symfony 5.2. + +The list of HTTP status codes that triggers a retry of the request. + http_version ............ @@ -901,6 +992,20 @@ http_version The HTTP version to use, typically ``'1.1'`` or ``'2.0'``. Leave it to ``null`` to let Symfony select the best version automatically. +jitter +...... + +**type**: ``float`` **default**: ``0.1`` (must be between 0.0 and 1.0) + +.. versionadded:: 5.2 + + The ``jitter`` option was introduced in Symfony 5.2. + +This option adds some randomness to the delay. It's useful to avoid sending +multiple requests to the server at the exact same time. The randomness is +calculated as ``delay * jitter``. For example: if delay is ``1000ms`` and jitter +is ``0.2``, the actual delay will be a number between ``800`` and ``1200`` (1000 +/- 20%). + local_cert .......... @@ -918,6 +1023,18 @@ local_pk The path of a file that contains the `PEM formatted`_ private key of the certificate defined in the ``local_cert`` option. +max_delay +......... + +**type**: ``integer`` **default**: ``0`` + +.. versionadded:: 5.2 + + The ``max_delay`` option was introduced in Symfony 5.2. + +The maximum amount of milliseconds initial to wait between retries. +Use ``0`` to not limit the duration. + max_duration ............ @@ -926,10 +1043,6 @@ max_duration 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. - max_host_connections .................... @@ -948,6 +1061,30 @@ max_redirects The maximum number of redirects to follow. Use ``0`` to not follow any redirection. +max_retries +........... + +**type**: ``integer`` **default**: ``3`` + +.. versionadded:: 5.2 + + The ``max_retries`` option was introduced in Symfony 5.2. + +The maximum number of retries for failing requests. When the maximum is reached, +the client returns the last received response. + +multiplier +.......... + +**type**: ``float`` **default**: ``2`` + +.. versionadded:: 5.2 + + The ``multiplier`` option was introduced in Symfony 5.2. + +This value is multiplied to the delay each time a retry occurs, to distribute +retries in time instead of making all of them sequentially. + no_proxy ........ @@ -1007,6 +1144,22 @@ client and to make your tests easier. The value of this option is an associative array of ``domain => IP address`` (e.g ``['symfony.com' => '46.137.106.254', ...]``). +retry_strategy +............... + +**type**: ``string`` + +.. versionadded:: 5.2 + + The ``retry_strategy`` option was introduced in Symfony 5.2. + +The service is used to decide if a request should be retried and to compute the +time to wait between retries. By default, it uses an instance of +:class:`Symfony\\Component\\HttpClient\\Retry\\GenericRetryStrategy` configured +with ``http_codes``, ``delay``, ``max_delay``, ``multiplier`` and ``jitter`` +options. This class has to implement +:class:`Symfony\\Component\\HttpClient\\Retry\\RetryStrategyInterface`. + scope ..... @@ -1102,6 +1255,35 @@ dsn The DSN where to store the profiling information. +rate_limiter +~~~~~~~~~~~~ + +.. _reference-rate-limiter-name: + +name +.... + +**type**: ``prototype`` + +Name of the rate limiter you want to create. + +lock_factory +"""""""""""" + +**type**: ``string`` **default:** ``lock.factory`` + +The service that is used to create a lock. The service has to be an instance of +the :class:`Symfony\\Component\\Lock\\LockFactory` class. + +policy +"""""" + +**type**: ``string`` **required** + +The name of the rate limiting algorithm to use. Example names are ``fixed_window``, +``sliding_window`` and ``no_limit``. See :ref:`Rate Limiter Policies `) +for more information. + request ~~~~~~~ @@ -1187,6 +1369,18 @@ The type of the resource to hint the loaders about the format. This isn't needed when you use the default routers with the expected file extensions (``.xml``, ``.yaml``, ``.php``). +default_uri +........... + +**type**: ``string`` + +.. versionadded:: 5.1 + + The ``default_uri`` option was introduced in Symfony 5.1. + +The default URI used to generate URLs in a non-HTTP context (see +:ref:`Generating URLs in Commands `). + http_port ......... @@ -1229,6 +1423,11 @@ utf8 **type**: ``boolean`` **default**: ``false`` +.. deprecated:: 5.1 + + Not setting this option is deprecated since Symfony 5.1. Moreover, the + default value of this option will change to ``true`` in Symfony 6.0. + When this option is set to ``true``, the regular expressions used in the :ref:`requirements of route parameters ` will be run using the `utf-8 modifier`_. This will for example match any UTF-8 character @@ -1355,7 +1554,7 @@ to the cookie specification. cookie_samesite ............... -**type**: ``string`` or ``null`` **default**: ``null`` +**type**: ``string`` or ``null`` **default**: ``'lax'`` It controls the way cookies are sent when the HTTP request was not originated from the same domain the cookies are associated to. Setting this option is @@ -1390,10 +1589,10 @@ The possible values for this option are: cookie_secure ............. -**type**: ``boolean`` or ``string`` **default**: ``false`` +**type**: ``boolean`` or ``null`` **default**: ``null`` This determines whether cookies should only be sent over secure connections. In -addition to ``true`` and ``false``, there's a special ``'auto'`` value that +addition to ``true`` and ``false``, there's a special ``null`` value that means ``true`` for HTTPS requests and ``false`` for HTTP requests. cookie_httponly @@ -1938,10 +2137,11 @@ json_manifest_path **type**: ``string`` **default**: ``null`` -The file path to a ``manifest.json`` file containing an associative array of asset -names and their respective compiled names. A common cache-busting technique using -a "manifest" file works by writing out assets with a "hash" appended to their -file names (e.g. ``main.ae433f1cb.css``) during a front-end compilation routine. +The file path or absolute URL to a ``manifest.json`` file containing an +associative array of asset names and their respective compiled names. A common +cache-busting technique using a "manifest" file works by writing out assets with +a "hash" appended to their file names (e.g. ``main.ae433f1cb.css``) during a +front-end compilation routine. .. tip:: @@ -1962,6 +2162,8 @@ package: assets: # this manifest is applied to every asset (including packages) json_manifest_path: "%kernel.project_dir%/public/build/manifest.json" + # you can use absolute URLs too and Symfony will download them automatically + # json_manifest_path: 'https://cdn.example.com/manifest.json' packages: foo_package: # this package uses its own manifest (the default file is ignored) @@ -1983,6 +2185,8 @@ package: + + [ // this manifest is applied to every asset (including packages) 'json_manifest_path' => '%kernel.project_dir%/public/build/manifest.json', + // you can use absolute URLs too and Symfony will download them automatically + // 'json_manifest_path' => 'https://cdn.example.com/manifest.json', 'packages' => [ 'foo_package' => [ // this package uses its own manifest (the default file is ignored) @@ -2015,6 +2221,11 @@ package: ], ]); +.. versionadded:: 5.1 + + The option to use an absolute URL in ``json_manifest_path`` was introduced + in Symfony 5.1. + .. note:: This parameter cannot be set at the same time as ``version`` or ``version_strategy``. @@ -2026,53 +2237,58 @@ package: If you request an asset that is *not found* in the ``manifest.json`` file, the original - *unmodified* - asset path will be returned. -templating +.. note:: + + If an URL is set, the JSON manifest is downloaded on each request using the `http_client`_. + +translator ~~~~~~~~~~ -.. deprecated:: 4.3 +cache_dir +......... - The integration of the Templating component in FrameworkBundle has been - deprecated since version 4.3 and will be removed in 5.0. That's why all the - configuration options defined under ``framework.templating`` are deprecated too. +**type**: ``string`` | ``null`` **default**: ``%kernel.cache_dir%/translations/`` -.. _reference-templating-form: +Defines the directory where the translation cache is stored. Use ``null`` to +disable this cache. -form -.... +.. _reference-translator-enabled: -.. _reference-templating-form-resources: +enabled +....... -resources -""""""""" +**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation + +Whether or not to enable the ``translator`` service in the service container. + +.. _reference-translator-enabled-locales: -**type**: ``string[]`` **default**: ``['FrameworkBundle:Form']`` +enabled_locales +............... -.. deprecated:: 4.3 +**type**: ``array`` **default**: ``[]`` (empty array = enable all locales) - The integration of the Templating component in FrameworkBundle has been - deprecated since version 4.3 and will be removed in 5.0. Form theming with - PHP templates will no longer be supported and you'll need to use Twig instead. +.. versionadded:: 5.1 -A list of all resources for form theming in PHP. This setting is not required -if you're :ref:`using the Twig format for your themes `. + The ``enabled_locales`` option was introduced in Symfony 5.1. -Assume you have custom global form themes in ``templates/form_themes/``, you can -configure this like: +Symfony applications generate by default the translation files for validation +and security messages in all locales. If your application only uses some +locales, use this option to restrict the files generated by Symfony and improve +performance a bit: .. configuration-block:: .. code-block:: yaml - # config/packages/framework.yaml + # config/packages/translation.yaml framework: - templating: - form: - resources: - - 'form_themes' + translator: + enabled_locales: ['en', 'es'] .. code-block:: xml - + - - - form_themes - - + + en + es + .. code-block:: php - // config/packages/framework.php + // config/packages/translation.php $container->loadFromExtension('framework', [ - 'templating' => [ - 'form' => [ - 'resources' => [ - 'form_themes', - ], - ], + 'translator' => [ + 'enabled_locales' => ['en', 'es'], ], ]); -.. note:: - - The default form templates from ``FrameworkBundle:Form`` will always - be included in the form resources. - -.. seealso:: - - See :ref:`forms-theming-global` for more information. - -.. _reference-templating-cache: - -cache -..... - -**type**: ``string`` - -The path to the cache directory for templates. When this is not set, caching -is disabled. - -.. note:: - - When using Twig templating, the caching is already handled by the - TwigBundle and doesn't need to be enabled for the FrameworkBundle. - -engines -....... - -**type**: ``string[]`` / ``string`` **required** - -The Templating Engine to use. This can either be a string (when only one -engine is configured) or an array of engines. - -At least one engine is required. - -loaders -....... - -**type**: ``string[]`` - -An array (or a string when configuring just one loader) of service ids for -templating loaders. Templating loaders are used to find and load templates -from a resource (e.g. a filesystem or database). Templating loaders must -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 -....... - -**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation - -Whether or not to enable the ``translator`` service in the service container. +If some user makes requests with a locale not included in this option, the +application won't display any error because Symfony will display contents using +the fallback locale. .. _fallback: fallbacks ......... -**type**: ``string|array`` **default**: ``['en']`` +**type**: ``string|array`` **default**: value of `default_locale`_ This option is used when the translation key for the current locale wasn't found. @@ -2242,6 +2389,32 @@ When enabled, the ``property_accessor`` service uses PHP's :ref:`magic __call() method ` when its ``getValue()`` method is called. +magic_get +......... + +**type**: ``boolean`` **default**: ``true`` + +When enabled, the ``property_accessor`` service uses PHP's +:ref:`magic __get() method ` when +its ``getValue()`` method is called. + +.. versionadded:: 5.2 + + The ``magic_get`` option was introduced in Symfony 5.2. + +magic_set +......... + +**type**: ``boolean`` **default**: ``true`` + +When enabled, the ``property_accessor`` service uses PHP's +:ref:`magic __set() method ` when +its ``setValue()`` method is called. + +.. versionadded:: 5.2 + + The ``magic_set`` option was introduced in Symfony 5.2. + throw_exception_on_invalid_index ................................ @@ -2255,10 +2428,6 @@ throw_exception_on_invalid_property_path **type**: ``boolean`` **default**: ``true`` -.. versionadded:: 4.3 - - The ``throw_exception_on_invalid_property_path`` option was introduced in Symfony 4.3. - When enabled, the ``property_accessor`` service throws an exception when you try to access an invalid property path of an object. @@ -2333,10 +2502,6 @@ enabled **type**: ``boolean`` **default**: ``true`` -.. versionadded:: 4.3 - - The ``enabled`` option was introduced in Symfony 4.3. - If you set this option to ``false``, no HTTP requests will be made and the given password will be considered valid. This is useful when you don't want or can't make HTTP requests, such as in ``dev`` and ``test`` environments or in @@ -2347,10 +2512,6 @@ endpoint **type**: ``string`` **default**: ``null`` -.. versionadded:: 4.3 - - The ``endpoint`` option was introduced in Symfony 4.3. - By default, the :doc:`NotCompromisedPassword ` constraint uses the public API provided by `haveibeenpwned.com`_. This option allows to define a different, but compatible, API endpoint to make the password @@ -2367,20 +2528,6 @@ metadata of the class. You can define an array of strings with the names of several methods. In that case, all of them will be called in that order to load the metadata. -strict_email -............ - -**type**: ``Boolean`` **default**: ``false`` - -.. deprecated:: 4.1 - - The ``strict_email`` option was deprecated in Symfony 4.1. Use the new - ``email_validation_mode`` option instead. - -If this option is enabled, the `egulias/email-validator`_ library will be -used by the :doc:`/reference/constraints/Email` constraint validator. Otherwise, -the validator uses a simple regular expression to validate email addresses. - email_validation_mode ..................... @@ -2809,9 +2956,14 @@ Can also be the service id of another cache pool where tags will be stored. default_lifetime """""""""""""""" -**type**: ``integer`` +**type**: ``integer`` | ``string`` -Default lifetime of your cache items in seconds. +Default lifetime of your cache items. Give an integer value to set the default +lifetime in seconds. A string value could be ISO 8601 time interval, like ``"PT5M"`` +or a PHP date expression that is accepted by ``strtotime()``, like ``"5 minutes"``. + +If no value is provided, the cache adapter will fallback to the default value on +the actual cache storage. provider """""""" @@ -2851,6 +3003,12 @@ It's also useful when using `blue/green deployment`_ strategies and more generally, when you need to abstract out the actual deployment directory (for example, when warming caches offline). +.. versionadded:: 5.2 + + Starting from Symfony 5.2, the ``%kernel.container_class%`` parameter is no + longer appended automatically to the value of this option. This allows + sharing caches between applications or different environments. + .. _reference-lock: lock @@ -2936,15 +3094,11 @@ Name of the lock you want to create. lock.invoice.retry_till_save.store: class: Symfony\Component\Lock\Store\RetryTillSaveStore decorates: lock.invoice.store - arguments: ['@lock.invoice.retry_till_save.store.inner', 100, 50] + arguments: ['@.inner', 100, 50] mailer ~~~~~~ -.. versionadded:: 4.3 - - The ``mailer`` settings were introduced in Symfony 4.3. - .. _mailer-dsn: dsn @@ -2963,6 +3117,18 @@ transports A :ref:`list of DSN ` that can be used by the mailer. A transport name is the key and the dsn is the value. +message_bus +........... + +.. versionadded:: 5.1 + + The ``message_bus`` option was introduced in Symfony 5.1. + +**type**: ``string`` **default**: ``null`` or default bus if Messenger component is installed + +Service identifier of the message bus to use when using the +:doc:`Messenger component ` (e.g. ``messenger.default_bus``). + envelope ........ @@ -3017,6 +3183,7 @@ recipients set in the code. // config/packages/mailer.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; + return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->extension('framework', [ 'mailer' => [ @@ -3031,6 +3198,19 @@ recipients set in the code. ]); }; +.. _mailer-headers: + +headers +....... + +.. versionadded:: 5.2 + + The ``headers`` mailer option was introduced in Symfony 5.2. + +**type**: ``array`` + +Headers to add to emails. The key (``name`` attribute in xml format) is the +header name and value the header value. web_link ~~~~~~~~ diff --git a/reference/configuration/kernel.rst b/reference/configuration/kernel.rst index 6b0ac4279ad..27707807ed4 100644 --- a/reference/configuration/kernel.rst +++ b/reference/configuration/kernel.rst @@ -12,12 +12,17 @@ Configuration ------------- * `Charset`_ -* `Kernel Name`_ * `Project Directory`_ * `Cache Directory`_ * `Log Directory`_ * `Container Build Time`_ +In previous Symfony versions there was another configuration option to define +the "kernel name", which is only important when +:doc:`using applications with multiple kernels `. +If you need a unique ID for your kernels use the ``kernel.container_class`` +parameter or the ``Kernel::getContainerClass()`` method. + .. _configuration-kernel-charset: Charset @@ -46,29 +51,6 @@ charset:: } } -Kernel Name -~~~~~~~~~~~ - -**type**: ``string`` **default**: ``src`` (i.e. the directory name holding -the kernel class) - -.. deprecated:: 4.2 - - The ``kernel.name`` parameter and the ``Kernel::getName()`` method were - deprecated in Symfony 4.2. If you need a unique ID for your kernels use the - ``kernel.container_class`` parameter or the ``Kernel::getContainerClass()`` method. - -The name of the kernel isn't usually directly important - it's used in the -generation of cache files - and you probably will only change it when -:doc:`using applications with multiple kernels `. - -This value is exposed via the ``kernel.name`` configuration parameter and the -:method:`Symfony\\Component\\HttpKernel\\Kernel::getName` method. - -To change this setting, override the ``getName()`` method. Alternatively, move -your kernel into a different directory. For example, if you moved the kernel -into a ``foo/`` directory (instead of ``src/``), the kernel name will be ``foo``. - .. _configuration-kernel-project-directory: Project Directory @@ -113,23 +95,40 @@ Cache Directory This returns the absolute path of the cache directory of your Symfony project. It's calculated automatically based on the current -:ref:`environment `. +:ref:`environment `. Data might be written to this +path at runtime. This value is exposed via the ``kernel.cache_dir`` configuration parameter and the :method:`Symfony\\Component\\HttpKernel\\Kernel::getCacheDir` method. To -change this setting, override the ``getCacheDir()`` method to return the right +change this setting, override the ``getCacheDir()`` method to return the correct cache directory. +Build Directory +~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``$this->getCacheDir()`` + +.. versionadded:: 5.2 + + The build directory feature was introduced in Symfony 5.2. + +This returns the absolute path of a build directory of your Symfony project. This +directory can be used to separate read-only cache (i.e. the compiled container) +from read-write cache (i.e. :doc:`cache pools `). Specify a non-default +value when the application is deployed in a read-only filesystem like a Docker +container or AWS Lambda. + +This value is exposed via the ``kernel.build_dir`` configuration parameter and +the :method:`Symfony\\Component\\HttpKernel\\Kernel::getBuildDir` method. To +change this setting, override the ``getBuildDir()`` method to return the correct +build directory. + + Log Directory ~~~~~~~~~~~~~ **type**: ``string`` **default**: ``$this->getProjectDir()/var/log`` -.. deprecated:: 4.2 - - The ``kernel.log_dir`` parameter was deprecated in Symfony 4.2, - use ``kernel.logs_dir`` instead. - This returns the absolute path of the log directory of your Symfony project. It's calculated automatically based on the current :ref:`environment `. diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 399794e2402..d4a37758798 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -71,10 +71,6 @@ When set to ``lazy``, Symfony loads the user (and starts the session) only if the application actually accesses the ``User`` object (e.g. via a ``is_granted()`` call in a template or ``isGranted()`` in a controller or service). -.. versionadded:: 4.4 - - The ``lazy`` value of the ``anonymous`` option was introduced in Symfony 4.4. - erase_credentials ~~~~~~~~~~~~~~~~~ @@ -159,7 +155,6 @@ encoding algorithm. Also, each algorithm defines different config options: algorithm: 'sodium' memory_cost: 16384 # Amount in KiB. (16384 = 16 MiB) time_cost: 2 # Number of iterations - threads: 4 # Number of parallel threads # MessageDigestPasswordEncoder encoder using SHA512 hashing with default options App\Entity\User: 'sha512' @@ -172,7 +167,9 @@ encoding algorithm. Also, each algorithm defines different config options: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -197,14 +194,12 @@ encoding algorithm. Also, each algorithm defines different config options: + time_cost: number of iterations --> @@ -244,7 +239,6 @@ encoding algorithm. Also, each algorithm defines different config options: 'algorithm' => 'sodium', 'memory_cost' => 16384, // Amount in KiB. (16384 = 16 MiB) 'time_cost' => 2, // Number of iterations - 'threads' => 4, // Number of parallel threads ], // MessageDigestPasswordEncoder encoder using SHA512 hashing with default options @@ -254,34 +248,80 @@ encoding algorithm. Also, each algorithm defines different config options: ], ]); -.. deprecated:: 4.3 - - The ``threads`` configuration option was deprecated in Symfony 4.3. No - alternative is provided because starting from Symfony 5.0 this value will be - hardcoded to ``1`` (one thread). - -.. versionadded:: 4.3 - - The ``sodium`` algorithm was introduced in Symfony 4.3. In previous Symfony - versions it was called ``argon2i``. - .. tip:: You can also create your own password encoders as services and you can even select a different password encoder for each user instance. Read :doc:`this article ` for more details. +.. tip:: + + Encoding passwords is resource intensive and takes time in order to generate + secure password hashes. In tests however, secure hashes are not important, so + you can change the encoders configuration in ``test`` environment to run tests faster: + + .. configuration-block:: + + .. code-block:: yaml + + # config/packages/test/security.yaml + encoders: + # Use your user class name here + App\Entity\User: + algorithm: auto # This should be the same value as in config/packages/security.yaml + cost: 4 # Lowest possible value for bcrypt + time_cost: 3 # Lowest possible value for argon + memory_cost: 10 # Lowest possible value for argon + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/test/security.php + use App\Entity\User; + + $container->loadFromExtension('security', [ + 'encoders' => [ + // Use your user class name here + User::class => [ + 'algorithm' => 'auto', // This should be the same value as in config/packages/security.yaml + 'cost' => 4, // Lowest possible value for bcrypt + 'time_cost' => 3, // Lowest possible value for argon + 'memory_cost' => 10, // Lowest possible value for argon + ] + ], + ]); + .. _reference-security-sodium: .. _using-the-argon2i-password-encoder: Using the Sodium Password Encoder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.3 - - The ``SodiumPasswordEncoder`` was introduced in Symfony 4.3. In previous - Symfony versions it was called ``Argon2iPasswordEncoder``. - It uses the `Argon2 key derivation function`_ and it's the encoder recommended by Symfony. Argon2 support was introduced in PHP 7.2, but if you use an earlier PHP version, you can install the `libsodium`_ PHP extension. @@ -363,7 +403,9 @@ application: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -415,8 +457,6 @@ depend on the authentication mechanism, which can be any of these: # ... remote_user: # ... - simple_preauth: - # ... guard: # ... form_login: @@ -425,8 +465,6 @@ depend on the authentication mechanism, which can be any of these: # ... json_login: # ... - simple_form: - # ... http_basic: # ... http_basic_ldap: @@ -566,24 +604,7 @@ The ``invalidate_session`` option allows to redefine this behavior. Set this option to ``false`` in every firewall and the user will only be logged out from the current firewall and not the other ones. -logout_on_user_change -~~~~~~~~~~~~~~~~~~~~~ - -**type**: ``boolean`` **default**: ``true`` - -.. deprecated:: 4.1 - - The ``logout_on_user_change`` option was deprecated in Symfony 4.1. - -If ``false`` this option makes Symfony to not trigger a logout when the user has -changed. Doing that is deprecated, so this option should be set to ``true`` or -unset to avoid getting deprecation messages. - -The user is considered to have changed when the user class implements -:class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface` and the -``isEqualTo()`` method returns ``false``. Also, when any of the properties -required by the :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface` -(like the username, password or salt) changes. +.. _reference-security-logout-success-handler: ``path`` ~~~~~~~~ @@ -596,6 +617,13 @@ you need to set up a route with a matching path. success_handler ~~~~~~~~~~~~~~~ +.. deprecated:: 5.1 + + This option is deprecated since Symfony 5.1. Register an + :doc:`event listener ` on the + :class:`Symfony\\Component\\Security\\Http\\Event\\LogoutEvent` + instead. + **type**: ``string`` **default**: ``'security.logout.success_handler'`` The service ID used for handling a successful logout. The service must implement @@ -721,7 +749,9 @@ multiple firewalls, the "context" could actually be shared: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 5fc8b95313c..5d5edd1d43c 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -42,7 +42,6 @@ Configuration * `debug`_ * `default_path`_ -* `exception_controller`_ * `form_themes`_ * `globals`_ * `number_format`_ @@ -196,29 +195,6 @@ The path to the directory where Symfony will look for the application Twig templates by default. If you store the templates in more than one directory, use the :ref:`paths ` option too. -.. _config-twig-exception-controller: - -exception_controller -~~~~~~~~~~~~~~~~~~~~ - -**type**: ``string`` **default**: ``twig.controller.exception:showAction`` - -.. deprecated:: 4.4 - - The ``exception_controller`` configuration option was deprecated in Symfony 4.4. - Set it to ``null`` and use the new ``error_controller`` option under ``framework`` - configuration instead. - -This is the controller that is activated after an exception is thrown anywhere -in your application. The default controller -(:class:`Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController`) -is what's responsible for rendering specific templates under different error -conditions (see :doc:`/controller/error_pages`). Modifying this -option is advanced. If you need to customize an error page you should use -the previous link. If you need to perform some behavior on an exception, -you should add an :doc:`event listener ` to the -:ref:`kernel.exception event `. - .. _config-twig-form-themes: form_themes @@ -387,7 +363,7 @@ Read more about :ref:`template directories and namespaces strict_variables ~~~~~~~~~~~~~~~~ -**type**: ``boolean`` **default**: ``false`` +**type**: ``boolean`` **default**: ``%kernel.debug%`` If set to ``true``, Symfony shows an exception whenever a Twig variable, attribute or method doesn't exist. If set to ``false`` these errors are ignored diff --git a/reference/constraints.rst b/reference/constraints.rst index 317a836e396..56acb087114 100644 --- a/reference/constraints.rst +++ b/reference/constraints.rst @@ -14,11 +14,14 @@ Validation Constraints Reference constraints/Type constraints/Email + constraints/ExpressionLanguageSyntax constraints/Length constraints/Url constraints/Regex + constraints/Hostname constraints/Ip constraints/Uuid + constraints/Ulid constraints/Json constraints/EqualTo @@ -61,7 +64,11 @@ Validation Constraints Reference constraints/Bic constraints/Isbn constraints/Issn + constraints/Isin + constraints/AtLeastOneOf + constraints/Sequentially + constraints/Compound constraints/Callback constraints/Expression constraints/All diff --git a/reference/constraints/AtLeastOneOf.rst b/reference/constraints/AtLeastOneOf.rst new file mode 100644 index 00000000000..fb29a86f8d8 --- /dev/null +++ b/reference/constraints/AtLeastOneOf.rst @@ -0,0 +1,196 @@ +AtLeastOneOf +============ + +This constraint checks that the value satisfies at least one of the given +constraints. The validation stops as soon as one constraint is satisfied. + +.. versionadded:: 5.1 + + The ``AtLeastOneOf`` constraint was introduced in Symfony 5.1. + +========== =================================================================== +Applies to :ref:`property or method ` +Options - `constraints`_ + - `includeInternalMessages`_ + - `message`_ + - `messageCollection`_ + - `groups`_ + - `payload`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\AtLeastOneOf` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\AtLeastOneOfValidator` +========== =================================================================== + +Basic Usage +----------- + +The following constraints ensure that: + +* the ``password`` of a ``Student`` either contains ``#`` or is at least ``10`` + characters long; +* the ``grades`` of a ``Student`` is an array which contains at least ``3`` + elements or that each element is greater than or equal to ``5``. + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Entity/Student.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Student + { + /** + * @Assert\AtLeastOneOf({ + * @Assert\Regex("/#/"), + * @Assert\Length(min=10) + * }) + */ + protected $password; + + /** + * @Assert\AtLeastOneOf({ + * @Assert\Count(min=3), + * @Assert\All( + * @Assert\GreaterThanOrEqual(5) + * ) + * }) + */ + protected $grades; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\Entity\Student: + properties: + password: + - AtLeastOneOf: + - Regex: '/#/' + - Length: + min: 10 + grades: + - AtLeastOneOf: + - Count: + min: 3 + - All: + - GreaterThanOrEqual: 5 + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // src/Entity/Student.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class Student + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('password', new Assert\AtLeastOneOf([ + 'constraints' => [ + new Assert\Regex(['pattern' => '/#/']), + new Assert\Length(['min' => 10]), + ], + ])); + + $metadata->addPropertyConstraint('grades', new Assert\AtLeastOneOf([ + 'constraints' => [ + new Assert\Count(['min' => 3]), + new Assert\All([ + 'constraints' => [ + new Assert\GreaterThanOrEqual(5), + ], + ]), + ], + ])); + } + } + +Options +------- + +constraints +~~~~~~~~~~~ + +**type**: ``array`` [:ref:`default option `] + +This required option is the array of validation constraints from which at least one of +has to be satisfied in order for the validation to succeed. + +includeInternalMessages +~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If set to ``true``, the message that is shown if the validation fails, +will include the list of messages for the internal constraints. See option +`message`_ for an example. + +message +~~~~~~~ + +**type**: ``string`` **default**: ``This value should satisfy at least one of the following constraints:`` + +This is the intro of the message that will be shown if the validation fails. By default, +it will be followed by the list of messages for the internal constraints +(configurable by `includeInternalMessages`_ option) . For example, +if the above ``grades`` property fails to validate, the message will be +``This value should satisfy at least one of the following constraints: +[1] This collection should contain 3 elements or more. +[2] Each element of this collection should satisfy its own set of constraints.`` + +messageCollection +~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``Each element of this collection should satisfy its own set of constraints.`` + +This is the message that will be shown if the validation fails +and the internal constraint is either :doc:`/reference/constraints/All` +or :doc:`/reference/constraints/Collection`. See option `message`_ for an example. + +.. include:: /reference/constraints/_groups-option.rst.inc + +.. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Bic.rst b/reference/constraints/Bic.rst index 6496ae63d54..076cbf29b6c 100644 --- a/reference/constraints/Bic.rst +++ b/reference/constraints/Bic.rst @@ -41,6 +41,19 @@ will contain a Business Identifier Code (BIC). protected $businessIdentifierCode; } + .. code-block:: php-attributes + + // src/Entity/Transaction.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Transaction + { + #[Assert\Bic] + protected $businessIdentifierCode; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -92,10 +105,6 @@ iban **type**: ``string`` **default**: ``null`` -.. versionadded:: 4.3 - - The ``iban`` option was introduced in Symfony 4.3. - An IBAN value to validate that its country code is the same as the BIC's one. ibanMessage @@ -103,10 +112,6 @@ ibanMessage **type**: ``string`` **default**: ``This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.`` -.. versionadded:: 4.3 - - The ``ibanMessage`` option was introduced in Symfony 4.3. - The default message supplied when the value does not pass the combined BIC/IBAN check. ibanPropertyPath @@ -114,10 +119,6 @@ ibanPropertyPath **type**: ``string`` **default**: ``null`` -.. versionadded:: 4.3 - - The ``ibanPropertyPath`` option was introduced in Symfony 4.3. - It defines the object property whose value stores the IBAN used to check the BIC with. For example, if you want to compare the ``$bic`` property of some object diff --git a/reference/constraints/Blank.rst b/reference/constraints/Blank.rst index 5f0c6191fc1..fbbd693e013 100644 --- a/reference/constraints/Blank.rst +++ b/reference/constraints/Blank.rst @@ -45,6 +45,19 @@ of an ``Author`` class were blank, you could do the following: protected $firstName; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Blank] + protected $firstName; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -102,6 +115,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst index 6985f3953e1..d15337ba9b5 100644 --- a/reference/constraints/Callback.rst +++ b/reference/constraints/Callback.rst @@ -50,6 +50,23 @@ Configuration } } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Context\ExecutionContextInterface; + + class Author + { + #[Assert\Callback] + public function validate(ExecutionContextInterface $context, $payload) + { + // ... + } + } + .. code-block:: yaml # config/validator/validation.yaml @@ -178,6 +195,19 @@ You can then use the following configuration to invoke this validator: { } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Acme\Validator; + use Symfony\Component\Validator\Constraints as Assert; + + #[Assert\Callback([Validator::class, 'validate'])] + class Author + { + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/CardScheme.rst b/reference/constraints/CardScheme.rst index 6362d9932ee..1a196970525 100644 --- a/reference/constraints/CardScheme.rst +++ b/reference/constraints/CardScheme.rst @@ -41,6 +41,22 @@ on an object that will contain a credit card number. protected $cardNumber; } + .. code-block:: php-attributes + + // src/Entity/Transaction.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Transaction + { + #[Assert\CardScheme( + schemes: [Assert\CardScheme::VISA], + message: 'Your credit card number is invalid.', + )] + protected $cardNumber; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -85,7 +101,7 @@ on an object that will contain a credit card number. { $metadata->addPropertyConstraint('cardNumber', new Assert\CardScheme([ 'schemes' => [ - 'VISA', + Assert\CardScheme::VISA, ], 'message' => 'Your credit card number is invalid.', ])); @@ -112,8 +128,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc schemes @@ -138,10 +159,6 @@ Valid values are: * ``UATP`` * ``VISA`` -.. versionadded:: 4.3 - - The ``UATP`` and ``MIR`` number schemes were introduced in Symfony 4.3. - For more information about the used schemes, see `Wikipedia: Issuer identification number (IIN)`_. diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst index b1407c8add0..4afa6b516d9 100644 --- a/reference/constraints/Choice.rst +++ b/reference/constraints/Choice.rst @@ -58,6 +58,24 @@ If your valid choice list is simple, you can pass them in directly via the protected $genre; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + const GENRES = ['fiction', 'non-fiction']; + + #[Assert\Choice(['New York', 'Berlin', 'Tokyo'])] + protected $city; + + #[Assert\Choice(choices: Author::GENRES, message: 'Choose a valid genre.')] + protected $genre; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -160,6 +178,19 @@ constraint. protected $genre; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Choice(callback: 'getGenres')] + protected $genre; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -225,6 +256,20 @@ you can pass the class name and the method as an array. protected $genre; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use App\Entity\Genre + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Choice(callback: [Genre::class, 'getGenres'])] + protected $genre; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -322,10 +367,6 @@ Parameter Description ``{{ value }}`` The current (invalid) value ================= ============================================================ -.. versionadded:: 4.3 - - The ``{{ choices }}`` parameter was introduced in Symfony 4.3. - message ~~~~~~~ @@ -371,10 +412,6 @@ Parameter Description ``{{ value }}`` The current (invalid) value ================= ============================================================ -.. versionadded:: 4.3 - - The ``{{ choices }}`` parameter was introduced in Symfony 4.3. - multiple ~~~~~~~~ @@ -400,6 +437,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Compound.rst b/reference/constraints/Compound.rst new file mode 100644 index 00000000000..6e0ab5db139 --- /dev/null +++ b/reference/constraints/Compound.rst @@ -0,0 +1,111 @@ +Compound +======== + +To the contrary to the other constraints, this constraint cannot be used on its own. +Instead, it allows you to create your own set of reusable constraints, representing +rules to use consistently across your application, by extending the constraint. + +.. versionadded:: 5.1 + + The ``Compound`` constraint was introduced in Symfony 5.1. + +========== =================================================================== +Applies to :ref:`class ` or :ref:`property or method ` +Options - `groups`_ + - `payload`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\Compound` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\CompoundValidator` +========== =================================================================== + +Basic Usage +----------- + +Suppose that you have different places where a user password must be validated, +you can create your own named set or requirements to be reused consistently everywhere:: + + // src/Validator/Constraints/PasswordRequirements.php + namespace App\Validator\Constraints; + + use Symfony\Component\Validator\Constraints\Compound; + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @Annotation + */ + class PasswordRequirements extends Compound + { + protected function getConstraints(array $options): array + { + return [ + new Assert\NotBlank(), + new Assert\Type('string'), + new Assert\Length(['min' => 12]), + new Assert\NotCompromisedPassword(), + ]; + } + } + +You can now use it anywhere you need it: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/User/RegisterUser.php + namespace App\User; + + use App\Validator\Constraints as AcmeAssert; + + class RegisterUser + { + /** + * @AcmeAssert\PasswordRequirements() + */ + public $password; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\User\RegisterUser: + properties: + password: + - App\Validator\Constraints\PasswordRequirements: ~ + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // src/User/RegisterUser.php + namespace App\User; + + use App\Validator\Constraints as AcmeAssert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class RegisterUser + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('password', new AcmeAssert\PasswordRequirements()); + } + } + +Options +------- + +.. include:: /reference/constraints/_groups-option.rst.inc + +.. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Count.rst b/reference/constraints/Count.rst index 2ff99b5adbb..c40294a8684 100644 --- a/reference/constraints/Count.rst +++ b/reference/constraints/Count.rst @@ -6,7 +6,9 @@ Countable) element count is *between* some minimum and maximum value. ========== =================================================================== Applies to :ref:`property or method ` -Options - `exactMessage`_ +Options - `divisibleBy`_ + - `divisibleByMessage`_ + - `exactMessage`_ - `groups`_ - `max`_ - `maxMessage`_ @@ -45,6 +47,24 @@ you might add the following: protected $emails = []; } + .. code-block:: php-attributes + + // src/Entity/Participant.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + #[Assert\Count( + min: 1, + max: 5, + minMessage: 'You must specify at least one email', + maxMessage: 'You cannot specify more than {{ limit }} emails', + )] + protected $emails = []; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -101,6 +121,44 @@ you might add the following: Options ------- +divisibleBy +~~~~~~~~~~~ + +**type**: ``integer`` **default**: null + +.. versionadded:: 5.1 + + The ``divisibleBy`` option was introduced in Symfony 5.1. + +Validates that the number of elements of the given collection is divisible by +a certain number. + +.. seealso:: + + If you need to validate that other types of data different from collections + are divisible by a certain number, use the + :doc:`DivisibleBy ` constraint. + +divisibleByMessage +~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The number of elements in this collection should be a multiple of {{ compared_value }}.`` + +.. versionadded:: 5.1 + + The ``divisibleByMessage`` option was introduced in Symfony 5.1. + +The message that will be shown if the number of elements of the given collection +is not divisible by the number defined in the ``divisibleBy`` option. + +You can use the following parameters in this message: + +======================== =================================================== +Parameter Description +======================== =================================================== +``{{ compared_value }}`` The number configured in the ``divisibleBy`` option +======================== =================================================== + exactMessage ~~~~~~~~~~~~ diff --git a/reference/constraints/Country.rst b/reference/constraints/Country.rst index 4582a930cad..62bf38bf2ba 100644 --- a/reference/constraints/Country.rst +++ b/reference/constraints/Country.rst @@ -5,7 +5,8 @@ Validates that a value is a valid `ISO 3166-1 alpha-2`_ country code. ========== =================================================================== Applies to :ref:`property or method ` -Options - `groups`_ +Options - `alpha3`_ + - `groups`_ - `message`_ - `payload`_ Class :class:`Symfony\\Component\\Validator\\Constraints\\Country` @@ -32,6 +33,19 @@ Basic Usage protected $country; } + .. code-block:: php-attributes + + // src/Entity/User.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class User + { + #[Assert\Country] + protected $country; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -76,6 +90,19 @@ Basic Usage Options ------- +alpha3 +~~~~~~ + +.. versionadded:: 5.1 + + The ``alpha3`` option was introduced in Symfony 5.1. + +**type**: ``boolean`` **default**: ``false`` + +If this option is ``true``, the constraint checks that the value is a +`ISO 3166-1 alpha-3`_ three-letter code (e.g. France = ``FRA``) instead +of the default `ISO 3166-1 alpha-2`_ two-letter code (e.g. France = ``FR``). + .. include:: /reference/constraints/_groups-option.rst.inc ``message`` @@ -96,3 +123,5 @@ Parameter Description .. include:: /reference/constraints/_payload-option.rst.inc .. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes +.. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3#Current_codes + diff --git a/reference/constraints/Currency.rst b/reference/constraints/Currency.rst index 901a989010b..e481c0ce01d 100644 --- a/reference/constraints/Currency.rst +++ b/reference/constraints/Currency.rst @@ -35,6 +35,19 @@ a valid currency, you could do the following: protected $currency; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\Currency] + protected $currency; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -94,8 +107,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc .. _`3-letter ISO 4217`: https://en.wikipedia.org/wiki/ISO_4217 diff --git a/reference/constraints/Date.rst b/reference/constraints/Date.rst index db55de84dd6..7376195960a 100644 --- a/reference/constraints/Date.rst +++ b/reference/constraints/Date.rst @@ -34,6 +34,19 @@ Basic Usage protected $birthday; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Date] + protected $birthday; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -98,6 +111,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/DateTime.rst b/reference/constraints/DateTime.rst index 41b4db2acc0..7e5501b5515 100644 --- a/reference/constraints/DateTime.rst +++ b/reference/constraints/DateTime.rst @@ -35,6 +35,22 @@ Basic Usage protected $createdAt; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + /** + * @var string A "Y-m-d H:i:s" formatted value + */ + #[Assert\DateTime] + protected $createdAt; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -107,6 +123,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/DivisibleBy.rst b/reference/constraints/DivisibleBy.rst index f4ae78ab0f8..3728080690e 100644 --- a/reference/constraints/DivisibleBy.rst +++ b/reference/constraints/DivisibleBy.rst @@ -3,6 +3,12 @@ DivisibleBy Validates that a value is divisible by another value, defined in the options. +.. seealso:: + + If you need to validate that the number of elements in a collection is + divisible by a certain number, use the :doc:`Count ` + constraint with the ``divisibleBy`` option. + ========== =================================================================== Applies to :ref:`property or method ` Options - `groups`_ @@ -46,6 +52,24 @@ The following constraints ensure that: protected $quantity; } + .. code-block:: php-attributes + + // src/Entity/Item.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Item + { + #[Assert\DivisibleBy(0.25)] + protected $weight; + + #[Assert\DivisibleBy( + value: 5, + )] + protected $quantity; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Email.rst b/reference/constraints/Email.rst index 5b149f0bf5f..fd2f2576a90 100644 --- a/reference/constraints/Email.rst +++ b/reference/constraints/Email.rst @@ -6,9 +6,7 @@ cast to a string before being validated. ========== =================================================================== Applies to :ref:`property or method ` -Options - `checkHost`_ - - `checkMX`_ - - `groups`_ +Options - `groups`_ - `message`_ - `mode`_ - `normalizer`_ @@ -33,13 +31,27 @@ Basic Usage { /** * @Assert\Email( - * message = "The email '{{ value }}' is not a valid email.", - * checkMX = true + * message = "The email '{{ value }}' is not a valid email." * ) */ protected $email; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Email( + message: 'The email {{ value }} is not a valid email.', + )] + protected $email; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -48,7 +60,6 @@ Basic Usage email: - Email: message: The email "{{ value }}" is not a valid email. - checkMX: true .. code-block:: xml @@ -62,7 +73,6 @@ Basic Usage - @@ -82,7 +92,6 @@ Basic Usage { $metadata->addPropertyConstraint('email', new Assert\Email([ 'message' => 'The email "{{ value }}" is not a valid email.', - 'checkMX' => true, ])); } } @@ -92,36 +101,6 @@ Basic Usage Options ------- -checkHost -~~~~~~~~~ - -**type**: ``boolean`` **default**: ``false`` - -.. deprecated:: 4.2 - - This option was deprecated in Symfony 4.2. - -If true, then the :phpfunction:`checkdnsrr` PHP function will be used to -check the validity of the MX *or* the A *or* the AAAA record of the host -of the given email. - -checkMX -~~~~~~~ - -**type**: ``boolean`` **default**: ``false`` - -.. deprecated:: 4.2 - - This option was deprecated in Symfony 4.2. - -If true, then the :phpfunction:`checkdnsrr` PHP function will be used to -check the validity of the MX record of the host of the given email. - -.. caution:: - - This option is not reliable because it depends on the network conditions - and some valid servers refuse to respond to those requests. - .. include:: /reference/constraints/_groups-option.rst.inc message @@ -137,8 +116,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + mode ~~~~ diff --git a/reference/constraints/EqualTo.rst b/reference/constraints/EqualTo.rst index 153d13a3098..75d80043cda 100644 --- a/reference/constraints/EqualTo.rst +++ b/reference/constraints/EqualTo.rst @@ -52,6 +52,24 @@ and that the ``age`` is ``20``, you could do the following: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\EqualTo("Mary")] + protected $firstName; + + #[Assert\EqualTo( + value: 20, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Expression.rst b/reference/constraints/Expression.rst index f3af00f1d3a..264ae3b02fc 100644 --- a/reference/constraints/Expression.rst +++ b/reference/constraints/Expression.rst @@ -78,6 +78,22 @@ One way to accomplish this is with the Expression constraint: // ... } + .. code-block:: php-attributes + + // src/Model/BlogPost.php + namespace App\Model; + + use Symfony\Component\Validator\Constraints as Assert; + + #[Assert\Expression( + "this.getCategory() in ['php', 'symfony'] or !this.isTechnicalPost()", + message: 'If this is a tech post, the category should be either php or symfony!', + )] + class BlogPost + { + // ... + } + .. code-block:: yaml # config/validator/validation.yaml @@ -163,6 +179,26 @@ more about the expression language syntax, see // ... } + .. code-block:: php-attributes + + // src/Model/BlogPost.php + namespace App\Model; + + use Symfony\Component\Validator\Constraints as Assert; + + class BlogPost + { + // ... + + #[Assert\Expression( + "this.getCategory() in ['php', 'symfony'] or value == false", + message: 'If this is a tech post, the category should be either php or symfony!', + )] + private $isTechnicalPost; + + // ... + } + .. code-block:: yaml # config/validator/validation.yaml @@ -260,8 +296,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc values @@ -294,6 +335,24 @@ type (numeric, boolean, strings, null, etc.) // ... } + .. code-block:: php-attributes + + // src/Model/Analysis.php + namespace App\Model; + + use Symfony\Component\Validator\Constraints as Assert; + + class Analysis + { + #[Assert\Expression( + 'value + error_margin < threshold', + values: ['error_margin' => 0.25, 'threshold' => 1.5], + )] + private $metric; + + // ... + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/ExpressionLanguageSyntax.rst b/reference/constraints/ExpressionLanguageSyntax.rst new file mode 100644 index 00000000000..0ab308e6bae --- /dev/null +++ b/reference/constraints/ExpressionLanguageSyntax.rst @@ -0,0 +1,150 @@ +ExpressionLanguageSyntax +======================== + +This constraint checks that the value is valid as an `ExpressionLanguage`_ +expression. + +.. versionadded:: 5.1 + + The ``ExpressionLanguageSyntax`` constraint was introduced in Symfony 5.1. + +========== =================================================================== +Applies to :ref:`property or method ` +Options - `allowedVariables`_ + - `groups`_ + - `message`_ + - `payload`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageSyntax` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\ExpressionLanguageSyntaxValidator` +========== =================================================================== + +Basic Usage +----------- + +The following constraints ensure that: + +* the ``promotion`` property stores a value which is valid as an + ExpressionLanguage expression; +* the ``shippingOptions`` property also ensures that the expression only uses + certain variables. + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + /** + * @Assert\ExpressionLanguageSyntax + */ + protected $promotion; + + /** + * @Assert\ExpressionLanguageSyntax( + * allowedVariables={"user", "shipping_centers"} + * ) + */ + protected $shippingOptions; + } + + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\ExpressionLanguageSyntax] + protected $promotion; + + #[Assert\ExpressionLanguageSyntax( + allowedVariables: ['user', 'shipping_centers'], + )] + protected $shippingOptions; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\Entity\Order: + properties: + promotion: + - ExpressionLanguageSyntax: ~ + shippingOptions: + - ExpressionLanguageSyntax: + allowedVariables: ['user', 'shipping_centers'] + + .. code-block:: xml + + + + + + + + + + + + + + + + + + .. code-block:: php + + // src/Entity/Student.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class Order + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('promotion', new Assert\ExpressionLanguageSyntax()); + + $metadata->addPropertyConstraint('shippingOptions', new Assert\ExpressionLanguageSyntax([ + 'allowedVariables' => ['user', 'shipping_centers'], + ])); + } + } + +Options +------- + +allowedVariables +~~~~~~~~~~~~~~~~ + +**type**: ``array`` or ``null`` **default**: ``null`` + +If this option is defined, the expression can only use the variables whose names +are included in this option. Unset this option or set its value to ``null`` to +allow any variables. + +.. include:: /reference/constraints/_groups-option.rst.inc + +message +~~~~~~~ + +**type**: ``string`` **default**: ``This value should be a valid expression.`` + +This is the message displayed when the validation fails. + +.. include:: /reference/constraints/_payload-option.rst.inc + +.. _`ExpressionLanguage`: https://symfony.com/components/ExpressionLanguage diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst index a865349f913..e57ff888488 100644 --- a/reference/constraints/File.rst +++ b/reference/constraints/File.rst @@ -93,6 +93,23 @@ below a certain file size and a valid PDF, add the following: protected $bioFile; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\File( + maxSize: '1024k', + mimeTypes: ['application/pdf', 'application/x-pdf'], + mimeTypesMessage: 'Please upload a valid PDF', + )] + protected $bioFile; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -252,10 +269,6 @@ You can find a list of existing mime types on the `IANA website`_. (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/GreaterThan.rst b/reference/constraints/GreaterThan.rst index d27017fdbe5..617fc71f2a0 100644 --- a/reference/constraints/GreaterThan.rst +++ b/reference/constraints/GreaterThan.rst @@ -49,6 +49,24 @@ The following constraints ensure that: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\GreaterThan(5)] + protected $siblings; + + #[Assert\GreaterThan( + value: 18, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -126,6 +144,19 @@ that a date must at least be the next day: protected $deliveryDate; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\GreaterThan('today')] + protected $deliveryDate; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -185,6 +216,19 @@ dates. If you want to fix the timezone, append it to the date string: protected $deliveryDate; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\GreaterThan('today UTC')] + protected $deliveryDate; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -245,6 +289,19 @@ current time: protected $deliveryDate; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\GreaterThan('+5 hours')] + protected $deliveryDate; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/GreaterThanOrEqual.rst b/reference/constraints/GreaterThanOrEqual.rst index 8a054e6bbb9..c09d4e250e0 100644 --- a/reference/constraints/GreaterThanOrEqual.rst +++ b/reference/constraints/GreaterThanOrEqual.rst @@ -48,6 +48,24 @@ The following constraints ensure that: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\GreaterThanOrEqual(5)] + protected $siblings; + + #[Assert\GreaterThanOrEqual( + value: 18, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -125,6 +143,19 @@ that a date must at least be the current day: protected $deliveryDate; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\GreaterThanOrEqual('today')] + protected $deliveryDate; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -184,6 +215,19 @@ dates. If you want to fix the timezone, append it to the date string: protected $deliveryDate; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\GreaterThanOrEqual('today UTC')] + protected $deliveryDate; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -244,6 +288,19 @@ current time: protected $deliveryDate; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\GreaterThanOrEqual('+5 hours')] + protected $deliveryDate; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Hostname.rst b/reference/constraints/Hostname.rst new file mode 100644 index 00000000000..0b0a02bbb60 --- /dev/null +++ b/reference/constraints/Hostname.rst @@ -0,0 +1,153 @@ +Hostname +======== + +This constraint ensures that the given value is a valid host name (internally it +uses the ``FILTER_VALIDATE_DOMAIN`` option of the :phpfunction:`filter_var` PHP +function). + +.. versionadded:: 5.1 + + The ``Hostname`` constraint was introduced in Symfony 5.1. + +========== =================================================================== +Applies to :ref:`property or method ` +Options - `groups`_ + - `message`_ + - `payload`_ + - `requireTld`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\Hostname` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\HostnameValidator` +========== =================================================================== + +Basic Usage +----------- + +To use the Hostname validator, apply it to a property on an object that +will contain a host name. + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Entity/ServerSettings.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class ServerSettings + { + /** + * @Assert\Hostname(message="The server name must be a valid hostname.") + */ + protected $name; + } + + .. code-block:: php-attributes + + // src/Entity/ServerSettings.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class ServerSettings + { + #[Assert\Hostname(message: 'The server name must be a valid hostname.')] + protected $name; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\Entity\ServerSettings: + properties: + name: + - Hostname: + message: The server name must be a valid hostname. + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // src/Entity/ServerSettings.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class ServerSettings + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('name', new Assert\Hostname([ + 'message' => 'The server name must be a valid hostname.', + ])); + } + } + +The following top-level domains (TLD) are reserved according to `RFC 2606`_ and +that's why hostnames containing them are not considered valid: ``.example``, +``.invalid``, ``.localhost``, and ``.test``. + +.. include:: /reference/constraints/_empty-values-are-valid.rst.inc + +Options +------- + +.. include:: /reference/constraints/_groups-option.rst.inc + +``message`` +~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value is not a valid hostname.`` + +The default message supplied when the value is not a valid hostname. + +You can use the following parameters in this message: + +=============== ============================================================== +Parameter Description +=============== ============================================================== +``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label +=============== ============================================================== + +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + +.. include:: /reference/constraints/_payload-option.rst.inc + +``requireTld`` +~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +By default, hostnames are considered valid only when they are fully qualified +and include their TLDs (top-level domain names). For instance, ``example.com`` +is valid but ``example`` is not. + +Set this option to ``false`` to not require any TLD in the hostnames. + +.. note:: + + This constraint does not validate that the given TLD value is included in + the `list of official top-level domains`_ (because that list is growing + continuously and it's hard to keep track of it). + +.. _`RFC 2606`: https://tools.ietf.org/html/rfc2606 +.. _`list of official top-level domains`: https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains diff --git a/reference/constraints/Iban.rst b/reference/constraints/Iban.rst index aa3caeb67f8..dcd60e3f408 100644 --- a/reference/constraints/Iban.rst +++ b/reference/constraints/Iban.rst @@ -40,6 +40,21 @@ will contain an International Bank Account Number. protected $bankAccountNumber; } + .. code-block:: php-attributes + + // src/Entity/Transaction.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Transaction + { + #[Assert\Iban( + message: 'This is not a valid International Bank Account Number (IBAN).', + )] + protected $bankAccountNumber; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -108,8 +123,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc .. _`International Bank Account Number (IBAN)`: https://en.wikipedia.org/wiki/International_Bank_Account_Number diff --git a/reference/constraints/IdenticalTo.rst b/reference/constraints/IdenticalTo.rst index 10f1fb52342..7dc71b475f0 100644 --- a/reference/constraints/IdenticalTo.rst +++ b/reference/constraints/IdenticalTo.rst @@ -54,6 +54,24 @@ The following constraints ensure that: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\IdenticalTo("Mary")] + protected $firstName; + + #[Assert\IdenticalTo( + value: 20, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst index e8b492bf4ae..5ffded599b5 100644 --- a/reference/constraints/Image.rst +++ b/reference/constraints/Image.rst @@ -100,6 +100,24 @@ that it is between a certain size, add the following: protected $headshot; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Image( + minWidth: 200, + maxWidth: 400, + minHeight: 200, + maxHeight: 400, + )] + protected $headshot; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -180,6 +198,22 @@ following code: protected $headshot; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Image( + allowLandscape: false, + allowPortrait: false, + )] + protected $headshot; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Ip.rst b/reference/constraints/Ip.rst index 60865c024bc..3686d6bfc41 100644 --- a/reference/constraints/Ip.rst +++ b/reference/constraints/Ip.rst @@ -36,6 +36,19 @@ Basic Usage protected $ipAddress; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Ip] + protected $ipAddress; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -95,8 +108,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_normalizer-option.rst.inc .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/IsFalse.rst b/reference/constraints/IsFalse.rst index e476fb25387..7cb38f308bc 100644 --- a/reference/constraints/IsFalse.rst +++ b/reference/constraints/IsFalse.rst @@ -58,6 +58,24 @@ method returns **false**: } } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\IsFalse( + message: "You've entered an invalid state." + )] + public function isStateInvalid() + { + // ... + } + } + .. code-block:: yaml # config/validator/validation.yaml @@ -125,6 +143,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/IsNull.rst b/reference/constraints/IsNull.rst index 2bded13311d..6fcd1e462ad 100644 --- a/reference/constraints/IsNull.rst +++ b/reference/constraints/IsNull.rst @@ -39,6 +39,19 @@ of an ``Author`` class exactly equal to ``null``, you could do the following: protected $firstName; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\IsNull] + protected $firstName; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -96,6 +109,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/IsTrue.rst b/reference/constraints/IsTrue.rst index 2066b6d4e73..dea5d9c5468 100644 --- a/reference/constraints/IsTrue.rst +++ b/reference/constraints/IsTrue.rst @@ -60,6 +60,24 @@ Then you can validate this method with ``IsTrue`` as follows: } } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + protected $token; + + #[Assert\IsTrue(message: 'The token is invalid.')] + public function isTokenValid() + { + return $this->token == $this->generateToken(); + } + } + .. code-block:: yaml # config/validator/validation.yaml @@ -129,6 +147,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Isbn.rst b/reference/constraints/Isbn.rst index 2820fce0052..9bfab789825 100644 --- a/reference/constraints/Isbn.rst +++ b/reference/constraints/Isbn.rst @@ -43,6 +43,22 @@ on an object that will contain an ISBN. protected $isbn; } + .. code-block:: php-attributes + + // src/Entity/Book.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + #[Assert\Isbn( + type: Assert\Isbn::ISBN_10, + message: 'This value is not valid.', + )] + protected $isbn; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -84,7 +100,7 @@ on an object that will contain an ISBN. public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('isbn', new Assert\Isbn([ - 'type' => 'isbn10', + 'type' => Assert\Isbn::ISBN_10, 'message' => 'This value is not valid.', ])); } @@ -109,8 +125,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_groups-option.rst.inc isbn10Message @@ -127,8 +148,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + isbn13Message ~~~~~~~~~~~~~ @@ -143,8 +169,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + message ~~~~~~~ @@ -159,8 +190,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc type diff --git a/reference/constraints/Isin.rst b/reference/constraints/Isin.rst new file mode 100644 index 00000000000..3efab915437 --- /dev/null +++ b/reference/constraints/Isin.rst @@ -0,0 +1,117 @@ +Isin +==== + +Validates that a value is a valid +`International Securities Identification Number (ISIN)`_. + +========== =================================================================== +Applies to :ref:`property or method ` +Options - `groups`_ + - `message`_ + - `payload`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\Isin` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\IsinValidator` +========== =================================================================== + +Basic Usage +----------- + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Entity/UnitAccount.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class UnitAccount + { + /** + * @Assert\Isin + */ + protected $isin; + } + + .. code-block:: php-attributes + + // src/Entity/UnitAccount.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class UnitAccount + { + #[Assert\Isin] + protected $isin; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\Entity\UnitAccount: + properties: + isin: + - Isin: ~ + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // src/Entity/UnitAccount.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class UnitAccount + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('isin', new Assert\Isin()); + } + } + +.. include:: /reference/constraints/_empty-values-are-valid.rst.inc + +Options +------- + +.. include:: /reference/constraints/_groups-option.rst.inc + +message +~~~~~~~ + +**type**: ``string`` default: ``This value is not a valid International Securities Identification Number (ISIN).`` + +The message shown if the given value is not a valid ISIN. + +You can use the following parameters in this message: + +=============== ============================================================== +Parameter Description +=============== ============================================================== +``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label +=============== ============================================================== + +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + +.. include:: /reference/constraints/_payload-option.rst.inc + +.. _`International Securities Identification Number (ISIN)`: https://en.wikipedia.org/wiki/International_Securities_Identification_Number diff --git a/reference/constraints/Issn.rst b/reference/constraints/Issn.rst index 374cc7d2751..8b8d0826610 100644 --- a/reference/constraints/Issn.rst +++ b/reference/constraints/Issn.rst @@ -35,6 +35,19 @@ Basic Usage protected $issn; } + .. code-block:: php-attributes + + // src/Entity/Journal.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Journal + { + #[Assert\Issn] + protected $issn; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -102,8 +115,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc requireHyphen diff --git a/reference/constraints/Json.rst b/reference/constraints/Json.rst index 6e8318077da..cd1abf69d6c 100644 --- a/reference/constraints/Json.rst +++ b/reference/constraints/Json.rst @@ -35,6 +35,21 @@ The ``Json`` constraint can be applied to a property or a "getter" method: private $chapters; } + .. code-block:: php-attributes + + // src/Entity/Book.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + #[Assert\Json( + message: "You've entered an invalid Json." + )] + private $chapters; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Language.rst b/reference/constraints/Language.rst index 70d1e2e51cc..0d9522dc882 100644 --- a/reference/constraints/Language.rst +++ b/reference/constraints/Language.rst @@ -6,7 +6,8 @@ Validates that a value is a valid language *Unicode language identifier* ========== =================================================================== Applies to :ref:`property or method ` -Options - `groups`_ +Options - `alpha3`_ + - `groups`_ - `message`_ - `payload`_ Class :class:`Symfony\\Component\\Validator\\Constraints\\Language` @@ -33,6 +34,19 @@ Basic Usage protected $preferredLanguage; } + .. code-block:: php-attributes + + // src/Entity/User.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class User + { + #[Assert\Language] + protected $preferredLanguage; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -77,6 +91,19 @@ Basic Usage Options ------- +alpha3 +~~~~~~ + +.. versionadded:: 5.1 + + The ``alpha3`` option was introduced in Symfony 5.1. + +**type**: ``boolean`` **default**: ``false`` + +If this option is ``true``, the constraint checks that the value is a +`ISO 639-2`_ three-letter code (e.g. French = ``fra``) instead of the default +`ISO 639-1`_ two-letter code (e.g. French = ``fr``). + .. include:: /reference/constraints/_groups-option.rst.inc ``message`` @@ -92,6 +119,14 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc + +.. _`ISO 639-1`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +.. _`ISO 639-2`: https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst index 11ae53ae6b9..13800f7daea 100644 --- a/reference/constraints/Length.rst +++ b/reference/constraints/Length.rst @@ -42,13 +42,31 @@ and "50", you might add the following: * min = 2, * max = 50, * minMessage = "Your first name must be at least {{ limit }} characters long", - * maxMessage = "Your first name cannot be longer than {{ limit }} characters", - * allowEmptyString = false + * maxMessage = "Your first name cannot be longer than {{ limit }} characters" * ) */ protected $firstName; } + .. code-block:: php-attributes + + // src/Entity/Participant.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + #[Assert\Length( + min: 2, + max: 50, + minMessage: 'Your first name must be at least {{ limit }} characters long', + maxMessage: 'Your first name cannot be longer than {{ limit }} characters', + )] + protected $firstName; + } + + .. code-block:: yaml # config/validator/validation.yaml @@ -60,7 +78,6 @@ and "50", you might add the following: max: 50 minMessage: 'Your first name must be at least {{ limit }} characters long' maxMessage: 'Your first name cannot be longer than {{ limit }} characters' - allowEmptyString: false .. code-block:: xml @@ -81,7 +98,6 @@ and "50", you might add the following: - @@ -104,7 +120,6 @@ and "50", you might add the following: 'max' => 50, 'minMessage' => 'Your first name must be at least {{ limit }} characters long', 'maxMessage' => 'Your first name cannot be longer than {{ limit }} characters', - 'allowEmptyString' => false, ])); } } @@ -117,16 +132,18 @@ Options allowEmptyString ~~~~~~~~~~~~~~~~ -**type**: ``boolean`` **default**: ``true`` +**type**: ``boolean`` **default**: ``false`` -.. versionadded:: 4.4 +.. deprecated:: 5.2 - The ``allowEmptyString`` option was introduced in Symfony 4.4. + The ``allowEmptyString`` option is deprecated since Symfony 5.2. If you + want to allow empty strings too, combine the ``Length`` constraint with + the :doc:`Blank constraint ` inside the + :doc:`AtLeastOneOf constraint `. -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. +If set to ``true``, empty strings are considered valid (which is the same +behavior as previous Symfony versions). The default ``false`` value considers +empty strings not valid. .. caution:: diff --git a/reference/constraints/LessThan.rst b/reference/constraints/LessThan.rst index abd0aab721c..495d3f4356a 100644 --- a/reference/constraints/LessThan.rst +++ b/reference/constraints/LessThan.rst @@ -49,6 +49,24 @@ The following constraints ensure that: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThan(5)] + protected $siblings; + + #[Assert\LessThan( + value: 80, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -126,6 +144,19 @@ that a date must be in the past like this: protected $dateOfBirth; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThan('today')] + protected $dateOfBirth; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -185,6 +216,19 @@ dates. If you want to fix the timezone, append it to the date string: protected $dateOfBirth; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThan('today UTC')] + protected $dateOfBirth; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -244,6 +288,19 @@ can check that a person must be at least 18 years old like this: protected $dateOfBirth; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThan('-18 years')] + protected $dateOfBirth; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/LessThanOrEqual.rst b/reference/constraints/LessThanOrEqual.rst index 42ec3e939e5..47d06cfc601 100644 --- a/reference/constraints/LessThanOrEqual.rst +++ b/reference/constraints/LessThanOrEqual.rst @@ -48,6 +48,24 @@ The following constraints ensure that: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThanOrEqual(5)] + protected $siblings; + + #[Assert\LessThanOrEqual( + value: 80, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -125,6 +143,19 @@ that a date must be today or in the past like this: protected $dateOfBirth; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThanOrEqual('today')] + protected $dateOfBirth; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -184,6 +215,19 @@ dates. If you want to fix the timezone, append it to the date string: protected $dateOfBirth; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThanOrEqual('today UTC')] + protected $dateOfBirth; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -243,6 +287,19 @@ can check that a person must be at least 18 years old like this: protected $dateOfBirth; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\LessThanOrEqual('-18 years')] + protected $dateOfBirth; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Locale.rst b/reference/constraints/Locale.rst index d7f16293b3a..936cfd24089 100644 --- a/reference/constraints/Locale.rst +++ b/reference/constraints/Locale.rst @@ -8,10 +8,13 @@ the two letter `ISO 639-1`_ *language* code (e.g. ``fr``), or the language code followed by an underscore (``_``) and the `ISO 3166-1 alpha-2`_ *country* code (e.g. ``fr_FR`` for French/France). +The given locale values are *canonicalized* before validating them to avoid +issues with wrong uppercase/lowercase values and to remove unneeded elements +(e.g. ``FR-fr.utf8`` will be validated as ``fr_FR``). + ========== =================================================================== Applies to :ref:`property or method ` -Options - `canonicalize`_ - - `groups`_ +Options - `groups`_ - `message`_ - `payload`_ Class :class:`Symfony\\Component\\Validator\\Constraints\\Locale` @@ -40,6 +43,21 @@ Basic Usage protected $locale; } + .. code-block:: php-attributes + + // src/Entity/User.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class User + { + #[Assert\Locale( + canonicalize: true, + )] + protected $locale; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -89,19 +107,6 @@ Basic Usage Options ------- -canonicalize -~~~~~~~~~~~~ - -**type**: ``boolean`` **default**: ``false`` - -.. deprecated:: 4.1 - - Using this option with value ``false`` was deprecated in Symfony 4.1 and it - will throw an exception in Symfony 5.0. Use ``true`` instead. - -If ``true``, the :phpmethod:`Locale::canonicalize` method will be applied before checking -the validity of the given locale (e.g. ``FR-fr.utf8`` is transformed into ``fr_FR``). - .. include:: /reference/constraints/_groups-option.rst.inc ``message`` @@ -117,8 +122,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc .. _`ICU format locale IDs`: http://userguide.icu-project.org/locale diff --git a/reference/constraints/Luhn.rst b/reference/constraints/Luhn.rst index 6d322349da4..24eb9b91947 100644 --- a/reference/constraints/Luhn.rst +++ b/reference/constraints/Luhn.rst @@ -37,6 +37,19 @@ will contain a credit card number. protected $cardNumber; } + .. code-block:: php-attributes + + // src/Entity/Transaction.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Transaction + { + #[Assert\Luhn(message: 'Please check your credit card number.')] + protected $cardNumber; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -101,8 +114,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc .. _`Luhn algorithm`: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/reference/constraints/Negative.rst b/reference/constraints/Negative.rst index 4e3edee87f2..0ee0bdcf3ea 100644 --- a/reference/constraints/Negative.rst +++ b/reference/constraints/Negative.rst @@ -1,10 +1,6 @@ Negative ======== -.. versionadded:: 4.3 - - The ``Negative`` constraint was introduced in Symfony 4.3. - Validates that a value is a negative number. Zero is neither positive nor negative, so you must use :doc:`/reference/constraints/NegativeOrZero` if you want to allow zero as value. @@ -41,6 +37,19 @@ The following constraint ensures that the ``withdraw`` of a bank account protected $withdraw; } + .. code-block:: php-attributes + + // src/Entity/TransferItem.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class TransferItem + { + #[Assert\Negative] + protected $withdraw; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/NegativeOrZero.rst b/reference/constraints/NegativeOrZero.rst index 5a77a36ab67..8559a57babf 100644 --- a/reference/constraints/NegativeOrZero.rst +++ b/reference/constraints/NegativeOrZero.rst @@ -1,10 +1,6 @@ NegativeOrZero ============== -.. versionadded:: 4.3 - - The ``NegativeOrZero`` constraint was introduced in Symfony 4.3. - Validates that a value is a negative number or equal to zero. If you don't want to allow zero as value, use :doc:`/reference/constraints/Negative` instead. @@ -40,6 +36,19 @@ is a negative number or equal to zero: protected $level; } + .. code-block:: php-attributes + + // src/Entity/TransferItem.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class UnderGroundGarage + { + #[Assert\NegativeOrZero] + protected $level; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/NotBlank.rst b/reference/constraints/NotBlank.rst index 342293b2394..6aabec4803c 100644 --- a/reference/constraints/NotBlank.rst +++ b/reference/constraints/NotBlank.rst @@ -40,6 +40,19 @@ class were not blank, you could do the following: protected $firstName; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\NotBlank] + protected $firstName; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -90,10 +103,6 @@ allowNull If set to ``true``, ``null`` values are considered valid and won't trigger a constraint violation. -.. versionadded:: 4.3 - - The ``allowNull`` option was introduced in Symfony 4.3. - .. include:: /reference/constraints/_groups-option.rst.inc ``message`` @@ -109,8 +118,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_normalizer-option.rst.inc .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/NotCompromisedPassword.rst b/reference/constraints/NotCompromisedPassword.rst index 1eded1463f9..236dfbf5d9b 100644 --- a/reference/constraints/NotCompromisedPassword.rst +++ b/reference/constraints/NotCompromisedPassword.rst @@ -1,10 +1,6 @@ NotCompromisedPassword ====================== -.. versionadded:: 4.3 - - The ``NotCompromisedPassword`` constraint was introduced in Symfony 4.3. - Validates that the given password has not been compromised by checking that it is not included in any of the public data breaches tracked by `haveibeenpwned.com`_. @@ -42,6 +38,19 @@ The following constraint ensures that the ``rawPassword`` property of the protected $rawPassword; } + .. code-block:: php-attributes + + // src/Entity/User.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class User + { + #[Assert\NotCompromisedPassword] + protected $rawPassword; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/NotEqualTo.rst b/reference/constraints/NotEqualTo.rst index e1436657ae8..ec5fa5000b5 100644 --- a/reference/constraints/NotEqualTo.rst +++ b/reference/constraints/NotEqualTo.rst @@ -53,6 +53,24 @@ the following: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\NotEqualTo('Mary')] + protected $firstName; + + #[Assert\NotEqualTo( + value: 15, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/NotIdenticalTo.rst b/reference/constraints/NotIdenticalTo.rst index 66ccb871670..ab96bde3806 100644 --- a/reference/constraints/NotIdenticalTo.rst +++ b/reference/constraints/NotIdenticalTo.rst @@ -54,6 +54,24 @@ The following constraints ensure that: protected $age; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\NotIdenticalTo('Mary')] + protected $firstName; + + #[Assert\NotIdenticalTo( + value: 15, + )] + protected $age; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/NotNull.rst b/reference/constraints/NotNull.rst index 2c548b2eb3e..ccf8839434d 100644 --- a/reference/constraints/NotNull.rst +++ b/reference/constraints/NotNull.rst @@ -37,6 +37,19 @@ class were not strictly equal to ``null``, you would: protected $firstName; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\NotNull] + protected $firstName; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -94,6 +107,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Positive.rst b/reference/constraints/Positive.rst index bfed77a763c..6e5d80c9250 100644 --- a/reference/constraints/Positive.rst +++ b/reference/constraints/Positive.rst @@ -1,10 +1,6 @@ Positive ======== -.. versionadded:: 4.3 - - The ``Positive`` constraint was introduced in Symfony 4.3. - Validates that a value is a positive number. Zero is neither positive nor negative, so you must use :doc:`/reference/constraints/PositiveOrZero` if you want to allow zero as value. @@ -41,6 +37,19 @@ positive number (greater than zero): protected $income; } + .. code-block:: php-attributes + + // src/Entity/Employee.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Employee + { + #[Assert\Positive] + protected $income; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/PositiveOrZero.rst b/reference/constraints/PositiveOrZero.rst index cc592f824c6..08435c2054f 100644 --- a/reference/constraints/PositiveOrZero.rst +++ b/reference/constraints/PositiveOrZero.rst @@ -1,10 +1,6 @@ PositiveOrZero ============== -.. versionadded:: 4.3 - - The ``PositiveOrZero`` constraint was introduced in Symfony 4.3. - Validates that a value is a positive number or equal to zero. If you don't want to allow zero as value, use :doc:`/reference/constraints/Positive` instead. @@ -40,6 +36,19 @@ is positive or zero: protected $siblings; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\PositiveOrZero] + protected $siblings; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Range.rst b/reference/constraints/Range.rst index 5743d8d04ef..c499187ee66 100644 --- a/reference/constraints/Range.rst +++ b/reference/constraints/Range.rst @@ -6,6 +6,7 @@ Validates that a given number or ``DateTime`` object is *between* some minimum a ========== =================================================================== Applies to :ref:`property or method ` Options - `groups`_ + - `invalidDateTimeMessage`_ - `invalidMessage`_ - `max`_ - `maxMessage`_ @@ -46,6 +47,23 @@ you might add the following: protected $height; } + .. code-block:: php-attributes + + // src/Entity/Participant.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Participant + { + #[Assert\Range( + min: 120, + max: 180, + notInRangeMessage: 'You must be between {{ min }}cm and {{ max }}cm tall to enter', + )] + protected $height; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -124,6 +142,22 @@ date must lie within the current year like this: protected $startDate; } + .. code-block:: php-attributes + + // src/Entity/Event.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Event + { + #[Assert\Range( + min: 'first day of January', + max: 'first day of January next year', + )] + protected $startDate; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -194,6 +228,22 @@ dates. If you want to fix the timezone, append it to the date string: protected $startDate; } + .. code-block:: php-attributes + + // src/Entity/Event.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Event + { + #[Assert\Range( + min: 'first day of January UTC', + max: 'first day of January next year UTC', + )] + protected $startDate; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -264,6 +314,22 @@ can check that a delivery date starts within the next five hours like this: protected $deliveryDate; } + .. code-block:: php-attributes + + // src/Entity/Order.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Order + { + #[Assert\Range( + min: 'now', + max: '+5 hours', + )] + protected $deliveryDate; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -316,13 +382,33 @@ Options .. include:: /reference/constraints/_groups-option.rst.inc +invalidDateTimeMessage +~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value should be a valid number.`` + +.. versionadded:: 5.2 + + The ``invalidDateTimeMessage`` option was introduced in Symfony 5.2. + +The message displayed when the ``min`` and ``max`` values are PHP datetimes but +the given value is not. + +You can use the following parameters in this message: + +=============== ============================================================== +Parameter Description +=============== ============================================================== +``{{ value }}`` The current (invalid) value +=============== ============================================================== + invalidMessage ~~~~~~~~~~~~~~ **type**: ``string`` **default**: ``This value should be a valid number.`` -The message that will be shown if the underlying value is not a number (per -the :phpfunction:`is_numeric` PHP function). +The message displayed when the ``min`` and ``max`` values are numeric (per +the :phpfunction:`is_numeric` PHP function) but the given value is not. You can use the following parameters in this message: @@ -330,8 +416,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + max ~~~ @@ -363,11 +454,7 @@ 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. +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 @@ -411,11 +498,7 @@ 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. +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 @@ -433,10 +516,6 @@ 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. diff --git a/reference/constraints/Regex.rst b/reference/constraints/Regex.rst index d7e3c8f2ebd..a6217c892f7 100644 --- a/reference/constraints/Regex.rst +++ b/reference/constraints/Regex.rst @@ -41,6 +41,19 @@ more word characters at the beginning of your string: protected $description; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Regex('/^\w+/')] + protected $description; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -110,6 +123,23 @@ it a custom message: protected $firstName; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Regex( + pattern: '/\d/', + match: false, + message: 'Your name cannot contain a number', + )] + protected $firstName; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -203,6 +233,22 @@ need to specify the HTML5 compatible pattern in the ``htmlPattern`` option: protected $name; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Regex( + pattern: '/^[a-z]+$/i', + match: '^[a-zA-Z]+$' + )] + protected $name; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -275,8 +321,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + pattern ~~~~~~~ diff --git a/reference/constraints/Sequentially.rst b/reference/constraints/Sequentially.rst new file mode 100644 index 00000000000..21e088ba689 --- /dev/null +++ b/reference/constraints/Sequentially.rst @@ -0,0 +1,144 @@ +Sequentially +============ + +This constraint allows you to apply a set of rules that should be validated +step-by-step, allowing to interrupt the validation once the first violation is raised. + +As an alternative in situations ``Sequentially`` cannot solve, you may consider +using :doc:`GroupSequence ` which allows more control. + +.. versionadded:: 5.1 + + The ``Sequentially`` constraint was introduced in Symfony 5.1. + +========== =================================================================== +Applies to :ref:`property or method ` +Options - `constraints`_ + - `groups`_ + - `payload`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\Sequentially` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\SequentiallyValidator` +========== =================================================================== + +Basic Usage +----------- + +Suppose that you have a ``Place`` object with an ``$address`` property which +must match the following requirements: + +* it's a non-blank string +* of at least 10 chars long +* with a specific format +* and geolocalizable using an external service + +In such situations, you may encounter three issues: + +* the ``Length`` or ``Regex`` constraints may fail hard with a :class:`Symfony\\Component\\Validator\\Exception\\UnexpectedValueException` + exception if the actual value is not a string, as enforced by ``Type``. +* you may end with multiple error messages for the same property +* you may perform a useless and heavy external call to geolocalize the address, + while the format isn't valid. + +You can validate each of these constraints sequentially to solve these issues: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Localization/Place.php + namespace App\Localization; + + use App\Validator\Constraints as AcmeAssert; + use Symfony\Component\Validator\Constraints as Assert; + + class Place + { + /** + * @var string + * + * @Assert\Sequentially({ + * @Assert\NotNull(), + * @Assert\Type("string"), + * @Assert\Length(min=10), + * @Assert\Regex(Place::ADDRESS_REGEX), + * @AcmeAssert\Geolocalizable(), + * }) + */ + public $address; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\Localization\Place: + properties: + address: + - Sequentially: + - NotNull: ~ + - Type: string + - Length: { min: 10 } + - Regex: !php/const App\Localization\Place::ADDRESS_REGEX + - App\Validator\Constraints\Geolocalizable: ~ + + .. code-block:: xml + + + + + + + + + + string + + + + + + + + + + + + + .. code-block:: php + + // src/Localization/Place.php + namespace App\Localization; + + use App\Validator\Constraints as AcmeAssert; + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class Place + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('address', new Assert\Sequentially([ + new Assert\NotNull(), + new Assert\Type('string'), + new Assert\Length(['min' => 10]), + new Assert\Regex(self::ADDRESS_REGEX), + new AcmeAssert\Geolocalizable(), + ])); + } + } + +Options +------- + +``constraints`` +~~~~~~~~~~~~~~~ + +**type**: ``array`` [:ref:`default option `] + +This required option is the array of validation constraints that you want +to apply sequentially. + +.. include:: /reference/constraints/_groups-option.rst.inc + +.. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Time.rst b/reference/constraints/Time.rst index a8e362d22dc..fb8a9b337fb 100644 --- a/reference/constraints/Time.rst +++ b/reference/constraints/Time.rst @@ -37,6 +37,22 @@ of the day when the event starts: protected $startsAt; } + .. code-block:: php-attributes + + // src/Entity/Event.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Event + { + /** + * @var string A "H:i:s" formatted value + */ + #[Assert\Time] + protected $startsAt; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -101,6 +117,11 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Timezone.rst b/reference/constraints/Timezone.rst index c5f27e1cbfb..36ebdd0b86b 100644 --- a/reference/constraints/Timezone.rst +++ b/reference/constraints/Timezone.rst @@ -1,10 +1,6 @@ Timezone ======== -.. versionadded:: 4.3 - - The ``Timezone`` constraint was introduced in Symfony 4.3. - Validates that a value is a valid timezone identifier (e.g. ``Europe/Paris``). ========== ====================================================================== @@ -42,6 +38,19 @@ string which contains any of the `PHP timezone identifiers`_ (e.g. ``America/New protected $timezone; } + .. code-block:: php-attributes + + // src/Entity/UserSettings.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class UserSettings + { + #[Assert\Timezone] + protected $timezone; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -127,8 +136,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc zone diff --git a/reference/constraints/Traverse.rst b/reference/constraints/Traverse.rst index aea7e051ee1..4c5754083a5 100644 --- a/reference/constraints/Traverse.rst +++ b/reference/constraints/Traverse.rst @@ -26,8 +26,8 @@ that all have constraints on their properties. // src/Entity/BookCollection.php namespace App\Entity; - use Doctrine\Collections\ArrayCollection; - use Doctrine\Collections\Collection + use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; @@ -89,6 +89,73 @@ that all have constraints on their properties. } } + .. code-block:: php-attributes + + // src/Entity/BookCollection.php + namespace App\Entity; + + use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection + use Doctrine\ORM\Mapping as ORM; + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @ORM\Entity + */ + #[Assert\Traverse] + class BookCollection implements \IteratorAggregate + { + /** + * @var string + * + * @ORM\Column + */ + #[Assert\NotBlank] + protected $name = ''; + + /** + * @var Collection|Book[] + * + * @ORM\ManyToMany(targetEntity="App\Entity\Book") + */ + protected $books; + + // some other properties + + public function __construct() + { + $this->books = new ArrayCollection(); + } + + // ... setter for name, adder and remover for books + + // the name can be validated by calling the getter + public function getName(): string + { + return $this->name; + } + + /** + * @return \Generator|Book[] The books for a given author + */ + public function getBooksForAuthor(Author $author): iterable + { + foreach ($this->books as $book) { + if ($book->isAuthoredBy($author)) { + yield $book; + } + } + } + + // neither the method above nor any other specific getter + // could be used to validated all nested books; + // this object needs to be traversed to call the iterator + public function getIterator() + { + return $this->books->getIterator(); + } + } + .. code-block:: yaml # config/validator/validation.yaml @@ -168,6 +235,21 @@ disable validating: // ... } + .. code-block:: php-attributes + + // src/Entity/BookCollection.php + + // ... same as above + + /** + * ... + */ + #[Assert\Traverse(false)] + class BookCollection implements \IteratorAggregate + { + // ... + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst index 8aa0edd1ba2..61189e7f989 100644 --- a/reference/constraints/Type.rst +++ b/reference/constraints/Type.rst @@ -59,6 +59,31 @@ This will check if ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, protected $accessCode; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Type('Ramsey\Uuid\UuidInterface')] + protected $id; + + #[Assert\Type('string')] + protected $firstName; + + #[Assert\Type( + type: 'integer', + message: 'The value {{ value }} is not a valid {{ type }}.', + )] + protected $age; + + #[Assert\Type(type: ['alpha', 'digit'])] + protected $accessCode; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -143,11 +168,6 @@ This will check if ``id`` is an instance of ``Ramsey\Uuid\UuidInterface``, } } -.. versionadded:: 4.4 - - The feature to define multiple types in the ``type`` option was introduced - in Symfony 4.4. - Options ------- @@ -167,8 +187,13 @@ Parameter Description =============== ============================================================== ``{{ type }}`` The expected type ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc .. _reference-constraint-type-type: @@ -178,11 +203,6 @@ type **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 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): diff --git a/reference/constraints/Ulid.rst b/reference/constraints/Ulid.rst new file mode 100644 index 00000000000..92315089350 --- /dev/null +++ b/reference/constraints/Ulid.rst @@ -0,0 +1,120 @@ +ULID +==== + +.. versionadded:: 5.2 + + The ULID validator was introduced in Symfony 5.2. + +Validates that a value is a valid `Universally Unique Lexicographically Sortable Identifier (ULID)`_. + +========== =================================================================== +Applies to :ref:`property or method ` +Options - `groups`_ + - `message`_ + - `normalizer`_ + - `payload`_ +Class :class:`Symfony\\Component\\Validator\\Constraints\\Ulid` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\UlidValidator` +========== =================================================================== + +Basic Usage +----------- + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Entity/File.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class File + { + /** + * @Assert\Ulid + */ + protected $identifier; + } + + .. code-block:: php-attributes + + // src/Entity/File.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class File + { + #[Assert\Ulid] + protected $identifier; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\Entity\File: + properties: + identifier: + - Ulid: ~ + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // src/Entity/File.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class File + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('identifier', new Assert\Ulid()); + } + } + +.. include:: /reference/constraints/_empty-values-are-valid.rst.inc + +Options +------- + +.. include:: /reference/constraints/_groups-option.rst.inc + +``message`` +~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This is not a valid ULID.`` + +This message is shown if the string is not a valid ULID. + +You can use the following parameters in this message: + +=============== ============================================================== +Parameter Description +=============== ============================================================== +``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label +=============== ============================================================== + +.. include:: /reference/constraints/_normalizer-option.rst.inc + +.. include:: /reference/constraints/_payload-option.rst.inc + + +.. _`Universally Unique Lexicographically Sortable Identifier (ULID)`: https://github.com/ulid/spec diff --git a/reference/constraints/Unique.rst b/reference/constraints/Unique.rst index 97cb6ff8602..497156ed9b4 100644 --- a/reference/constraints/Unique.rst +++ b/reference/constraints/Unique.rst @@ -50,6 +50,19 @@ strings: protected $contactEmails; } + .. code-block:: php-attributes + + // src/Entity/Person.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Person + { + #[Assert\Unique] + protected $contactEmails; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index e6e449d949b..c76a31e6a4c 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -59,6 +59,31 @@ between all of the rows in your user table: protected $email; } + .. code-block:: php-attributes + + // src/Entity/User.php + namespace App\Entity; + + use Doctrine\ORM\Mapping as ORM; + + // DON'T forget the following use statement!!! + use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; + + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @ORM\Entity + */ + #[UniqueEntity('email')] + class User + { + /** + * @ORM\Column(name="email", type="string", length=255, unique=true) + */ + #[Assert\Email] + protected $email; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -188,6 +213,35 @@ Consider this example: public $port; } + .. code-block:: php-attributes + + // src/Entity/Service.php + namespace App\Entity; + + use Doctrine\ORM\Mapping as ORM; + use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; + + /** + * @ORM\Entity + */ + #[UniqueEntity( + fields: ['host', 'port'], + errorPath: 'port', + message: 'This port is already in use on that host.', + )] + class Service + { + /** + * @ORM\ManyToOne(targetEntity="App\Entity\Host") + */ + public $host; + + /** + * @ORM\Column(type="integer") + */ + public $port; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -291,8 +345,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_payload-option.rst.inc repositoryMethod diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst index 4c9885d0147..91714131294 100644 --- a/reference/constraints/Url.rst +++ b/reference/constraints/Url.rst @@ -5,9 +5,7 @@ Validates that a value is a valid URL string. ========== =================================================================== Applies to :ref:`property or method ` -Options - `checkDNS`_ - - `dnsMessage`_ - - `groups`_ +Options - `groups`_ - `message`_ - `normalizer`_ - `payload`_ @@ -37,70 +35,7 @@ Basic Usage protected $bioUrl; } - .. code-block:: yaml - - # config/validator/validation.yaml - App\Entity\Author: - properties: - bioUrl: - - Url: ~ - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // src/Entity/Author.php - namespace App\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - use Symfony\Component\Validator\Mapping\ClassMetadata; - - class Author - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('bioUrl', new Assert\Url()); - } - } - -.. include:: /reference/constraints/_empty-values-are-valid.rst.inc - -Options -------- - -checkDNS -~~~~~~~~ - -**type**: ``boolean`` **default**: ``false`` - -.. deprecated:: 4.1 - - This option was deprecated in Symfony 4.1 and will be removed in Symfony 5.0, - because checking the DNS records is not reliable enough to validate the - existence of the host. Use the :phpfunction:`checkdnsrr` PHP function if you - still want to use this kind of validation. - -By default, this constraint just validates the syntax of the given URL. If you -also need to check whether the associated host exists, set the ``checkDNS`` -option to the value of any of the ``CHECK_DNS_TYPE_*`` constants in the -:class:`Symfony\\Component\\Validator\\Constraints\\Url` class: - -.. configuration-block:: - - .. code-block:: php-annotations + .. code-block:: php-attributes // src/Entity/Author.php namespace App\Entity; @@ -109,12 +44,8 @@ option to the value of any of the ``CHECK_DNS_TYPE_*`` constants in the class Author { - /** - * @Assert\Url( - * checkDNS = "ANY" - * ) - */ - protected $bioUrl; + #[Assert\Url] + protected $bioUrl; } .. code-block:: yaml @@ -123,7 +54,7 @@ option to the value of any of the ``CHECK_DNS_TYPE_*`` constants in the App\Entity\Author: properties: bioUrl: - - Url: { checkDNS: 'ANY' } + - Url: ~ .. code-block:: xml @@ -135,9 +66,7 @@ option to the value of any of the ``CHECK_DNS_TYPE_*`` constants in the - - - + @@ -154,91 +83,18 @@ option to the value of any of the ``CHECK_DNS_TYPE_*`` constants in the { public static function loadValidatorMetadata(ClassMetadata $metadata) { - $metadata->addPropertyConstraint('bioUrl', new Assert\Url([ - 'checkDNS' => Assert\Url::CHECK_DNS_TYPE_ANY, - ])); + $metadata->addPropertyConstraint('bioUrl', new Assert\Url()); } } -This option uses the :phpfunction:`checkdnsrr` PHP function to check the validity -of the DNS record corresponding to the host associated with the given URL. - -dnsMessage -~~~~~~~~~~ - -**type**: ``string`` **default**: ``The host could not be resolved.`` - -.. deprecated:: 4.1 - - This option was deprecated in Symfony 4.1 and will be removed in Symfony 5.0, - because checking the DNS records is not reliable enough to validate the - existence of the host. Use the :phpfunction:`checkdnsrr` PHP function if you - still want to use this kind of validation. - -This message is shown when the ``checkDNS`` option is set to ``true`` and the -DNS check failed. - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Entity/Author.php - namespace App\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Author - { - /** - * @Assert\Url( - * dnsMessage = "The host '{{ value }}' could not be resolved." - * ) - */ - protected $bioUrl; - } - - .. code-block:: yaml - - # config/validator/validation.yaml - App\Entity\Author: - properties: - bioUrl: - - Url: { dnsMessage: 'The host "{{ value }}" could not be resolved.' } - - .. code-block:: xml - - - - +This constraint doesn't check that the host of the given URL really exists, +because the information of the DNS records is not reliable. Use the +:phpfunction:`checkdnsrr` PHP function if you still want to check that. - - - - - - - - - - .. code-block:: php - - // src/Entity/Author.php - namespace App\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - use Symfony\Component\Validator\Mapping\ClassMetadata; +.. include:: /reference/constraints/_empty-values-are-valid.rst.inc - class Author - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('bioUrl', new Assert\Url([ - 'dnsMessage' => 'The host "{{ value }}" could not be resolved.', - ])); - } - } +Options +------- .. include:: /reference/constraints/_groups-option.rst.inc @@ -255,8 +111,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. configuration-block:: .. code-block:: php-annotations @@ -276,6 +137,21 @@ Parameter Description protected $bioUrl; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Url( + message: 'The url {{ value }} is not a valid url', + )] + protected $bioUrl; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -352,6 +228,21 @@ the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing protected $bioUrl; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Url( + protocols: ['http', 'https', 'ftp'], + )] + protected $bioUrl; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -427,6 +318,21 @@ also relative URLs that contain no protocol (e.g. ``//example.com``). protected $bioUrl; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Url( + relativeProtocol: true, + )] + protected $bioUrl; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/UserPassword.rst b/reference/constraints/UserPassword.rst index 91017168a82..0ffedadd20c 100644 --- a/reference/constraints/UserPassword.rst +++ b/reference/constraints/UserPassword.rst @@ -51,6 +51,21 @@ the user's current password: protected $oldPassword; } + .. code-block:: php-attributes + + // src/Form/Model/ChangePassword.php + namespace App\Form\Model; + + use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert; + + class ChangePassword + { + #[SecurityAssert\UserPassword( + message: 'Wrong value for your current password', + )] + protected $oldPassword; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/Uuid.rst b/reference/constraints/Uuid.rst index 4406555ac30..c7b2d94900b 100644 --- a/reference/constraints/Uuid.rst +++ b/reference/constraints/Uuid.rst @@ -38,6 +38,19 @@ Basic Usage protected $identifier; } + .. code-block:: php-attributes + + // src/Entity/File.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class File + { + #[Assert\Uuid] + protected $identifier; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -97,8 +110,13 @@ You can use the following parameters in this message: Parameter Description =============== ============================================================== ``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label =============== ============================================================== +.. versionadded:: 5.2 + + The ``{{ label }}`` parameter was introduced in Symfony 5.2. + .. include:: /reference/constraints/_normalizer-option.rst.inc .. include:: /reference/constraints/_payload-option.rst.inc @@ -119,9 +137,9 @@ will allow alternate input formats like: ``versions`` ~~~~~~~~~~~~ -**type**: ``int[]`` **default**: ``[1,2,3,4,5]`` +**type**: ``int[]`` **default**: ``[1,2,3,4,5,6]`` -This option can be used to only allow specific `UUID versions`_. Valid versions are 1 - 5. +This option can be used to only allow specific `UUID versions`_. Valid versions are 1 - 6. The following PHP constants can also be used: * ``Uuid::V1_MAC`` @@ -129,8 +147,13 @@ The following PHP constants can also be used: * ``Uuid::V3_MD5`` * ``Uuid::V4_RANDOM`` * ``Uuid::V5_SHA1`` +* ``Uuid::V6_SORTABLE`` + +All six versions are allowed by default. + +.. versionadded:: 5.2 -All five versions are allowed by default. + The UUID 6 version support was introduced in Symfony 5.2. .. _`Universally unique identifier (UUID)`: https://en.wikipedia.org/wiki/Universally_unique_identifier .. _`RFC 4122`: https://tools.ietf.org/html/rfc4122 diff --git a/reference/constraints/Valid.rst b/reference/constraints/Valid.rst index 1cb992128ac..8378f34cbec 100644 --- a/reference/constraints/Valid.rst +++ b/reference/constraints/Valid.rst @@ -87,6 +87,40 @@ stores an ``Address`` instance in the ``$address`` property:: protected $address; } + .. code-block:: php-attributes + + // src/Entity/Address.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Address + { + #[Assert\NotBlank] + protected $street; + + #[Assert\NotBlank] + #[Assert\Length(max: 5)] + protected $zipCode; + } + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\NotBlank] + #[Assert\Length(min: 4)] + protected $firstName; + + #[Assert\NotBlank] + protected $lastName; + + protected $address; + } + .. code-block:: yaml # config/validator/validation.yaml @@ -196,6 +230,19 @@ an invalid address. To prevent that, add the ``Valid`` constraint to the protected $address; } + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Valid] + protected $address; + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/reference/constraints/_comparison-propertypath-option.rst.inc b/reference/constraints/_comparison-propertypath-option.rst.inc index 0491d7dea1f..35f0da4d189 100644 --- a/reference/constraints/_comparison-propertypath-option.rst.inc +++ b/reference/constraints/_comparison-propertypath-option.rst.inc @@ -15,7 +15,3 @@ with regard to the ``$startDate`` property of the same object, use ``{{ 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/constraints/map.rst.inc b/reference/constraints/map.rst.inc index 438338af8c4..020e84cde65 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -16,12 +16,15 @@ String Constraints ~~~~~~~~~~~~~~~~~~ * :doc:`Email ` +* :doc:`ExpressionLanguageSyntax ` * :doc:`Length ` * :doc:`Url ` * :doc:`Regex ` +* :doc:`Hostname ` * :doc:`Ip ` * :doc:`Json ` * :doc:`Uuid ` +* :doc:`Ulid ` * :doc:`UserPassword ` * :doc:`NotCompromisedPassword ` @@ -79,10 +82,14 @@ Financial and other Number Constraints * :doc:`Iban ` * :doc:`Isbn ` * :doc:`Issn ` +* :doc:`Isin ` Other Constraints ~~~~~~~~~~~~~~~~~ +* :doc:`AtLeastOneOf ` +* :doc:`Sequentially ` +* :doc:`Compound ` * :doc:`Callback ` * :doc:`Expression ` * :doc:`All ` diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst index 7bb0aca674e..0aca3c91777 100644 --- a/reference/dic_tags.rst +++ b/reference/dic_tags.rst @@ -14,6 +14,8 @@ Tag Name Usage `auto_alias`_ Define aliases based on the value of container parameters `console.command`_ Add a command `container.hot_path`_ Add to list of always needed services +`container.no_preload`_ Remove a class from the list of classes preloaded by PHP +`container.preload`_ Add some class to the list of classes preloaded by PHP `controller.argument_value_resolver`_ Register a value resolver for controller arguments such as ``Request`` `data_collector`_ Create a class that collects custom data for the profiler `doctrine.event_listener`_ Add a Doctrine event listener @@ -38,7 +40,6 @@ Tag Name Usage `serializer.encoder`_ Register a new encoder in the ``serializer`` service `serializer.normalizer`_ Register a new normalizer in the ``serializer`` service `swiftmailer.default.plugin`_ Register a custom SwiftMailer Plugin -`templating.helper`_ Make your service available in PHP templates `translation.loader`_ Register a custom service that loads translations `translation.extractor`_ Register a custom service that extracts translation messages from a file `translation.dumper`_ Register a custom service that dumps translation messages @@ -181,10 +182,12 @@ wrapping their names with ``%`` characters). sense most of the times to prevent accessing those services directly instead of using the generic service alias. -.. note:: +.. versionadded:: 5.1 - You need to manually add the ``Symfony\Component\DependencyInjection\Compiler\AutoAliasServicePass`` - compiler pass to the container for this feature to work. + In Symfony versions prior to 5.1, you needed to manually add the + ``Symfony\Component\DependencyInjection\Compiler\AutoAliasServicePass`` + compiler pass to the container for this feature to work. This compiler pass + is now added automatically. console.command --------------- @@ -211,6 +214,113 @@ for services and their class hierarchy. The result is as significant performance Use this tag with great caution, you have to be sure that the tagged service is always used. +.. _dic-tags-container-nopreload: + +container.no_preload +-------------------- + +**Purpose**: Remove a class from the list of classes preloaded by PHP + +.. versionadded:: 5.1 + + The ``container.no_preload`` tag was introduced in Symfony 5.1. + +Add this tag to a service and its class won't be preloaded when using +`PHP class preloading`_: + +.. configuration-block:: + + .. code-block:: yaml + + services: + App\SomeNamespace\SomeService: + tags: ['container.no_preload'] + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + use App\SomeNamespace\SomeService; + + $container + ->register(SomeService::class) + ->addTag('container.no_preload') + ; + +If you add some service tagged with ``container.no_preload`` as an argument of +another service, the ``container.no_preload`` tag is applied automatically to +that service too. + +.. _dic-tags-container-preload: + +container.preload +----------------- + +**Purpose**: Add some class to the list of classes preloaded by PHP + +.. versionadded:: 5.1 + + The ``container.preload`` tag was introduced in Symfony 5.1. + +When using `PHP class preloading`_, this tag allows you to define which PHP +classes should be preloaded. This can improve performance by making some of the +classes used by your service always available for all requests (until the server +is restarted): + +.. configuration-block:: + + .. code-block:: yaml + + services: + App\SomeNamespace\SomeService: + tags: + - { name: 'container.preload', class: 'App\SomeClass' } + - { name: 'container.preload', class: 'App\Some\OtherClass' } + # ... + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + use App\Some\OtherClass; + use App\SomeClass; + use App\SomeNamespace\SomeService; + + $container + ->register(SomeService::class) + ->addTag('container.preload', ['class' => SomeClass::class) + ->addTag('container.preload', ['class' => OtherClass::class) + // ... + ; + controller.argument_value_resolver ---------------------------------- @@ -300,7 +410,7 @@ service class:: class MyClearer implements CacheClearerInterface { - public function clear($cacheDirectory) + public function clear(string $cacheDirectory) { // clear your cache } @@ -362,6 +472,7 @@ the :class:`Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface` i // src/Cache/MyCustomWarmer.php namespace App\Cache; + use App\Foo\Bar; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; class MyCustomWarmer implements CacheWarmerInterface @@ -369,6 +480,17 @@ the :class:`Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface` i public function warmUp($cacheDirectory) { // ... do some sort of operations to "warm" your cache + + $filesAndClassesToPreload = []; + $filesAndClassesToPreload[] = Bar::class; + + foreach (scandir($someCacheDir) as $file) { + if (!is_dir($file = $someCacheDir.'/'.$file)) { + $filesAndClassesToPreload[] = $file; + } + } + + return $filesAndClassesToPreload; } public function isOptional() @@ -377,6 +499,16 @@ the :class:`Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface` i } } +The ``warmUp()`` method must return an array with the files and classes to +preload. Files must be absolute paths and classes must be fully-qualified class +names. The only restriction is that files must be stored in the cache directory. +If you don't need to preload anything, return an empty array + +.. deprecated:: 5.1 + + Not returning an array from the ``warmUp()`` method with the files to + preload is deprecated since Symfony 5.1. + The ``isOptional()`` method should return true if it's possible to use the application without calling this cache warmer. In Symfony, optional warmers are always executed by default (you can change this by using the @@ -508,10 +640,6 @@ This tag is used to register your own :ref:`MIME type guessers ` don't fit your needs. -.. versionadded:: 4.3 - - The ``mime.mime_type_guesser`` tag was introduced in Symfony 4.3. - .. _dic_tags-monolog: monolog.logger @@ -834,53 +962,6 @@ For more information on plugins, see `SwiftMailer's Plugin Documentation`_. Several SwiftMailer plugins are core to Symfony and can be activated via different configuration. For details, see :doc:`/reference/configuration/swiftmailer`. -templating.helper ------------------ - -**Purpose**: Make your service available in PHP templates - -.. deprecated:: 4.3 - - The ``templating.helper`` tag is deprecated since version 4.3 and will be - removed in 5.0; use Twig instead. - -To enable a custom template helper, add it as a regular service in one -of your configuration, tag it with ``templating.helper`` and define an -``alias`` attribute (the helper will be accessible via this alias in the -templates): - -.. configuration-block:: - - .. code-block:: yaml - - services: - App\Templating\AppHelper: - tags: - - { name: templating.helper, alias: alias_name } - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - use App\Templating\AppHelper; - - $container->register(AppHelper::class) - ->addTag('templating.helper', ['alias' => 'alias_name']) - ; - .. _dic-tags-translation-loader: translation.loader @@ -981,7 +1062,7 @@ required option: ``alias``, which defines the name of the extractor:: /** * Sets the prefix that should be used for new found messages. */ - public function setPrefix($prefix) + public function setPrefix(string $prefix) { $this->prefix = $prefix; } @@ -1264,3 +1345,4 @@ Bridge. .. _`Twig's documentation`: https://twig.symfony.com/doc/2.x/advanced.html#creating-an-extension .. _`SwiftMailer's Plugin Documentation`: https://swiftmailer.symfony.com/docs/plugins.html .. _`Twig Loader`: https://twig.symfony.com/doc/2.x/api.html#loaders +.. _`PHP class preloading`: https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.preload diff --git a/reference/forms/types/birthday.rst b/reference/forms/types/birthday.rst index 6299dbf1e09..94cff698cb4 100644 --- a/reference/forms/types/birthday.rst +++ b/reference/forms/types/birthday.rst @@ -14,51 +14,57 @@ This type is essentially the same as the :doc:`DateType `) | -+----------------------+-------------------------------------------------------------------------------+ -| Rendered as | can be three select boxes or 1 or 3 text boxes, based on the `widget`_ option | -+----------------------+-------------------------------------------------------------------------------+ -| Overridden options | - `years`_ | -+----------------------+-------------------------------------------------------------------------------+ -| Inherited options | from the :doc:`DateType `: | -| | | -| | - `choice_translation_domain`_ | -| | - `days`_ | -| | - `placeholder`_ | -| | - `format`_ | -| | - `input`_ | -| | - `input_format`_ | -| | - `model_timezone`_ | -| | - `months`_ | -| | - `view_timezone`_ | -| | - `widget`_ | -| | | -| | from the :doc:`FormType `: | -| | | -| | - `attr`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `inherit_data`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `mapped`_ | -| | - `row_attr`_ | -+----------------------+-------------------------------------------------------------------------------+ -| Parent type | :doc:`DateType ` | -+----------------------+-------------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\BirthdayType` | -+----------------------+-------------------------------------------------------------------------------+ ++---------------------------+-------------------------------------------------------------------------------+ +| Underlying Data Type | can be ``DateTime``, ``string``, ``timestamp``, or ``array`` | +| | (see the :ref:`input option `) | ++---------------------------+-------------------------------------------------------------------------------+ +| Rendered as | can be three select boxes or 1 or 3 text boxes, based on the `widget`_ option | ++---------------------------+-------------------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | +| | - `years`_ | ++---------------------------+-------------------------------------------------------------------------------+ +| Inherited options | from the :doc:`DateType `: | +| | | +| | - `choice_translation_domain`_ | +| | - `days`_ | +| | - `placeholder`_ | +| | - `format`_ | +| | - `input`_ | +| | - `input_format`_ | +| | - `model_timezone`_ | +| | - `months`_ | +| | - `view_timezone`_ | +| | - `widget`_ | +| | | +| | from the :doc:`FormType `: | +| | | +| | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++---------------------------+-------------------------------------------------------------------------------+ +| Default invalid message | Please enter a valid birthdate. | ++---------------------------+-------------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-------------------------------------------------------------------------------+ +| Parent type | :doc:`DateType ` | ++---------------------------+-------------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\BirthdayType` | ++---------------------------+-------------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc Overridden Options ------------------ +.. include:: /reference/forms/types/options/invalid_message.rst.inc + ``years`` ~~~~~~~~~ @@ -128,8 +134,6 @@ These options inherit from the :doc:`FormType `: .. 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 diff --git a/reference/forms/types/button.rst b/reference/forms/types/button.rst index 20ae46ca6d4..655d515215b 100644 --- a/reference/forms/types/button.rst +++ b/reference/forms/types/button.rst @@ -13,6 +13,7 @@ A simple, non-responsive button. | options | - `attr_translation_parameters`_ | | | - `disabled`_ | | | - `label`_ | +| | - `label_html`_ | | | - `label_translation_parameters`_ | | | - `row_attr`_ | | | - `translation_domain`_ | @@ -53,6 +54,8 @@ as a key. This can be useful when you need to set a custom class for the button: .. include:: /reference/forms/types/options/button_label.rst.inc +.. include:: /reference/forms/types/options/label_html.rst.inc + .. include:: /reference/forms/types/options/button_translation_domain.rst.inc label_translation_parameters @@ -60,10 +63,6 @@ label_translation_parameters **type**: ``array`` **default**: ``[]`` -.. versionadded:: 4.3 - - The ``label_translation_parameters`` option was introduced in Symfony 4.3. - The content of the `label`_ option is translated before displaying it, so it can contain :ref:`translation placeholders `. This option defines the values used to replace those placeholders. diff --git a/reference/forms/types/checkbox.rst b/reference/forms/types/checkbox.rst index aef03ef1e44..d4fdd17580c 100644 --- a/reference/forms/types/checkbox.rst +++ b/reference/forms/types/checkbox.rst @@ -11,34 +11,39 @@ you can specify an array of values that, if submitted, will be evaluated to "false" as well (this differs from what HTTP defines, but can be handy if you want to handle submitted values like "0" or "false"). -+-------------+------------------------------------------------------------------------+ -| Rendered as | ``input`` ``checkbox`` field | -+-------------+------------------------------------------------------------------------+ -| Options | - `false_values`_ | -| | - `value`_ | -+-------------+------------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | - `empty_data`_ | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType` | -+-------------+------------------------------------------------------------------------+ ++---------------------------+------------------------------------------------------------------------+ +| Rendered as | ``input`` ``checkbox`` field | ++---------------------------+------------------------------------------------------------------------+ +| Options | - `false_values`_ | +| | - `value`_ | ++---------------------------+------------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `empty_data`_ | +| | - `invalid_message`_ | ++---------------------------+------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+------------------------------------------------------------------------+ +| Default invalid message | The checkbox has an invalid value. | ++---------------------------+------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+------------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType` | ++---------------------------+------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -74,6 +79,8 @@ Overridden Options .. include:: /reference/forms/types/options/checkbox_empty_data.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/choice.rst b/reference/forms/types/choice.rst index a7fad757749..d01d90f262d 100644 --- a/reference/forms/types/choice.rst +++ b/reference/forms/types/choice.rst @@ -9,51 +9,58 @@ It can be rendered as a ``select`` tag, radio buttons, or checkboxes. To use this field, you must specify *either* ``choices`` or ``choice_loader`` option. -+-------------+------------------------------------------------------------------------------+ -| Rendered as | can be various tags (see below) | -+-------------+------------------------------------------------------------------------------+ -| Options | - `choices`_ | -| | - `choice_attr`_ | -| | - `choice_label`_ | -| | - `choice_loader`_ | -| | - `choice_name`_ | -| | - `choice_translation_domain`_ | -| | - `choice_value`_ | -| | - `expanded`_ | -| | - `group_by`_ | -| | - `multiple`_ | -| | - `placeholder`_ | -| | - `preferred_choices`_ | -+-------------+------------------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `trim`_ | -+-------------+------------------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `by_reference`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `inherit_data`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -| | - `translation_domain`_ | -| | - `label_translation_parameters`_ | -| | - `attr_translation_parameters`_ | -| | - `help_translation_parameters`_ | -+-------------+------------------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+------------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType` | -+-------------+------------------------------------------------------------------------------+ ++---------------------------+----------------------------------------------------------------------+ +| Rendered as | can be various tags (see below) | ++---------------------------+----------------------------------------------------------------------+ +| Options | - `choices`_ | +| | - `choice_attr`_ | +| | - `choice_filter`_ | +| | - `choice_label`_ | +| | - `choice_loader`_ | +| | - `choice_name`_ | +| | - `choice_translation_domain`_ | +| | - `choice_translation_parameters`_ | +| | - `choice_value`_ | +| | - `expanded`_ | +| | - `group_by`_ | +| | - `multiple`_ | +| | - `placeholder`_ | +| | - `preferred_choices`_ | ++---------------------------+----------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `trim`_ | +| | - `invalid_message`_ | ++---------------------------+----------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `by_reference`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | +| | - `translation_domain`_ | +| | - `label_translation_parameters`_ | +| | - `attr_translation_parameters`_ | +| | - `help_translation_parameters`_ | ++---------------------------+----------------------------------------------------------------------+ +| Default invalid message | The selected choice is invalid. | ++---------------------------+----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+----------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType` | ++---------------------------+----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -181,6 +188,11 @@ To get fancier, use the `group_by`_ option instead. Field Options ------------- +.. versionadded:: 5.1 + + The :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` class was + introduced in Symfony 5.1, to help configuring choices options. + choices ~~~~~~~ @@ -207,40 +219,22 @@ correct types will be assigned to the model. .. include:: /reference/forms/types/options/choice_attr.rst.inc +.. include:: /reference/forms/types/options/choice_filter.rst.inc + .. _reference-form-choice-label: .. include:: /reference/forms/types/options/choice_label.rst.inc -choice_loader -~~~~~~~~~~~~~ - -**type**: :class:`Symfony\\Component\\Form\\ChoiceList\\Loader\\ChoiceLoaderInterface` +.. _reference-form-choice-loader: -The ``choice_loader`` can be used to only partially load the choices in cases where -a fully-loaded list is not necessary. This is only needed in advanced cases and -would replace the ``choices`` option. - -You can use an instance of :class:`Symfony\\Component\\Form\\ChoiceList\\Loader\\CallbackChoiceLoader` -if you want to take advantage of lazy loading:: - - use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader; - use Symfony\Component\Form\Extension\Core\Type\ChoiceType; - // ... - - $builder->add('constants', ChoiceType::class, [ - 'choice_loader' => new CallbackChoiceLoader(function() { - return StaticClass::getConstants(); - }), - ]); - -This will cause the call of ``StaticClass::getConstants()`` to not happen if the -request is redirected and if there is no pre set or submitted data. Otherwise -the choice options would need to be resolved thus triggering the callback. +.. include:: /reference/forms/types/options/choice_loader.rst.inc .. include:: /reference/forms/types/options/choice_name.rst.inc .. include:: /reference/forms/types/options/choice_translation_domain_enabled.rst.inc +.. include:: /reference/forms/types/options/choice_translation_parameters.rst.inc + .. include:: /reference/forms/types/options/choice_value.rst.inc .. include:: /reference/forms/types/options/expanded.rst.inc @@ -286,6 +280,8 @@ the parent field (the form in most cases). .. include:: /reference/forms/types/options/choice_type_trim.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/collection.rst b/reference/forms/types/collection.rst index f3f0c8f4562..9e1eb170933 100644 --- a/reference/forms/types/collection.rst +++ b/reference/forms/types/collection.rst @@ -11,37 +11,43 @@ forms, which is useful when creating forms that expose one-to-many relationships (e.g. a product from where you can manage many related product photos). -+-------------+-----------------------------------------------------------------------------+ -| Rendered as | depends on the `entry_type`_ option | -+-------------+-----------------------------------------------------------------------------+ -| Options | - `allow_add`_ | -| | - `allow_delete`_ | -| | - `delete_empty`_ | -| | - `entry_options`_ | -| | - `entry_type`_ | -| | - `prototype`_ | -| | - `prototype_data`_ | -| | - `prototype_name`_ | -+-------------+-----------------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `by_reference`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+-----------------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+-----------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CollectionType` | -+-------------+-----------------------------------------------------------------------------+ ++---------------------------+--------------------------------------------------------------------------+ +| Rendered as | depends on the `entry_type`_ option | ++---------------------------+--------------------------------------------------------------------------+ +| Options | - `allow_add`_ | +| | - `allow_delete`_ | +| | - `delete_empty`_ | +| | - `entry_options`_ | +| | - `entry_type`_ | +| | - `prototype`_ | +| | - `prototype_data`_ | +| | - `prototype_name`_ | ++---------------------------+--------------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+--------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `by_reference`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+--------------------------------------------------------------------------+ +| Default invalid message | The collection is invalid. | ++---------------------------+--------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+--------------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+--------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CollectionType` | ++---------------------------+--------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -396,6 +402,11 @@ If you have several collections in your form, or worse, nested collections you may want to change the placeholder so that unrelated placeholders are not replaced with the same value. +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/color.rst b/reference/forms/types/color.rst index f6e4ec5ba58..6bbc28da2a7 100644 --- a/reference/forms/types/color.rst +++ b/reference/forms/types/color.rst @@ -14,33 +14,62 @@ The value of the underlying ```` field is always a That's why it's not possible to select semi-transparent colors with this element. -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``color`` field (a text box) | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -| | - `trim`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`TextType ` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType` | -+-------------+---------------------------------------------------------------------+ ++---------------------------+---------------------------------------------------------------------+ +| Rendered as | ``input`` ``color`` field (a text box) | ++---------------------------+---------------------------------------------------------------------+ +| Options | - `html5`_ | ++---------------------------+---------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+---------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | +| | - `trim`_ | ++---------------------------+---------------------------------------------------------------------+ +| Default invalid message | Please select a valid color. | ++---------------------------+---------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+---------------------------------------------------------------------+ +| Parent type | :doc:`TextType ` | ++---------------------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType` | ++---------------------------+---------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc +Field Options +------------- + +html5 +~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +.. versionadded:: 5.1 + + This option was introduced in Symfony 5.1. + +When this option is set to ``true``, the form type checks that its value matches +the `HTML5 color format`_ (``/^#[0-9a-f]{6}$/i``). If it doesn't match it, +you'll see the following error message: *"This value is not a valid HTML5 color"*. + +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -83,3 +112,5 @@ The default value is ``''`` (the empty string). .. include:: /reference/forms/types/options/row_attr.rst.inc .. include:: /reference/forms/types/options/trim.rst.inc + +.. _`HTML5 color format`: https://www.w3.org/TR/html52/sec-forms.html#color-state-typecolor diff --git a/reference/forms/types/country.rst b/reference/forms/types/country.rst index f5281aacff0..10cf652947a 100644 --- a/reference/forms/types/country.rst +++ b/reference/forms/types/country.rst @@ -18,45 +18,50 @@ Unlike the ``ChoiceType``, you don't need to specify a ``choices`` option as the field type automatically uses all of the countries of the world. You *can* specify 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 | - `alpha3`_ | -| | - `choice_translation_locale`_ | -+-------------+-----------------------------------------------------------------------+ -| Overridden | - `choices`_ | -| options | - `choice_translation_domain`_ | -+-------------+-----------------------------------------------------------------------+ -| Inherited | from the :doc:`ChoiceType ` | -| options | | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `expanded`_ | -| | - `multiple`_ | -| | - `placeholder`_ | -| | - `preferred_choices`_ | -| | - `trim`_ | -| | | -| | from the :doc:`FormType ` | -| | | -| | - `attr`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+-----------------------------------------------------------------------+ -| Parent type | :doc:`ChoiceType ` | -+-------------+-----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CountryType` | -+-------------+-----------------------------------------------------------------------+ ++---------------------------+-----------------------------------------------------------------------+ +| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | ++---------------------------+-----------------------------------------------------------------------+ +| Options | - `alpha3`_ | +| | - `choice_translation_locale`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Overridden options | - `choices`_ | +| | - `choice_translation_domain`_ | +| | - `invalid_message`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Inherited options | from the :doc:`ChoiceType ` | +| | | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `expanded`_ | +| | - `multiple`_ | +| | - `placeholder`_ | +| | - `preferred_choices`_ | +| | - `trim`_ | +| | | +| | from the :doc:`FormType ` | +| | | +| | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Default invalid message | Please select a valid country. | ++---------------------------+-----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-----------------------------------------------------------------------+ +| Parent type | :doc:`ChoiceType ` | ++---------------------------+-----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CountryType` | ++---------------------------+-----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -68,10 +73,6 @@ 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``). @@ -96,6 +97,8 @@ The locale is used to translate the countries names. .. include:: /reference/forms/types/options/choice_translation_domain_disabled.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/currency.rst b/reference/forms/types/currency.rst index 77da0481942..e28b39c328a 100644 --- a/reference/forms/types/currency.rst +++ b/reference/forms/types/currency.rst @@ -11,43 +11,48 @@ Unlike the ``ChoiceType``, you don't need to specify a ``choices`` option as the field type automatically uses a large list of currencies. You *can* specify 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`_ | -+-------------+------------------------------------------------------------------------+ -| Overridden | - `choices`_ | -| options | - `choice_translation_domain`_ | -+-------------+------------------------------------------------------------------------+ -| Inherited | from the :doc:`ChoiceType ` | -| options | | -| | - `error_bubbling`_ | -| | - `expanded`_ | -| | - `multiple`_ | -| | - `placeholder`_ | -| | - `preferred_choices`_ | -| | - `trim`_ | -| | | -| | from the :doc:`FormType ` type | -| | | -| | - `attr`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`ChoiceType ` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CurrencyType` | -+-------------+------------------------------------------------------------------------+ ++---------------------------+------------------------------------------------------------------------+ +| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | ++---------------------------+------------------------------------------------------------------------+ +| Options | - `choice_translation_locale`_ | ++---------------------------+------------------------------------------------------------------------+ +| Overridden options | - `choices`_ | +| | - `choice_translation_domain`_ | +| | - `invalid_message`_ | ++---------------------------+------------------------------------------------------------------------+ +| Inherited options | from the :doc:`ChoiceType ` | +| | | +| | - `error_bubbling`_ | +| | - `expanded`_ | +| | - `multiple`_ | +| | - `placeholder`_ | +| | - `preferred_choices`_ | +| | - `trim`_ | +| | | +| | from the :doc:`FormType ` type | +| | | +| | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+------------------------------------------------------------------------+ +| Default invalid message | Please select a valid currency. | ++---------------------------+------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+------------------------------------------------------------------------+ +| Parent type | :doc:`ChoiceType ` | ++---------------------------+------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CurrencyType` | ++---------------------------+------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -73,6 +78,8 @@ The choices option defaults to all currencies. .. include:: /reference/forms/types/options/choice_translation_domain_disabled.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/date.rst b/reference/forms/types/date.rst index c98b51eac15..5a12ad24b9c 100644 --- a/reference/forms/types/date.rst +++ b/reference/forms/types/date.rst @@ -10,46 +10,50 @@ different HTML elements. This field can be rendered in a variety of different ways via the `widget`_ option and can understand a number of different input formats via the `input`_ option. -+----------------------+-----------------------------------------------------------------------------+ -| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | -+----------------------+-----------------------------------------------------------------------------+ -| Rendered as | single text box or three select fields | -+----------------------+-----------------------------------------------------------------------------+ -| Options | - `days`_ | -| | - `placeholder`_ | -| | - `format`_ | -| | - `html5`_ | -| | - `input`_ | -| | - `input_format`_ | -| | - `model_timezone`_ | -| | - `months`_ | -| | - `view_timezone`_ | -| | - `widget`_ | -| | - `years`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Overridden options | - `by_reference`_ | -| | - `choice_translation_domain`_ | -| | - `compound`_ | -| | - `data_class`_ | -| | - `error_bubbling`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `error_mapping`_ | -| | - `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\\DateType` | -+----------------------+-----------------------------------------------------------------------------+ ++---------------------------+-----------------------------------------------------------------------------+ +| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | ++---------------------------+-----------------------------------------------------------------------------+ +| Rendered as | single text box or three select fields | ++---------------------------+-----------------------------------------------------------------------------+ +| Options | - `days`_ | +| | - `placeholder`_ | +| | - `format`_ | +| | - `html5`_ | +| | - `input`_ | +| | - `input_format`_ | +| | - `model_timezone`_ | +| | - `months`_ | +| | - `view_timezone`_ | +| | - `widget`_ | +| | - `years`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Overridden options | - `by_reference`_ | +| | - `choice_translation_domain`_ | +| | - `compound`_ | +| | - `data_class`_ | +| | - `error_bubbling`_ | +| | - `invalid_message`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Default invalid message | Please enter a valid date. | ++---------------------------+-----------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-----------------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+-----------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateType` | ++---------------------------+-----------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -171,11 +175,6 @@ values for the year, month and day fields:: .. include:: /reference/forms/types/options/date_format.rst.inc -.. deprecated:: 4.3 - - Using the ``format`` option when the ``html5`` option is enabled is deprecated - since Symfony 4.3. - .. include:: /reference/forms/types/options/html5.rst.inc .. _form-reference-date-input: @@ -215,6 +214,8 @@ The ``DateTime`` classes are treated as immutable objects. **default**: ``false`` +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -236,8 +237,6 @@ These options inherit from the :doc:`FormType `: .. 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 diff --git a/reference/forms/types/dateinterval.rst b/reference/forms/types/dateinterval.rst index 84986f93c87..5248ca88739 100644 --- a/reference/forms/types/dateinterval.rst +++ b/reference/forms/types/dateinterval.rst @@ -12,47 +12,52 @@ The field can be rendered in a variety of different ways (see `widget`_) and can give you a ``DateInterval`` object, an `ISO 8601`_ duration string (e.g. ``P1DT12H``) or an array (see `input`_). -+----------------------+----------------------------------------------------------------------------------+ -| Underlying Data Type | can be ``DateInterval``, string or array (see the ``input`` option) | -+----------------------+----------------------------------------------------------------------------------+ -| Rendered as | single text box, multiple text boxes or select fields - see the `widget`_ option | -+----------------------+----------------------------------------------------------------------------------+ -| Options | - `days`_ | -| | - `hours`_ | -| | - `minutes`_ | -| | - `months`_ | -| | - `seconds`_ | -| | - `weeks`_ | -| | - `input`_ | -| | - `labels`_ | -| | - `placeholder`_ | -| | - `widget`_ | -| | - `with_days`_ | -| | - `with_hours`_ | -| | - `with_invert`_ | -| | - `with_minutes`_ | -| | - `with_months`_ | -| | - `with_seconds`_ | -| | - `with_weeks`_ | -| | - `with_years`_ | -| | - `years`_ | -+----------------------+----------------------------------------------------------------------------------+ -| 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\\DateIntervalType` | -+----------------------+----------------------------------------------------------------------------------+ ++---------------------------+----------------------------------------------------------------------------------+ +| Underlying Data Type | can be ``DateInterval``, string or array (see the ``input`` option) | ++---------------------------+----------------------------------------------------------------------------------+ +| Rendered as | single text box, multiple text boxes or select fields - see the `widget`_ option | ++---------------------------+----------------------------------------------------------------------------------+ +| Options | - `days`_ | +| | - `hours`_ | +| | - `minutes`_ | +| | - `months`_ | +| | - `seconds`_ | +| | - `weeks`_ | +| | - `input`_ | +| | - `labels`_ | +| | - `placeholder`_ | +| | - `widget`_ | +| | - `with_days`_ | +| | - `with_hours`_ | +| | - `with_invert`_ | +| | - `with_minutes`_ | +| | - `with_months`_ | +| | - `with_seconds`_ | +| | - `with_weeks`_ | +| | - `with_years`_ | +| | - `years`_ | ++---------------------------+----------------------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+----------------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++---------------------------+----------------------------------------------------------------------------------+ +| Default invalid message | Please choose a valid date interval. | ++---------------------------+----------------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+----------------------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+----------------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateIntervalType` | ++---------------------------+----------------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -333,6 +338,11 @@ when the ``widget`` option is set to ``choice``:: // values displayed to users range from 1 to 100 (both inclusive) 'years' => array_combine(range(1, 100), range(1, 100)), +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -352,8 +362,6 @@ These options inherit from the :doc:`form ` type: .. 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 diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index ed31a0cae8d..e742048fd24 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -10,55 +10,59 @@ date and time (e.g. ``1984-06-05 12:15:30``). Can be rendered as a text input or select tags. The underlying format of the data can be a ``DateTime`` object, a string, a timestamp or an array. -+----------------------+-----------------------------------------------------------------------------+ -| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | -+----------------------+-----------------------------------------------------------------------------+ -| Rendered as | single text box or three select fields | -+----------------------+-----------------------------------------------------------------------------+ -| Options | - `choice_translation_domain`_ | -| | - `date_format`_ | -| | - `date_label`_ | -| | - `date_widget`_ | -| | - `days`_ | -| | - `placeholder`_ | -| | - `format`_ | -| | - `hours`_ | -| | - `html5`_ | -| | - `input`_ | -| | - `input_format`_ | -| | - `minutes`_ | -| | - `model_timezone`_ | -| | - `months`_ | -| | - `seconds`_ | -| | - `time_label`_ | -| | - `time_widget`_ | -| | - `view_timezone`_ | -| | - `widget`_ | -| | - `with_minutes`_ | -| | - `with_seconds`_ | -| | - `years`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Overridden options | - `by_reference`_ | -| | - `compound`_ | -| | - `data_class`_ | -| | - `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\\DateTimeType` | -+----------------------+-----------------------------------------------------------------------------+ ++---------------------------+-----------------------------------------------------------------------------+ +| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | ++---------------------------+-----------------------------------------------------------------------------+ +| Rendered as | single text box or three select fields | ++---------------------------+-----------------------------------------------------------------------------+ +| Options | - `choice_translation_domain`_ | +| | - `date_format`_ | +| | - `date_label`_ | +| | - `date_widget`_ | +| | - `days`_ | +| | - `placeholder`_ | +| | - `format`_ | +| | - `hours`_ | +| | - `html5`_ | +| | - `input`_ | +| | - `input_format`_ | +| | - `minutes`_ | +| | - `model_timezone`_ | +| | - `months`_ | +| | - `seconds`_ | +| | - `time_label`_ | +| | - `time_widget`_ | +| | - `view_timezone`_ | +| | - `widget`_ | +| | - `with_minutes`_ | +| | - `with_seconds`_ | +| | - `years`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Overridden options | - `by_reference`_ | +| | - `compound`_ | +| | - `data_class`_ | +| | - `error_bubbling`_ | +| | - `invalid_message`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Default invalid message | Please enter a valid date and time. | ++---------------------------+-----------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-----------------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+-----------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateTimeType` | ++---------------------------+-----------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -76,11 +80,6 @@ Defines the ``format`` option that will be passed down to the date field. See the :ref:`DateType's format option ` for more details. -.. deprecated:: 4.3 - - Using the ``date_format`` option when the form is rendered as an HTML 5 - datetime input is deprecated since Symfony 4.3. - date_label ~~~~~~~~~~ @@ -100,11 +99,6 @@ date_widget .. include:: /reference/forms/types/options/date_widget_description.rst.inc -.. deprecated:: 4.3 - - Using the ``date_widget`` option when the ``widget`` option is set to - ``single_text`` is deprecated since Symfony 4.3. - .. include:: /reference/forms/types/options/days.rst.inc placeholder @@ -146,11 +140,6 @@ used by the HTML5 ``datetime-local`` field. Keeping the default value will cause the field to be rendered as an ``input`` field with ``type="datetime-local"``. For more information on valid formats, see `Date/Time Format Syntax`_. -.. deprecated:: 4.3 - - Using the ``format`` option when the ``html5`` option is enabled is deprecated - since Symfony 4.3. - .. include:: /reference/forms/types/options/hours.rst.inc .. include:: /reference/forms/types/options/html5.rst.inc @@ -210,11 +199,6 @@ time_widget Defines the ``widget`` option for the :doc:`TimeType `. -.. deprecated:: 4.3 - - Using the ``time_widget`` option when the ``widget`` option is set to - ``single_text`` is deprecated since Symfony 4.3. - .. include:: /reference/forms/types/options/view_timezone.rst.inc widget @@ -251,6 +235,8 @@ error_bubbling **default**: ``false`` +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -270,8 +256,6 @@ These options inherit from the :doc:`FormType `: .. 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 diff --git a/reference/forms/types/email.rst b/reference/forms/types/email.rst index 74eeaa95272..3dfe77db44f 100644 --- a/reference/forms/types/email.rst +++ b/reference/forms/types/email.rst @@ -7,33 +7,44 @@ EmailType Field The ``EmailType`` field is a text field that is rendered using the HTML5 ```` tag. -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``email`` field (a text box) | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -| | - `trim`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`TextType ` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType` | -+-------------+---------------------------------------------------------------------+ ++---------------------------+---------------------------------------------------------------------+ +| Rendered as | ``input`` ``email`` field (a text box) | ++---------------------------+---------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+---------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | +| | - `trim`_ | ++---------------------------+---------------------------------------------------------------------+ +| Default invalid message | Please enter a valid email address. | ++---------------------------+---------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+---------------------------------------------------------------------+ +| Parent type | :doc:`TextType ` | ++---------------------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType` | ++---------------------------+---------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/file.rst b/reference/forms/types/file.rst index 66a18560577..50bc55fee88 100644 --- a/reference/forms/types/file.rst +++ b/reference/forms/types/file.rst @@ -6,33 +6,38 @@ FileType Field The ``FileType`` represents a file input in your form. -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``file`` field | -+-------------+---------------------------------------------------------------------+ -| Options | - `multiple`_ | -+-------------+---------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | - `data_class`_ | -| | - `empty_data`_ | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `disabled`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType` | -+-------------+---------------------------------------------------------------------+ ++---------------------------+--------------------------------------------------------------------+ +| Rendered as | ``input`` ``file`` field | ++---------------------------+--------------------------------------------------------------------+ +| Options | - `multiple`_ | ++---------------------------+--------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `data_class`_ | +| | - `empty_data`_ | +| | - `invalid_message`_ | ++---------------------------+--------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `disabled`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+--------------------------------------------------------------------+ +| Default invalid message | Please select a valid file. | ++---------------------------+--------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+--------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+--------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType` | ++---------------------------+--------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -120,6 +125,11 @@ This option sets the appropriate file-related data mapper to be used by the type This option determines what value the field will return when the submitted value is empty. +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/form.rst b/reference/forms/types/form.rst index 4099436430b..9e1a5d47227 100644 --- a/reference/forms/types/form.rst +++ b/reference/forms/types/form.rst @@ -7,50 +7,55 @@ FormType Field The ``FormType`` predefines a couple of options that are then available on all types for which ``FormType`` is the parent. -+-----------+--------------------------------------------------------------------+ -| Options | - `action`_ | -| | - `allow_extra_fields`_ | -| | - `by_reference`_ | -| | - `compound`_ | -| | - `constraints`_ | -| | - `data`_ | -| | - `data_class`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `extra_fields_message`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `help_translation_parameters`_ | -| | - `inherit_data`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `method`_ | -| | - `post_max_size_message`_ | -| | - `property_path`_ | -| | - `required`_ | -| | - `trim`_ | -| | - `validation_groups`_ | -+-----------+--------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `auto_initialize`_ | -| | - `block_name`_ | -| | - `block_prefix`_ | -| | - `disabled`_ | -| | - `label`_ | -| | - `row_attr`_ | -| | - `translation_domain`_ | -| | - `label_translation_parameters`_ | -| | - `attr_translation_parameters`_ | -+-----------+--------------------------------------------------------------------+ -| Parent | none | -+-----------+--------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType` | -+-----------+--------------------------------------------------------------------+ ++---------------------------+--------------------------------------------------------------------+ +| Options | - `action`_ | +| | - `allow_extra_fields`_ | +| | - `by_reference`_ | +| | - `compound`_ | +| | - `constraints`_ | +| | - `data`_ | +| | - `data_class`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `extra_fields_message`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `help_translation_parameters`_ | +| | - `inherit_data`_ | +| | - `invalid_message`_ | +| | - `invalid_message_parameters`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `method`_ | +| | - `post_max_size_message`_ | +| | - `property_path`_ | +| | - `required`_ | +| | - `trim`_ | +| | - `validation_groups`_ | ++---------------------------+--------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `auto_initialize`_ | +| | - `block_name`_ | +| | - `block_prefix`_ | +| | - `disabled`_ | +| | - `label`_ | +| | - `label_html`_ | +| | - `row_attr`_ | +| | - `translation_domain`_ | +| | - `label_translation_parameters`_ | +| | - `attr_translation_parameters`_ | ++---------------------------+--------------------------------------------------------------------+ +| Default invalid message | This value is not valid. | ++---------------------------+--------------------------------------------------------------------+ +| Legacy invalid message | This value is not valid. | ++---------------------------+--------------------------------------------------------------------+ +| Parent | none | ++---------------------------+--------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType` | ++---------------------------+--------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -172,6 +177,8 @@ of the form type tree (i.e. it cannot be used as a form type on its own). .. include:: /reference/forms/types/options/label.rst.inc +.. include:: /reference/forms/types/options/label_html.rst.inc + .. include:: /reference/forms/types/options/row_attr.rst.inc .. include:: /reference/forms/types/options/translation_domain.rst.inc diff --git a/reference/forms/types/hidden.rst b/reference/forms/types/hidden.rst index 1a74e107555..00e858303bf 100644 --- a/reference/forms/types/hidden.rst +++ b/reference/forms/types/hidden.rst @@ -6,25 +6,30 @@ HiddenType Field The hidden type represents a hidden input field. -+-------------+----------------------------------------------------------------------+ -| Rendered as | ``input`` ``hidden`` field | -+-------------+----------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | - `error_bubbling`_ | -| | - `required`_ | -+-------------+----------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `empty_data`_ | -| | - `error_mapping`_ | -| | - `mapped`_ | -| | - `property_path`_ | -| | - `row_attr`_ | -+-------------+----------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\HiddenType` | -+-------------+----------------------------------------------------------------------+ ++---------------------------+----------------------------------------------------------------------+ +| Rendered as | ``input`` ``hidden`` field | ++---------------------------+----------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `error_bubbling`_ | +| | - `invalid_message`_ | +| | - `required`_ | ++---------------------------+----------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `empty_data`_ | +| | - `error_mapping`_ | +| | - `mapped`_ | +| | - `property_path`_ | +| | - `row_attr`_ | ++---------------------------+----------------------------------------------------------------------+ +| Default invalid message | The hidden field is invalid. | ++---------------------------+----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+----------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\HiddenType` | ++---------------------------+----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -40,6 +45,8 @@ Overridden Options Pass errors to the root form, otherwise they will not be visible. +.. include:: /reference/forms/types/options/invalid_message.rst.inc + ``required`` ~~~~~~~~~~~~ diff --git a/reference/forms/types/integer.rst b/reference/forms/types/integer.rst index d228f4f8145..4ab06627214 100644 --- a/reference/forms/types/integer.rst +++ b/reference/forms/types/integer.rst @@ -13,37 +13,40 @@ This field has different options on how to handle input values that aren't integers. By default, all non-integer values (e.g. 6.78) will round down (e.g. 6). -+-------------+-----------------------------------------------------------------------+ -| Rendered as | ``input`` ``number`` field | -+-------------+-----------------------------------------------------------------------+ -| Options | - `grouping`_ | -| | - `rounding_mode`_ | -+-------------+-----------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | - `scale`_ | -+-------------+-----------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+-----------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+-----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\IntegerType` | -+-------------+-----------------------------------------------------------------------+ ++---------------------------+-----------------------------------------------------------------------+ +| Rendered as | ``input`` ``number`` field | ++---------------------------+-----------------------------------------------------------------------+ +| Options | - `grouping`_ | +| | - `rounding_mode`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `invalid_message`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `invalid_message_parameters`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Default invalid message | Please enter an integer. | ++---------------------------+-----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-----------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+-----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\IntegerType` | ++---------------------------+-----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -55,51 +58,48 @@ Field Options ``rounding_mode`` ~~~~~~~~~~~~~~~~~ -**type**: ``integer`` **default**: ``IntegerToLocalizedStringTransformer::ROUND_DOWN`` +**type**: ``integer`` **default**: ``\NumberFormatter::ROUND_HALFUP`` By default, if the user enters a non-integer number, it will be rounded -down. There are several other rounding methods and each is a constant -on the :class:`Symfony\\Component\\Form\\Extension\\Core\\DataTransformer\\IntegerToLocalizedStringTransformer`: +down. You have several configurable options for that rounding. Each option +is a constant on the :phpclass:`NumberFormatter` class: -* ``IntegerToLocalizedStringTransformer::ROUND_DOWN`` Round towards zero. +* ``\NumberFormatter::ROUND_DOWN`` Round towards zero. It + rounds ``1.4`` to ``1`` and ``-1.4`` to ``-1``. -* ``IntegerToLocalizedStringTransformer::ROUND_FLOOR`` Round towards negative - infinity. +* ``\NumberFormatter::ROUND_FLOOR`` Round towards negative + infinity. It rounds ``1.4`` to ``1`` and ``-1.4`` to ``-2``. -* ``IntegerToLocalizedStringTransformer::ROUND_UP`` Round away from zero. +* ``\NumberFormatter::ROUND_UP`` Round away from zero. It + rounds ``1.4`` to ``2`` and ``-1.4`` to ``-2``. -* ``IntegerToLocalizedStringTransformer::ROUND_CEILING`` Round towards - positive infinity. +* ``\NumberFormatter::ROUND_CEILING`` Round towards positive + infinity. It rounds ``1.4`` to ``2`` and ``-1.4`` to ``-1``. -* ``IntegerToLocalizedStringTransformer::ROUND_HALF_DOWN`` Round towards the - "nearest neighbor". If both neighbors are equidistant, round down. +* ``\NumberFormatter::ROUND_HALFDOWN`` Round towards the + "nearest neighbor". If both neighbors are equidistant, round down. It rounds + ``2.5`` and ``1.6`` to ``2``, ``1.5`` and ``1.4`` to ``1``. -* ``IntegerToLocalizedStringTransformer::ROUND_HALF_EVEN`` Round towards the - "nearest neighbor". If both neighbors are equidistant, round towards the - even neighbor. +* ``\NumberFormatter::ROUND_HALFEVEN`` Round towards the + "nearest neighbor". If both neighbors are equidistant, round towards the even + neighbor. It rounds ``2.5``, ``1.6`` and ``1.5`` to ``2`` and ``1.4`` to ``1``. -* ``IntegerToLocalizedStringTransformer::ROUND_HALF_UP`` Round towards the - "nearest neighbor". If both neighbors are equidistant, round up. +* ``\NumberFormatter::ROUND_HALFUP`` Round towards the + "nearest neighbor". If both neighbors are equidistant, round up. It rounds + ``2.5`` to ``3``, ``1.6`` and ``1.5`` to ``2`` and ``1.4`` to ``1``. + +.. deprecated:: 5.1 + + In Symfony versions prior to 5.1, these constants were also defined as aliases + in the :class:`Symfony\\Component\\Form\\Extension\\Core\\DataTransformer\\NumberToLocalizedStringTransformer` + class, but they are now deprecated in favor of the :phpclass:`NumberFormatter` constants. Overridden Options ------------------ .. include:: /reference/forms/types/options/compound_type.rst.inc -``scale`` -~~~~~~~~~ - -**type**: ``integer`` **default**: ``0`` - -.. deprecated:: 4.2 - - The ``scale`` option is deprecated since Symfony 4.2 and will be removed - in 5.0. - -This specifies how many decimals will be allowed until the field rounds the -submitted value (via ``rounding_mode``). This option inherits from -:doc:`number ` type and is overridden to ``0`` for -``IntegerType``. +.. include:: /reference/forms/types/options/invalid_message.rst.inc Inherited Options ----------------- @@ -130,8 +130,6 @@ The default value is ``''`` (the empty string). .. include:: /reference/forms/types/options/help_html.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/label.rst.inc diff --git a/reference/forms/types/language.rst b/reference/forms/types/language.rst index 89c14193a80..f74016d1a0c 100644 --- a/reference/forms/types/language.rst +++ b/reference/forms/types/language.rst @@ -20,45 +20,51 @@ Unlike the ``ChoiceType``, you don't need to specify a ``choices`` option as the field type automatically uses a large list of languages. You *can* specify 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 | - `alpha3`_ | -| | - `choice_translation_locale`_ | -+-------------+------------------------------------------------------------------------+ -| Overridden | - `choices`_ | -| options | - `choice_translation_domain`_ | -+-------------+------------------------------------------------------------------------+ -| Inherited | from the :doc:`ChoiceType ` | -| options | | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `expanded`_ | -| | - `multiple`_ | -| | - `placeholder`_ | -| | - `preferred_choices`_ | -| | - `trim`_ | -| | | -| | from the :doc:`FormType ` | -| | | -| | - `attr`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`ChoiceType ` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LanguageType` | -+-------------+------------------------------------------------------------------------+ ++---------------------------+------------------------------------------------------------------------+ +| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | ++---------------------------+------------------------------------------------------------------------+ +| Options | - `alpha3`_ | +| | - `choice_self_translation`_ | +| | - `choice_translation_locale`_ | ++---------------------------+------------------------------------------------------------------------+ +| Overridden options | - `choices`_ | +| | - `choice_translation_domain`_ | +| | - `invalid_message`_ | ++---------------------------+------------------------------------------------------------------------+ +| Inherited options | from the :doc:`ChoiceType ` | +| | | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `expanded`_ | +| | - `multiple`_ | +| | - `placeholder`_ | +| | - `preferred_choices`_ | +| | - `trim`_ | +| | | +| | from the :doc:`FormType ` | +| | | +| | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+------------------------------------------------------------------------+ +| Default invalid message | Please select a valid language. | ++---------------------------+------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+------------------------------------------------------------------------+ +| Parent type | :doc:`ChoiceType ` | ++---------------------------+------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LanguageType` | ++---------------------------+------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -70,14 +76,29 @@ 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``). +choice_self_translation +~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +.. versionadded:: 5.1 + + The ``choice_self_translation`` option was introduced in Symfony 5.1. + +By default, language names are translated into the current locale of the +application. For example, when browsing the application in English, you'll get +an array like ``[..., 'cs' => 'Czech', ..., 'es' => 'Spanish', ..., 'zh' => 'Chinese']`` +and when browsing it in French, you'll get the following array: +``[..., 'cs' => 'tchèque', ..., 'es' => 'espagnol', ..., 'zh' => 'chinois']``. + +If this option is ``true``, each language is translated into its own language, +regardless of the current application locale: +``[..., 'cs' => 'čeština', ..., 'es' => 'español', ..., 'zh' => '中文']``. + .. include:: /reference/forms/types/options/choice_translation_locale.rst.inc Overridden Options @@ -98,6 +119,8 @@ The default locale is used to translate the languages names. .. include:: /reference/forms/types/options/choice_translation_domain_disabled.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/locale.rst b/reference/forms/types/locale.rst index 385cc4f6fd8..bab466d262f 100644 --- a/reference/forms/types/locale.rst +++ b/reference/forms/types/locale.rst @@ -21,44 +21,49 @@ Unlike the ``ChoiceType``, you don't need to specify a ``choices`` option as the field type automatically uses a large list of locales. You *can* specify these options 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`_ | -+-------------+------------------------------------------------------------------------+ -| Overridden | - `choices`_ | -| options | - `choice_translation_domain`_ | -+-------------+------------------------------------------------------------------------+ -| Inherited | from the :doc:`ChoiceType ` | -| options | | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `expanded`_ | -| | - `multiple`_ | -| | - `placeholder`_ | -| | - `preferred_choices`_ | -| | - `trim`_ | -| | | -| | from the :doc:`FormType ` | -| | | -| | - `attr`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`ChoiceType ` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LocaleType` | -+-------------+------------------------------------------------------------------------+ ++---------------------------+----------------------------------------------------------------------+ +| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | ++---------------------------+----------------------------------------------------------------------+ +| Options | - `choice_translation_locale`_ | ++---------------------------+----------------------------------------------------------------------+ +| Overridden options | - `choices`_ | +| | - `choice_translation_domain`_ | +| | - `invalid_message`_ | ++---------------------------+----------------------------------------------------------------------+ +| Inherited options | from the :doc:`ChoiceType ` | +| | | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `expanded`_ | +| | - `multiple`_ | +| | - `placeholder`_ | +| | - `preferred_choices`_ | +| | - `trim`_ | +| | | +| | from the :doc:`FormType ` | +| | | +| | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+----------------------------------------------------------------------+ +| Default invalid message | Please select a valid locale. | ++---------------------------+----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+----------------------------------------------------------------------+ +| Parent type | :doc:`ChoiceType ` | ++---------------------------+----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LocaleType` | ++---------------------------+----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -85,6 +90,8 @@ specify the language. .. include:: /reference/forms/types/options/choice_translation_domain_disabled.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/money.rst b/reference/forms/types/money.rst index 9fd1d4a95f4..162d8543b20 100644 --- a/reference/forms/types/money.rst +++ b/reference/forms/types/money.rst @@ -11,40 +11,44 @@ This field type allows you to specify a currency, whose symbol is rendered next to the text field. There are also several other options for customizing how the input and output of the data is handled. -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+---------------------------------------------------------------------+ -| Options | - `currency`_ | -| | - `divisor`_ | -| | - `grouping`_ | -| | - `rounding_mode`_ | -| | - `scale`_ | -+-------------+---------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\MoneyType` | -+-------------+---------------------------------------------------------------------+ ++---------------------------+---------------------------------------------------------------------+ +| Rendered as | ``input`` ``text`` field | ++---------------------------+---------------------------------------------------------------------+ +| Options | - `currency`_ | +| | - `divisor`_ | +| | - `grouping`_ | +| | - `html5`_ | +| | - `rounding_mode`_ | +| | - `scale`_ | ++---------------------------+---------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `invalid_message`_ | ++---------------------------+---------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `invalid_message_parameters`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+---------------------------------------------------------------------+ +| Default invalid message | Please enter a valid money amount. | ++---------------------------+---------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+---------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\MoneyType` | ++---------------------------+---------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -90,6 +94,22 @@ be set back on your object. .. include:: /reference/forms/types/options/rounding_mode.rst.inc +html5 +~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +.. versionadded:: 5.2 + + This option was introduced in Symfony 5.2. + +If set to ``true``, the HTML input will be rendered as a native HTML5 +```` element. + +.. caution:: + + As HTML5 number format is normalized, it is incompatible with ``grouping`` option. + scale ~~~~~ @@ -105,6 +125,8 @@ Overridden Options .. include:: /reference/forms/types/options/compound_type.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -134,8 +156,6 @@ The default value is ``''`` (the empty string). .. include:: /reference/forms/types/options/help_html.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/label.rst.inc diff --git a/reference/forms/types/number.rst b/reference/forms/types/number.rst index 6e9215c276a..99d80628d33 100644 --- a/reference/forms/types/number.rst +++ b/reference/forms/types/number.rst @@ -8,40 +8,43 @@ Renders an input text field and specializes in handling number input. This type offers different options for the scale, rounding and grouping that you want to use for your number. -+-------------+----------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+----------------------------------------------------------------------+ -| Options | - `grouping`_ | -| | - `html5`_ | -| | - `input`_ | -| | - `scale`_ | -| | - `rounding_mode`_ | -+-------------+----------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | | -+-------------+----------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+----------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\NumberType` | -+-------------+----------------------------------------------------------------------+ ++---------------------------+----------------------------------------------------------------------+ +| Rendered as | ``input`` ``text`` field | ++---------------------------+----------------------------------------------------------------------+ +| Options | - `grouping`_ | +| | - `html5`_ | +| | - `input`_ | +| | - `scale`_ | +| | - `rounding_mode`_ | ++---------------------------+----------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `invalid_message`_ | ++---------------------------+----------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `invalid_message_parameters`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+----------------------------------------------------------------------+ +| Default invalid message | Please enter a number. | ++---------------------------+----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+----------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\NumberType` | ++---------------------------+----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -55,10 +58,6 @@ html5 **type**: ``boolean`` **default**: ``false`` -.. versionadded:: 4.3 - - The ``html5`` option was introduced in Symfony 4.3. - If set to ``true``, the HTML input will be rendered as a native HTML5 ``type="number"`` form. @@ -67,10 +66,6 @@ input **type**: ``string`` **default**: ``number`` -.. versionadded:: 4.3 - - The ``input`` option was introduced in Symfony 4.3. - The format of the input data - i.e. the format that the number is stored on your underlying object. Valid values are ``number`` and ``string``. Setting this option to ``string`` can be useful if the underlying data is a string @@ -94,6 +89,8 @@ Overridden Options .. include:: /reference/forms/types/options/compound_type.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -123,8 +120,6 @@ The default value is ``''`` (the empty string). .. include:: /reference/forms/types/options/help_html.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/label.rst.inc diff --git a/reference/forms/types/options/attr_translation_parameters.rst.inc b/reference/forms/types/options/attr_translation_parameters.rst.inc index 98326ba4abe..71187cd75c5 100644 --- a/reference/forms/types/options/attr_translation_parameters.rst.inc +++ b/reference/forms/types/options/attr_translation_parameters.rst.inc @@ -3,10 +3,6 @@ attr_translation_parameters **type**: ``array`` **default**: ``[]`` -.. versionadded:: 4.3 - - The ``attr_translation_parameters`` option was introduced in Symfony 4.3. - The content of the ``title`` and ``placeholder`` values defined in the `attr`_ option is translated before displaying it, so it can contain :ref:`translation placeholders `. This diff --git a/reference/forms/types/options/block_prefix.rst.inc b/reference/forms/types/options/block_prefix.rst.inc index f02feb0ce70..db012bc3c42 100644 --- a/reference/forms/types/options/block_prefix.rst.inc +++ b/reference/forms/types/options/block_prefix.rst.inc @@ -4,10 +4,6 @@ block_prefix **type**: ``string`` or ``null`` **default**: ``null`` (see :ref:`Knowing which block to customize `) -.. versionadded:: 4.3 - - The ``block_prefix`` option was introduced in Symfony 4.3. - Allows you to add a custom block prefix and override the block name used to render the form type. Useful for example if you have multiple instances of the same form and you need to personalize the rendering diff --git a/reference/forms/types/options/choice_attr.rst.inc b/reference/forms/types/options/choice_attr.rst.inc index b86b7450778..5a0add4f195 100644 --- a/reference/forms/types/options/choice_attr.rst.inc +++ b/reference/forms/types/options/choice_attr.rst.inc @@ -38,3 +38,20 @@ If an array, the keys of the ``choices`` array must be used as keys:: return ['class' => 'attending_'.strtolower($key)]; }, ]); + +.. tip:: + + When defining a custom type, you should use the + :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` class helper:: + + use App\Entity\Category; + use Symfony\Component\Form\ChoiceList\ChoiceList; + + // ... + $builder->add('choices', ChoiceType::class, [ + 'choice_attr' => ChoiceList::attr($this, function (?Category $category) { + return $category ? ['data-uuid' => $category->getUuid()] : []; + }), + ]); + + See the :ref:`"choice_loader" option documentation `. diff --git a/reference/forms/types/options/choice_filter.rst.inc b/reference/forms/types/options/choice_filter.rst.inc new file mode 100644 index 00000000000..d7563dc8a1c --- /dev/null +++ b/reference/forms/types/options/choice_filter.rst.inc @@ -0,0 +1,82 @@ +``choice_filter`` +~~~~~~~~~~~~~~~~~ + +**type**: ``callable``, ``string`` or :class:`Symfony\\Component\\PropertyAccess\\PropertyPath` **default**: ``null`` + +.. versionadded:: 5.1 + + The ``choice_filter`` option has been introduced in Symfony 5.1. + +When using predefined choice types from Symfony core or vendor libraries (i.e. +:doc:`CountryType `) this option lets you +define a callable that takes each choice as the only argument and must return +``true`` to keep it or ``false`` to discard it:: + + // src/Form/Type/AddressType.php + namespace App\Form\Type; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\Extension\Core\Type\CountryType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolver; + + class AddressType extends AbstractType + { + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + // enable this type to accept a limited set of countries + 'allowed_countries' => null, + ]) + ; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $allowedCountries = $options['allowed_countries']; + + $builder + // ... + ->add('country', CountryType::class, [ + // if the AddressType "allowed_countries" option is passed, + // use it to create a filter + 'choice_filter' => $allowedCountries ? function ($countryCode) use ($allowedCountries) { + return in_array($countryCode, $allowedCountries, true); + } : null, + + ]) + ; + } + +The option can be a callable or a property path when choices are objects:: + + // ... + $builder + ->add('category', ChoiceType::class, [ + // ... + 'choice_filter' => 'isSelectable', + ]) + ; + +.. tip:: + + Considering this ``AddressType`` could be an entry of a ``CollectionType`` + you should use the :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` + class helper to enable caching:: + + // src/Form/Type/AddressType.php + // ... + use Symfony\Component\Form\ChoiceList\ChoiceList; + + // ... + 'choice_filter' => $allowedCountries ? ChoiceList::filter( + // pass the type as first argument + $this, + function ($countryCode) use ($allowedCountries) { + return in_array($countryCode, $allowedCountries, true); + }, + // pass the option that makes the filter "vary" to compute a unique hash + $allowedCountries + ) : null, + // ... diff --git a/reference/forms/types/options/choice_label.rst.inc b/reference/forms/types/options/choice_label.rst.inc index 53cd469b916..6cfac9323ae 100644 --- a/reference/forms/types/options/choice_label.rst.inc +++ b/reference/forms/types/options/choice_label.rst.inc @@ -53,3 +53,17 @@ If your choice values are objects, then ``choice_label`` can also be a If set to ``false``, all the tag labels will be discarded for radio or checkbox inputs. You can also return ``false`` from the callable to discard certain labels. + +.. tip:: + + When defining a custom type, you should use the + :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` class helper:: + + use Symfony\Component\Form\ChoiceList\ChoiceList; + + // ... + $builder->add('choices', ChoiceType::class, [ + 'choice_label' => ChoiceList::label($this, 'displayName'), + ]); + + See the :ref:`"choice_loader" option documentation `. diff --git a/reference/forms/types/options/choice_loader.rst.inc b/reference/forms/types/options/choice_loader.rst.inc new file mode 100644 index 00000000000..c44601ed3eb --- /dev/null +++ b/reference/forms/types/options/choice_loader.rst.inc @@ -0,0 +1,75 @@ +choice_loader +~~~~~~~~~~~~~ + +**type**: :class:`Symfony\\Component\\Form\\ChoiceList\\Loader\\ChoiceLoaderInterface` + +The ``choice_loader`` option can be used instead of the ``choices`` option. It +allows to create a list lazily or partially when fetching only the choices for a +set of submitted values (i.e. querying a search engine like ``ElasticSearch`` +can be a heavy process). + +You can use an instance of :class:`Symfony\\Component\\Form\\ChoiceList\\Loader\\CallbackChoiceLoader` +if you want to take advantage of lazy loading:: + + use App\StaticClass; + use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader; + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + // ... + + $builder->add('loaded_choices', ChoiceType::class, [ + 'choice_loader' => new CallbackChoiceLoader(function() { + return StaticClass::getConstants(); + }), + ]); + +This will cause the call of ``StaticClass::getConstants()`` to not happen if the +request is redirected and if there is no pre set or submitted data. Otherwise +the choice options would need to be resolved thus triggering the callback. + +When you're defining a custom choice type that may be reused in many fields +(like entries of a collection) or reused in multiple forms at once, you +should use the :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` +static methods to wrap the loader and make the choice list cacheable for +better performance:: + + use App\Form\ChoiceList\CustomChoiceLoader; + use App\StaticClass; + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\ChoiceList\ChoiceList; + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + use Symfony\Component\OptionsResolver\Options; + use Symfony\Component\OptionsResolver\OptionsResolver; + + class ConstantsType extends AbstractType + { + public static function getExtendedTypes(): iterable + { + return [ChoiceType::class]; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + // the example below will create a CallbackChoiceLoader from the callable + 'choice_loader' => ChoiceList::lazy($this, function() { + return StaticClass::getConstants(); + }), + + // you can pass your own loader as well, depending on other options + 'some_key' => null, + 'choice_loader' => function (Options $options) { + return ChoiceList::loader( + // pass the instance of the type or type extension which is + // currently configuring the choice list as first argument + $this, + // pass the other option to the loader + new CustomChoiceLoader($options['some_key']), + // ensure the type stores a loader per key + // by using the special third argument "$vary" + // an array containing anything that "changes" the loader + [$options['some_key']] + ); + }, + ]); + } + } diff --git a/reference/forms/types/options/choice_name.rst.inc b/reference/forms/types/options/choice_name.rst.inc index ed39bad5e9c..4ec8abb6ffe 100644 --- a/reference/forms/types/options/choice_name.rst.inc +++ b/reference/forms/types/options/choice_name.rst.inc @@ -11,6 +11,20 @@ attribute. This can be a callable or a property path. See `choice_label`_ for similar usage. By default, the choice key or an incrementing integer may be used (starting at ``0``). +.. tip:: + + When defining a custom type, you should use the + :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` class helper:: + + use Symfony\Component\Form\ChoiceList\ChoiceList; + + // ... + $builder->add('choices', ChoiceType::class, [ + 'choice_name' => ChoiceList::fieldName($this, 'name'), + ]); + + See the :ref:`"choice_loader" option documentation `. + .. caution:: The configured value must be a valid form name. Make sure to only return diff --git a/reference/forms/types/options/choice_translation_parameters.rst.inc b/reference/forms/types/options/choice_translation_parameters.rst.inc new file mode 100644 index 00000000000..32e66393383 --- /dev/null +++ b/reference/forms/types/options/choice_translation_parameters.rst.inc @@ -0,0 +1,80 @@ +choice_translation_parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``array``, ``callable``, ``string`` or :class:`Symfony\\Component\\PropertyAccess\\PropertyPath` **default**: ``[]`` + +The choice values are translated before displaying it, so it can contain +:ref:`translation placeholders `. +This option defines the values used to replace those placeholders. This can be +an associative array where the keys match the choice keys and the values +are the attributes for each choice, a callable or a property path +(just like `choice_label`_). + +Given this translation message: + +.. configuration-block:: + + .. code-block:: yaml + + # translations/messages.en.yaml + form.order.yes: 'I confirm my order to the company %company%' + form.order.no: 'I cancel my order' + + .. code-block:: xml + + + + + + + + form.order.yes + I confirm my order to the company %company% + + + form.order.no + I cancel my order + + + + + + .. code-block:: php + + // translations/messages.fr.php + return [ + 'form.order.yes' => "I confirm my order to the company %company%", + 'form.order.no' => "I cancel my order", + ]; + +You can specify the placeholder values as follows:: + + $builder->add('id', null, [ + 'choice' => [ + 'form.order.yes' => true, + 'form.order.no' => false, + ], + 'choice_translation_parameters' => function ($choice, $key, $value) { + if (false === $choice) { + return []; + } + + return ['%company%' => 'ACME Inc.'] + }, + ]); + +If an array, the keys of the ``choices`` array must be used as keys:: + + $builder->add('id', null, [ + 'choice' => [ + 'form.order.yes' => true, + 'form.order.no' => false, + ], + 'choice_translation_parameters' => [ + 'form.order.yes' => ['%company%' => 'ACME Inc.'], + 'form.order.no' => [], + ], + ]); + +The translation parameters of child fields are merged with the same option of +their parents, so children can reuse and/or override any of the parent placeholders. diff --git a/reference/forms/types/options/choice_value.rst.inc b/reference/forms/types/options/choice_value.rst.inc index a37a36cf299..13bc324cd2a 100644 --- a/reference/forms/types/options/choice_value.rst.inc +++ b/reference/forms/types/options/choice_value.rst.inc @@ -18,3 +18,17 @@ for each choice or ``null`` in a placeholder is used, which you need to handle:: 'choice_value' => function (?MyOptionEntity $entity) { return $entity ? $entity->getId() : ''; }, + +.. tip:: + + When defining a custom type, you should use the + :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` class helper:: + + use Symfony\Component\Form\ChoiceList\ChoiceList; + + // ... + $builder->add('choices', ChoiceType::class, [ + 'choice_value' => ChoiceList::value($this, 'uuid'), + ]); + + See the :ref:`"choice_loader" option documentation `. diff --git a/reference/forms/types/options/date_input_format_description.rst.inc b/reference/forms/types/options/date_input_format_description.rst.inc index 4cd9b353e31..e411cd12d70 100644 --- a/reference/forms/types/options/date_input_format_description.rst.inc +++ b/reference/forms/types/options/date_input_format_description.rst.inc @@ -1,7 +1,3 @@ -.. versionadded:: 4.3 - - The ``input_format`` option was introduced in Symfony 4.3. - If the ``input`` option is set to ``string``, this option specifies the format of the date. This must be a valid `PHP date format`_. diff --git a/reference/forms/types/options/extra_fields_message.rst.inc b/reference/forms/types/options/extra_fields_message.rst.inc index ca54c91ec54..5c969f7afce 100644 --- a/reference/forms/types/options/extra_fields_message.rst.inc +++ b/reference/forms/types/options/extra_fields_message.rst.inc @@ -1,9 +1,17 @@ ``extra_fields_message`` ~~~~~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 5.1 + + Pluralization support was introduced in Symfony 5.1. + **type**: ``string`` **default**: ``This form should not contain extra fields.`` This is the validation error message that's used if the submitted form data contains one or more fields that are not part of the form definition. The placeholder ``{{ extra_fields }}`` can be used to display a comma separated list of the submitted extra field names. + +This message can be pluralized, see +:ref:`formatting pluralized messages ` for +details. diff --git a/reference/forms/types/options/group_by.rst.inc b/reference/forms/types/options/group_by.rst.inc index b649793e9ff..ca747683662 100644 --- a/reference/forms/types/options/group_by.rst.inc +++ b/reference/forms/types/options/group_by.rst.inc @@ -40,3 +40,17 @@ a "Later" ````: If you return ``null``, the option won't be grouped. You can also pass a string "property path" that will be called to get the group. See the `choice_label`_ for details about using a property path. + +.. tip:: + + When defining a custom type, you should use the + :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` class helper:: + + use Symfony\Component\Form\ChoiceList\ChoiceList; + + // ... + $builder->add('choices', ChoiceType::class, [ + 'group_by' => ChoiceList::groupBy($this, 'category'), + ]); + + See the :ref:`"choice_loader" option documentation `. diff --git a/reference/forms/types/options/help_translation_parameters.rst.inc b/reference/forms/types/options/help_translation_parameters.rst.inc index 4294fb2b185..2b69e237941 100644 --- a/reference/forms/types/options/help_translation_parameters.rst.inc +++ b/reference/forms/types/options/help_translation_parameters.rst.inc @@ -3,10 +3,6 @@ help_translation_parameters **type**: ``array`` **default**: ``[]`` -.. versionadded:: 4.3 - - The ``help_translation_parameters`` option was introduced in Symfony 4.3. - The content of the `help`_ option is translated before displaying it, so it can contain :ref:`translation placeholders `. This option defines the values used to replace those placeholders. diff --git a/reference/forms/types/options/label_html.rst.inc b/reference/forms/types/options/label_html.rst.inc new file mode 100644 index 00000000000..a87ad4ab6db --- /dev/null +++ b/reference/forms/types/options/label_html.rst.inc @@ -0,0 +1,12 @@ +``label_html`` +~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +.. versionadded:: 5.1 + + The ``label_html`` option was introduced in Symfony 5.1. + +By default, the contents of the ``label`` option are escaped before rendering +them in the template. Set this option to ``true`` to not escape them, which is +useful when the label contains HTML elements. diff --git a/reference/forms/types/options/label_translation_parameters.rst.inc b/reference/forms/types/options/label_translation_parameters.rst.inc index 443c6706f14..815b780553e 100644 --- a/reference/forms/types/options/label_translation_parameters.rst.inc +++ b/reference/forms/types/options/label_translation_parameters.rst.inc @@ -3,10 +3,6 @@ label_translation_parameters **type**: ``array`` **default**: ``[]`` -.. versionadded:: 4.3 - - The ``label_translation_parameters`` option was introduced in Symfony 4.3. - The content of the `label`_ option is translated before displaying it, so it can contain :ref:`translation placeholders `. This option defines the values used to replace those placeholders. diff --git a/reference/forms/types/options/preferred_choices.rst.inc b/reference/forms/types/options/preferred_choices.rst.inc index 11eb2b7f8b4..bffb021f864 100644 --- a/reference/forms/types/options/preferred_choices.rst.inc +++ b/reference/forms/types/options/preferred_choices.rst.inc @@ -20,12 +20,6 @@ form of languages, you can list the most popular on top, like 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:: @@ -69,3 +63,17 @@ when rendering the field: widget($form['publishAt'], [ 'separator' => '=====', ]) ?> + +.. tip:: + + When defining a custom type, you should use the + :class:`Symfony\\Component\\Form\\ChoiceList\\ChoiceList` class helper:: + + use Symfony\Component\Form\ChoiceList\ChoiceList; + + // ... + $builder->add('choices', ChoiceType::class, [ + 'preferred_choices' => ChoiceList::preferred($this, 'taggedAsFavorite'), + ]); + + See the :ref:`"choice_loader" option documentation `. diff --git a/reference/forms/types/options/rounding_mode.rst.inc b/reference/forms/types/options/rounding_mode.rst.inc index 066a1da0a22..525f5d99cdf 100644 --- a/reference/forms/types/options/rounding_mode.rst.inc +++ b/reference/forms/types/options/rounding_mode.rst.inc @@ -1,32 +1,38 @@ rounding_mode ~~~~~~~~~~~~~ -**type**: ``integer`` **default**: ``NumberToLocalizedStringTransformer::ROUND_HALF_UP`` +**type**: ``integer`` **default**: ``\NumberFormatter::ROUND_HALFUP`` -If a submitted number needs to be rounded (based on the `scale`_ -option), you have several configurable options for that rounding. Each -option is a constant on the :class:`Symfony\\Component\\Form\\Extension\\Core\\DataTransformer\\NumberToLocalizedStringTransformer`: +If a submitted number needs to be rounded (based on the `scale`_ option), you +have several configurable options for that rounding. Each option is a constant +on the :phpclass:`NumberFormatter` class: -* ``NumberToLocalizedStringTransformer::ROUND_DOWN`` Round towards zero. It +* ``\NumberFormatter::ROUND_DOWN`` Round towards zero. It rounds ``1.4`` to ``1`` and ``-1.4`` to ``-1``. -* ``NumberToLocalizedStringTransformer::ROUND_FLOOR`` Round towards negative +* ``\NumberFormatter::ROUND_FLOOR`` Round towards negative infinity. It rounds ``1.4`` to ``1`` and ``-1.4`` to ``-2``. -* ``NumberToLocalizedStringTransformer::ROUND_UP`` Round away from zero. It +* ``\NumberFormatter::ROUND_UP`` Round away from zero. It rounds ``1.4`` to ``2`` and ``-1.4`` to ``-2``. -* ``NumberToLocalizedStringTransformer::ROUND_CEILING`` Round towards positive +* ``\NumberFormatter::ROUND_CEILING`` Round towards positive infinity. It rounds ``1.4`` to ``2`` and ``-1.4`` to ``-1``. -* ``NumberToLocalizedStringTransformer::ROUND_HALF_DOWN`` Round towards the +* ``\NumberFormatter::ROUND_HALFDOWN`` Round towards the "nearest neighbor". If both neighbors are equidistant, round down. It rounds ``2.5`` and ``1.6`` to ``2``, ``1.5`` and ``1.4`` to ``1``. -* ``NumberToLocalizedStringTransformer::ROUND_HALF_EVEN`` Round towards the +* ``\NumberFormatter::ROUND_HALFEVEN`` Round towards the "nearest neighbor". If both neighbors are equidistant, round towards the even neighbor. It rounds ``2.5``, ``1.6`` and ``1.5`` to ``2`` and ``1.4`` to ``1``. -* ``NumberToLocalizedStringTransformer::ROUND_HALF_UP`` Round towards the +* ``\NumberFormatter::ROUND_HALFUP`` Round towards the "nearest neighbor". If both neighbors are equidistant, round up. It rounds ``2.5`` to ``3``, ``1.6`` and ``1.5`` to ``2`` and ``1.4`` to ``1``. + +.. deprecated:: 5.1 + + In Symfony versions prior to 5.1, these constants were also defined as aliases + in the :class:`Symfony\\Component\\Form\\Extension\\Core\\DataTransformer\\NumberToLocalizedStringTransformer` + class, but they are now deprecated in favor of the :phpclass:`NumberFormatter` constants. diff --git a/reference/forms/types/options/row_attr.rst.inc b/reference/forms/types/options/row_attr.rst.inc index 4cb9775b5e5..e8cbaa6b564 100644 --- a/reference/forms/types/options/row_attr.rst.inc +++ b/reference/forms/types/options/row_attr.rst.inc @@ -14,7 +14,3 @@ to render the :ref:`form type row `:: Use the ``attr`` option if you want to add these attributes to the the :ref:`form type widget ` element. - -.. versionadded:: 4.3 - - The ``row_attr`` option was introduced in Symfony 4.3. diff --git a/reference/forms/types/password.rst b/reference/forms/types/password.rst index 37acff1a616..18be51b396f 100644 --- a/reference/forms/types/password.rst +++ b/reference/forms/types/password.rst @@ -6,33 +6,37 @@ PasswordType Field The ``PasswordType`` field renders an input password text box. -+-------------+------------------------------------------------------------------------+ -| Rendered as | ``input`` ``password`` field | -+-------------+------------------------------------------------------------------------+ -| Options | - `always_empty`_ | -+-------------+------------------------------------------------------------------------+ -| Overridden | - `trim`_ | -| options | | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`TextType ` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PasswordType` | -+-------------+------------------------------------------------------------------------+ ++---------------------------+------------------------------------------------------------------------+ +| Rendered as | ``input`` ``password`` field | ++---------------------------+------------------------------------------------------------------------+ +| Options | - `always_empty`_ | ++---------------------------+------------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | +| | - `trim`_ | ++---------------------------+------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+------------------------------------------------------------------------+ +| Default invalid message | The password is invalid. | ++---------------------------+------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+------------------------------------------------------------------------+ +| Parent type | :doc:`TextType ` | ++---------------------------+------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PasswordType` | ++---------------------------+------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -54,6 +58,8 @@ entered into the box, set this to false and submit the form. Overridden Options ------------------ +.. include:: /reference/forms/types/options/invalid_message.rst.inc + ``trim`` ~~~~~~~~ diff --git a/reference/forms/types/percent.rst b/reference/forms/types/percent.rst index b6a516fffc0..36ffdcc1e1b 100644 --- a/reference/forms/types/percent.rst +++ b/reference/forms/types/percent.rst @@ -12,61 +12,82 @@ you can use this field out-of-the-box. If you store your data as a number When ``symbol`` is not ``false``, the field will render the given string after the input. -+-------------+-----------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+-----------------------------------------------------------------------+ -| Options | - `scale`_ | -| | - `symbol`_ | -| | - `type`_ | -+-------------+-----------------------------------------------------------------------+ -| Overridden | - `compound`_ | -| options | | -+-------------+-----------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+-----------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+-----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PercentType` | -+-------------+-----------------------------------------------------------------------+ ++---------------------------+-----------------------------------------------------------------------+ +| Rendered as | ``input`` ``text`` field | ++---------------------------+-----------------------------------------------------------------------+ +| Options | - `html5`_ | +| | - `rounding_mode`_ | +| | - `scale`_ | +| | - `symbol`_ | +| | - `type`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Overridden options | - `compound`_ | +| | - `invalid_message`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `invalid_message_parameters`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+-----------------------------------------------------------------------+ +| Default invalid message | Please enter a percentage value. | ++---------------------------+-----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-----------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+-----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PercentType` | ++---------------------------+-----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc Field Options ------------- +.. include:: /reference/forms/types/options/rounding_mode.rst.inc + +.. versionadded:: 5.1 + + The ``rounding_mode`` option was introduced in Symfony 5.1. + +html5 +~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +.. versionadded:: 5.2 + + This option was introduced in Symfony 5.2. + +If set to ``true``, the HTML input will be rendered as a native HTML5 +```` element. + scale ~~~~~ **type**: ``integer`` **default**: ``0`` -By default, the input numbers are rounded. To allow for more decimal places, -use this option. +This specifies how many decimals will be allowed until the field rounds +the submitted value (via ``rounding_mode``). For example, if ``scale`` is set +to ``2``, a submitted value of ``20.123`` will be rounded to, for example, +``20.12`` (depending on your `rounding_mode`_). symbol ~~~~~~ **type**: ``boolean`` or ``string`` **default**: ``%`` -.. versionadded:: 4.3 - - The ``symbol`` option was introduced in Symfony 4.3. - By default, fields are rendered with a percentage sign ``%`` after the input. Setting the value to ``false`` will not display the percentage sign. Setting the value to a ``string`` (e.g. ``‱``), will show that string instead of the default @@ -97,6 +118,8 @@ Overridden Options .. include:: /reference/forms/types/options/compound_type.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -126,8 +149,6 @@ The default value is ``''`` (the empty string). .. include:: /reference/forms/types/options/help_html.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/label.rst.inc diff --git a/reference/forms/types/radio.rst b/reference/forms/types/radio.rst index ae0d58d2fe4..93fbe3ecfd9 100644 --- a/reference/forms/types/radio.rst +++ b/reference/forms/types/radio.rst @@ -13,38 +13,49 @@ The ``RadioType`` isn't usually used directly. More commonly it's used internally by other types such as :doc:`ChoiceType `. If you want to have a boolean field, use :doc:`CheckboxType `. -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``radio`` field | -+-------------+---------------------------------------------------------------------+ -| Inherited | from the :doc:`CheckboxType `: | -| options | | -| | - `value`_ | -| | | -| | from the :doc:`FormType `: | -| | | -| | - `attr`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`CheckboxType ` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RadioType` | -+-------------+---------------------------------------------------------------------+ ++---------------------------+---------------------------------------------------------------------+ +| Rendered as | ``input`` ``radio`` field | ++---------------------------+---------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+---------------------------------------------------------------------+ +| Inherited options | from the :doc:`CheckboxType `: | +| | | +| | - `value`_ | +| | | +| | from the :doc:`FormType `: | +| | | +| | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+---------------------------------------------------------------------+ +| Default invalid message | Please select a valid option. | ++---------------------------+---------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+---------------------------------------------------------------------+ +| Parent type | :doc:`CheckboxType ` | ++---------------------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RadioType` | ++---------------------------+---------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/range.rst b/reference/forms/types/range.rst index e328a1bbe97..f8284f1b7eb 100644 --- a/reference/forms/types/range.rst +++ b/reference/forms/types/range.rst @@ -7,29 +7,35 @@ RangeType Field The ``RangeType`` field is a slider that is rendered using the HTML5 ```` tag. -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``range`` field (slider in HTML5 supported browser) | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -| | - `trim`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`TextType ` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RangeType` | -+-------------+---------------------------------------------------------------------+ ++---------------------------+---------------------------------------------------------------------+ +| Rendered as | ``input`` ``range`` field (slider in HTML5 supported browser) | ++---------------------------+---------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+---------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | +| | - `trim`_ | ++---------------------------+---------------------------------------------------------------------+ +| Default invalid message | Please choose a valid range. | ++---------------------------+---------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+---------------------------------------------------------------------+ +| Parent type | :doc:`TextType ` | ++---------------------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RangeType` | ++---------------------------+---------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -48,6 +54,11 @@ Basic Usage ] ]); +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/repeated.rst b/reference/forms/types/repeated.rst index c78e6cc318e..8c36c64ddd5 100644 --- a/reference/forms/types/repeated.rst +++ b/reference/forms/types/repeated.rst @@ -9,34 +9,37 @@ values must match (or a validation error is thrown). The most common use is when you need the user to repeat their password or email to verify accuracy. -+-------------+------------------------------------------------------------------------+ -| Rendered as | input ``text`` field by default, but see `type`_ option | -+-------------+------------------------------------------------------------------------+ -| Options | - `first_name`_ | -| | - `first_options`_ | -| | - `options`_ | -| | - `second_name`_ | -| | - `second_options`_ | -| | - `type`_ | -+-------------+------------------------------------------------------------------------+ -| Overridden | - `error_bubbling`_ | -| options | | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `mapped`_ | -| | - `row_attr`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`FormType ` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RepeatedType` | -+-------------+------------------------------------------------------------------------+ ++---------------------------+------------------------------------------------------------------------+ +| Rendered as | input ``text`` field by default, but see `type`_ option | ++---------------------------+------------------------------------------------------------------------+ +| Options | - `first_name`_ | +| | - `first_options`_ | +| | - `options`_ | +| | - `second_name`_ | +| | - `second_options`_ | +| | - `type`_ | ++---------------------------+------------------------------------------------------------------------+ +| Overridden options | - `error_bubbling`_ | +| | - `invalid_message`_ | ++---------------------------+------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++---------------------------+------------------------------------------------------------------------+ +| Default invalid message | The values do not match. | ++---------------------------+------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+------------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RepeatedType` | ++---------------------------+------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -184,6 +187,8 @@ Overridden Options **default**: ``false`` +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -201,8 +206,6 @@ These options inherit from the :doc:`FormType `: .. include:: /reference/forms/types/options/help_html.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 diff --git a/reference/forms/types/search.rst b/reference/forms/types/search.rst index e0f8233aa5b..d6dceeb0264 100644 --- a/reference/forms/types/search.rst +++ b/reference/forms/types/search.rst @@ -9,32 +9,43 @@ special functionality supported by some browsers. Read about the input search field at `DiveIntoHTML5.info`_ -+-------------+----------------------------------------------------------------------+ -| Rendered as | ``input search`` field | -+-------------+----------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -| | - `trim`_ | -+-------------+----------------------------------------------------------------------+ -| Parent type | :doc:`TextType ` | -+-------------+----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\SearchType` | -+-------------+----------------------------------------------------------------------+ ++---------------------------+----------------------------------------------------------------------+ +| Rendered as | ``input search`` field | ++---------------------------+----------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+----------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | +| | - `trim`_ | ++---------------------------+----------------------------------------------------------------------+ +| Default invalid message | Please enter a valid search term. | ++---------------------------+----------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+----------------------------------------------------------------------+ +| Parent type | :doc:`TextType ` | ++---------------------------+----------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\SearchType` | ++---------------------------+----------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/submit.rst b/reference/forms/types/submit.rst index 2743a447760..0554aef8a8e 100644 --- a/reference/forms/types/submit.rst +++ b/reference/forms/types/submit.rst @@ -45,10 +45,6 @@ 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. diff --git a/reference/forms/types/tel.rst b/reference/forms/types/tel.rst index f6c19391ada..19847431dd3 100644 --- a/reference/forms/types/tel.rst +++ b/reference/forms/types/tel.rst @@ -13,33 +13,44 @@ Nevertheless, it may be useful to use this type in web applications because some browsers (e.g. smartphone browsers) adapt the input keyboard to make it easier to input phone numbers. -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``tel`` field (a text box) | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -| | - `trim`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`TextType ` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TelType` | -+-------------+---------------------------------------------------------------------+ ++---------------------------+-------------------------------------------------------------------+ +| Rendered as | ``input`` ``tel`` field (a text box) | ++---------------------------+-------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+-------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | +| | - `trim`_ | ++---------------------------+-------------------------------------------------------------------+ +| Default invalid message | Please provide a valid phone number. | ++---------------------------+-------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-------------------------------------------------------------------+ +| Parent type | :doc:`TextType ` | ++---------------------------+-------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TelType` | ++---------------------------+-------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/time.rst b/reference/forms/types/time.rst index 2ecc657e6b1..cac168d569e 100644 --- a/reference/forms/types/time.rst +++ b/reference/forms/types/time.rst @@ -10,48 +10,52 @@ This can be rendered as a text field, a series of text fields (e.g. hour, minute, second) or a series of select fields. The underlying data can be stored as a ``DateTime`` object, a string, a timestamp or an array. -+----------------------+-----------------------------------------------------------------------------+ -| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | -+----------------------+-----------------------------------------------------------------------------+ -| Rendered as | can be various tags (see below) | -+----------------------+-----------------------------------------------------------------------------+ -| Options | - `choice_translation_domain`_ | -| | - `placeholder`_ | -| | - `hours`_ | -| | - `html5`_ | -| | - `input`_ | -| | - `input_format`_ | -| | - `minutes`_ | -| | - `model_timezone`_ | -| | - `reference_date`_ | -| | - `seconds`_ | -| | - `view_timezone`_ | -| | - `widget`_ | -| | - `with_minutes`_ | -| | - `with_seconds`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Overridden options | - `by_reference`_ | -| | - `compound`_ | -| | - `data_class`_ | -| | - `error_bubbling`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `inherit_data`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -| | - `mapped`_ | -| | - `row_attr`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Parent type | FormType | -+----------------------+-----------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimeType` | -+----------------------+-----------------------------------------------------------------------------+ ++---------------------------+-----------------------------------------------------------------------------+ +| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | ++---------------------------+-----------------------------------------------------------------------------+ +| Rendered as | can be various tags (see below) | ++---------------------------+-----------------------------------------------------------------------------+ +| Options | - `choice_translation_domain`_ | +| | - `placeholder`_ | +| | - `hours`_ | +| | - `html5`_ | +| | - `input`_ | +| | - `input_format`_ | +| | - `minutes`_ | +| | - `model_timezone`_ | +| | - `reference_date`_ | +| | - `seconds`_ | +| | - `view_timezone`_ | +| | - `widget`_ | +| | - `with_minutes`_ | +| | - `with_seconds`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Overridden options | - `by_reference`_ | +| | - `compound`_ | +| | - `data_class`_ | +| | - `error_bubbling`_ | +| | - `invalid_message`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++---------------------------+-----------------------------------------------------------------------------+ +| Default invalid message | Please enter a valid time. | ++---------------------------+-----------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-----------------------------------------------------------------------------+ +| Parent type | FormType | ++---------------------------+-----------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimeType` | ++---------------------------+-----------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -140,10 +144,6 @@ input_format **type**: ``string`` **default**: ``H:i:s`` -.. versionadded:: 4.3 - - The ``input_format`` option was introduced in Symfony 4.3. - If the ``input`` option is set to ``string``, this option specifies the format of the time. This must be a valid `PHP time format`_. @@ -161,10 +161,6 @@ reference_date **type**: ``DateTimeInterface`` **default**: ``null`` -.. versionadded:: 4.4 - - The ``reference_date`` option was introduced in Symfony 4.4. - Configuring a reference date is required when the `model_timezone`_ and `view_timezone`_ are different. Timezone conversions will be calculated based on this date. @@ -173,6 +169,9 @@ based on this date. .. include:: /reference/forms/types/options/view_timezone.rst.inc +When no `reference_date`_ is set the ``view_timezone`` defaults to the +configured `model_timezone`_. + .. caution:: When using different values for `model_timezone`_ and ``view_timezone``, @@ -225,6 +224,8 @@ error_bubbling **default**: ``false`` +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -246,8 +247,6 @@ These options inherit from the :doc:`FormType `: .. 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 diff --git a/reference/forms/types/timezone.rst b/reference/forms/types/timezone.rst index 9458e3a7d1a..987f26c9036 100644 --- a/reference/forms/types/timezone.rst +++ b/reference/forms/types/timezone.rst @@ -14,46 +14,50 @@ Unlike the ``ChoiceType``, you don't need to specify a ``choices`` option as the field type automatically uses a large list of timezones. You *can* specify 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 | - `input`_ | -| | - `intl`_ | -| | - `regions`_ | -+-------------+------------------------------------------------------------------------+ -| Overridden | - `choices`_ | -| options | - `choice_translation_domain`_ | -+-------------+------------------------------------------------------------------------+ -| Inherited | from the :doc:`ChoiceType ` | -| options | | -| | - `expanded`_ | -| | - `multiple`_ | -| | - `placeholder`_ | -| | - `preferred_choices`_ | -| | - `trim`_ | -| | | -| | from the :doc:`FormType ` | -| | | -| | - `attr`_ | -| | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`ChoiceType ` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimezoneType` | -+-------------+------------------------------------------------------------------------+ ++---------------------------+------------------------------------------------------------------------+ +| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | ++---------------------------+------------------------------------------------------------------------+ +| Options | - `input`_ | +| | - `intl`_ | ++---------------------------+------------------------------------------------------------------------+ +| Overridden options | - `choices`_ | +| | - `choice_translation_domain`_ | +| | - `invalid_message`_ | ++---------------------------+------------------------------------------------------------------------+ +| Inherited options | from the :doc:`ChoiceType ` | +| | | +| | - `expanded`_ | +| | - `multiple`_ | +| | - `placeholder`_ | +| | - `preferred_choices`_ | +| | - `trim`_ | +| | | +| | from the :doc:`FormType ` | +| | | +| | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | ++---------------------------+------------------------------------------------------------------------+ +| Default invalid message | Please select a valid timezone. | ++---------------------------+------------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+------------------------------------------------------------------------+ +| Parent type | :doc:`ChoiceType ` | ++---------------------------+------------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimezoneType` | ++---------------------------+------------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -72,19 +76,11 @@ on your underlying object. Valid values are: * ``intltimezone`` (an ``\IntlTimeZone`` object) * ``string`` (e.g. ``America/New_York``) -.. versionadded:: 4.3 - - The ``intltimezone`` input type was introduced in Symfony 4.3. - intl ~~~~ **type**: ``boolean`` **default**: ``false`` -.. versionadded:: 4.3 - - This option was introduced in Symfony 4.3. - If this option is set to ``true``, the timezone selector will display the timezones from the `ICU Project`_ via the :doc:`Intl component ` instead of the regular PHP timezones. @@ -98,17 +94,6 @@ with the ``choice_translation_locale`` option. The :doc:`Timezone constraint ` can validate both timezone sets and adapts to the selected set automatically. -``regions`` -~~~~~~~~~~~ - -**type**: ``integer`` **default**: ``\DateTimeZone::ALL`` - -.. deprecated:: 4.2 - - This option was deprecated in Symfony 4.2. - -The available regions in the timezone choice list. For example: ``DateTimeZone::AMERICA | DateTimeZone::EUROPE`` - Overridden Options ------------------ @@ -127,6 +112,8 @@ The Timezone type defaults the choices to all timezones returned by .. include:: /reference/forms/types/options/choice_translation_domain_disabled.rst.inc +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/url.rst b/reference/forms/types/url.rst index a03f1532021..13f425f1d70 100644 --- a/reference/forms/types/url.rst +++ b/reference/forms/types/url.rst @@ -8,32 +8,38 @@ The ``UrlType`` field is a text field that prepends the submitted value with a given protocol (e.g. ``http://``) if the submitted value doesn't already have a protocol. -+-------------+-------------------------------------------------------------------+ -| Rendered as | ``input url`` field | -+-------------+-------------------------------------------------------------------+ -| Options | - `default_protocol`_ | -+-------------+-------------------------------------------------------------------+ -| Inherited | - `attr`_ | -| options | - `data`_ | -| | - `disabled`_ | -| | - `empty_data`_ | -| | - `error_bubbling`_ | -| | - `error_mapping`_ | -| | - `help`_ | -| | - `help_attr`_ | -| | - `help_html`_ | -| | - `label`_ | -| | - `label_attr`_ | -| | - `label_format`_ | -| | - `mapped`_ | -| | - `required`_ | -| | - `row_attr`_ | -| | - `trim`_ | -+-------------+-------------------------------------------------------------------+ -| Parent type | :doc:`TextType ` | -+-------------+-------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\UrlType` | -+-------------+-------------------------------------------------------------------+ ++---------------------------+-------------------------------------------------------------------+ +| Rendered as | ``input url`` field | ++---------------------------+-------------------------------------------------------------------+ +| Options | - `default_protocol`_ | ++---------------------------+-------------------------------------------------------------------+ +| Overridden options | - `invalid_message`_ | ++---------------------------+-------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `empty_data`_ | +| | - `error_bubbling`_ | +| | - `error_mapping`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `label`_ | +| | - `label_attr`_ | +| | - `label_format`_ | +| | - `mapped`_ | +| | - `required`_ | +| | - `row_attr`_ | +| | - `trim`_ | ++---------------------------+-------------------------------------------------------------------+ +| Default invalid message | Please enter a valid URL. | ++---------------------------+-------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+-------------------------------------------------------------------+ +| Parent type | :doc:`TextType ` | ++---------------------------+-------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\UrlType` | ++---------------------------+-------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -49,6 +55,11 @@ If a value is submitted that doesn't begin with some protocol (e.g. ``http://``, ``ftp://``, etc), this protocol will be prepended to the string when the data is submitted to the form. +Overridden Options +------------------ + +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- diff --git a/reference/forms/types/week.rst b/reference/forms/types/week.rst index 754139c9cd6..99762f803e3 100644 --- a/reference/forms/types/week.rst +++ b/reference/forms/types/week.rst @@ -4,49 +4,49 @@ 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` | -+----------------------+-----------------------------------------------------------------------------+ ++---------------------------+--------------------------------------------------------------------+ +| 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`_ | +| | - `invalid_message`_ | ++---------------------------+--------------------------------------------------------------------+ +| Inherited options | - `attr`_ | +| | - `data`_ | +| | - `disabled`_ | +| | - `help`_ | +| | - `help_attr`_ | +| | - `help_html`_ | +| | - `inherit_data`_ | +| | - `invalid_message_parameters`_ | +| | - `mapped`_ | +| | - `row_attr`_ | ++---------------------------+--------------------------------------------------------------------+ +| Default invalid message | Please enter a valid week. | ++---------------------------+--------------------------------------------------------------------+ +| Legacy invalid message | The value {{ value }} is not valid. | ++---------------------------+--------------------------------------------------------------------+ +| Parent type | :doc:`FormType ` | ++---------------------------+--------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\WeekType` | ++---------------------------+--------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_debug_form.rst.inc @@ -142,6 +142,8 @@ error_bubbling **default**: ``false`` +.. include:: /reference/forms/types/options/invalid_message.rst.inc + Inherited Options ----------------- @@ -161,8 +163,6 @@ These options inherit from the :doc:`FormType `: .. 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 diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index f44f5043d92..270c9c678c8 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -144,19 +144,13 @@ is_granted {{ is_granted(role, object = null, field = null) }} ``role`` - **type**: ``string``, ``string[]`` + **type**: ``string`` ``object`` *(optional)* **type**: ``object`` ``field`` *(optional)* **type**: ``string`` -Returns ``true`` if the current user has the given role. If several roles are -passed in an array, ``true`` is returned if the user has at least one of -them. - -.. deprecated:: 4.4 - - The feature to pass an array of roles to ``is_granted()`` was deprecated in Symfony 4.4. +Returns ``true`` if the current user has the given role. Optionally, an object can be passed to be used by the voter. More information can be found in :ref:`security-template`. @@ -273,6 +267,66 @@ expression Creates an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` related to the :doc:`ExpressionLanguage component `. +impersonation_exit_path +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: twig + + {{ impersonation_exit_path(exitTo = null) }} + +``exitTo`` *(optional)* + **type**: ``string`` + +.. versionadded:: 5.2 + + The ``impersonation_exit_path()`` function was introduced in Symfony 5.2. + +Generates a URL that you can visit to exit :doc:`user impersonation `. +After exiting impersonation, the user is redirected to the current URI. If you +prefer to redirect to a different URI, define its value in the ``exitTo`` argument. + +If no user is being impersonated, the function returns an empty string. + +impersonation_exit_url +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: twig + + {{ impersonation_exit_url(exitTo = null) }} + +``exitTo`` *(optional)* + **type**: ``string`` + +.. versionadded:: 5.2 + + The ``impersonation_exit_url()`` function was introduced in Symfony 5.2. + +It's similar to the `impersonation_exit_path`_ function, but it generates +absolute URLs instead of relative URLs. + +.. _reference-twig-function-t: + +t +~ + +.. code-block:: twig + + {{ t(message, parameters = [], domain = 'messages')|trans }} + +``message`` + **type**: ``string`` +``parameters`` *(optional)* + **type**: ``array`` **default**: ``[]`` +``domain`` *(optional)* + **type**: ``string`` **default**: ``messages`` + +.. versionadded:: 5.2 + + The ``t()`` function was introduced in Symfony 5.2. + +Creates a ``Translatable`` object that can be passed to the +:ref:`trans filter `. + Form Related Functions ~~~~~~~~~~~~~~~~~~~~~~ @@ -310,6 +364,8 @@ Makes a technical name human readable (i.e. replaces underscores by spaces or transforms camelCase text like ``helloWorld`` to ``hello world`` and then capitalizes the string). +.. _reference-twig-filter-trans: + trans ~~~~~ @@ -318,7 +374,7 @@ trans {{ message|trans(arguments = [], domain = null, locale = null) }} ``message`` - **type**: ``string`` + **type**: ``string`` | ``Translatable`` ``arguments`` *(optional)* **type**: ``array`` **default**: ``[]`` ``domain`` *(optional)* @@ -326,34 +382,11 @@ trans ``locale`` *(optional)* **type**: ``string`` **default**: ``null`` -Translates the text into the current language. More information in -:ref:`Translation Filters `. - -transchoice -~~~~~~~~~~~ - -.. deprecated:: 4.2 - - The ``transchoice`` filter is deprecated since Symfony 4.2 and will be - removed in 5.0. Use the :doc:`ICU MessageFormat ` with - the ``trans`` filter instead. - -.. code-block:: twig +.. versionadded:: 5.2 - {{ message|transchoice(count, arguments = [], domain = null, locale = null) }} + ``message`` accepting ``Translatable`` as a valid type was introduced in Symfony 5.2. -``message`` - **type**: ``string`` -``count`` - **type**: ``integer`` -``arguments`` *(optional)* - **type**: ``array`` **default**: ``[]`` -``domain`` *(optional)* - **type**: ``string`` **default**: ``null`` -``locale`` *(optional)* - **type**: ``string`` **default**: ``null`` - -Translates the text with pluralization support. More information in +Translates the text into the current language. More information in :ref:`Translation Filters `. yaml_encode @@ -564,31 +597,6 @@ trans Renders the translation of the content. More information in :ref:`translation-tags`. -transchoice -~~~~~~~~~~~ - -.. deprecated:: 4.2 - - The ``transchoice`` tag is deprecated since Symfony 4.2 and will be - removed in 5.0. Use the :doc:`ICU MessageFormat ` with - the ``trans`` tag instead. - -.. code-block:: twig - - {% transchoice count with vars from domain into locale %}{% endtranschoice %} - -``count`` - **type**: ``integer`` -``vars`` *(optional)* - **type**: ``array`` **default**: ``[]`` -``domain`` *(optional)* - **type**: ``string`` **default**: ``null`` -``locale`` *(optional)* - **type**: ``string`` **default**: ``null`` - -Renders the translation of the content with pluralization support, more -information in :ref:`translation-tags`. - trans_default_domain ~~~~~~~~~~~~~~~~~~~~ diff --git a/routing.rst b/routing.rst index 86defd2bafc..bf971924311 100644 --- a/routing.rst +++ b/routing.rst @@ -15,22 +15,33 @@ provides other useful features, like generating SEO-friendly URLs (e.g. Creating Routes --------------- -Routes can be configured in YAML, XML, PHP or using annotations. All formats -provide the same features and performance, so choose your favorite. -:ref:`Symfony recommends annotations ` +Routes can be configured in YAML, XML, PHP or using either attributes or +annotations. All formats provide the same features and performance, so choose +your favorite. +:ref:`Symfony recommends attributes ` because it's convenient to put the route and controller in the same place. -Creating Routes as Annotations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Creating Routes as Attributes or Annotations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On PHP 8, you can use native attributes to configure routes right away. On +PHP 7, where attributes are not available, you can use annotations instead, +provided by the Doctrine Annotations library. -Run this command once in your application to add support for annotations: +In case you want to use annotations instead of attributes, run this command +once in your application to enable them: .. code-block:: terminal $ composer require doctrine/annotations -In addition to installing the needed dependencies, this command creates the -following configuration file: +.. versionadded:: 5.2 + + The ability to use PHP attributes to configure routes was introduced in + Symfony 5.2. Prior to this, Doctrine Annotations were the only way to + annotate controller actions with routing configuration. + +This command also creates the following configuration file: .. code-block:: yaml @@ -47,25 +58,45 @@ This configuration tells Symfony to look for routes defined as annotations in any PHP class stored in the ``src/Controller/`` directory. Suppose you want to define a route for the ``/blog`` URL in your application. To -do so, create a :doc:`controller class ` like the following:: +do so, create a :doc:`controller class ` like the following: - // src/Controller/BlogController.php - namespace App\Controller; +.. configuration-block:: - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\Routing\Annotation\Route; + .. code-block:: php-annotations - class BlogController extends AbstractController - { - /** - * @Route("/blog", name="blog_list") - */ - public function list(): Response + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController { - // ... + /** + * @Route("/blog", name="blog_list") + */ + public function list() + { + // ... + } + } + + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + #[Route('/blog', name: 'blog_list')] + public function list() + { + // ... + } } - } This configuration defines a route called ``blog_list`` that matches when the user requests the ``/blog`` URL. When the match occurs, the application runs @@ -147,6 +178,12 @@ the ``BlogController``: ; }; +.. versionadded:: 5.1 + + Starting from Symfony 5.1, by default Symfony only loads the routes defined + in YAML format. If you define routes in XML and/or PHP formats, update the + ``src/Kernel.php`` file to add support for the ``.xml`` and ``.php`` file extensions. + .. _routing-matching-http-methods: Matching HTTP Methods @@ -185,6 +222,28 @@ Use the ``methods`` option to restrict the verbs each route should respond to: } } + .. code-block:: php-attributes + + // src/Controller/BlogApiController.php + namespace App\Controller; + + // ... + + class BlogApiController extends AbstractController + { + #[Route('/api/posts/{id}', methods: ['GET', 'HEAD'])] + public function show(int $id) + { + // ... return a JSON response with the post + } + + #[Route('/api/posts/{id}', methods: ['PUT'])] + public function edit(int $id) + { + // ... edit a post + } + } + .. code-block:: yaml # config/routes.yaml @@ -278,6 +337,29 @@ arbitrary matching logic: } } + .. code-block:: php-attributes + + // src/Controller/DefaultController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class DefaultController extends AbstractController + { + #[Route( + '/contact', + name: 'contact', + condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'", + )] + // expressions can also include config parameters: + // condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'" + public function contact() + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -429,6 +511,28 @@ defined as ``/blog/{slug}``: } } + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + // ... + + #[Route('/blog/{slug}', name: 'blog_show')] + public function show(string $slug) + { + // $slug will equal the dynamic part of the URL + // e.g. at /blog/yay-routing, then $slug='yay-routing' + + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -513,6 +617,29 @@ the ``{page}`` parameter using the ``requirements`` option: } } + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])] + public function list(int $page) + { + // ... + } + + #[Route('/blog/{slug}', name: 'blog_show')] + public function show($slug) + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -617,6 +744,23 @@ concise, but it can decrease route readability when requirements are complex: } } + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + #[Route('/blog/{page<\d+>}', name: 'blog_list')] + public function list(int $page) + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -686,6 +830,23 @@ other configuration formats they are defined with the ``defaults`` option: } } + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])] + public function list(int $page = 1) + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -746,11 +907,6 @@ If you want to always include some default value in the generated URL (for example to force the generation of ``/blog/1`` instead of ``/blog`` in the previous example) add the ``!`` character before the parameter name: ``/blog/{!page}`` -.. versionadded:: 4.3 - - The feature to force the inclusion of default values in generated URLs was - introduced in Symfony 4.3. - As it happens with requirements, default values can also be inlined in each parameter using the syntax ``{parameter_name?default_value}``. This feature is compatible with inlined requirements, so you can inline both in a single @@ -778,6 +934,23 @@ parameter: } } + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + #[Route('/blog/{page<\d+>?1}', name: 'blog_list')] + public function list(int $page) + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -817,6 +990,85 @@ parameter: To give a ``null`` default value to any parameter, add nothing after the ``?`` character (e.g. ``/blog/{page?}``). +Priority Parameter +~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.1 + + The ``priority`` parameter was introduced in Symfony 5.1 + +When defining a greedy pattern that matches many routes, this may be at the +beginning of your routing collection and prevents any route defined after to be +matched. +A ``priority`` optional parameter is available in order to let you choose the +order of your routes, and it is only available when using annotations. + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + /** + * This route has a greedy pattern and is defined first. + * + * @Route("/blog/{slug}", name="blog_show") + */ + public function show(string $slug) + { + // ... + } + + /** + * This route could not be matched without defining a higher priority than 0. + * + * @Route("/blog/list", name="blog_list", priority=2) + */ + public function list() + { + // ... + } + } + + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + /** + * This route has a greedy pattern and is defined first. + */ + #[Route('/blog/{slug}', name: 'blog_show')] + public function show(string $slug) + { + // ... + } + + /** + * This route could not be matched without defining a higher priority than 0. + */ + #[Route('/blog/list', name: 'blog_list', priority: 2)] + public function list() + { + // ... + } + } + +The priority parameter expects an integer value. Routes with higher priority +are sorted before routes with lower priority. The default value when it is not +defined is ``0``. + Parameter Conversion ~~~~~~~~~~~~~~~~~~~~ @@ -825,12 +1077,11 @@ integer acting as the user ID) into another value (e.g. the object that represents the user). This feature is called "param converter" and is only available when using annotations to define routes. -In case you didn't run this command before, run it now to add support for -annotations and "param converters": +To add support for "param converters" we need SensioFrameworkExtraBundle: .. code-block:: terminal - $ composer require annotations + $ composer require sensio/framework-extra-bundle Now, keep the previous route configuration, but change the arguments of the controller action. Instead of ``string $slug``, add ``BlogPost $post``:: @@ -925,6 +1176,28 @@ and in route imports. Symfony defines some special attributes with the same name } } + .. code-block:: php-attributes + + // src/Controller/ArticleController.php + namespace App\Controller; + + // ... + class ArticleController extends AbstractController + { + #[Route( + path: '/articles/{_locale}/search.{_format}', + locale: 'en', + format: 'html', + requirements: [ + '_locale' => 'en|fr', + '_format' => 'html|xml', + ], + )] + public function search() + { + } + } + .. code-block:: yaml # config/routes.yaml @@ -977,10 +1250,6 @@ and in route imports. Symfony defines some special attributes with the same name ; }; -.. versionadded:: 4.3 - - The special attributes were introduced in Symfony 4.3. - Extra Parameters ~~~~~~~~~~~~~~~~ @@ -1010,6 +1279,22 @@ the controllers of the routes: } } + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Component\Routing\Annotation\Route; + + class BlogController + { + #[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1, 'title' => 'Hello world!'])] + public function index(int $page, string $title) + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -1085,6 +1370,22 @@ A possible solution is to change the parameter requirements to be more permissiv } } + .. code-block:: php-attributes + + // src/Controller/DefaultController.php + namespace App\Controller; + + use Symfony\Component\Routing\Annotation\Route; + + class DefaultController + { + #[Route('/share/{token}', name: 'share', requirements: ['token' => '.+'])] + public function share($token) + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -1149,9 +1450,10 @@ It's common for a group of routes to share some options (e.g. all routes related to the blog start with ``/blog``) That's why Symfony includes a feature to share route configuration. -When defining routes as annotations, put the common configuration in the -``@Route`` annotation of the controller class. In other routing formats, define -the common configuration using options when importing the routes. +When defining routes as attributes or annotations, put the common configuration +in the ``#[Route]`` attribute (or ``@Route`` annotation) of the controller +class. In other routing formats, define the common configuration using options +when importing the routes. .. configuration-block:: @@ -1186,6 +1488,29 @@ the common configuration using options when importing the routes. } } + .. code-block:: php-attributes + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Component\Routing\Annotation\Route; + + #[Route('/blog', requirements: ['_locale' => 'en|es|fr'], name: 'blog_')] + class BlogController + { + #[Route('/{_locale}', name: 'index')] + public function index() + { + // ... + } + + #[Route('/{_locale}/posts/{slug}', name: 'show')] + public function show(Post $post) + { + // ... + } + } + .. code-block:: yaml # config/routes/annotations.yaml @@ -1258,11 +1583,6 @@ the common configuration using options when importing the routes. ; }; -.. versionadded:: 4.4 - - The option to exclude some files or subdirectories when loading annotations - was introduced in Symfony 4.4. - In this example, the route of the ``index()`` action will be called ``blog_index`` and its URL will be ``/blog/``. The route of the ``show()`` action will be called ``blog_show`` and its URL will be ``/blog/{_locale}/posts/{slug}``. Both routes @@ -1443,13 +1763,6 @@ Use the ``RedirectController`` to redirect to other routes and URLs: 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 @@ -1509,6 +1822,29 @@ host name: } } + .. code-block:: php-attributes + + // src/Controller/MainController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class MainController extends AbstractController + { + #[Route('/', name: 'mobile_homepage', host: 'm.example.com')] + public function mobileHomepage() + { + // ... + } + + #[Route('/', name: 'homepage')] + public function homepage() + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -1595,6 +1931,35 @@ multi-tenant applications) and these parameters can be validated too with } } + .. code-block:: php-attributes + + // src/Controller/MainController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class MainController extends AbstractController + { + #[Route( + '/', + name: 'mobile_homepage', + host: '{subdomain}.example.com', + defaults: ['subdomain' => 'm'], + requirements: ['subdomain' => 'm|mobile'], + )] + public function mobileHomepage() + { + // ... + } + + #[Route('/', name: 'homepage')] + public function homepage() + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -1677,15 +2042,21 @@ these routes. // ['HTTP_HOST' => 'm.' . $client->getContainer()->getParameter('domain')] ); +.. tip:: + + You can also use the inline defaults and requirements format in the + ``host`` option: ``{subdomain?m}.example.com`` + +.. versionadded:: 5.2 + + Inline parameter default values support in hosts were introduced in + Symfony 5.2. Prior to Symfony 5.2, they were supported in the path only. + .. _i18n-routing: Localized Routes (i18n) ----------------------- -.. versionadded:: 4.1 - - The i18n routing was introduced in Symfony 4.1. - If your application is translated into multiple languages, each route can define a different URL per each :doc:`translation locale `. This avoids the need for duplicating routes, which also reduces the potential bugs: @@ -1715,6 +2086,26 @@ avoids the need for duplicating routes, which also reduces the potential bugs: } } + .. code-block:: php-attributes + + // src/Controller/CompanyController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class CompanyController extends AbstractController + { + #[Route(path: [ + 'en' => '/about-us', + 'nl' => '/over-ons' + ], name: 'about_us')] + public function about() + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -1754,6 +2145,11 @@ avoids the need for duplicating routes, which also reduces the potential bugs: ; }; +.. note:: + + When using PHP attributes for localized routes, you have to use the ``path`` + named parameter to specify the array of paths. + When a localized route is matched, Symfony uses the same locale automatically during the entire request. @@ -1810,6 +2206,101 @@ with a locale. This can be done by defining a different prefix for each locale ; }; +.. _stateless-routing: + +Stateless Routes +---------------- + +.. versionadded:: 5.1 + + The ``stateless`` option was introduced in Symfony 5.1. + +Sometimes, when an HTTP response should be cached, it is important to ensure +that can happen. However, whenever session is started during a request, Symfony +turns the response into a private non-cacheable response. + +For details, see :doc:`/http_cache`. + +Routes can configure a ``stateless`` boolean option in order to declare that the +session shouldn't be used when matching a request: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Controller/MainController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class MainController extends AbstractController + { + /** + * @Route("/", name="homepage", stateless=true) + */ + public function homepage() + { + // ... + } + } + + .. code-block:: php-attributes + + // src/Controller/MainController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class MainController extends AbstractController + { + #[Route('/', name: 'homepage', stateless: true)] + public function homepage() + { + // ... + } + } + + .. code-block:: yaml + + # config/routes.yaml + homepage: + controller: App\Controller\MainController::homepage + path: / + stateless: true + + .. code-block:: xml + + + + + + + + .. code-block:: php + + // config/routes.php + use App\Controller\MainController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->add('homepage', '/') + ->controller([MainController::class, 'homepage']) + ->stateless() + ; + }; + +Now, if the session is used, the application will report it based on your +``kernel.debug`` parameter: +* ``enabled``: will throw an :class:`Symfony\\Component\\HttpKernel\\Exception\\UnexpectedSessionUsageException` exception +* ``disabled``: will log a warning + +It will help you understand and hopefully fixing unexpected behavior in your application. + .. _routing-generating-urls: Generating URLs @@ -1954,54 +2445,64 @@ If you need to generate URLs dynamically or if you are using pure JavaScript code, this solution doesn't work. In those cases, consider using the `FOSJsRoutingBundle`_. +.. _router-generate-urls-commands: + Generating URLs in Commands ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Generating URLs in commands works the same as :ref:`generating URLs in services `. The -only difference is that commands are not executed in the HTTP context, so they -don't have access to HTTP requests. In practice, this means that if you generate -absolute URLs, you'll get ``http://localhost/`` as the host name instead of your -real host name. +only difference is that commands are not executed in the HTTP context. Therefore, +if you generate absolute URLs, you'll get ``http://localhost/`` as the host name +instead of your real host name. -The solution is to configure the "request context" used by commands when they -generate URLs. This context can be configured globally for all commands: +The solution is to configure the ``default_uri`` option to define the +"request context" used by commands when they generate URLs: .. configuration-block:: .. code-block:: yaml - # config/services.yaml - parameters: - router.request_context.host: 'example.org' - router.request_context.base_url: 'my/path' - asset.request_context.base_path: '%router.request_context.base_url%' + # config/packages/routing.yaml + framework: + router: + # ... + default_uri: 'https://example.org/my/path/' .. code-block:: xml - - + + - - - example.org - my/path - %router.request_context.base_url% - + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> + + + + + .. code-block:: php - // config/services.php - $container->setParameter('router.request_context.host', 'example.org'); - $container->setParameter('router.request_context.base_url', 'my/path'); - $container->setParameter('asset.request_context.base_path', $container->getParameter('router.request_context.base_url')); + // config/packages/routing.php + $container->loadFromExtension('framework', [ + 'router' => [ + // ... + 'default_uri' => "https://example.org/my/path/", + ], + ]); -This information can be configured per command too:: +.. versionadded:: 5.1 + + The ``default_uri`` option was introduced in Symfony 5.1. + +Now you'll get the expected results when generating URLs in your commands:: // src/Command/SomeCommand.php namespace App\Command; @@ -2026,11 +2527,6 @@ This information can be configured per command too:: protected function execute(InputInterface $input, OutputInterface $output): int { - // these values override any global configuration - $context = $this->router->getContext(); - $context->setHost('example.com'); - $context->setBaseUrl('my/path'); - // generate a URL with no route arguments $signUpPage = $this->router->generate('sign_up'); @@ -2051,6 +2547,12 @@ This information can be configured per command too:: } } +.. note:: + + By default, the URLs generated for web assets use the same ``default_uri`` + value, but you can change it with the ``asset.request_context.base_path`` + and ``asset.request_context.secure`` container parameters. + Checking if a Route Exists ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2139,6 +2641,23 @@ each route explicitly: } } + .. code-block:: php-attributes + + // src/Controller/SecurityController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class SecurityController extends AbstractController + { + #[Route('/login', name: 'login', schemes: ['https'])] + public function login() + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml diff --git a/routing/custom_route_loader.rst b/routing/custom_route_loader.rst index b438ed7bb11..a339ec74f61 100644 --- a/routing/custom_route_loader.rst +++ b/routing/custom_route_loader.rst @@ -206,10 +206,6 @@ implement the :class:`Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInte 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 @@ -220,11 +216,6 @@ tag it manually with ``routing.route_loader``. If your service is invokable, you don't need to precise the method to use. -.. versionadded:: 4.3 - - The support of the ``__invoke()`` method to create invokable service route - loaders was introduced in Symfony 4.3. - Creating a custom Loader ------------------------ @@ -252,7 +243,7 @@ you do. The resource name itself is not actually used in the example:: { private $isLoaded = false; - public function load($resource, $type = null) + public function load($resource, string $type = null) { if (true === $this->isLoaded) { throw new \RuntimeException('Do not add the "extra" loader twice'); @@ -279,7 +270,7 @@ you do. The resource name itself is not actually used in the example:: return $routes; } - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return 'extra' === $type; } @@ -336,11 +327,17 @@ Now define a service for the ``ExtraLoader``: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use App\Routing\ExtraLoader; - $container->autowire(ExtraLoader::class) - ->addTag('routing.loader') - ; + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(ExtraLoader::class) + ->tag('routing.loader') + ; + }; Notice the tag ``routing.loader``. All services with this *tag* will be marked as potential route loaders and added as specialized route loaders to the @@ -418,7 +415,7 @@ configuration file - you can call the class AdvancedLoader extends Loader { - public function load($resource, $type = null) + public function load($resource, string $type = null) { $routes = new RouteCollection(); @@ -432,7 +429,7 @@ configuration file - you can call the return $routes; } - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return 'advanced_extra' === $type; } diff --git a/security.rst b/security.rst index 070c18b687c..de13f7db4f6 100644 --- a/security.rst +++ b/security.rst @@ -37,6 +37,49 @@ install the security feature before using it: $ composer require symfony/security-bundle + +.. tip:: + + A :doc:`new experimental Security ` + was introduced in Symfony 5.1, which will eventually replace security in + Symfony 6.0. This system is almost fully backwards compatible with the + current Symfony security, add this line to your security configuration to start + using it: + + .. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + enable_authenticator_manager: true + # ... + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + // ... + ]); + .. _initial-security-yml-setup-authentication: .. _initial-security-yaml-setup-authentication: .. _create-user-class: @@ -189,7 +232,9 @@ command will pre-configure this for you: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -224,6 +269,8 @@ Now that Symfony knows *how* you want to encode the passwords, you can use the ``UserPasswordEncoderInterface`` service to do this before saving your users to the database. +.. _user-data-fixture: + For example, by using :ref:`DoctrineFixturesBundle `, you can create dummy database users: @@ -279,6 +326,11 @@ You can manually encode a password by running: 3a) Authentication & Firewalls ------------------------------ +.. versionadded:: 5.1 + + The ``lazy: true`` option was introduced in Symfony 5.1. Prior to version 5.1, + it was enabled using ``anonymous: lazy`` + The security system is configured in ``config/packages/security.yaml``. The *most* important section is ``firewalls``: @@ -293,7 +345,8 @@ important section is ``firewalls``: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - anonymous: lazy + anonymous: true + lazy: true .. code-block:: xml @@ -303,15 +356,18 @@ important section is ``firewalls``: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> - - + @@ -326,15 +382,12 @@ important section is ``firewalls``: 'security' => false, ], 'main' => [ - 'anonymous' => 'lazy', + 'anonymous' => true, + 'lazy' => true, ], ], ]); -.. versionadded:: 4.4 - - The ``lazy`` anonymous mode has been introduced in Symfony 4.4. - A "firewall" is your authentication system: the configuration below it defines *how* your users will be able to authenticate (e.g. login form, API token, etc). @@ -528,7 +581,9 @@ start with ``/admin``, you can: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -604,7 +659,9 @@ the list and stops when it finds the first match: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -746,7 +803,7 @@ You can use ``IS_AUTHENTICATED_FULLY`` anywhere roles are used: like ``access_control`` or in Twig. ``IS_AUTHENTICATED_FULLY`` isn't a role, but it kind of acts like one, and every -user that has logged in will have this. Actually, there are 3 special attributes +user that has logged in will have this. Actually, there are some special attributes like this: * ``IS_AUTHENTICATED_REMEMBERED``: *All* logged in users have this, even @@ -762,6 +819,21 @@ like this: this - this is useful when *whitelisting* URLs to guarantee access - some details are in :doc:`/security/access_control`. +* ``IS_ANONYMOUS``: *Only* anonymous users are matched by this attribute. + +* ``IS_REMEMBERED``: *Only* users authenticated using the + :doc:`remember me functionality `, (i.e. a + remember-me cookie). + +* ``IS_IMPERSONATOR``: When the current user is + :doc:`impersonating ` another user in this + session, this attribute will match. + +.. versionadded:: 5.1 + + The ``IS_ANONYMOUS``, ``IS_REMEMBERED`` and ``IS_IMPERSONATOR`` + attributes were introduced in Symfony 5.1. + .. _retrieving-the-user-object: 5a) Fetching the User Object @@ -858,7 +930,9 @@ To enable logging out, activate the ``logout`` config parameter under your fire xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -941,11 +1015,93 @@ Next, you'll need to create a route for this URL (but not a controller): And that's it! By sending a user to the ``app_logout`` route (i.e. to ``/logout``) Symfony will un-authenticate the current user and redirect them. +Customizing Logout +~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.1 + + The ``LogoutEvent`` was introduced in Symfony 5.1. Prior to this + version, you had to use a + :ref:`logout success handler ` + to customize the logout. + +In some cases you need to execute extra logic upon logout (e.g. invalidate +some tokens) or want to customize what happens after a logout. During +logout, a :class:`Symfony\\Component\\Security\\Http\\Event\\LogoutEvent` +is dispatched. Register an :doc:`event listener or subscriber ` +to execute custom logic. The following information is available in the +event class: + +``getToken()`` + Returns the security token of the session that is about to be logged + out. +``getRequest()`` + Returns the current request. +``getResponse()`` + Returns a response, if it is already set by a custom listener. Use + ``setResponse()`` to configure a custom logout response. + + .. tip:: - Need more control of what happens after logout? Add a ``success_handler`` key - under ``logout`` and point it to a service id of a class that implements - :class:`Symfony\\Component\\Security\\Http\\Logout\\LogoutSuccessHandlerInterface`. + Every Security firewall has its own event dispatcher + (``security.event_dispatcher.FIREWALLNAME``). The logout event is + dispatched on both the global and firewall dispatcher. You can register + on the firewall dispatcher if you want your listener to only be + executed for a specific firewall. For instance, if you have an ``api`` + and ``main`` firewall, use this configuration to register only on the + logout event in the ``main`` firewall: + + .. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + # ... + + App\EventListener\CustomLogoutSubscriber: + tags: + - name: kernel.event_subscriber + dispatcher: security.event_dispatcher.main + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\EventListener\CustomLogoutListener; + use App\EventListener\CustomLogoutSubscriber; + use Symfony\Component\Security\Http\Event\LogoutEvent; + + return function(ContainerConfigurator $configurator) { + $services = $configurator->services(); + + $services->set(CustomLogoutSubscriber::class) + ->tag('kernel.event_subscriber', [ + 'dispatcher' => 'security.event_dispatcher.main', + ]); + }; .. _security-role-hierarchy: @@ -975,7 +1131,9 @@ rules by creating a role hierarchy: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -1066,6 +1224,7 @@ Authentication (Identifying/Logging in the User) .. toctree:: :maxdepth: 1 + security/experimental_authenticators security/form_login_setup security/reset_password security/json_login_setup diff --git a/security/access_control.rst b/security/access_control.rst index 53d216c8ce2..9c44e5cb85d 100644 --- a/security/access_control.rst +++ b/security/access_control.rst @@ -25,7 +25,7 @@ access control should be used on this request. The following ``access_control`` options are used for matching: * ``path``: a regular expression (without delimiters) -* ``ip`` or ``ips``: netmasks are also supported +* ``ip`` or ``ips``: netmasks are also supported (can be a comma-separated string) * ``port``: an integer * ``host``: a regular expression * ``methods``: one or many methods @@ -37,6 +37,9 @@ Take the following ``access_control`` entries as an example: .. code-block:: yaml # config/packages/security.yaml + parameters: + env(TRUSTED_IPS): '10.0.0.1, 10.0.0.2' + security: # ... access_control: @@ -44,8 +47,10 @@ Take the following ``access_control`` entries as an example: - { path: '^/admin', roles: ROLE_USER_IP, ip: 127.0.0.1 } - { path: '^/admin', roles: ROLE_USER_HOST, host: symfony\.com$ } - { path: '^/admin', roles: ROLE_USER_METHOD, methods: [POST, PUT] } - # when defining multiple roles, users must have at least one of them (it's like an OR condition) - - { path: '^/admin', roles: [ROLE_MANAGER, ROLE_ADMIN] } + + # ips can be comma-separated, which is especially useful when using env variables + - { path: '^/admin', roles: ROLE_USER_IP, ips: '%env(TRUSTED_IPS)%' } + - { path: '^/admin', roles: ROLE_USER_IP, ips: [127.0.0.1, ::1, '%env(TRUSTED_IPS)%'] } .. code-block:: xml @@ -55,7 +60,13 @@ Take the following ``access_control`` entries as an example: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> + + + 10.0.0.1, 10.0.0.2 + @@ -63,14 +74,21 @@ Take the following ``access_control`` entries as an example: - - + + + + + 127.0.0.1 + ::1 + %env(TRUSTED_IPS)% + .. code-block:: php // config/packages/security.php + $container->setParameter('env(TRUSTED_IPS)', '10.0.0.1, 10.0.0.2'); $container->loadFromExtension('security', [ // ... 'access_control' => [ @@ -95,14 +113,29 @@ Take the following ``access_control`` entries as an example: 'roles' => 'ROLE_USER_METHOD', 'methods' => 'POST, PUT', ], + + // ips can be comma-separated, which is especially useful when using env variables + [ + 'path' => '^/admin', + 'roles' => 'ROLE_USER_IP', + 'ips' => '%env(TRUSTED_IPS)%', + ], [ 'path' => '^/admin', - // when defining multiple roles, users must have at least one of them (it's like an OR condition) - 'roles' => ['ROLE_MANAGER', 'ROLE_ADMIN'], + 'roles' => 'ROLE_USER_IP', + 'ips' => [ + '127.0.0.1', + '::1', + '%env(TRUSTED_IPS)%', + ], ], ], ]); +.. versionadded:: 5.2 + + Support for comma-separated IP addresses was introduced in Symfony 5.2. + For each incoming request, Symfony will decide which ``access_control`` to use based on the URI, the client's IP address, the incoming host name, and the request method. Remember, the first rule that matches is used, and @@ -130,11 +163,6 @@ if ``ip``, ``port``, ``host`` or ``method`` are not specified for an entry, that | ``/admin/user`` | 168.0.0.1 | 80 | example.com | POST | rule #4 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first two entries, | | | | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. | +-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | 80 | example.com | GET | rule #4 (``ROLE_MANAGER``) | The ``ip``, ``host`` and ``method`` prevent the first | -| | | | | | | three entries from matching. But since the URI matches the | -| | | | | | | ``path`` pattern, then the ``ROLE_MANAGER`` (or the | -| | | | | | | ``ROLE_ADMIN``) is used. | -+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ | ``/foo`` | 127.0.0.1 | 80 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its | | | | | | | | URI doesn't match any of the ``path`` values. | +-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ @@ -227,7 +255,9 @@ pattern so that it is only accessible by requests from the local server itself: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -314,7 +344,9 @@ key: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -390,7 +422,9 @@ access those URLs via a specific port. This could be useful for example for xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -441,7 +475,9 @@ the user will be redirected to ``https``: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/access_denied_handler.rst b/security/access_denied_handler.rst index 42ee7fe5dd9..2a7566fafed 100644 --- a/security/access_denied_handler.rst +++ b/security/access_denied_handler.rst @@ -28,7 +28,6 @@ unauthenticated user tries to access a protected resource:: use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; @@ -36,18 +35,16 @@ unauthenticated user tries to access a protected resource:: class AuthenticationEntryPoint implements AuthenticationEntryPointInterface { private $urlGenerator; - private $session; - public function __construct(UrlGeneratorInterface $urlGenerator, SessionInterface $session) + public function __construct(UrlGeneratorInterface $urlGenerator) { $this->urlGenerator = $urlGenerator; - $this->session = $session; } public function start(Request $request, AuthenticationException $authException = null): RedirectResponse { // add a custom flash message and redirect to the login page - $this->session->getFlashBag()->add('note', 'You have to login in order to access this page.'); + $request->getSession()->getFlashBag()->add('note', 'You have to login in order to access this page.'); return new RedirectResponse($this->urlGenerator->generate('security_login')); } diff --git a/security/auth_providers.rst b/security/auth_providers.rst index f5ec3723c7b..349f16a219a 100644 --- a/security/auth_providers.rst +++ b/security/auth_providers.rst @@ -21,8 +21,6 @@ use-case matches one of these exactly, they're a great option: * :doc:`json_login ` * :ref:`X.509 Client Certificate Authentication (x509) ` * :ref:`REMOTE_USER Based Authentication (remote_user) ` -* ``simple_form`` -* ``simple_pre_auth`` .. _security-http_basic: @@ -57,7 +55,9 @@ To support HTTP Basic authentication, add the ``http_basic`` key to your firewal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -123,7 +123,9 @@ Enable the x509 authentication for a particular firewall in the security configu xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -199,7 +201,12 @@ corresponding firewall in your security configuration: + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:srv="http://symfony.com/schema/dic/services" + xsi:schemaLocation="http://symfony.com/schema/dic/services + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/csrf.rst b/security/csrf.rst index 9da64168379..47b9396d285 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -85,7 +85,7 @@ this can be customized on a form-by-form basis:: // src/Form/TaskType.php namespace App\Form; - + // ... use App\Entity\Task; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -116,10 +116,6 @@ You can also customize the rendering of the CSRF form field creating a custom the field (e.g. define ``{% block csrf_token_widget %} ... {% endblock %}`` to customize the entire form field contents). -.. versionadded:: 4.3 - - The ``csrf_token`` form field prefix was introduced in Symfony 4.3. - CSRF Protection in Login Forms ------------------------------ @@ -166,4 +162,19 @@ to check its validity:: } } +CSRF Tokens and Compression Side-Channel Attacks +------------------------------------------------ + +`BREACH`_ and `CRIME`_ are security exploits against HTTPS when using HTTP +compression. Attackers can leverage information leaked by compression to recover +targeted parts of the plaintext. To mitigate these attacks, and prevent an +attacker from guessing the CSRF tokens, a random mask is prepended to the token +and used to scramble it. + +.. versionadded:: 5.3 + + The randomization of tokens was introduced in Symfony 5.3 + .. _`Cross-site request forgery`: https://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _`BREACH`: https://en.wikipedia.org/wiki/BREACH +.. _`CRIME`: https://en.wikipedia.org/wiki/CRIME diff --git a/security/custom_authentication_provider.rst b/security/custom_authentication_provider.rst index 920c8315f05..8c5581964ea 100644 --- a/security/custom_authentication_provider.rst +++ b/security/custom_authentication_provider.rst @@ -307,7 +307,7 @@ create a class which implements class WsseFactory implements SecurityFactoryInterface { - public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) + public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) { $providerId = 'security.authentication.provider.wsse.'.$id; $container @@ -410,7 +410,7 @@ to service ids that may not exist yet: ``App\Security\Authentication\Provider\Ws - + @@ -433,13 +433,14 @@ to service ids that may not exist yet: ``App\Security\Authentication\Provider\Ws $services = $configurator->services(); $services->set(WsseProvider::class) - ->arg('$cachePool', ref('cache.app')) + ->arg('$cachePool', service('cache.app')) ; $services->set(WsseListener::class) ->args([ - ref('security.token_storage'), - ref('security.authentication.manager'), + // In versions earlier to Symfony 5.1 the service() function was called ref() + service('security.token_storage'), + service('security.authentication.manager'), ]) ; }; @@ -488,7 +489,9 @@ You are finished! You can now define parts of your app as under WSSE protection. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -568,7 +571,7 @@ in order to put it to use:: class WsseFactory implements SecurityFactoryInterface { - public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) + public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) { $providerId = 'security.authentication.provider.wsse.'.$id; $container @@ -612,7 +615,9 @@ set to any desirable value per firewall. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/experimental_authenticators.rst b/security/experimental_authenticators.rst new file mode 100644 index 00000000000..eb0ffa098e0 --- /dev/null +++ b/security/experimental_authenticators.rst @@ -0,0 +1,626 @@ +Using the new Authenticator-based Security +========================================== + +.. versionadded:: 5.1 + + Authenticator-based security was introduced as an + :doc:`experimental feature ` in + Symfony 5.1. + +In Symfony 5.1, a new authentication system was introduced. This system +changes the internals of Symfony Security, to make it more extensible +and more understandable. + +.. _security-enable-authenticator-manager: + +Enabling the System +------------------- + +The authenticator-based system can be enabled using the +``enable_authenticator_manager`` setting: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + enable_authenticator_manager: true + # ... + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + // ... + ]); + +The new system is backwards compatible with the current authentication +system, with some exceptions that will be explained in this article: + +* :ref:`Anonymous users no longer exist ` +* :ref:`Configuring the authentication entry point is required when more than one authenticator is used ` +* :ref:`The authentication providers are refactored into Authenticators ` + +.. _authenticators-removed-anonymous: + +Adding Support for Unsecured Access (i.e. Anonymous Users) +---------------------------------------------------------- + +In Symfony, visitors that haven't yet logged in to your website were called +:ref:`anonymous users `. The new system no longer +has anonymous authentication. Instead, these sessions are now treated as +unauthenticated (i.e. there is no security token). When using +``isGranted()``, the result will always be ``false`` (i.e. denied) as this +session is handled as a user without any privileges. + +In the ``access_control`` configuration, you can use the new +``PUBLIC_ACCESS`` security attribute to whitelist some routes for +unauthenticated access (e.g. the login page): + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + enable_authenticator_manager: true + + # ... + access_control: + # allow unauthenticated users to access the login form + - { path: ^/admin/login, roles: PUBLIC_ACCESS } + + # but require authentication for all other admin routes + - { path: ^/admin, roles: ROLE_ADMIN } + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use Symfony\Component\Security\Http\Firewall\AccessListener; + + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + + // ... + 'access_control' => [ + // allow unauthenticated users to access the login form + ['path' => '^/admin/login', 'roles' => AccessListener::PUBLIC_ACCESS], + + // but require authentication for all other admin routes + ['path' => '^/admin', 'roles' => 'ROLE_ADMIN'], + ], + ]); + +Granting Anonymous Users Access in a Custom Voter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The ``NullToken`` class was introduced in Symfony 5.2. + +If you're using a :doc:`custom voter `, you can allow +anonymous users access by checking for a special +:class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\NullToken`. This token is used +in the voters to represent the unauthenticated access:: + + // src/Security/PostVoter.php + namespace App\Security; + + // ... + use Symfony\Component\Security\Core\Authentication\Token\NullToken; + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Authorization\Voter\Voter; + + class PostVoter extends Voter + { + // ... + + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + { + // ... + + if ($token instanceof NullToken) { + // the user is not authenticated, e.g. only allow them to + // see public posts + return $subject->isPublic(); + } + } + } + +.. _authenticators-required-entry-point: + +Configuring the Authentication Entry Point +------------------------------------------ + +Sometimes, one firewall has multiple ways to authenticate (e.g. both a form +login and an API token authentication). In these cases, it is now required +to configure the *authentication entry point*. The entry point is used to +generate a response when the user is not yet authenticated but tries to access +a page that requires authentication. This can be used for instance to redirect +the user to the login page. + +You can configure this using the ``entry_point`` setting: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + enable_authenticator_manager: true + + # ... + firewalls: + main: + # allow authentication using a form or HTTP basic + form_login: ~ + http_basic: ~ + + # configure the form authentication as the entry point for unauthenticated users + entry_point: form_login + + .. code-block:: xml + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use Symfony\Component\Security\Http\Firewall\AccessListener; + + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + + // ... + 'firewalls' => [ + 'main' => [ + // allow authentication using a form or HTTP basic + 'form_login' => null, + 'http_basic' => null, + + // configure the form authentication as the entry point for unauthenticated users + 'entry_point' => 'form_login' + ], + ], + ]); + +.. note:: + + You can also create your own authentication entry point by creating a + class that implements + :class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`. + You can then set ``entry_point`` to the service id (e.g. + ``entry_point: App\Security\CustomEntryPoint``) + +.. _authenticators-removed-authentication-providers: + +Creating a Custom Authenticator +------------------------------- + +Security traditionally could be extended by writing +:doc:`custom authentication providers `. +The authenticator-based system dropped support for these providers and +introduced a new authenticator interface as a base for custom +authentication methods. + +.. tip:: + + :doc:`Guard authenticators ` are still + supported in the authenticator-based system. It is however recommended + to also update these when you're refactoring your application to the + new system. The new authenticator interface has many similarities with the + guard authenticator interface, making the rewrite easier. + +Authenticators should implement the +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AuthenticatorInterface`. +You can also extend +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AbstractAuthenticator`, +which has a default implementation for the ``createAuthenticatedToken()`` +method that fits most use-cases:: + + // src/Security/ApiKeyAuthenticator.php + namespace App\Security; + + use Symfony\Component\HttpFoundation\JsonResponse; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Exception\AuthenticationException; + use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; + use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; + use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; + + class ApiKeyAuthenticator extends AbstractAuthenticator + { + /** + * Called on every request to decide if this authenticator should be + * used for the request. Returning `false` will cause this authenticator + * to be skipped. + */ + public function supports(Request $request): ?bool + { + return $request->headers->has('X-AUTH-TOKEN'); + } + + public function authenticate(Request $request): PassportInterface + { + $apiToken = $request->headers->get('X-AUTH-TOKEN'); + if (null === $apiToken) { + // The token header was empty, authentication fails with HTTP Status + // Code 401 "Unauthorized" + throw new CustomUserMessageAuthenticationException('No API token provided'); + } + + return new SelfValidatingPassport(new UserBadge($apiToken)); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + // on success, let the request continue + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $data = [ + // you may want to customize or obfuscate the message first + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()) + + // or to translate this message + // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) + ]; + + return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); + } + } + +The authenticator can be enabled using the ``custom_authenticators`` setting: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + enable_authenticator_manager: true + + # ... + firewalls: + main: + custom_authenticators: + - App\Security\ApiKeyAuthenticator + + # don't forget to also configure the entry_point if the + # authenticator implements AuthenticationEntryPointInterface + # entry_point: App\Security\CustomFormLoginAuthenticator + + .. code-block:: xml + + + + + + + + + + + + App\Security\ApiKeyAuthenticator + + + + + .. code-block:: php + + // config/packages/security.php + use App\Security\ApiKeyAuthenticator; + use Symfony\Component\Security\Http\Firewall\AccessListener; + + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + + // ... + 'firewalls' => [ + 'main' => [ + 'custom_authenticators' => [ + ApiKeyAuthenticator::class, + ], + + // don't forget to also configure the entry_point if the + // authenticator implements AuthenticatorEntryPointInterface + // 'entry_point' => [App\Security\CustomFormLoginAuthenticator::class], + ], + ], + ]); + +The ``authenticate()`` method is the most important method of the +authenticator. Its job is to extract credentials (e.g. username & +password, or API tokens) from the ``Request`` object and transform these +into a security +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Passport`. + +.. tip:: + + If you want to customize the login form, you can also extend from the + :class:`Symfony\\Component\\Security\\Http\\Authenticator\\AbstractLoginFormAuthenticator` + class instead. + +Security Passports +~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + The ``UserBadge`` was introduced in Symfony 5.2. Prior to 5.2, the user + instance was provided directly to the passport. + +A passport is an object that contains the user that will be authenticated as +well as other pieces of information, like whether a password should be checked +or if "remember me" functionality should be enabled. + +The default +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Passport` +requires a user and credentials. + +Use the +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\UserBadge` +to attach the user to the passport. The ``UserBadge`` requires a user +identifier (e.g. the username or email), which is used to load the user +using :ref:`the user provider `:: + + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + + // ... + $passport = new Passport(new UserBadge($email), $credentials); + +.. note:: + + You can optionally pass a user loader as second argument to the + ``UserBadge``. This callable receives the ``$userIdentifier`` + and must return a ``UserInterface`` object (otherwise a + ``UsernameNotFoundException`` is thrown):: + + // src/Security/CustomAuthenticator.php + namespace App\Security; + + use App\Repository\UserRepository; + // ... + + class CustomAuthenticator extends AbstractAuthenticator + { + private $userRepository; + + public function __construct(UserRepository $userRepository) + { + $this->userRepository = $userRepository; + } + + public function authenticate(Request $request): PassportInterface + { + // ... + + return new Passport( + new UserBadge($email, function ($userIdentifier) { + return $this->userRepository->findOneBy(['email' => $userIdentifier]); + }), + $credentials + ); + } + } + +The following credential classes are supported by default: + +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\PasswordCredentials` + This requires a plaintext ``$password``, which is validated using the + :ref:`password encoder configured for the user `:: + + use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; + + // ... + return new Passport($user, new PasswordCredentials($plaintextPassword)); + +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\CustomCredentials` + Allows a custom closure to check credentials:: + + use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; + + // ... + return new Passport($user, new CustomCredentials( + // If this function returns anything else than `true`, the credentials + // are marked as invalid. + // The $credentials parameter is equal to the next argument of this class + function ($credentials, UserInterface $user) { + return $user->getApiToken() === $credentials; + }, + + // The custom credentials + $apiToken + )); + + +Self Validating Passport +........................ + +If you don't need any credentials to be checked (e.g. when using API +tokens), you can use the +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\SelfValidatingPassport`. +This class only requires a ``UserBadge`` object and optionally `Passport +Badges`_. + +Passport Badges +~~~~~~~~~~~~~~~ + +The ``Passport`` also optionally allows you to add *security badges*. +Badges attach more data to the passport (to extend security). By default, +the following badges are supported: + +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\RememberMeBadge` + When this badge is added to the passport, the authenticator indicates + remember me is supported. Whether remember me is actually used depends + on special ``remember_me`` configuration. Read + :doc:`/security/remember_me` for more information. + +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PasswordUpgradeBadge` + This is used to automatically upgrade the password to a new hash upon + successful login. This badge requires the plaintext password and a + password upgrader (e.g. the user repository). See :doc:`/security/password_migration`. + +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\CsrfTokenBadge` + Automatically validates CSRF tokens for this authenticator during + authentication. The constructor requires a token ID (unique per form) + and CSRF token (unique per request). See :doc:`/security/csrf`. + +:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PreAuthenticatedUserBadge` + Indicates that this user was pre-authenticated (i.e. before Symfony was + initiated). This skips the + :doc:`pre-authentication user checker `. + +.. versionadded:: 5.2 + + Since 5.2, the ``PasswordUpgradeBadge`` is automatically added to + the passport if the passport has ``PasswordCredentials``. + +For instance, if you want to add CSRF to your custom authenticator, you +would initialize the passport like this:: + + // src/Service/LoginAuthenticator.php + namespace App\Service; + + // ... + use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + use Symfony\Component\Security\Http\Authenticator\Passport\Passport; + use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; + + class LoginAuthenticator extends AbstractAuthenticator + { + public function authenticate(Request $request): PassportInterface + { + $password = $request->request->get('password'); + $username = $request->request->get('username'); + $csrfToken = $request->request->get('csrf_token'); + + // ... validate no parameter is empty + + return new Passport( + new UserBadge($user), + new PasswordCredentials($password), + [new CsrfTokenBadge('login', $csrfToken)] + ); + } + } + +.. tip:: + + Besides badges, passports can define attributes, which allows the + ``authenticate()`` method to store arbitrary information in the + passport to access it from other authenticator methods (e.g. + ``createAuthenticatedToken()``):: + + // ... + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + + class LoginAuthenticator extends AbstractAuthenticator + { + // ... + + public function authenticate(Request $request): PassportInterface + { + // ... process the request + + $passport = new SelfValidatingPassport(new UserBadge($username), []); + + // set a custom attribute (e.g. scope) + $passport->setAttribute('scope', $oauthScope); + + return $passport; + } + + public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface + { + // read the attribute value + return new CustomOauthToken($passport->getUser(), $passport->getAttribute('scope')); + } + } + +.. versionadded:: 5.2 + + Passport attributes were introduced in Symfony 5.2. diff --git a/security/expressions.rst b/security/expressions.rst index 2013d6656d7..cd8a0f36941 100644 --- a/security/expressions.rst +++ b/security/expressions.rst @@ -18,7 +18,7 @@ accepts an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` object:: public function index() { $this->denyAccessUnlessGranted(new Expression( - '"ROLE_ADMIN" in roles or (not is_anonymous() and user.isSuperAdmin())' + '"ROLE_ADMIN" in role_names or (not is_anonymous() and user.isSuperAdmin())' )); // ... @@ -38,7 +38,7 @@ Inside the expression, you have access to a number of variables: ``user`` The user object (or the string ``anon`` if you're not authenticated). -``roles`` +``role_names`` The array of roles the user has. This array includes any roles granted indirectly via the :ref:`role hierarchy ` but it does not include the ``IS_AUTHENTICATED_*`` attributes (see the functions below). diff --git a/security/firewall_restriction.rst b/security/firewall_restriction.rst index 0ce0c2e3ff5..ee0950083bc 100644 --- a/security/firewall_restriction.rst +++ b/security/firewall_restriction.rst @@ -49,7 +49,9 @@ if the request path matches the configured ``pattern``. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -106,7 +108,9 @@ only initialize if the host from the request matches against the configuration. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -164,7 +168,9 @@ the provided HTTP methods. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -220,7 +226,9 @@ If the above options don't fit your needs you can configure any service implemen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/force_https.rst b/security/force_https.rst index 797446ce4b9..9492e0fece0 100644 --- a/security/force_https.rst +++ b/security/force_https.rst @@ -36,7 +36,9 @@ access control: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/form_login.rst b/security/form_login.rst index 374e21a78b7..6400a62206b 100644 --- a/security/form_login.rst +++ b/security/form_login.rst @@ -28,7 +28,8 @@ First, enable ``form_login`` under your firewall: firewalls: main: - anonymous: lazy + anonymous: true + lazy: true form_login: login_path: login check_path: login @@ -41,11 +42,12 @@ First, enable ``form_login`` under your firewall: xmlns:srv="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> - - + @@ -57,7 +59,8 @@ First, enable ``form_login`` under your firewall: $container->loadFromExtension('security', [ 'firewalls' => [ 'main' => [ - 'anonymous' => 'lazy', + 'anonymous' => true, + 'lazy' => true, 'form_login' => [ 'login_path' => 'login', 'check_path' => 'login', @@ -280,7 +283,9 @@ security component: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -362,7 +367,9 @@ After this, you have protected your login form against CSRF attacks. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -436,7 +443,9 @@ a relative/absolute URL or a Symfony route name: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -493,7 +502,9 @@ previously requested URL and always redirect to the default page: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -581,7 +592,9 @@ parameter is included in the request, you may use the value of the xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -647,7 +660,9 @@ option to define a new target via a relative/absolute URL or a Symfony route nam xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -722,7 +737,9 @@ redirects can be customized using the ``target_path_parameter`` and xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/form_login_setup.rst b/security/form_login_setup.rst index 768ce725a72..1d269eba380 100644 --- a/security/form_login_setup.rst +++ b/security/form_login_setup.rst @@ -112,7 +112,9 @@ Edit the ``security.yaml`` file in order to declare the ``/logout`` path: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -324,7 +326,9 @@ a traditional HTML form that submits to ``/login``: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -473,7 +477,6 @@ whenever the user browses a page:: namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Http\Util\TargetPathTrait; @@ -482,13 +485,6 @@ whenever the user browses a page:: { use TargetPathTrait; - private $session; - - public function __construct(SessionInterface $session) - { - $this->session = $session; - } - public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); @@ -500,7 +496,7 @@ whenever the user browses a page:: return; } - $this->saveTargetPath($this->session, 'main', $request->getUri()); + $this->saveTargetPath($request->getSession(), 'main', $request->getUri()); } public static function getSubscribedEvents() diff --git a/security/guard_authentication.rst b/security/guard_authentication.rst index 7a6a25b9618..99d86c6c781 100644 --- a/security/guard_authentication.rst +++ b/security/guard_authentication.rst @@ -14,6 +14,11 @@ Guard authentication can be used to: and many more. In this example, we'll build an API token authentication system, so we can learn more about Guard in detail. +.. tip:: + + A :doc:`new experimental authenticator-based system ` + was introduced in Symfony 5.1, which will eventually replace Guards in Symfony 6.0. + Step 1) Prepare your User Class ------------------------------- @@ -243,7 +248,8 @@ Finally, configure your ``firewalls`` key in ``security.yaml`` to use this authe # ... main: - anonymous: lazy + anonymous: true + lazy: true logout: ~ guard: @@ -263,14 +269,15 @@ Finally, configure your ``firewalls`` key in ``security.yaml`` to use this authe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> - - + @@ -293,7 +300,8 @@ Finally, configure your ``firewalls`` key in ``security.yaml`` to use this authe 'firewalls' => [ 'main' => [ 'pattern' => '^/', - 'anonymous' => 'lazy', + 'anonymous' => true, + 'lazy' => true, 'logout' => true, 'guard' => [ 'authenticators' => [ @@ -357,7 +365,7 @@ Each authenticator needs the following methods: (or throw an :ref:`AuthenticationException `), authentication will fail. -**onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)** +**onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)** This is called after successful authentication and your job is to either return a :class:`Symfony\\Component\\HttpFoundation\\Response` object that will be sent to the client or ``null`` to continue the request diff --git a/security/impersonating_user.rst b/security/impersonating_user.rst index 5f44a7fad23..1269cbbdae1 100644 --- a/security/impersonating_user.rst +++ b/security/impersonating_user.rst @@ -38,7 +38,9 @@ listener: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -90,22 +92,23 @@ role to the users that need it. Knowing When Impersonation Is Active ------------------------------------ -When a user is being impersonated, Symfony grants them a special role called -``ROLE_PREVIOUS_ADMIN`` (in addition to the roles the user may have). Use this -special role, for instance, to show a link to exit impersonation in a template: +You can use the special attribute ``IS_IMPERSONATOR`` to check if the +impersonation is active in this session. Use this special role, for +instance, to show a link to exit impersonation in a template: .. code-block:: html+twig - {% if is_granted('ROLE_PREVIOUS_ADMIN') %} -
Exit impersonation + {% if is_granted('IS_IMPERSONATOR') %} + Exit impersonation {% endif %} -Finding the Original User -------------------------- +.. versionadded:: 5.1 -.. versionadded:: 4.3 + The ``IS_IMPERSONATOR`` was introduced in Symfony 5.1. Use + ``ROLE_PREVIOUS_ADMIN`` prior to Symfony 5.1. - The ``SwitchUserToken`` class was introduced in Symfony 4.3. +Finding the Original User +------------------------- In some cases, you may need to get the object that represents the impersonator user rather than the impersonated user. When a user is impersonated the token @@ -172,7 +175,9 @@ also adjust the query parameter name via the ``parameter`` setting: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -229,7 +234,9 @@ be called): xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/json_login_setup.rst b/security/json_login_setup.rst index a4be5dac0e7..97c76e1ab8a 100644 --- a/security/json_login_setup.rst +++ b/security/json_login_setup.rst @@ -17,7 +17,8 @@ First, enable the JSON login under your firewall: firewalls: main: - anonymous: lazy + anonymous: true + lazy: true json_login: check_path: /login @@ -29,11 +30,12 @@ First, enable the JSON login under your firewall: xmlns:srv="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> - - + @@ -45,7 +47,8 @@ First, enable the JSON login under your firewall: $container->loadFromExtension('security', [ 'firewalls' => [ 'main' => [ - 'anonymous' => 'lazy', + 'anonymous' => true, + 'lazy' => true, 'json_login' => [ 'check_path' => '/login', ], @@ -164,7 +167,8 @@ The security configuration should be: firewalls: main: - anonymous: lazy + anonymous: true + lazy: true json_login: check_path: login username_path: security.credentials.login @@ -178,11 +182,12 @@ The security configuration should be: xmlns:srv="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> - - + @@ -196,7 +201,8 @@ The security configuration should be: $container->loadFromExtension('security', [ 'firewalls' => [ 'main' => [ - 'anonymous' => 'lazy', + 'anonymous' => true, + 'lazy' => true, 'json_login' => [ 'check_path' => 'login', 'username_path' => 'security.credentials.login', diff --git a/security/ldap.rst b/security/ldap.rst index 3b5b549e7ec..ffbf5714b78 100644 --- a/security/ldap.rst +++ b/security/ldap.rst @@ -34,12 +34,6 @@ 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 ------------ @@ -165,7 +159,9 @@ use the ``ldap`` user provider. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -279,10 +275,6 @@ 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. @@ -389,7 +381,9 @@ Configuration example for form login xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -442,7 +436,9 @@ Configuration example for HTTP Basic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -502,7 +498,9 @@ Configuration example for form login and query_string xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -536,11 +534,6 @@ Configuration example for form login and query_string ] ]); -.. 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`: https://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/login_link.rst b/security/login_link.rst new file mode 100644 index 00000000000..b92dd694178 --- /dev/null +++ b/security/login_link.rst @@ -0,0 +1,656 @@ +.. index:: + single: Security; Login link + single: Security; Magic link login + +How to use Passwordless Login Link Authentication +================================================= + +Login links, also called "magic links", are a passwordless authentication +mechanism. Whenever a user wants to login, a new link is generated and sent to +them (e.g. using an email). The link fully authenticates the user in the +application when clicking on it. + +This authentication method can help you eliminate most of the customer support +related to authentication (e.g. I forgot my password, how can I change or reset +my password, etc.) + +Login links are supported by Symfony when using the experimental +authenticator system. You must +:ref:`enable the authenticator system ` +in your configuration to use this feature. + +Using the Login Link Authenticator +---------------------------------- + +This guide assumes you have setup security and have created a user object +in your application. Follow :doc:`the main security guide ` if +this is not yet the case. + +1) Configure the Login Link Authenticator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The login link authenticator is configured using the ``login_link`` option +under the firewall. You must configure a ``check_route`` and +``signature_properties`` when enabling this authenticator: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + login_link: + check_route: login_check + signature_properties: ['id'] + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + 'firewalls' => [ + 'main' => [ + 'login_link' => [ + 'check_route' => 'login_check', + ], + ], + ], + ]); + +The ``signature_properties`` are used to create a signed URL. This must +contain at least one property of your ``User`` object that uniquely +identifies this user (e.g. the user ID). Read more about this setting +:ref:`further down below `. + +The ``check_route`` must be an existing route and it will be used to +generate the login link that will authenticate the user. You don't need a +controller (or it can be empty) because the login link authenticator will +intercept requests to this route: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Controller/SecurityController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class SecurityController extends AbstractController + { + /** + * @Route("/login_check", name="login_check") + */ + public function check() + { + throw new \LogicException('This code should never be reached'); + } + } + + .. code-block:: yaml + + # config/routes.yaml + + # ... + login_check: + path: /login_check + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + // config/routes.php + use App\Controller\DefaultController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + // ... + $routes->add('login_check', '/login_check'); + }; + +2) Generate the Login Link +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Now that the authenticator is able to check the login links, you must +create a page where a user can request a login link and log in to your +website. + +The login link can be generated using the +:class:`Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkHandlerInterface`. +The correct login link handler is autowired for you when type-hinting for +this interface:: + + // src/Controller/SecurityController.php + namespace App\Controller; + + use App\Repository\UserRepository; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Component\Security\Http\LoginLink\LoginLinkHandlerInterface; + + class SecurityController extends AbstractController + { + /** + * @Route("/login", name="login") + */ + public function requestLoginLink(LoginLinkHandlerInterface $loginLinkHandler, UserRepository $userRepository, Request $request) + { + // check if login form is submitted + if ($request->isMethod('POST')) { + // load the user in some way (e.g. using the form input) + $email = $request->request->get('email'); + $user = $userRepository->findOneBy(['email' => $email]); + + // create a login link for $user this returns an instance + // of LoginLinkDetails + $loginLinkDetails = $loginLinkHandler->createLoginLink($user); + $loginLink = $loginLinkDetails->getUrl(); + + // ... send the link and return a response (see next section) + } + + // if it's not submitted, render the "login" form + return $this->render('security/login.html.twig'); + } + + // ... + } + +.. code-block:: html+twig + + {# templates/security/login.html.twig #} + {% extends 'base.html.twig' %} + + {% block body %} +
+ + +
+ {% endblock %} + +In this controller, the user is submitting their e-mail address to the +controller. Based on this property, the correct user is loaded and a login +link is created using +:method:`Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkHandlerInterface::createLoginLink`. + +.. caution:: + + It is important to send this link to the user and **not show it directly**, + as that would allow anyone to login. For instance, use the + :doc:`mailer ` component to mail the login link to the user. + Or use the component to send an SMS to the + user's device. + +3) Send the Login Link to the User +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Now the link is created, it needs to be send to the user. Anyone with the +link is able to login as this user, so you need to make sure to send it to +a known device of them (e.g. using e-mail or SMS). + +You can send the link using any library or method. However the login link +authenticator provides integration with the :doc:`Notifier component `. +Use the special :class:`Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkNotification` +to create a notification and send it to the user's email address or phone +number:: + + // src/Controller/SecurityController.php + + // ... + use Symfony\Component\Notifier\NotifierInterface; + use Symfony\Component\Notifier\Recipient\Recipient; + use Symfony\Component\Security\Http\LoginLink\LoginLinkNotification; + + class SecurityController extends AbstractController + { + /** + * @Route("/login", name="login") + */ + public function requestLoginLink(NotifierInterface $notifier, LoginLinkHandlerInterface $loginLinkHandler, UserRepository $userRepository, Request $request) + { + if ($request->isMethod('POST')) { + $email = $request->request->get('email'); + $user = $userRepository->findOneBy(['email' => $email]); + + $loginLinkDetails = $loginLinkHandler->createLoginLink($user); + + // create a notification based on the login link details + $notification = new LoginLinkNotification( + $loginLinkDetails, + 'Welcome to MY WEBSITE!' // email subject + ); + // create a recipient for this user + $recipient = new Recipient($user->getEmail()); + + // send the notification to the user + $notifier->send($notification, $recipient); + + // render a "Login link is sent!" page + return $this->render('security/login_link_sent.html.twig'); + } + + return $this->render('security/login.html.twig'); + } + + // ... + } + +.. note:: + + This integration requires the :doc:`Notifier ` and + :doc:`Mailer ` components to be installed and configured. + Install all required packages using: + + .. code-block:: terminal + + $ composer require symfony/mailer symfony/notifier \ + symfony/twig-bundle twig/extra-bundle \ + twig/cssinliner-extra twig/inky-extra + +This will send an email like this to the user: + +.. image:: /_images/security/login_link_email.png + :align: center + +.. tip:: + + You can customize this e-mail template by extending the + ``LoginLinkNotification`` and configuring another ``htmlTemplate``:: + + // src/Notifier/CustomLoginLinkNotification + namespace App\Notifier; + + use Symfony\Component\Security\Http\LoginLink\LoginLinkNotification; + + class CustomLoginLinkNotification extends LoginLinkNotification + { + public function asEmailMessage(EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage + { + $emailMessage = parent::asEmailMessage($recipient, $transport); + + // get the NotificationEmail object and override the template + $email = $emailMessage->getMessage(); + $email->htmlTemplate('emails/custom_login_link_email.html.twig'); + + return $emailMessage; + } + } + + Then, use this new ``CustomLoginLinkNotification`` in the controller + instead. + +Important Considerations +------------------------ + +Login links are a convenient way of authenticating users, but it is also +considered less secure than a traditional username and password form. It is +not recommended to use login links in security critical applications. + +However, the implementation in Symfony does have a couple extension points +to make the login links more secure. In this section, the most important +configuration decisions are discussed: + +* `Limit Login Link Lifetime`_ +* `Invalidate Login Links`_ +* `Allow a Link to only be Used Once`_ + +Limit Login Link Lifetime +~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is important for login links to have a limited lifetime. This reduces +the risk that someone can intercept the link and use it to login as +somebody else. By default, Symfony defines a lifetime of 10 minutes (600 +seconds). You can customize this using the ``lifetime`` option: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + login_link: + check_route: login_check + # lifetime in seconds + lifetime: 300 + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + 'firewalls' => [ + 'main' => [ + 'login_link' => [ + 'check_route' => 'login_check', + // lifetime in seconds + 'lifetime' => 300, + ], + ], + ], + ]); + +.. _security-login-link-signature: + +Invalidate Login Links +~~~~~~~~~~~~~~~~~~~~~~ + +Symfony uses signed URLs to implement login links. The advantage of this is +that valid links do not have to be stored in a database. The signed URLs +allow Symfony to still invalidate already sent login links when important +information changes (e.g. a user's email address). + +The signed URL contains 3 parameters: + +``expires`` + The UNIX timestamp when the link expires. + +``user`` + The value returned from ``$user->getUsername()`` for this user. + +``hash`` + A hash of ``expires``, ``user`` and any configured signature + properties. Whenever these change, the hash changes and previous login + links are invalidated. + +You can add more properties to the ``hash`` by using the +``signature_properties`` option: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + login_link: + check_route: login_check + signature_properties: [id, email] + + .. code-block:: xml + + + + + + + + + id + email + + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + 'firewalls' => [ + 'main' => [ + 'login_link' => [ + 'check_route' => 'login_check', + 'signature_properties' => ['id', 'email'], + ], + ], + ], + ]); + +The properties are fetched from the user object using the +:doc:`PropertyAccess component ` (e.g. using +``getEmail()`` or a public ``$email`` property in this example). + +.. tip:: + + You can also use the signature properties to add very advanced + invalidating logic to your login links. For instance, if you store a + ``$lastLinkRequestedAt`` property on your users that you update in the + ``requestLoginLink()`` controller, you can invalidate all login links + whenever a user requests a new link. + +Configure a Maximum Use of a Link +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is a common characteristic of login links to limit the number of times +it can be used. Symfony can support this by storing used login links in the +cache. Enable this support by setting the ``max_uses`` option: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + login_link: + check_route: login_check + # only allow the link to be used 3 times + max_uses: 3 + + # optionally, configure the cache pool + #used_link_cache: 'cache.redis' + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + 'firewalls' => [ + 'main' => [ + 'login_link' => [ + 'check_route' => 'login_check', + // only allow the link to be used 3 times + 'max_uses' => 3, + + // optionally, configure the cache pool + //'used_link_cache' => 'cache.redis', + ], + ], + ], + ]); + +Make sure there is enough space left in the cache, otherwise invalid links +can no longer be stored (and thus become valid again). Expired invalid +links are automatically removed from the cache. + +The cache pools are not cleared by the ``cache:clear`` command, but +removing ``var/cache/`` manually may remove the cache if the cache +component is configured to store its cache in that location. Read the +:doc:`/cache` guide for more information. + +Allow a Link to only be Used Once +................................. + +When setting ``max_uses`` to ``1``, you must take extra precautions to +make it work as expected. Email providers and browsers often load a +preview of the links, meaning that the link is already invalidated by +the preview loader. + +In order to solve this issue, first set the ``check_post_only`` option let +the authenticator only handle HTTP POST methods: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + login_link: + check_route: login_check + check_post_only: true + max_uses: 1 + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + $container->loadFromExtension('security', [ + 'firewalls' => [ + 'main' => [ + 'login_link' => [ + 'check_route' => 'login_check', + 'check_post_only' => true, + 'max_uses' => 1, + ], + ], + ], + ]); + +Then, use the ``check_route`` controller to render a page that lets the +user create this POST request (e.g. by clicking a button):: + + // src/Controller/SecurityController.php + namespace App\Controller; + + // ... + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + class SecurityController extends AbstractController + { + /** + * @Route("/login_check", name="login_check") + */ + public function check(Request $request) + { + // get the login link query parameters + $expires = $request->query->get('expires'); + $username = $request->query->get('user'); + $hash = $request->query->get('hash'); + + // and render a template with the button + return $this->render('security/process_login_link.html.twig', [ + 'expires' => $expires, + 'user' => $username, + 'hash' => $hash, + ]); + } + } + +.. code-block:: html+twig + + {# templates/security/process_login_link.html.twig #} + {% extends 'base.html.twig' %} + + {% block body %} +

Hi! You are about to login to ...

+ + +
+ + + + + +
+ {% endblock %} diff --git a/security/multiple_guard_authenticators.rst b/security/multiple_guard_authenticators.rst index f9145e1d897..b6ea9ca5f11 100644 --- a/security/multiple_guard_authenticators.rst +++ b/security/multiple_guard_authenticators.rst @@ -27,7 +27,8 @@ This is how your security configuration can look in action: # ... firewalls: default: - anonymous: lazy + anonymous: true + lazy: true guard: authenticators: - App\Security\LoginFormAuthenticator @@ -42,12 +43,13 @@ This is how your security configuration can look in action: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> - - + App\Security\LoginFormAuthenticator App\Security\FacebookConnectAuthenticator @@ -66,7 +68,8 @@ This is how your security configuration can look in action: // ... 'firewalls' => [ 'default' => [ - 'anonymous' => 'lazy', + 'anonymous' => true, + 'lazy' => true, 'guard' => [ 'entry_point' => LoginFormAuthenticator::class, 'authenticators' => [ @@ -103,7 +106,8 @@ the solution is to split the configuration into two separate firewalls: authenticators: - App\Security\ApiTokenAuthenticator default: - anonymous: lazy + anonymous: true + lazy: true guard: authenticators: - App\Security\LoginFormAuthenticator @@ -120,7 +124,9 @@ the solution is to split the configuration into two separate firewalls: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -129,8 +135,7 @@ the solution is to split the configuration into two separate firewalls: App\Security\ApiTokenAuthenticator - - + App\Security\LoginFormAuthenticator @@ -159,7 +164,8 @@ the solution is to split the configuration into two separate firewalls: ], ], 'default' => [ - 'anonymous' => 'lazy', + 'anonymous' => true, + 'lazy' => true, 'guard' => [ 'authenticators' => [ LoginFormAuthenticator::class, diff --git a/security/named_encoders.rst b/security/named_encoders.rst index 22bc333dec4..29ca8c278d7 100644 --- a/security/named_encoders.rst +++ b/security/named_encoders.rst @@ -27,7 +27,9 @@ to apply to all instances of a specific class: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd" + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd" > @@ -81,7 +83,9 @@ be done with named encoders: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd" + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd" > @@ -158,7 +162,9 @@ you must register a service for it in order to use it as a named encoder: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd" + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd" > diff --git a/security/password_migration.rst b/security/password_migration.rst index bb77a0504ab..2bdd6c83b95 100644 --- a/security/password_migration.rst +++ b/security/password_migration.rst @@ -4,10 +4,6 @@ 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 your system, the user's password should be *rehashed* using the newer algorithm and @@ -54,6 +50,8 @@ on the new encoder to point to the old, legacy encoder(s): xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://symfony.com/schema/dic/security" xsi:schemaLocation="http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd + http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/remember_me.rst b/security/remember_me.rst index a2fa1bcda96..de9f51afddf 100644 --- a/security/remember_me.rst +++ b/security/remember_me.rst @@ -38,7 +38,9 @@ the session lasts using a cookie with the ``remember_me`` firewall option: xmlns:srv="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -132,6 +134,14 @@ The ``remember_me`` firewall defines the following configuration options: Defines the service id of a token provider to use. If you want to store tokens in the database, see :ref:`remember-me-token-in-database`. +``service`` (default value: ``null``) + Defines the ID of the service used to handle the Remember Me feature. It's + useful if you need to overwrite the current behavior entirely. + + .. versionadded:: 5.1 + + The ``service`` option was introduced in Symfony 5.1. + Forcing the User to Opt-Out of the Remember Me Feature ------------------------------------------------------ @@ -168,7 +178,8 @@ visiting the site. In some cases, however, you may want to force the user to actually re-authenticate before accessing certain resources. For example, you might not allow "remember me" -users to change their password. You can do this by leveraging a few special "roles":: +users to change their password. You can do this by leveraging a few special +"attributes":: // src/Controller/AccountController.php // ... @@ -192,6 +203,15 @@ users to change their password. You can do this by leveraging a few special "rol // ... } +.. tip:: + + There is also a ``IS_REMEMBERED`` attribute that grants *only* when the + user is authenticated via the remember me mechanism. + +.. versionadded:: 5.1 + + The ``IS_REMEMBERED`` attribute was introduced in Symfony 5.1. + .. _remember-me-token-in-database: Storing Remember Me Tokens in the Database @@ -303,7 +323,9 @@ service you created before: xmlns:srv="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/reset_password.rst b/security/reset_password.rst index 7899440ba86..bbde221f015 100644 --- a/security/reset_password.rst +++ b/security/reset_password.rst @@ -12,7 +12,7 @@ Generating the Reset Password Code .. code-block:: terminal - $ php composer require symfonycasts/reset-password-bundle + $ composer require symfonycasts/reset-password-bundle ..... $ php bin/console make:reset-password diff --git a/security/user_checkers.rst b/security/user_checkers.rst index d5374e44b03..b215191e680 100644 --- a/security/user_checkers.rst +++ b/security/user_checkers.rst @@ -15,15 +15,18 @@ User checkers are classes that must implement the :class:`Symfony\\Component\\Security\\Core\\User\\UserCheckerInterface`. This interface defines two methods called ``checkPreAuth()`` and ``checkPostAuth()`` to perform checks before and after user authentication. If one or more conditions -are not met, an exception should be thrown which extends the -:class:`Symfony\\Component\\Security\\Core\\Exception\\AccountStatusException` -or :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`:: +are not met, throw an exception which extends the +:class:`Symfony\\Component\\Security\\Core\\Exception\\AccountStatusException` class. +Consider using :class:`Symfony\\Component\\Security\\Core\\Exception\\CustomUserMessageAccountStatusException`, +which extends ``AccountStatusException`` and allows to customize the error message +displayed to the user:: namespace App\Security; use App\Entity\User as AppUser; use App\Exception\AccountDeletedException; use Symfony\Component\Security\Core\Exception\AccountExpiredException; + use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -35,9 +38,9 @@ or :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExceptio return; } - // user is deleted, show a generic Account Not Found message. if ($user->isDeleted()) { - throw new AccountDeletedException(); + // the message passed to this exception is meant to be displayed to the user + throw new CustomUserMessageAccountStatusException('Your user account no longer exists.'); } } @@ -54,6 +57,10 @@ or :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExceptio } } +.. versionadded:: 5.1 + + The ``CustomUserMessageAccountStatusException`` class was introduced in Symfony 5.1. + Enabling the Custom User Checker -------------------------------- @@ -86,7 +93,9 @@ is the service id of your user checker: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> diff --git a/security/user_provider.rst b/security/user_provider.rst index 07227868afe..00e7c5a58d8 100644 --- a/security/user_provider.rst +++ b/security/user_provider.rst @@ -135,7 +135,7 @@ interface only requires one method: ``loadUserByUsername($username)``:: { // ... - public function loadUserByUsername($usernameOrEmail) + public function loadUserByUsername(string $usernameOrEmail) { $entityManager = $this->getEntityManager(); @@ -174,7 +174,9 @@ To finish this, remove the ``property`` key from the user provider in xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd"> @@ -239,7 +241,9 @@ users will encode their passwords: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd" + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd" > @@ -373,7 +377,7 @@ command will generate a nice skeleton to get you started:: * * @throws UsernameNotFoundException if the user is not found */ - public function loadUserByUsername($username) + public function loadUserByUsername(string $username) { // Load a User object from your data source or throw UsernameNotFoundException. // The $username argument may not actually be a username: @@ -409,7 +413,7 @@ command will generate a nice skeleton to get you started:: /** * Tells Symfony to use this provider for this User class. */ - public function supportsClass($class) + public function supportsClass(string $class) { return User::class === $class || is_subclass_of($class, User::class); } diff --git a/security/voters.rst b/security/voters.rst index 98ba54519af..8970289aaff 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -38,7 +38,7 @@ uses the authorization checker), or by Ultimately, Symfony takes the responses from all voters and makes the final decision (to allow or deny access to the resource) according to the strategy defined -in the application, which can be: affirmative, consensus or unanimous. +in the application, which can be: affirmative, consensus, unanimous or priority. For more information take a look at :ref:`the section about access decision managers `. @@ -56,8 +56,8 @@ which makes creating a voter even easier:: abstract class Voter implements VoterInterface { - abstract protected function supports($attribute, $subject); - abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token); + abstract protected function supports(string $attribute, $subject); + abstract protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token); } .. _how-to-use-the-voter-in-a-controller: @@ -130,7 +130,7 @@ would look like this:: const VIEW = 'view'; const EDIT = 'edit'; - protected function supports($attribute, $subject) + protected function supports(string $attribute, $subject) { // if the attribute isn't one we support, return false if (!in_array($attribute, [self::VIEW, self::EDIT])) { @@ -145,7 +145,7 @@ would look like this:: return true; } - protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token) { $user = $token->getUser(); @@ -190,7 +190,7 @@ That's it! The voter is done! Next, :ref:`configure it security = $security; } - protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token) { // ... @@ -283,7 +283,15 @@ There are three strategies available: ``unanimous`` This only grants access if there is no voter denying access. If all voters abstained from voting, the decision is based on the ``allow_if_all_abstain`` - config option (which defaults to ``false``). + config option (which defaults to ``false``); + +``priority`` + This grants or denies access by the first voter that does not abstain, + based on their service priority; + + .. versionadded:: 5.1 + + The ``priority`` version strategy was introduced in Symfony 5.1. In the above scenario, both voters should grant access in order to grant access to the user to read the post. In this case, the default strategy is no longer @@ -308,7 +316,9 @@ security configuration: xmlns:srv="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd" + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/security + https://symfony.com/schema/dic/security/security-1.0.xsd" > diff --git a/serializer.rst b/serializer.rst index d416f8fb40a..b4dd7e03d52 100644 --- a/serializer.rst +++ b/serializer.rst @@ -67,25 +67,16 @@ As well as the following normalizers: for :phpclass:`DateInterval` objects * :class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` to transform :phpclass:`SplFileInfo` objects in `Data URIs`_ +* :class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` for + objects implementing the :class:`Symfony\\Component\\Form\\FormInterface` to + normalize form errors. * :class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer` to deal with objects implementing the :phpclass:`JsonSerializable` interface * :class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer` to - denormalize arrays of objects using a format like `MyObject[]` (note the `[]` suffix) + denormalize arrays of objects using a notation like ``MyObject[]`` (note the ``[]`` suffix) * :class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer` for objects implementing the :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface` interface * :class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` for :class:`Symfony\\Component\\ErrorHandler\\Exception\\FlattenException` objects -.. versionadded:: 4.1 - - The ``ConstraintViolationListNormalizer`` was introduced in Symfony 4.1. - -.. versionadded:: 4.3 - - The ``DateTimeZoneNormalizer`` was introduced in Symfony 4.3. - -.. versionadded:: 4.4 - - The ``ProblemNormalizer`` was introduced in Symfony 4.4. - Custom normalizers and/or encoders can also be loaded by tagging them as :ref:`serializer.normalizer ` and :ref:`serializer.encoder `. It's also diff --git a/serializer/custom_encoders.rst b/serializer/custom_encoders.rst index d212f94a2f5..7f8a0e1b4f2 100644 --- a/serializer/custom_encoders.rst +++ b/serializer/custom_encoders.rst @@ -28,22 +28,22 @@ create your own encoder that uses the class YamlEncoder implements EncoderInterface, DecoderInterface { - public function encode($data, $format, array $context = []) + public function encode($data, string $format, array $context = []) { return Yaml::dump($data); } - public function supportsEncoding($format) + public function supportsEncoding(string $format) { return 'yaml' === $format; } - public function decode($data, $format, array $context = []) + public function decode(string $data, string $format, array $context = []) { return Yaml::parse($data); } - public function supportsDecoding($format) + public function supportsDecoding(string $format) { return 'yaml' === $format; } diff --git a/serializer/custom_normalizer.rst b/serializer/custom_normalizer.rst index a19dd7c8047..2a0a60244ed 100644 --- a/serializer/custom_normalizer.rst +++ b/serializer/custom_normalizer.rst @@ -36,7 +36,7 @@ to customize the normalized data. To do that, leverage the ``ObjectNormalizer``: $this->normalizer = $normalizer; } - public function normalize($topic, $format = null, array $context = []) + public function normalize($topic, string $format = null, array $context = []) { $data = $this->normalizer->normalize($topic, $format, $context); @@ -48,7 +48,7 @@ to customize the normalized data. To do that, leverage the ``ObjectNormalizer``: return $data; } - public function supportsNormalization($data, $format = null, array $context = []) + public function supportsNormalization($data, string $format = null, array $context = []) { return $data instanceof Topic; } diff --git a/serializer/normalizers.rst b/serializer/normalizers.rst index 5aef4568dc6..78cc103d763 100644 --- a/serializer/normalizers.rst +++ b/serializer/normalizers.rst @@ -37,6 +37,9 @@ Symfony includes the following normalizers but you can also * :class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer` to normalize PHP object using an object that implements :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface`; +* :class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` for + objects implementing the :class:`Symfony\\Component\\Form\\FormInterface` to + normalize form errors; * :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` to normalize PHP object using the getter and setter methods of the object; * :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer` to diff --git a/service_container.rst b/service_container.rst index 9c4892712ea..98fb5e61318 100644 --- a/service_container.rst +++ b/service_container.rst @@ -62,9 +62,6 @@ What other services are available? Find out by running: Request stack that controls the lifecycle of requests. Symfony\Component\HttpFoundation\RequestStack (request_stack) - Interface for the session. - Symfony\Component\HttpFoundation\Session\SessionInterface (session) - RouterInterface is the interface that all Router classes must implement. Symfony\Component\Routing\RouterInterface (router.default) @@ -80,7 +77,7 @@ in the container. .. tip:: There are actually *many* more services in the container, and each service has - a unique id in the container, like ``session`` or ``router.default``. For a full + a unique id in the container, like ``request_stack`` or ``router.default``. For a full list, you can run ``php bin/console debug:container``. But most of the time, you won't need to worry about this. See :ref:`services-wire-specific-service`. See :doc:`/service_container/debug`. @@ -165,7 +162,7 @@ each time you ask for it. # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/*' - exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' + exclude: '../src/{DependencyInjection,Entity,Tests,Kernel.php}' # ... @@ -184,7 +181,7 @@ each time you ask for it. - + @@ -207,7 +204,7 @@ each time you ask for it. // 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/*') - ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'); + ->exclude('../src/{DependencyInjection,Entity,Tests,Kernel.php}'); }; .. tip:: @@ -283,9 +280,6 @@ type-hints by running: Request stack that controls the lifecycle of requests. Symfony\Component\HttpFoundation\RequestStack (request_stack) - Interface for the session. - Symfony\Component\HttpFoundation\Session\SessionInterface (session) - RouterInterface is the interface that all Router classes must implement. Symfony\Component\Routing\RouterInterface (router.default) @@ -417,7 +411,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume # same as before App\: resource: '../src/*' - exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' + exclude: '../src/{DependencyInjection,Entity,Tests,Kernel.php}' # explicitly configure the service App\Service\SiteUpdateManager: @@ -440,7 +434,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume @@ -462,7 +456,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume // same as before $services->load('App\\', '../src/*') - ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'); + ->exclude('../src/{DependencyInjection,Entity,Tests,Kernel.php}'); $services->set(SiteUpdateManager::class) ->arg('$adminEmail', 'manager@example.com') @@ -491,7 +485,7 @@ all their types (string, boolean, array, binary and PHP constant parameters). However, there is another type of parameter related to services. In YAML config, any string which starts with ``@`` is considered as the ID of a service, instead of a regular string. In XML config, use the ``type="service"`` type for the -parameter and in PHP config use the ``ref`` function: +parameter and in PHP config use the ``service()`` function: .. configuration-block:: @@ -536,7 +530,8 @@ parameter and in PHP config use the ``ref`` function: $services = $configurator->services(); $services->set(MessageGenerator::class) - ->args([ref('logger')]) + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->args([service('logger')]) ; }; @@ -643,7 +638,7 @@ But, you can control this and pass in a different logger: // explicitly configure the service $services->set(SiteUpdateManager::class) - ->arg('$logger', ref('monolog.logger.request')) + ->arg('$logger', service('monolog.logger.request')) ; }; @@ -748,25 +743,21 @@ You can also use the ``bind`` keyword to bind specific arguments by name or type // pass this service to any $requestLogger argument for any // service that's defined in this file - ->bind('$requestLogger', ref('monolog.logger.request')) + ->bind('$requestLogger', service('monolog.logger.request')) // pass this service for any LoggerInterface type-hint for any // service that's defined in this file - ->bind(LoggerInterface::class, ref('monolog.logger.request')) + ->bind(LoggerInterface::class, service('monolog.logger.request')) // 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(LoggerInterface::class.' $requestLogger', service('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 @@ -811,10 +802,6 @@ constructor arguments without any configuration. Linting Service Definitions --------------------------- -.. versionadded:: 4.4 - - The ``lint:container`` command was introduced in Symfony 4.4. - The ``lint:container`` command checks that the arguments injected into services match their type declarations. It's useful to run it before deploying your application to production (e.g. in your continuous integration server): @@ -835,47 +822,16 @@ loss, enable the compiler pass in your application. Public Versus Private Services ------------------------------ -From Symfony 4.0, every service defined is private by default. +Every service defined is private by default. When a service is private, you +cannot access it directly from the container using ``$container->get()``. As a +best practice, you should only create *private* services and you should fetch +services using dependency injection instead of using ``$container->get()``. -What does this mean? When a service **is** public, you can access it directly -from the container object, which can also be injected thanks to autowiring. -This is mostly useful when you want to fetch services lazily:: +If you need to fetch services lazily, instead of using public services you +should consider using a :ref:`service locator `. - namespace App\Generator; - - use Psr\Container\ContainerInterface; - - class MessageGenerator - { - private $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - public function generate(string $message, string $template = null, array $context = []): string - { - if ($template && $this->container->has('twig')) { - // there IS a public "twig" service in the container - $twig = $this->container->get('twig'); - - return $twig->render($template, $context + ['message' => $message]); - } - - // if no template is passed, the "twig" service will not be loaded - - // ... - } - -As a best practice, you should only create *private* services. This allows for -safe container optimizations, e.g. removing unused services. You should not use -``$container->get()`` to fetch public services, as it will make it harder to -make those services private later. Instead consider -:ref:`injecting services ` or using -:doc:`Service Subscribers or Locators `. - -But, if you *do* need to make a service public, override the ``public`` setting: +But, if you *do* need to make a service public, override the ``public`` +setting: .. configuration-block:: @@ -922,10 +878,10 @@ But, if you *do* need to make a service public, override the ``public`` setting: ; }; -.. note:: +.. deprecated:: 5.1 - Instead of injecting the container you should consider using a - :ref:`service locator ` instead. + As of Symfony 5.1, it is no longer possible to autowire the service + container by type-hinting ``Psr\Container\ContainerInterface``. .. _service-psr4-loader: @@ -947,7 +903,7 @@ key. For example, the default Symfony configuration contains this: # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/*' - exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' + exclude: '../src/{DependencyInjection,Entity,Tests,Kernel.php}' .. code-block:: xml @@ -961,7 +917,7 @@ key. For example, the default Symfony configuration contains this: - + @@ -976,7 +932,7 @@ key. For example, the default Symfony configuration contains this: // 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/*') - ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'); + ->exclude('../src/{DependencyInjection,Entity,Tests,Kernel.php}'); }; .. tip:: @@ -1162,16 +1118,16 @@ admin email. In this case, each needs to have a unique service id: ->autowire(false) // manually wire all arguments ->args([ - ref(MessageGenerator::class), - ref('mailer'), - 'superadmin@example.com', + service(MessageGenerator::class), + service('mailer'), + 'superadmin@example.com', ]); $services->set('site_update_manager.normal_users', SiteUpdateManager::class) ->autowire(false) ->args([ - ref(MessageGenerator::class), - ref('mailer'), + service(MessageGenerator::class), + service('mailer'), 'contact@example.com', ]); diff --git a/service_container/alias_private.rst b/service_container/alias_private.rst index bac0b8fdc5f..6e032b10a6f 100644 --- a/service_container/alias_private.rst +++ b/service_container/alias_private.rst @@ -158,6 +158,12 @@ This means that when using the container directly, you can access the Deprecating Service Aliases ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 5.1 + + The ``package`` and ``version`` options were introduced in Symfony 5.1. + Prior to 5.1, you had to use ``deprecated: true`` or + ``deprecated: 'Custom message'``. + If you decide to deprecate the use of a service alias (because it is outdated or you decided not to maintain it anymore), you can deprecate its definition: @@ -168,11 +174,17 @@ or you decided not to maintain it anymore), you can deprecate its definition: app.mailer: alias: '@App\Mail\PhpMailer' - # this will display a generic deprecation message... - deprecated: true + # this outputs the following generic deprecation message: + # Since acme/package 1.2: The "app.mailer" service alias is deprecated. You should stop using it, as it will be removed in the future + deprecated: + package: 'acme/package' + version: '1.2' - # ...but you can also define a custom deprecation message - deprecated: 'The "%alias_id%" alias is deprecated. Do not use it anymore.' + # you can also define a custom deprecation message (%alias_id% placeholder is available) + deprecated: + package: 'acme/package' + version: '1.2' + message: 'The "%alias_id%" alias is deprecated. Do not use it anymore.' .. code-block:: xml @@ -183,11 +195,14 @@ or you decided not to maintain it anymore), you can deprecate its definition: - - - - - The "%alias_id%" service alias is deprecated. Don't use it anymore. + + + + + + The "%alias_id%" service alias is deprecated. Don't use it anymore. + @@ -197,12 +212,14 @@ or you decided not to maintain it anymore), you can deprecate its definition: $container ->setAlias('app.mailer', 'App\Mail\PhpMailer') - // this will display a generic deprecation message... - ->setDeprecated(true) + // this outputs the following generic deprecation message: + // Since acme/package 1.2: The "app.mailer" service alias is deprecated. You should stop using it, as it will be removed in the future + ->setDeprecated('acme/package', '1.2') - // ...but you can also define a custom deprecation message + // you can also define a custom deprecation message (%alias_id% placeholder is available) ->setDeprecated( - true, + 'acme/package', + '1.2', 'The "%alias_id%" service alias is deprecated. Don\'t use it anymore.' ) ; @@ -214,10 +231,6 @@ The message is actually a message template, which replaces occurrences of the ``%alias_id%`` placeholder by the service alias id. You **must** have at least one occurrence of the ``%alias_id%`` placeholder in your template. -.. versionadded:: 4.3 - - The ``deprecated`` option for service aliases was introduced in Symfony 4.3. - Anonymous Services ------------------ @@ -269,7 +282,8 @@ The following example shows how to inject an anonymous service into another serv $services = $configurator->services(); $services->set(Foo::class) - ->args([inline(AnonymousBar::class)]) + // In versions earlier to Symfony 5.1 the inline_service() function was called inline() + ->args([inline_service(AnonymousBar::class)]) }; .. note:: @@ -277,7 +291,7 @@ The following example shows how to inject an anonymous service into another serv Anonymous services do *NOT* inherit the definitions provided from the defaults defined in the configuration. So you'll need to explicitly mark service as autowired or autoconfigured when doing an anonymous service - e.g.: ``inline(Foo::class)->autowire()->autoconfigure()``. + e.g.: ``inline_service(Foo::class)->autowire()->autoconfigure()``. Using an anonymous service as a factory looks like this: @@ -320,7 +334,7 @@ Using an anonymous service as a factory looks like this: $services = $configurator->services(); $services->set(Foo::class) - ->factory([inline(AnonymousBar::class), 'constructFoo']) + ->factory([inline_service(AnonymousBar::class), 'constructFoo']) }; Deprecating Services diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index 04058c6b9ac..06130427463 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -271,11 +271,6 @@ class is type-hinted. adds an alias: ``Psr\Log\LoggerInterface`` that points to the ``logger`` service. This is why arguments type-hinted with ``Psr\Log\LoggerInterface`` can be autowired. -.. versionadded:: 4.2 - - Since Monolog Bundle 3.5 each channel bind into container by type-hinted alias. - More info in the part about :ref:`how to autowire monolog channels `. - .. _autowiring-interface-alias: Working with Interfaces @@ -524,7 +519,7 @@ the injection:: // If you wanted to choose the non-default service and do not // want to use a named autowiring alias, wire it manually: - // ->arg('$transformer', ref(UppercaseTransformer::class)) + // ->arg('$transformer', service(UppercaseTransformer::class)) // ... }; @@ -535,10 +530,6 @@ If the argument is named ``$shoutyTransformer``, But, you can also manually wire any *other* service by specifying the argument under the arguments key. -.. versionadded:: 4.2 - - Named autowiring aliases have been introduced in Symfony 4.2. - Fixing Non-Autowireable Arguments --------------------------------- @@ -551,39 +542,118 @@ You wire up the difficult arguments, Symfony takes care of the rest. .. _autowiring-calls: -Autowiring other Methods (e.g. Setters) ---------------------------------------- +Autowiring other Methods (e.g. Setters and Public Typed Properties) +------------------------------------------------------------------- When autowiring is enabled for a service, you can *also* configure the container to call methods on your class when it's instantiated. For example, suppose you want -to inject the ``logger`` service, and decide to use setter-injection:: +to inject the ``logger`` service, and decide to use setter-injection: - // src/Util/Rot13Transformer.php - namespace App\Util; +.. configuration-block:: - class Rot13Transformer - { - private $logger; + .. code-block:: php-annotations - /** - * @required - */ - public function setLogger(LoggerInterface $logger): void + // src/Util/Rot13Transformer.php + namespace App\Util; + + class Rot13Transformer { - $this->logger = $logger; + private $logger; + + /** + * @required + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + public function transform($value): string + { + $this->logger->info('Transforming '.$value); + // ... + } } - public function transform(string $value): string + .. code-block:: php-attributes + + // src/Util/Rot13Transformer.php + namespace App\Util; + + use Symfony\Contracts\Service\Attribute\Required; + + class Rot13Transformer { - $this->logger->info('Transforming '.$value); - // ... + private $logger; + + #[Required] + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + public function transform($value): string + { + $this->logger->info('Transforming '.$value); + // ... + } } - } -Autowiring will automatically call *any* method with the ``@required`` annotation +Autowiring will automatically call *any* method with the ``#[Required]`` attribute above it, autowiring each argument. If you need to manually wire some of the arguments to a method, you can always explicitly :doc:`configure the method call `. +If your PHP version doesn't support attributes (they were introduced in PHP 8), +you can use the ``@required`` annotation instead. + +.. versionadded:: 5.2 + + The ``#[Required]`` attribute was introduced in Symfony 5.2. + +Despite property injection has some :ref:`drawbacks `, +autowiring with ``#[Required]`` or ``@required`` can also be applied to public +typed properties: + +.. configuration-block:: + + .. code-block:: php-annotations + + namespace App\Util; + + class Rot13Transformer + { + /** @required */ + public LoggerInterface $logger; + + public function transform($value) + { + $this->logger->info('Transforming '.$value); + // ... + } + } + + .. code-block:: php-attributes + + namespace App\Util; + + use Symfony\Contracts\Service\Attribute\Required; + + class Rot13Transformer + { + #[Required] + public LoggerInterface $logger; + + public function transform($value) + { + $this->logger->info('Transforming '.$value); + // ... + } + } + +.. versionadded:: 5.1 + + Public typed properties autowiring was introduced in Symfony 5.1. + Autowiring Controller Action Methods ------------------------------------ diff --git a/service_container/calls.rst b/service_container/calls.rst index 78418aadf13..9f7ac768976 100644 --- a/service_container/calls.rst +++ b/service_container/calls.rst @@ -6,7 +6,7 @@ Service Method Calls and Setter Injection .. tip:: - If you're using autowiring, you can use ``@required`` to + If you're using autowiring, you can use ``#[Required]`` or ``@required`` to :ref:`automatically configure method calls `. Usually, you'll want to inject your dependencies via the constructor. But sometimes, @@ -73,14 +73,10 @@ To configure the container to call the ``setLogger`` method, use the ``calls`` k // ... $services->set(MessageGenerator::class) - ->call('setLogger', [ref('logger')]); + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->call('setLogger', [service('logger')]); }; - -.. versionadded:: 4.3 - - The ``immutable-setter`` injection was introduced in Symfony 4.3. - To provide immutable services, some classes implement immutable setters. Such setters return a new instance of the configured class instead of mutating the object they were called on:: @@ -94,9 +90,6 @@ instead of mutating the object they were called on:: { private $logger; - /** - * @return static - */ public function withLogger(LoggerInterface $logger): self { $new = clone $this; @@ -150,3 +143,31 @@ The configuration to tell the container it should do so would be like: $container->register(MessageGenerator::class) ->addMethodCall('withLogger', [new Reference('logger')], true); + +.. tip:: + + If autowire is enabled, you can also use annotations; with the previous + example it would be:: + + /** + * @required + * @return static + */ + public function withLogger(LoggerInterface $logger) + { + $new = clone $this; + $new->logger = $logger; + + return $new; + } + + You can also leverage the PHP 8 ``static`` return type instead of the + ``@return static`` annotation. If you don't want a method with a + PHP 8 ``static`` return type and a ``@required`` annotation to behave as + a wither, you can add a ``@return $this`` annotation to disable the + *returns clone* feature. + + .. versionadded:: 5.1 + + Support for the PHP 8 ``static`` return type was introduced in + Symfony 5.1. diff --git a/service_container/configurators.rst b/service_container/configurators.rst index 5331d0ba7ac..1ade37244c3 100644 --- a/service_container/configurators.rst +++ b/service_container/configurators.rst @@ -179,19 +179,16 @@ all the classes are already loaded as services. All you need to do is specify th $services->load('App\\', '../src/*'); // override the services to set the configurator + // In versions earlier to Symfony 5.1 the service() function was called ref() $services->set(NewsletterManager::class) - ->configurator(ref(EmailConfigurator::class), 'configure'); + ->configurator(service(EmailConfigurator::class), 'configure'); $services->set(GreetingCardManager::class) - ->configurator(ref(EmailConfigurator::class), 'configure'); + ->configurator(service(EmailConfigurator::class), 'configure'); }; .. _configurators-invokable: -.. versionadded:: 4.3 - - Invokable configurators for services were introduced in Symfony 4.3. - Services can be configured via invokable configurators (replacing the ``configure()`` method with ``__invoke()``) by omitting the method name: @@ -253,10 +250,10 @@ Services can be configured via invokable configurators (replacing the // override the services to set the configurator $services->set(NewsletterManager::class) - ->configurator(ref(EmailConfigurator::class)); + ->configurator(service(EmailConfigurator::class)); $services->set(GreetingCardManager::class) - ->configurator(ref(EmailConfigurator::class)); + ->configurator(service(EmailConfigurator::class)); }; That's it! When requesting the ``App\Mail\NewsletterManager`` or diff --git a/service_container/factories.rst b/service_container/factories.rst index 9f01e7dc7a6..f67b8c35ebd 100644 --- a/service_container/factories.rst +++ b/service_container/factories.rst @@ -163,7 +163,8 @@ Configuration of the service container then looks like this: // second, use the factory service as the first argument of the 'factory' // method and the factory method as the second argument $services->set(NewsletterManager::class) - ->factory([ref(NewsletterManagerFactory::class), 'createNewsletterManager']); + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->factory([service(NewsletterManagerFactory::class), 'createNewsletterManager']); }; .. _factories-invokable: @@ -190,10 +191,6 @@ factory service can be used as a callback:: } } -.. versionadded:: 4.3 - - Invokable factories for services were introduced in Symfony 4.3. - Services can be created and configured via invokable factories by omitting the method name: @@ -240,7 +237,7 @@ method name: $services = $configurator->services(); $services->set(NewsletterManager::class) - ->factory(ref(NewsletterManagerFactory::class)); + ->factory(service(NewsletterManagerFactory::class)); }; .. _factories-passing-arguments-factory-method: @@ -300,8 +297,8 @@ previous examples takes the ``templating`` service as an argument: $services = $configurator->services(); $services->set(NewsletterManager::class) - ->factory([ref(NewsletterManagerFactory::class), 'createNewsletterManager']) - ->args([ref('templating')]) + ->factory([service(NewsletterManagerFactory::class), 'createNewsletterManager']) + ->args([service('templating')]) ; }; diff --git a/service_container/injection_types.rst b/service_container/injection_types.rst index 6b7b74d1d24..1a0c5351d02 100644 --- a/service_container/injection_types.rst +++ b/service_container/injection_types.rst @@ -78,10 +78,10 @@ service container configuration: $services = $configurator->services(); $services->set(NewsletterManager::class) - ->args(ref('mailer')); + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->args(service('mailer')); }; - .. tip:: Type hinting the injected object means that you can be sure that a suitable @@ -109,10 +109,6 @@ then extending it and overriding the constructor becomes problematic. Immutable-setter Injection -------------------------- -.. versionadded:: 4.3 - - The ``immutable-setter`` injection was introduced in Symfony 4.3. - Another possible injection is to use a method which returns a separate instance by cloning the original service, this approach allows you to make a service immutable:: @@ -285,7 +281,7 @@ that accepts the dependency:: $services = $configurator->services(); $services->set(NewsletterManager::class) - ->call('setMailer', [ref('mailer')]); + ->call('setMailer', [service('mailer')]); }; This time the advantages are: @@ -310,6 +306,8 @@ The disadvantages of setter injection are: * You cannot be sure the setter will be called and so you need to add checks that any required dependencies are injected. +.. _property-injection: + Property Injection ------------------ @@ -365,7 +363,7 @@ Another possibility is setting public fields of the class directly:: $services = $configurator->services(); $services->set('app.newsletter_manager', NewsletterManager::class) - ->property('mailer', ref('mailer')); + ->property('mailer', service('mailer')); }; There are mainly only disadvantages to using property injection, it is similar diff --git a/service_container/optional_dependencies.rst b/service_container/optional_dependencies.rst index b981877a942..e05e050ba9c 100644 --- a/service_container/optional_dependencies.rst +++ b/service_container/optional_dependencies.rst @@ -42,10 +42,10 @@ if the service does not exist: $services = $configurator->services(); $services->set(NewsletterManager::class) - ->args([ref('logger')->nullOnInvalid()]); + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->args([service('logger')->nullOnInvalid()]); }; - .. note:: The "null" strategy is not currently supported by the YAML driver. @@ -99,7 +99,7 @@ call if the service exists and remove the method call if it does not: $services = $configurator->services(); $services->set(NewsletterManager::class) - ->call('setLogger', [ref('logger')->ignoreOnInvalid()]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) ; }; diff --git a/service_container/parent_services.rst b/service_container/parent_services.rst index de0d5658b15..7df74b37a43 100644 --- a/service_container/parent_services.rst +++ b/service_container/parent_services.rst @@ -127,8 +127,9 @@ avoid duplicated service definitions: $services->set(BaseDoctrineRepository::class) ->abstract() - ->args([ref('doctrine.orm.entity_manager')]) - ->call('setLogger', [ref('logger')]) + ->args([service('doctrine.orm.entity_manager')]) + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->call('setLogger', [service('logger')]) ; $services->set(DoctrineUserRepository::class) @@ -149,12 +150,6 @@ be called when ``App\Repository\DoctrineUserRepository`` is instantiated. All attributes on the parent service are shared with the child **except** for ``shared``, ``abstract`` and ``tags``. These are *not* inherited from the parent. -.. note:: - - If you have a ``_defaults`` section in your ``services.yaml`` file, all child - services are required to explicitly override those values to avoid ambiguity. - You will see a clear error message about this. - .. tip:: In the examples shown, the classes sharing the same configuration also @@ -252,13 +247,13 @@ the child class: // appends the '@app.username_checker' argument to the parent // argument list - ->args([ref('app.username_checker')]) + ->args([service('app.username_checker')]) ; $services->set(DoctrinePostRepository::class) ->parent(BaseDoctrineRepository::class) # overrides the first argument - ->arg(0, ref('doctrine.custom_entity_manager')) + ->arg(0, service('doctrine.custom_entity_manager')) ; }; diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst index 70572c4e77a..4c7f2ed0158 100644 --- a/service_container/service_decoration.rst +++ b/service_container/service_decoration.rst @@ -58,7 +58,7 @@ Most of the time, that's exactly what you want to do. But sometimes, you might want to decorate the old one instead (i.e. apply the `Decorator pattern`_). In this case, the old service should be kept around to be able to reference it in the new one. This configuration replaces ``App\Mailer`` with a new one, -but keeps a reference of the old one as ``App\DecoratingMailer.inner``: +but keeps a reference of the old one as ``.inner``: .. configuration-block:: @@ -70,7 +70,7 @@ but keeps a reference of the old one as ``App\DecoratingMailer.inner``: App\DecoratingMailer: # overrides the App\Mailer service - # but that service is still available as App\DecoratingMailer.inner + # but that service is still available as ".inner" decorates: App\Mailer .. code-block:: xml @@ -84,6 +84,8 @@ but keeps a reference of the old one as ``App\DecoratingMailer.inner``: + @@ -106,7 +108,7 @@ but keeps a reference of the old one as ``App\DecoratingMailer.inner``: $services->set(DecoratingMailer::class) // overrides the App\Mailer service - // but that service is still available as App\DecoratingMailer.inner + // but that service is still available as ".inner" ->decorate(Mailer::class); }; @@ -119,7 +121,7 @@ decorating service has one argument type-hinted with the decorated service class If you are not using autowiring or the decorating service has more than one constructor argument type-hinted with the decorated service class, you must inject the decorated service explicitly (the ID of the decorated service is -automatically changed to ``decorating_service_id + '.inner'``): +automatically changed to ``'.inner'``): .. configuration-block:: @@ -132,7 +134,7 @@ automatically changed to ``decorating_service_id + '.inner'``): App\DecoratingMailer: decorates: App\Mailer # pass the old service as an argument - arguments: ['@App\DecoratingMailer.inner'] + arguments: ['@.inner'] .. code-block:: xml @@ -149,7 +151,7 @@ automatically changed to ``decorating_service_id + '.inner'``): decorates="App\Mailer" > - + @@ -170,9 +172,15 @@ automatically changed to ``decorating_service_id + '.inner'``): $services->set(DecoratingMailer::class) ->decorate(Mailer::class) // pass the old service as an argument - ->args([ref(DecoratingMailer::class.'.inner')]); + // In versions earlier to Symfony 5.1 the service() function was called ref() + ->args([service('.inner')]); }; +.. versionadded:: 5.1 + + The special ``.inner`` value was introduced in Symfony 5.1. In previous + versions you needed to use: ``decorating_service_id + '.inner'``. + .. tip:: The visibility of the decorated ``App\Mailer`` service (which is an alias @@ -235,7 +243,7 @@ automatically changed to ``decorating_service_id + '.inner'``): $services->set(DecoratingMailer::class) ->decorate(Mailer::class, DecoratingMailer::class.'.wooz') - ->args([ref(DecoratingMailer::class.'.wooz')]); + ->args([service(DecoratingMailer::class.'.wooz')]); }; Decoration Priority @@ -255,12 +263,12 @@ the ``decoration_priority`` option. Its value is an integer that defaults to Bar: decorates: Foo decoration_priority: 5 - arguments: ['@Bar.inner'] + arguments: ['@.inner'] Baz: decorates: Foo decoration_priority: 1 - arguments: ['@Baz.inner'] + arguments: ['@.inner'] .. code-block:: xml @@ -275,11 +283,11 @@ the ``decoration_priority`` option. Its value is an integer that defaults to - + - + @@ -296,11 +304,11 @@ the ``decoration_priority`` option. Its value is an integer that defaults to $services->set(Bar::class) ->decorate(Foo::class, null, 5) - ->args([ref(Bar::class.'.inner')]); + ->args([service('.inner')]); $services->set(Baz::class) ->decorate(Foo::class, null, 1) - ->args([ref(Baz::class.'.inner')]); + ->args([service('.inner')]); }; @@ -311,11 +319,6 @@ The generated code will be the following:: Control the Behavior When the Decorated Service Does Not Exist -------------------------------------------------------------- -.. versionadded:: 4.4 - - The ``decoration_on_invalid`` option has been introduced in Symfony 4.4. - In previous versions, a ``ServiceNotFoundException`` was always thrown. - When you decorate a service that doesn't exist, the ``decoration_on_invalid`` option allows you to choose the behavior to adopt. @@ -335,7 +338,7 @@ Three different behaviors are available: Bar: decorates: Foo decoration_on_invalid: ignore - arguments: ['@Bar.inner'] + arguments: ['@.inner'] .. code-block:: xml @@ -350,7 +353,7 @@ Three different behaviors are available: - + @@ -369,7 +372,7 @@ Three different behaviors are available: $services->set(Bar::class) ->decorate(Foo::class, null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE) - ->args([ref(Bar::class.'.inner')]) + ->args([service('.inner')]) ; }; diff --git a/service_container/service_subscribers_locators.rst b/service_container/service_subscribers_locators.rst index fc702a52f27..051a0ab592c 100644 --- a/service_container/service_subscribers_locators.rst +++ b/service_container/service_subscribers_locators.rst @@ -65,7 +65,7 @@ through a **Service Locator**, a separate lazy-loaded container. Defining a Service Subscriber ----------------------------- -First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface`. +First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Contracts\\Service\\ServiceSubscriberInterface`. Use its ``getSubscribedServices()`` method to include as many services as needed in the service subscriber and change the type hint of the container to a PSR-11 ``ContainerInterface``:: @@ -300,15 +300,6 @@ argument of type ``service_locator``: ])]); }; -.. versionadded:: 4.2 - - The ability to add services without specifying an array key was introduced - in Symfony 4.2. - -.. versionadded:: 4.2 - - The ``service_locator`` argument type was introduced in Symfony 4.2. - As shown in the previous sections, the constructor of the ``CommandBus`` class must type-hint its argument with ``ContainerInterface``. Then, you can get any of the service locator services via their ID (e.g. ``$this->locator->get('App\FooCommand')``). @@ -382,9 +373,10 @@ other services. To do so, create a new service definition using the $services = $configurator->services(); $services->set('app.command_handler_locator', ServiceLocator::class) + // In versions earlier to Symfony 5.1 the service() function was called ref() ->args([[ - 'App\FooCommand' => ref('app.command_handler.foo'), - 'App\BarCommand' => ref('app.command_handler.bar'), + 'App\FooCommand' => service('app.command_handler.foo'), + 'App\BarCommand' => service('app.command_handler.bar'), ]]) // if you are not using the default service autoconfiguration, // add the following tag to the service definition: @@ -394,16 +386,15 @@ other services. To do so, create a new service definition using the // if the element has no key, the ID of the original service is used $services->set('app.another_command_handler_locator', ServiceLocator::class) ->args([[ - ref('app.command_handler.baz'), + service('app.command_handler.baz'), ]]) ; }; -.. versionadded:: 4.1 +.. note:: - The service locator autoconfiguration was introduced in Symfony 4.1. In - previous Symfony versions you always needed to add the - ``container.service_locator`` tag explicitly. + The services defined in the service locator argument must include keys, + which later become their unique identifiers inside the locator. Now you can inject the service locator in any other services: @@ -444,7 +435,7 @@ Now you can inject the service locator in any other services: $services = $configurator->services(); $services->set(CommandBus::class) - ->args([ref('app.command_handler_locator')]); + ->args([service('app.command_handler_locator')]); }; Using Service Locators in Compiler Passes diff --git a/service_container/tags.rst b/service_container/tags.rst index 6783ef06d4e..038c2f4ef63 100644 --- a/service_container/tags.rst +++ b/service_container/tags.rst @@ -275,7 +275,8 @@ For example, you may add the following transports as services: $services = $configurator->services(); $services->set(\Swift_SmtpTransport::class) - ->args(['%mailer_host%']) + // the param() method was introduced in Symfony 5.2. + ->args([param('mailer_host')]) ->tag('app.mail_transport') ; @@ -441,7 +442,8 @@ To answer this, change the service declaration: $services = $configurator->services(); $services->set(\Swift_SmtpTransport::class) - ->args(['%mailer_host%']) + // the param() method was introduced in Symfony 5.2. + ->args([param('mailer_host')]) ->tag('app.mail_transport', ['alias' => 'smtp']) ; @@ -596,10 +598,6 @@ application handlers:: Tagged Services with Priority ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.4 - - The ability to prioritize tagged services was introduced in Symfony 4.4. - The tagged services can be prioritized using the ``priority`` attribute. The priority is a positive or negative integer. The higher the number, the earlier the tagged service will be located in the collection: diff --git a/session.rst b/session.rst index 8d465516a58..da265d739b9 100644 --- a/session.rst +++ b/session.rst @@ -127,25 +127,26 @@ Check out the Symfony config reference to learn more about the other available Basic Usage ----------- -Symfony provides a session service that is injected in your services and +The sessions is available througth the Request and the RequestStack. +Symfony provides a request_stack service that is injected in your services and controllers if you type-hint an argument with -:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`:: +:class:`Symfony\\Component\\HttpFoundation\\RequestStack`:: - use Symfony\Component\HttpFoundation\Session\SessionInterface; + use Symfony\Component\HttpFoundation\RequestStack; class SomeService { - private $session; + private $requestStack; - public function __construct(SessionInterface $session) + public function __construct(RequestStack $requestStack) { - $this->session = $session; + $this->requestStack = $requestStack; } public function someMethod() { // stores an attribute in the session for later reuse - $this->session->set('attribute-name', 'attribute-value'); + $this->requestStack->getSession()->set('attribute-name', 'attribute-value'); // gets an attribute by name $foo = $this->session->get('foo'); @@ -157,35 +158,63 @@ controllers if you type-hint an argument with } } -.. tip:: +.. deprecated:: 5.3 - Every ``SessionInterface`` implementation is supported. If you have your - own implementation, type-hint this in the argument instead. + The ``SessionInterface`` and ``session`` service were deprecated in + Symfony 5.3. Instead, inject the ``RequestStack`` service to get the session + object of the current request. Stored attributes remain in the session for the remainder of that user's session. By default, session attributes are key-value pairs managed with the :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag` class. +.. deprecated:: 5.3 + + The ``NamespacedAttributeBag`` class is deprecated since Symfony 5.3. + If you need this feature, you will have to implement the class yourself. + If your application needs are complex, you may prefer to use :ref:`namespaced session attributes ` which are managed with the :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag` -class. Before using them, override the ``session`` service definition to replace -the default ``AttributeBag`` by the ``NamespacedAttributeBag``: +class. Before using them, override the ``session_listener`` service definition to build +your ``Session`` object with the default ``AttributeBag`` by the ``NamespacedAttributeBag``: .. configuration-block:: .. code-block:: yaml # config/services.yaml - session: - public: true - class: Symfony\Component\HttpFoundation\Session\Session - arguments: ['@session.storage', '@session.namespacedattributebag', '@session.flash_bag'] + session_listener: + autoconfigure: true + class: App\EventListener\SessionListener + arguments: + - !service_locator + logger: '@?logger' + session_collector: '@?data_collector.request.session_collector' + session_storage: '@session.storage' + session_attributes: '@session.namespacedattributebag' + - '%kernel.debug%' session.namespacedattributebag: class: Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag +.. code-block:: php + + namespace App\EventListener; + + use Symfony\Component\HttpFoundation\Session\Session; + use Symfony\Component\HttpFoundation\Session\SessionInterface; + use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener; + + class SessionListener extends AbstractSessionListener + { + protected function getSession(): ?SessionInterface + { + return new Session($this->container->get('session_storage'), $this->container->get('session_attributes')); + } + } + .. _session-avoid-start: Avoid Starting Sessions for Anonymous Users diff --git a/session/database.rst b/session/database.rst index a0fb1b3d4a6..01c663911e1 100644 --- a/session/database.rst +++ b/session/database.rst @@ -217,16 +217,22 @@ first register a new handler service with your database credentials: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; - $storageDefinition = $container->autowire(PdoSessionHandler::class) - ->setArguments([ - '%env(DATABASE_URL)%', - // you can also use PDO configuration, but requires passing two arguments: - // 'mysql:dbname=mydatabase; host=myhost; port=myport', - // ['db_username' => 'myuser', 'db_password' => 'mypassword'], - ]) - ; + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(PdoSessionHandler::class) + ->args([ + '%env(DATABASE_URL)%', + // you can also use PDO configuration, but requires passing two arguments: + // 'mysql:dbname=mydatabase; host=myhost; port=myport', + // ['db_username' => 'myuser', 'db_password' => 'mypassword'], + ]) + ; + }; Next, use the :ref:`handler_id ` configuration option to tell Symfony to use this service as the session handler: @@ -306,15 +312,20 @@ passed to the ``PdoSessionHandler`` service: .. code-block:: php // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; - // ... - $container->autowire(PdoSessionHandler::class) - ->setArguments([ - '%env(DATABASE_URL)%', - ['db_table' => 'customer_session', 'db_id_col' => 'guid'], - ]) - ; + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(PdoSessionHandler::class) + ->args([ + '%env(DATABASE_URL)%', + ['db_table' => 'customer_session', 'db_id_col' => 'guid'], + ]) + ; + }; These are parameters that you can configure: @@ -468,13 +479,19 @@ the MongoDB connection as argument: .. code-block:: php // config/services.php - use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; + namespace Symfony\Component\DependencyInjection\Loader\Configurator; - $storageDefinition = $container->autowire(MongoDbSessionHandler::class) - ->setArguments([ - new Reference('doctrine_mongodb.odm.default_connection'), - ]) - ; + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(MongoDbSessionHandler::class) + ->args([ + service('doctrine_mongodb.odm.default_connection'), + ]) + ; + }; Next, use the :ref:`handler_id ` configuration option to tell Symfony to use this service as the session handler: @@ -571,15 +588,20 @@ configure these values with the second argument passed to the .. code-block:: php // config/services.php - use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; - // ... + namespace Symfony\Component\DependencyInjection\Loader\Configurator; - $container->autowire(MongoDbSessionHandler::class) - ->setArguments([ - '...', - ['id_field' => '_guid', 'expiry_field' => 'eol'], - ]) - ; + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + return static function (ContainerConfigurator $container) { + $services = $configurator->services(); + + $services->set(MongoDbSessionHandler::class) + ->args([ + service('doctrine_mongodb.odm.default_connection'), + ['id_field' => '_guid', 'expiry_field' => 'eol'],, + ]) + ; + }; These are parameters that you can configure: diff --git a/session/locale_sticky_session.rst b/session/locale_sticky_session.rst index f8caef23370..13d620381aa 100644 --- a/session/locale_sticky_session.rst +++ b/session/locale_sticky_session.rst @@ -28,7 +28,7 @@ correct locale however you want:: { private $defaultLocale; - public function __construct($defaultLocale = 'en') + public function __construct(string $defaultLocale = 'en') { $this->defaultLocale = $defaultLocale; } @@ -146,7 +146,7 @@ event:: namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Symfony\Component\HttpFoundation\Session\SessionInterface; + use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; @@ -156,11 +156,11 @@ event:: */ class UserLocaleSubscriber implements EventSubscriberInterface { - private $session; + private $requestStack; - public function __construct(SessionInterface $session) + public function __construct(RequestStack $requestStack) { - $this->session = $session; + $this->requestStack = $requestStack; } public function onInteractiveLogin(InteractiveLoginEvent $event) @@ -168,7 +168,7 @@ event:: $user = $event->getAuthenticationToken()->getUser(); if (null !== $user->getLocale()) { - $this->session->set('_locale', $user->getLocale()); + $this->requestStack->getSession()->set('_locale', $user->getLocale()); } } diff --git a/setup.rst b/setup.rst index 68200d31020..bca61758e65 100644 --- a/setup.rst +++ b/setup.rst @@ -17,7 +17,7 @@ Technical Requirements Before creating your first Symfony application you must: -* Install PHP 7.1 or higher and these PHP extensions (which are installed and +* Install PHP 7.2.5 or higher and these PHP extensions (which are installed and enabled by default in most PHP 7 installations): `Ctype`_, `iconv`_, `JSON`_, `PCRE`_, `Session`_, `SimpleXML`_, and `Tokenizer`_; * `Install Composer`_, which is used to install PHP packages. @@ -50,10 +50,10 @@ application: .. code-block:: terminal # run this if you are building a traditional web application - $ symfony new my_project_name --version=4.4 --full + $ symfony new my_project_name --version=next --full # run this if you are building a microservice, console application or API - $ symfony new my_project_name --version=4.4 + $ symfony new my_project_name --version=next The only difference between these two commands is the number of packages installed by default. The ``--full`` option installs all the packages that you @@ -65,10 +65,10 @@ Symfony application using Composer: .. code-block:: terminal # run this if you are building a traditional web application - $ composer create-project symfony/website-skeleton:"^4.4" my_project_name + $ composer create-project symfony/website-skeleton:"5.3.x@dev" my_project_name # run this if you are building a microservice, console application or API - $ composer create-project symfony/skeleton:"^4.4" my_project_name + $ composer create-project symfony/skeleton:"5.3.x@dev" my_project_name No matter which command you run to create the Symfony application. All of them will create a new ``my_project_name/`` directory, download some dependencies diff --git a/setup/built_in_web_server.rst b/setup/built_in_web_server.rst deleted file mode 100644 index 70432ed342e..00000000000 --- a/setup/built_in_web_server.rst +++ /dev/null @@ -1,127 +0,0 @@ -.. index:: - single: Web Server; Built-in Web Server - -How to Use PHP's built-in Web Server -==================================== - -.. 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. - - 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 -demonstrations. This way, you don't have to bother configuring a full-featured -web server such as :doc:`Apache or nginx `. - -.. caution:: - - The built-in web server is meant to be run in a controlled environment. - It is not designed to be used on public networks. - -Symfony provides a web server built on top of this PHP server to simplify your -local setup. This server is distributed as a bundle, so you must first install -and enable the server bundle. - -Installing the Web Server Bundle --------------------------------- - -Move into your project directory and run this command: - -.. code-block:: terminal - - $ cd your-project/ - $ composer require --dev symfony/web-server-bundle - -Starting the Web Server ------------------------ - -To run a Symfony application using PHP's built-in web server, run the -``server:start`` command: - -.. code-block:: terminal - - $ php bin/console server:start - -This starts the web server at ``localhost:8000`` in the background that serves -your Symfony application. - -By default, the web server listens on port 8000 on the loopback device. You -can change the socket passing an IP address and a port as a command-line argument: - -.. code-block:: terminal - - # passing a specific IP and port - $ php bin/console server:start 192.168.0.1:8080 - - # passing '*' as the IP means to use 0.0.0.0 (i.e. any local IP address) - $ php bin/console server:start *:8080 - -.. note:: - - You can use the ``server:status`` command to check if a web server is - listening: - - .. code-block:: terminal - - $ php bin/console server:status - -.. tip:: - - Some systems do not support the ``server:start`` command, in these cases - you can execute the ``server:run`` command. This command behaves slightly - different. Instead of starting the server in the background, it will block - the current terminal until you terminate it (this is usually done by - pressing Ctrl and C). - -.. sidebar:: Using the built-in Web Server from inside a Virtual Machine - - If you want to use the built-in web server from inside a virtual machine - and then load the site from a browser on your host machine, you'll need - to listen on the ``0.0.0.0:8000`` address (i.e. on all IP addresses that - are assigned to the virtual machine): - - .. code-block:: terminal - - $ php bin/console server:start 0.0.0.0:8000 - - .. caution:: - - You should **NEVER** listen to all interfaces on a computer that is - directly accessible from the Internet. The built-in web server is - not designed to be used on public networks. - -Command Options -~~~~~~~~~~~~~~~ - -The built-in web server expects a "router" script (read about the "router" -script on `php.net`_) as an argument. Symfony already passes such a router -script when the command is executed in the ``prod`` or ``dev`` environment. -Use the ``--router`` option to use your own router script: - -.. code-block:: terminal - - $ php bin/console server:start --router=config/my_router.php - -If your application's document root differs from the standard directory layout, -you have to pass the correct location using the ``--docroot`` option: - -.. code-block:: terminal - - $ php bin/console server:start --docroot=public_html - -Stopping the Server -------------------- - -When you finish your work, you can stop the web server with the following command: - -.. code-block:: terminal - - $ php bin/console server:stop - -.. _`built-in web server`: https://www.php.net/manual/en/features.commandline.webserver.php -.. _`php.net`: https://www.php.net/manual/en/features.commandline.webserver.php#example-415 diff --git a/setup/flex.rst b/setup/flex.rst index eaba102d073..52708275077 100644 --- a/setup/flex.rst +++ b/setup/flex.rst @@ -193,9 +193,9 @@ If you customize these paths, some files copied from a recipe still may contain references to the original path. In other words: you may need to update some things manually after a recipe is installed. -.. _`default services.yaml file`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/config/services.yaml +.. _`default services.yaml file`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/config/services.yaml .. _`shown in this example`: https://github.com/symfony/skeleton/blob/8e33fe617629f283a12bbe0a6578bd6e6af417af/composer.json#L24-L33 .. _`shown in this example of the skeleton-project`: https://github.com/symfony/skeleton/blob/8e33fe617629f283a12bbe0a6578bd6e6af417af/composer.json#L44-L46 -.. _`copying Symfony's index.php source`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.4/public/index.php -.. _`copying Symfony's bin/console source`: https://github.com/symfony/recipes/blob/master/symfony/console/3.4/bin/console +.. _`copying Symfony's index.php source`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/5.1/public/index.php +.. _`copying Symfony's bin/console source`: https://github.com/symfony/recipes/blob/master/symfony/console/5.1/bin/console .. _`Symfony Requirements Checker`: https://github.com/symfony/requirements-checker diff --git a/setup/upgrade_patch.rst b/setup/upgrade_patch.rst index de532e02d36..632f6602550 100644 --- a/setup/upgrade_patch.rst +++ b/setup/upgrade_patch.rst @@ -1,7 +1,7 @@ .. index:: single: Upgrading; Patch Version -Upgrading a Patch Version (e.g. 4.1.0 to 4.1.1) +Upgrading a Patch Version (e.g. 5.0.0 to 5.0.1) =============================================== When a new patch version is released (only the last number changed), it is a diff --git a/templates.rst b/templates.rst index 09314868c2d..9f1f5233b34 100644 --- a/templates.rst +++ b/templates.rst @@ -496,6 +496,11 @@ provided by Symfony: # whether or not caching should apply for client caches only private: true + # optionally you can define some arguments passed to the template + context: + site_name: 'ACME' + theme: 'dark' + .. code-block:: xml @@ -513,8 +518,15 @@ provided by Symfony: 86400 86400 - + + true + + + + ACME + dark + @@ -537,22 +549,30 @@ provided by Symfony: // whether or not caching should apply for client caches only 'private' => true, + + // optionally you can define some arguments passed to the template + 'context' => [ + 'site_name' => 'ACME', + 'theme' => 'dark', + ] ]) ; }; +.. versionadded:: 5.1 + + The ``context`` option was introduced in Symfony 5.1. + Checking if a Template Exists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Templates are loaded in the application using a `Twig template loader`_, which also provides a method to check for template existence. First, get the loader:: - // in a controller extending from AbstractController - $loader = $this->get('twig')->getLoader(); - - // in a service using autowiring use Twig\Environment; + // this code assumes that your service uses autowiring to inject dependencies + // otherwise, inject the service called 'twig' manually public function __construct(Environment $twig) { $loader = $twig->getLoader(); @@ -589,12 +609,6 @@ errors. It's useful to run it before deploying your application to production # 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1043,12 +1057,6 @@ When rendering a template, Symfony looks for it first in the ``twig.paths`` directories that don't define a namespace and then falls back to the default template directory (usually, ``templates/``). -.. deprecated:: 4.2 - - Symfony looks for templates in the ``src/Resources/views/`` too before - falling back to the default directory. But that behavior is deprecated since - Symfony 4.2 and will be removed in Symfony 5.0. - Using the above configuration, if your application renders for example the ``layout.html.twig`` template, Symfony will first look for ``email/default/templates/layout.html.twig`` and ``backend/templates/layout.html.twig``. diff --git a/templating/PHP.rst b/templating/PHP.rst index cafd622dd8d..9928984f8d8 100644 --- a/templating/PHP.rst +++ b/templating/PHP.rst @@ -4,571 +4,7 @@ How to Use PHP instead of Twig for Templates ============================================ -.. deprecated:: 4.3 - - PHP templates have been deprecated in Symfony 4.3 and they will no longer be - supported in Symfony 5.0. Use :ref:`Twig templates ` instead. - -Symfony defaults to Twig for its template engine, but you can still use -plain PHP code if you want. Both templating engines are supported equally in -Symfony. Symfony adds some nice features on top of PHP to make writing -templates with PHP more powerful. - -.. tip:: - - If you choose *not* use Twig and you disable it, you'll need to implement - your own exception handler via the ``kernel.exception`` event. - -Rendering PHP Templates ------------------------ - -If you want to use the PHP templating engine, first install the templating component: - -.. code-block:: terminal - - $ composer require symfony/templating - -.. deprecated:: 4.3 - - The integration of the Templating component in FrameworkBundle has been - deprecated since version 4.3 and will be removed in 5.0. - -Next, enable the PHP engine: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/framework.yaml - framework: - # ... - templating: - engines: ['twig', 'php'] - - .. code-block:: xml - - - - - - - - - - - - - - - .. code-block:: php - - // config/packages/framework.php - $container->loadFromExtension('framework', [ - // ... - 'templating' => [ - 'engines' => ['twig', 'php'], - ], - ]); - -You can now render a PHP template instead of a Twig one by using the ``.php`` -extension in the template name instead of ``.twig``. The controller below -renders the ``index.html.php`` template:: - - // src/Controller/HelloController.php - - // ... - public function index($name) - { - // template is stored in src/Resources/views/hello/index.html.php - return $this->render('hello/index.html.php', [ - 'name' => $name - ]); - } - .. caution:: - Enabling the ``php`` and ``twig`` template engines simultaneously is - allowed, but it will produce an undesirable side effect in your application: - the ``@`` notation for Twig namespaces will no longer be supported for the - ``render()`` method:: - - public function index() - { - // ... - - // namespaced templates will no longer work in controllers - $this->render('@SomeNamespace/hello/index.html.twig'); - - // you must use the traditional template notation - $this->render('hello/index.html.twig'); - } - - .. code-block:: twig - - {# inside a Twig template, namespaced templates work as expected #} - {{ include('@SomeNamespace/hello/index.html.twig') }} - - {# traditional template notation will also work #} - {{ include('hello/index.html.twig') }} - -.. index:: - single: Templating; Layout - single: Layout - -Decorating Templates --------------------- - -More often than not, templates in a project share common elements, like the -well-known header and footer. In Symfony, this problem is thought about -differently: a template can be decorated by another one. - -The ``index.html.php`` template is decorated by ``layout.html.php``, thanks to -the ``extend()`` call: - -.. code-block:: html+php - - - extend('layout.html.php') ?> - - Hello ! - -Now, have a look at the ``layout.html.php`` file: - -.. code-block:: html+php - - - extend('base.html.php') ?> - -

Hello Application

- - output('_content') ?> - -The layout is itself decorated by another one (``base.html.php``). Symfony -supports multiple decoration levels: a layout can itself be decorated by -another one: - -.. code-block:: html+php - - - - - - - <?php $view['slots']->output('title', 'Hello Application') ?> - - - output('_content') ?> - - - -For both layouts, the ``$view['slots']->output('_content')`` expression is -replaced by the content of the child template, ``index.html.php`` and -``layout.html.php`` respectively (more on slots in the next section). - -As you can see, Symfony provides methods on a mysterious ``$view`` object. In -a template, the ``$view`` variable is always available and refers to a special -object that provides a bunch of methods that makes the template engine tick. - -.. index:: - single: Templating; Slot - single: Slot - -Working with Slots ------------------- - -A slot is a snippet of code, defined in a template, and reusable in any layout -decorating the template. In the ``index.html.php`` template, define a -``title`` slot: - -.. code-block:: html+php - - - extend('layout.html.php') ?> - - set('title', 'Hello World Application') ?> - - Hello ! - -The base layout already has the code to output the title in the header: - -.. code-block:: html+php - - - - - <?php $view['slots']->output('title', 'Hello Application') ?> - - -The ``output()`` method inserts the content of a slot and optionally takes a -default value if the slot is not defined. And ``_content`` is a special -slot that contains the rendered child template. - -For large slots, there is also an extended syntax: - -.. code-block:: html+php - - start('title') ?> - Some large amount of HTML - stop() ?> - -.. index:: - single: Templating; Include - -Including other Templates -------------------------- - -The best way to share a snippet of template code is to define a template that -can then be included into other templates. - -Create a ``hello.html.php`` template: - -.. code-block:: html+php - - - Hello ! - -And change the ``index.html.php`` template to include it: - -.. code-block:: html+php - - - extend('layout.html.php') ?> - - render('hello/hello.html.php', ['name' => $name]) ?> - -The ``render()`` method evaluates and returns the content of another template -(this is the exact same method as the one used in the controller). - -.. index:: - single: Templating; Embedding pages - -Embedding other Controllers ---------------------------- - -And what if you want to embed the result of another controller in a template? -That's very useful when working with Ajax, or when the embedded template needs -some variable not available in the main template. - -If you create a ``fancy`` action, and want to include it into the -``index.html.php`` template, use the following code: - -.. code-block:: html+php - - - render( - new \Symfony\Component\HttpKernel\Controller\ControllerReference( - 'App\Controller\HelloController::fancy', - [ - 'name' => $name, - 'color' => 'green', - ] - ) - ) ?> - -But where is the ``$view['actions']`` array element defined? Like -``$view['slots']``, it's called a template helper, and the next section tells -you more about those. - -.. index:: - single: Templating; Helpers - -Using Template Helpers ----------------------- - -The Symfony templating system can be extended via helpers. Helpers are -PHP objects that provide features useful in a template context. ``actions`` and -``slots`` are two of the built-in Symfony helpers. - -Creating Links between Pages -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Speaking of web applications, creating links between pages is a must. Instead -of hardcoding URLs in templates, the ``router`` helper knows how to generate -URLs based on the routing configuration. That way, all your URLs can be -updated by changing the configuration: - -.. code-block:: html+php - - - Greet Thomas! - - -The ``path()`` method takes the route name and an array of parameters as -arguments. The route name is the main key under which routes are referenced -and the parameters are the values of the placeholders defined in the route -pattern: - -.. code-block:: yaml - - # config/routes.yaml - hello: - path: /hello/{name} - controller: App\Controller\HelloController::index - -Using Assets: Images, JavaScripts and Stylesheets -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -What would the Internet be without images, JavaScripts, and stylesheets? -Symfony provides the ``assets`` tag to deal with them: - -.. code-block:: html+php - - - - - -The ``assets`` helper's main purpose is to make your application more -portable. Thanks to this helper, you can move the application root directory -anywhere under your web root directory without changing anything in your -template's code. - -Profiling Templates -~~~~~~~~~~~~~~~~~~~ - -By using the ``stopwatch`` helper, you are able to time parts of your template -and display it on the timeline of the WebProfilerBundle:: - - start('foo') ?> - ... things that get timed - stop('foo') ?> - -.. tip:: - - If you use the same name more than once in your template, the times are - grouped on the same line in the timeline. - -Output Escaping ---------------- - -When using PHP templates, escape variables whenever they are displayed to the -user:: - - escape($var) ?> - -By default, the ``escape()`` method assumes that the variable is outputted -within an HTML context. The second argument lets you change the context. For -instance, to output something in a JavaScript script, use the ``js`` context:: - - escape($var, 'js') ?> - -Form Theming in PHP -------------------- - -When using PHP as a templating engine, the only method to customize a fragment -is to create a new template file - this is similar to the second method used by -Twig. - -The template file must be named after the fragment. You must create a ``integer_widget.html.php`` -file in order to customize the ``integer_widget`` fragment. - -.. code-block:: html+php - - -
- block( - $form, - 'form_widget_simple', - ['type' => isset($type) ? $type : "number"] - ) ?> -
- -Now that you've created the customized form template, you need to tell Symfony -to use it. Inside the template where you're actually rendering your form, -tell Symfony to use the theme via the ``setTheme()`` helper method:: - - setTheme($form, [':form']) ?> - - widget($form['age']) ?> - -When the ``form.age`` widget is rendered, Symfony will use the customized -``integer_widget.html.php`` template and the ``input`` tag will be wrapped in -the ``div`` element. - -If you want to apply a theme to a specific child form, pass it to the ``setTheme()`` -method:: - - setTheme($form['child'], ':form') ?> - -.. note:: - - The ``:form`` syntax is based on the functional names for templates: - ``Bundle:Directory``. As the form directory lives in the - ``templates/`` directory, the ``Bundle`` part is empty, resulting - in ``:form``. - -Making Application-wide Customizations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you'd like a certain form customization to be global to your application, -you can accomplish this by making the form customizations in an external -template and then importing it inside your application configuration. - -By using the following configuration, any customized form fragments inside the -``templates/form`` folder will be used globally when a -form is rendered. - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/framework.yaml - framework: - templating: - form: - resources: - - 'App:Form' - # ... - - .. code-block:: xml - - - - - - - - - App:Form - - - - - - - .. code-block:: php - - // config/packages/framework.php - // PHP - $container->loadFromExtension('framework', [ - 'templating' => [ - 'form' => [ - 'resources' => [ - 'App:Form', - ], - ], - ], - - // ... - ]); - -By default, the PHP engine uses a *div* layout when rendering forms. Some people, -however, may prefer to render forms in a *table* layout. Use the ``FrameworkBundle:FormTable`` -resource to use such a layout: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/framework.yaml - framework: - templating: - form: - resources: - - 'FrameworkBundle:FormTable' - - .. code-block:: xml - - - - - - - - - FrameworkBundle:FormTable - - - - - - - .. code-block:: php - - // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'templating' => [ - 'form' => [ - 'resources' => [ - 'FrameworkBundle:FormTable', - ], - ], - ], - - // ... - ]); - -If you only want to make the change in one template, add the following line to -your template file rather than adding the template as a resource: - -.. code-block:: html+php - - setTheme($form, ['FrameworkBundle:FormTable']) ?> - -Note that the ``$form`` variable in the above code is the form view variable -that you passed to your template. - -Adding a "Required" Asterisk to Field Labels -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you want to denote all of your required fields with a required asterisk -(``*``), you can do this by customizing the ``form_label`` fragment. - -When using PHP as a templating engine you have to copy the content from the -original template: - -.. code-block:: html+php - - - - - - - humanize($name); } ?> - - - - - * - - -Adding "help" Messages -~~~~~~~~~~~~~~~~~~~~~~ - -You can also customize your form widgets to have an optional "help" message. - -When using PHP as a templating engine you have to copy the content from the -original template: - -.. code-block:: html+php - - - - - value="escape($value) ?>" - block($form, 'widget_attributes') ?> - /> - - - - escape($help) ?> - + Starting from Symfony 5.0, PHP templates are no longer supported. Use + :doc:`Twig ` instead to create your templates. diff --git a/templating/hinclude.rst b/templating/hinclude.rst index d832cdbdba2..eed8f09b5e7 100644 --- a/templating/hinclude.rst +++ b/templating/hinclude.rst @@ -67,12 +67,6 @@ default content rendering some template: ], ]); -.. versionadded:: 4.3 - - The ``framework.fragments.hinclude_default_template`` option was introduced - in Symfony 4.3. In previous Symfony versions it was called - ``framework.templating.hinclude_default_template``. - You can define default templates per ``render()`` function (which will override any global default template that is defined): diff --git a/testing.rst b/testing.rst index 130ed0ab2ae..463a6129a8d 100644 --- a/testing.rst +++ b/testing.rst @@ -221,10 +221,6 @@ the page and check for its existence, attributes, text, etc. The ``assertSelectorTextContains`` method is not a native PHPUnit assertion and is available thanks to the ``WebTestCase`` class. -.. versionadded:: 4.3 - - The ``WebTestCase`` assertions were introduced in Symfony 4.3 - The crawler can also be used to interact with the page. Click on a link by first selecting it with the crawler using either an XPath expression or a CSS selector, then use the client to click on it:: @@ -267,7 +263,7 @@ Or test against the response content directly if you just want to assert that the content contains some text or in case that the response is not an XML/HTML document:: - $this->assertContains( + $this->assertStringContainsString( 'Hello World', $client->getResponse()->getContent() ); @@ -310,7 +306,7 @@ document:: )); // asserts that the response content contains a string - $this->assertContains('foo', $client->getResponse()->getContent()); + $this->assertStringContainsString('foo', $client->getResponse()->getContent()); // ...or matches a regex $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent()); @@ -336,12 +332,6 @@ document:: // ...or check that the response is a redirect to any URL $this->assertResponseRedirects(); -.. versionadded:: 4.3 - - The ``assertResponseHeaderSame()``, ``assertResponseIsSuccessful()``, - ``assertResponseStatusCodeSame()``, ``assertResponseRedirects()`` and other - related methods were introduced in Symfony 4.3. - .. _testing-data-providers: Testing against Different Sets of Data @@ -404,13 +394,13 @@ returns a ``Crawler`` instance. The full signature of the ``request()`` method is:: request( - $method, - $uri, + string $method, + string $uri, array $parameters = [], array $files = [], array $server = [], - $content = null, - $changeHistory = true + string $content = null, + bool $changeHistory = true ) The ``server`` array is the raw values that you'd expect to normally @@ -602,6 +592,64 @@ container is a service that can be get via the normal container:: If the information you need to check is available from the profiler, use it instead. +.. _testing_logging_in_users: + +Logging in Users (Authentication) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.1 + + The ``loginUser()`` method was introduced in Symfony 5.1. + +When you want to add functional tests for protected pages, you have to +first "login" as a user. Reproducing the actual steps - such as +submitting a login form - make a test very slow. For this reason, Symfony +provides a ``loginUser()`` method to simulate logging in in your functional +tests. + +Instead of login in with real users, it's recommended to create a user only for +tests. You can do that with Doctrine :ref:`data fixtures `, +to load the testing users only in the test database. + +After loading users in your database, use your user repository to fetch +this user and use +:method:`$client->loginUser() ` +to simulate a login request:: + + // tests/Controller/ProfileControllerTest.php + namespace App\Tests\Controller; + + use App\Repository\UserRepository; + use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + + class ProfileControllerTest extends WebTestCase + { + // ... + + public function testVisitingWhileLoggedIn() + { + $client = static::createClient(); + $userRepository = static::$container->get(UserRepository::class); + + // retrieve the test user + $testUser = $userRepository->findOneByEmail('john.doe@example.com'); + + // simulate $testUser being logged in + $client->loginUser($testUser); + + // test e.g. the profile page + $client->request('GET', '/profile'); + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('h1', 'Hello John!'); + } + } + +You can pass any +:class:`Symfony\\Component\\Security\\Core\\User\\UserInterface` instance to +``loginUser()``. This method creates a special +:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\TestBrowserToken` object and +stores in the session of the test client. + Accessing the Profiler Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -672,7 +720,7 @@ selects the last one on the page, and then selects its immediate parent element: $newCrawler = $crawler->filter('input[type=submit]') ->last() - ->parents() + ->ancestors() ->first() ; @@ -694,8 +742,8 @@ Many other methods are also available: All following siblings. ``previousAll()`` All preceding siblings. -``parents()`` - Returns the parent nodes. +``ancestors()`` + Returns the ancestor nodes. ``children()`` Returns children nodes. ``reduce($lambda)`` @@ -724,6 +772,8 @@ Extracting Information The Crawler can extract information from the nodes:: + use Symfony\Component\DomCrawler\Crawler; + // returns the attribute value for the first node $crawler->attr('class'); @@ -744,14 +794,10 @@ The Crawler can extract information from the nodes:: $info = $crawler->extract(['_text', 'href']); // executes a lambda for each node and return an array of results - $data = $crawler->each(function ($node, $i) { + $data = $crawler->each(function (Crawler $node, $i) { return $node->attr('href'); }); -.. versionadded:: 4.4 - - The option to trim white spaces in ``text()`` was introduced in Symfony 4.4. - Links ~~~~~ @@ -857,10 +903,6 @@ their type:: :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/bootstrap.rst b/testing/bootstrap.rst index cf1b63621a2..bdd7448a519 100644 --- a/testing/bootstrap.rst +++ b/testing/bootstrap.rst @@ -6,21 +6,21 @@ running those tests. For example, if you're running a functional test and have introduced a new translation resource, then you will need to clear your cache before running those tests. -To do this, first add a file that executes your bootstrap work:: +Symfony already created the following ``tests/bootstrap.php`` file when installing +the package to work with tests. If you don't have this file, create it:: // tests/bootstrap.php - if (isset($_ENV['BOOTSTRAP_CLEAR_CACHE_ENV'])) { - // executes the "php bin/console cache:clear" command - passthru(sprintf( - 'APP_ENV=%s php "%s/../bin/console" cache:clear --no-warmup', - $_ENV['BOOTSTRAP_CLEAR_CACHE_ENV'], - __DIR__ - )); - } + use Symfony\Component\Dotenv\Dotenv; + + require dirname(__DIR__).'/vendor/autoload.php'; - require __DIR__.'/../config/bootstrap.php'; + if (file_exists(dirname(__DIR__).'/config/bootstrap.php')) { + require dirname(__DIR__).'/config/bootstrap.php'; + } elseif (method_exists(Dotenv::class, 'bootEnv')) { + (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); + } -Then, configure ``phpunit.xml.dist`` to execute this ``bootstrap.php`` file +Then, check that your ``phpunit.xml.dist`` file runs this ``bootstrap.php`` file before running the tests: .. code-block:: xml diff --git a/testing/database.rst b/testing/database.rst index 7e3acfd1db7..06193237cdd 100644 --- a/testing/database.rst +++ b/testing/database.rst @@ -221,7 +221,7 @@ so, get the entity manager via the service container as follows:: */ private $entityManager; - protected function setUp() + protected function setUp(): void { $kernel = self::bootKernel(); @@ -240,7 +240,7 @@ so, get the entity manager via the service container as follows:: $this->assertSame(14.50, $product->getPrice()); } - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); diff --git a/testing/functional_tests_assertions.rst b/testing/functional_tests_assertions.rst index edf1a6bb679..56f6a1d4c8c 100644 --- a/testing/functional_tests_assertions.rst +++ b/testing/functional_tests_assertions.rst @@ -4,11 +4,6 @@ Functional Test specific Assertions =================================== -.. versionadded:: 4.3 - - The shortcut methods for assertions using ``WebTestCase`` were introduced - in Symfony 4.3. - When doing functional tests, sometimes you need to make complex assertions in order to check whether the ``Request``, the ``Response`` or the ``Crawler`` contain the expected information to make your test succeed. @@ -23,23 +18,17 @@ This is the same example using the assertions provided by Symfony:: $this->assertResponseRedirects('https://example.com', 301); -.. note:: - - These assertions only work if a request has been made with the ``Client`` - in a test case extending the ``WebTestCase`` class. - 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 ~~~~~~~~ +.. note:: + + The following assertions only work if a request has been made with the + ``Client`` in a test case extending the ``WebTestCase`` class. + - ``assertResponseIsSuccessful()`` - ``assertResponseStatusCodeSame()`` - ``assertResponseRedirects()`` @@ -50,16 +39,32 @@ Response - ``assertResponseHasCookie()`` - ``assertResponseNotHasCookie()`` - ``assertResponseCookieValueSame()`` +- ``assertResponseFormatSame()`` (the response format is the value returned by + the :method:`Symfony\\Component\\HttpFoundation\\Response::getFormat` method). + +.. versionadded:: 5.3 + + The ``assertResponseFormatSame()`` method was introduced in Symfony 5.3. Request ~~~~~~~ +.. note:: + + The following assertions only work if a request has been made with the + ``Client`` in a test case extending the ``WebTestCase`` class. + - ``assertRequestAttributeValueSame()`` - ``assertRouteSame()`` Browser ~~~~~~~ +.. note:: + + The following assertions only work if a request has been made with the + ``Client`` in a test case extending the ``WebTestCase`` class. + - ``assertBrowserHasCookie()`` - ``assertBrowserNotHasCookie()`` - ``assertBrowserCookieValueSame()`` @@ -67,6 +72,12 @@ Browser Crawler ~~~~~~~ +.. note:: + + The following assertions only work if a request has been made with the + ``Client`` in a test case extending the ``WebTestCase`` class. In addition, + they are not available when using `symfony/panther`_ for end-to-end testing. + - ``assertSelectorExists()`` - ``assertSelectorNotExists()`` - ``assertSelectorTextContains()`` (note: it only checks the first selector occurrence) @@ -76,10 +87,25 @@ Crawler - ``assertPageTitleContains()`` - ``assertInputValueSame()`` - ``assertInputValueNotSame()`` +- ``assertCheckboxChecked()`` +- ``assertCheckboxNotChecked()`` +- ``assertFormValue()`` +- ``assertNoFormValue()`` + +.. versionadded:: 5.2 + + The ``assertCheckboxChecked()``, ``assertCheckboxNotChecked()``, + ``assertFormValue()`` and ``assertNoFormValue()`` methods were introduced + in Symfony 5.2. Mailer ~~~~~~ +.. versionadded:: 5.1 + + Starting from Symfony 5.1, the following assertions no longer require to make + a request with the ``Client`` in a test case extending the ``WebTestCase`` class. + - ``assertEmailCount()`` - ``assertQueuedEmailCount()`` - ``assertEmailIsQueued()`` @@ -95,8 +121,4 @@ Mailer - ``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/testing/http_authentication.rst b/testing/http_authentication.rst index 336f6f12b39..a55ae639e0b 100644 --- a/testing/http_authentication.rst +++ b/testing/http_authentication.rst @@ -4,194 +4,14 @@ How to Simulate HTTP Authentication in a Functional Test ======================================================== -Authenticating requests in functional tests can slow down the entire test suite. -This could become an issue especially when the tests reproduce the same steps -that users follow to authenticate, such as submitting a login form or using -OAuth authentication services. +.. caution:: -This article explains some of the most popular techniques to avoid these issues -and create fast tests when using authentication. + Starting from Symfony 5.1, a ``loginUser()`` method was introduced to + ease testing secured applications. See :ref:`testing_logging_in_users` + for more information about this. -Hashing Passwords Faster Only for Tests ---------------------------------------- + If you are still using an older version of Symfony, view + `previous versions of this article`_ for information on how to simulate + HTTP authentication. -By default, :ref:`password encoders ` are -resource intensive and take time. This is important to generate secure password -hashes. In tests however, secure hashes are not important, so you can change the -encoders configuration to generate password hashes as fast as possible: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/test/security.yaml - encoders: - # Use your user class name here - App\Entity\User: - algorithm: auto # This should be the same value as in config/packages/security.yaml - cost: 4 # Lowest possible value for bcrypt - time_cost: 3 # Lowest possible value for argon - memory_cost: 10 # Lowest possible value for argon - - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php - - // config/packages/test/security.php - use App\Entity\User; - - $container->loadFromExtension('security', [ - 'encoders' => [ - // Use your user class name here - User::class => [ - 'algorithm' => 'auto', // This should be the same value as in config/packages/security.yaml - 'cost' => 4, // Lowest possible value for bcrypt - 'time_cost' => 3, // Lowest possible value for argon - 'memory_cost' => 10, // Lowest possible value for argon - ] - ], - ]); - -Using a Faster Authentication Mechanism Only for Tests ------------------------------------------------------- - -When your application is using a ``form_login`` authentication, you can make -your tests faster by allowing them to use HTTP authentication. This way your -tests authenticate with the simple and fast HTTP Basic method whilst your real -users still log in via the normal login form. - -The trick is to use the ``http_basic`` authentication in your application -firewall, but only in the configuration file used by tests: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/test/security.yaml - security: - firewalls: - # replace 'main' by the name of your own firewall - main: - http_basic: ~ - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // config/packages/test/security.php - $container->loadFromExtension('security', [ - 'firewalls' => [ - // replace 'main' by the name of your own firewall - 'main' => [ - 'http_basic' => [], - ], - ], - ]); - -Tests can now authenticate via HTTP passing the username and password as server -variables using the second argument of ``createClient()``:: - - $client = static::createClient([], [ - 'PHP_AUTH_USER' => 'username', - 'PHP_AUTH_PW' => 'pa$$word', - ]); - -The username and password can also be passed on a per request basis:: - - $client->request('DELETE', '/post/12', [], [], [ - 'PHP_AUTH_USER' => 'username', - 'PHP_AUTH_PW' => 'pa$$word', - ]); - -Creating the Authentication Token ---------------------------------- - -If your application uses a more advanced authentication mechanism, you can't -use the previous trick, but it's still possible to make tests faster. The trick -now is to bypass the authentication process, create the *authentication token* -yourself and store it in the session. - -This technique requires some knowledge of the Security component internals, -but the following example shows a complete example that you can adapt to your -needs:: - - // tests/Controller/DefaultControllerTest.php - namespace App\Tests\Controller; - - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - use Symfony\Component\BrowserKit\Cookie; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; - - class DefaultControllerTest extends WebTestCase - { - private $client = null; - - public function setUp() - { - $this->client = static::createClient(); - } - - public function testSecuredHello() - { - $this->logIn(); - $crawler = $this->client->request('GET', '/admin'); - - $this->assertSame(Response::HTTP_OK, $this->client->getResponse()->getStatusCode()); - $this->assertSame('Admin Dashboard', $crawler->filter('h1')->text()); - } - - private function logIn() - { - $session = self::$container->get('session'); - - // somehow fetch the user (e.g. using the user repository) - $user = ...; - - $firewallName = 'secure_area'; - // if you don't define multiple connected firewalls, the context defaults to the firewall name - // See https://symfony.com/doc/current/reference/configuration/security.html#firewall-context - $firewallContext = 'secured_area'; - - // you may need to use a different token class depending on your application. - // for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken - $token = new UsernamePasswordToken($user, null, $firewallName, $user->getRoles()); - $session->set('_security_'.$firewallContext, serialize($token)); - $session->save(); - - $cookie = new Cookie($session->getName(), $session->getId()); - $this->client->getCookieJar()->set($cookie); - } - } +.. _previous versions of this article: https://symfony.com/doc/5.0/testing/http_authentication.html diff --git a/translation.rst b/translation.rst index 20025b8b5cd..4788da4a36c 100644 --- a/translation.rst +++ b/translation.rst @@ -292,11 +292,44 @@ To manage these situations, Symfony follows the `ICU MessageFormat`_ syntax by using PHP's :phpclass:`MessageFormatter` class. Read more about this in :doc:`/translation/message_format`. -.. versionadded:: 4.2 +Translatable Objects +-------------------- - Support for ICU MessageFormat was introduced in Symfony 4.2. Prior to this, - pluralization was managed by the - :method:`Symfony\\Component\\Translation\\Translator::transChoice` method. +.. versionadded:: 5.2 + + Translatable objects were introduced in Symfony 5.2. + +Sometimes translating contents in templates is cumbersome because you need the +original message, the translation parameters and the translation domain for +each content. Making the translation in the controller or services simplifies +your templates, but requires injecting the translator service in different +parts of your application and mocking it in your tests. + +Instead of translating a string at the time of creation, you can use a +"translatable object", which is an instance of the +:class:`Symfony\\Component\\Translation\\TranslatableMessage` class. This object stores +all the information needed to fully translate its contents when needed:: + + use Symfony\Component\Translation\TranslatableMessage; + + // the first argument is required and it's the original message + $message = new TranslatableMessage('Symfony is great!'); + // the optional second argument defines the translation parameters and + // the optional third argument is the translation domain + $status = new TranslatableMessage('order.status', ['%status%' => $order->getStatus()], 'store'); + +Templates are now much simpler because you can pass translatable objects to the +``trans`` filter: + +.. code-block:: html+twig + +

{{ message|trans }}

+

{{ status|trans }}

+ +.. tip:: + + There's also a :ref:`function called t() `, + available both in Twig and PHP, as a shortcut to create translatable objects. .. _translation-in-templates: @@ -354,11 +387,6 @@ The ``translation:update`` command looks for missing translations in: * Any PHP file/class that injects or :doc:`autowires ` the ``translator`` service and makes calls to the ``trans()`` function. -.. versionadded:: 4.3 - - The extraction of missing translation strings from PHP files was introduced - in Symfony 4.3. - .. _translation-resource-locations: Translation Resource/File Names and Locations @@ -366,18 +394,11 @@ Translation Resource/File Names and Locations Symfony looks for message files (i.e. translations) in the following default locations: -#. the ``translations/`` directory (at the root of the project); -#. the ``src/Resources//translations/`` directory; -#. the ``Resources/translations/`` directory inside of any bundle. - -.. deprecated:: 4.2 - - Using the ``src/Resources//translations/`` directory to store - translations was deprecated in Symfony 4.2. Use instead the directory - defined in the ``default_path`` option (which is ``translations/`` by default). +* the ``translations/`` directory (at the root of the project); +* the ``Resources/translations/`` directory inside of any bundle. The locations are listed here with the highest priority first. That is, you can -override the translation messages of a bundle in any of the top two directories. +override the translation messages of a bundle in the first directory. The override mechanism works at a key level: only the overridden keys need to be listed in a higher priority message file. When a key is not found @@ -477,13 +498,6 @@ if you're generating translations with specialized programs or teams. :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. See the :ref:`dic-tags-translation-loader` tag for more information. -.. versionadded:: 4.3 - - Starting from Symfony 4.3, when you create a new translation file (or - install a bundle that includes translation files), you don't have to clear - the cache with the command ``php bin/console cache:clear`` as you had to do - in previous Symfony versions. - Handling the User's Locale -------------------------- @@ -550,12 +564,6 @@ checks translation resources for several locales: // ... ]); -.. 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:: When Symfony can't find a translation in the given locale, it will diff --git a/translation/debug.rst b/translation/debug.rst index afe1c778e77..74e52783245 100644 --- a/translation/debug.rst +++ b/translation/debug.rst @@ -180,3 +180,33 @@ unused or only the missing messages, by using the ``--only-unused`` or $ php bin/console debug:translation en --only-unused $ php bin/console debug:translation en --only-missing + +Debug Command Exit Codes +------------------------ + +The exit code of the ``debug:translation`` command changes depending on the +status of the translations. Use the following public constants to check it:: + + use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; + + // generic failure (e.g. there are no translations) + TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR; + + // there are missing translations + TranslationDebugCommand::EXIT_CODE_MISSING; + + // there are unused translations + TranslationDebugCommand::EXIT_CODE_UNUSED; + + // some translations are using the fallback translation + TranslationDebugCommand::EXIT_CODE_FALLBACK; + +These constants are defined as "bit masks", so you can combine them as follows:: + + if (TranslationDebugCommand::EXIT_CODE_MISSING | TranslationDebugCommand::EXIT_CODE_UNUSED) { + // ... there are missing and/or unused translations + } + +.. versionadded:: 5.1 + + The exit codes were introduced in Symfony 5.1 diff --git a/translation/lint.rst b/translation/lint.rst index 29cec3c5008..14693f32826 100644 --- a/translation/lint.rst +++ b/translation/lint.rst @@ -32,3 +32,29 @@ The linter results can be exported to JSON using the ``--format`` option: $ php bin/console lint:yaml translations/ --format=json $ php bin/console lint:xliff translations/ --format=json + +When running the YAML linter inside `GitHub Actions`_, the output is automatically +adapted to the format required by GitHub, but you can force that format too: + +.. code-block:: terminal + + $ php bin/console lint:yaml translations/ --format=github + +.. versionadded:: 5.3 + + The ``github`` output format was introduced in Symfony 5.3. + +.. tip:: + + The Yaml component provides a stand-alone ``yaml-lint`` binary allowing + you to lint YAML files without having to create a console application: + + .. code-block:: terminal + + $ php vendor/bin/yaml-lint translations/ + + .. versionadded:: 5.1 + + The ``yaml-lint`` binary was introduced in Symfony 5.1. + +.. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions diff --git a/translation/message_format.rst b/translation/message_format.rst index 4e637a4335e..1df79fee60b 100644 --- a/translation/message_format.rst +++ b/translation/message_format.rst @@ -4,10 +4,6 @@ How to Translate Messages using the ICU MessageFormat ===================================================== -.. versionadded:: 4.2 - - Support for ICU MessageFormat was introduced in Symfony 4.2. - Messages (i.e. strings) in applications are almost never completely static. They contain variables or other complex logic like pluralization. In order to handle this, the Translator component supports the `ICU MessageFormat`_ syntax. @@ -177,6 +173,25 @@ you to use literal text in the select statements: readable for translators and, as you can see in the ``other`` case, other parts of the sentence might be influenced by the variables. +.. tip:: + + It's possible to translate ICU MessageFormat messages directly in code, + without having to define them in any file:: + + $invitation = '{organizer_gender, select, + female {{organizer_name} has invited you for her party!} + male {{organizer_name} has invited you for his party!} + other {{organizer_name} have invited you for their party!} + }'; + + // prints "Ryan has invited you for his party!" + echo $translator->trans($invitation, [ + 'organizer_name' => 'Ryan', + 'organizer_gender' => 'male', + ]); + +.. _component-translation-pluralization: + Pluralization ------------- diff --git a/translation/templates.rst b/translation/templates.rst index 903f1934d92..b820bfb0fba 100644 --- a/translation/templates.rst +++ b/translation/templates.rst @@ -9,27 +9,13 @@ Twig Templates Using Twig Tags ~~~~~~~~~~~~~~~ -Symfony provides specialized Twig tags (``trans`` and ``transchoice``) to -help with message translation of *static blocks of text*: +Symfony provides a specialized Twig tag ``trans`` to help with message +translation of *static blocks of text*: .. code-block:: twig {% trans %}Hello %name%{% endtrans %} - {% transchoice count %} - {0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples - {% endtranschoice %} - -The ``transchoice`` tag automatically gets the ``%count%`` variable from -the current context and passes it to the translator. This mechanism only -works when you use a placeholder following the ``%var%`` pattern. - -.. deprecated:: 4.2 - - The ``transchoice`` tag is deprecated since Symfony 4.2 and will be - removed in 5.0. Use the :doc:`ICU MessageFormat ` with - the ``trans`` tag instead. - .. caution:: The ``%var%`` notation of placeholders is required when translating in @@ -48,34 +34,19 @@ You can also specify the message domain and pass some additional variables: {% trans with {'%name%': 'Fabien'} from 'app' into 'fr' %}Hello %name%{% endtrans %} - {% transchoice count with {'%name%': 'Fabien'} from 'app' %} - {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples - {% endtranschoice %} - .. _translation-filters: Using Twig Filters ~~~~~~~~~~~~~~~~~~ -The ``trans`` and ``transchoice`` filters can be used to translate *variable -texts* and complex expressions: +The ``trans`` filter can be used to translate *variable texts* and complex expressions: .. code-block:: twig {{ message|trans }} - {{ message|transchoice(5) }} - {{ message|trans({'%name%': 'Fabien'}, 'app') }} - {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }} - -.. deprecated:: 4.2 - - The ``transchoice`` filter is deprecated since Symfony 4.2 and will be - removed in 5.0. Use the :doc:`ICU MessageFormat ` with - the ``trans`` filter instead. - .. tip:: Using the translation tags or filters have the same effect, but with @@ -116,8 +87,3 @@ The translator service is accessible in PHP templates through the trans('Symfony is great') ?> - transChoice( - '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', - 10, - ['%count%' => 10] - ) ?> diff --git a/validation.rst b/validation.rst index ab81c528462..466c25e1158 100644 --- a/validation.rst +++ b/validation.rst @@ -725,6 +725,46 @@ constraint that's applied to the class itself. When that class is validated, methods specified by that constraint are simply executed so that each can provide more custom validation. +Debugging the Constraints +------------------------- + +.. versionadded:: 5.2 + + The ``debug:validator`` command was introduced in Symfony 5.2. + +Use the ``debug:validator`` command to list the validation constraints of a +given class: + +.. code-block:: terminal + + $ php bin/console debug:validator 'App\Entity\SomeClass' + + App\Entity\SomeClass + ----------------------------------------------------- + + +---------------+--------------------------------------------------+---------+------------------------------------------------------------+ + | Property | Name | Groups | Options | + +---------------+--------------------------------------------------+---------+------------------------------------------------------------+ + | firstArgument | Symfony\Component\Validator\Constraints\NotBlank | Default | [ | + | | | | "message" => "This value should not be blank.", | + | | | | "allowNull" => false, | + | | | | "normalizer" => null, | + | | | | "payload" => null | + | | | | ] | + | firstArgument | Symfony\Component\Validator\Constraints\Email | Default | [ | + | | | | "message" => "This value is not a valid email address.", | + | | | | "mode" => null, | + | | | | "normalizer" => null, | + | | | | "payload" => null | + | | | | ] | + +---------------+--------------------------------------------------+---------+------------------------------------------------------------+ + +You can also validate all the classes stored in a given directory: + +.. code-block:: terminal + + $ php bin/console debug:validator src/Entity + Final Thoughts -------------- diff --git a/validation/custom_constraint.rst b/validation/custom_constraint.rst index 3cebc7a0c72..9cecde12b8a 100644 --- a/validation/custom_constraint.rst +++ b/validation/custom_constraint.rst @@ -12,20 +12,43 @@ alphanumeric characters. Creating the Constraint Class ----------------------------- -First you need to create a Constraint class and extend :class:`Symfony\\Component\\Validator\\Constraint`:: +First you need to create a Constraint class and extend :class:`Symfony\\Component\\Validator\\Constraint`: - // src/Validator/ContainsAlphanumeric.php - namespace App\Validator; +.. configuration-block:: - use Symfony\Component\Validator\Constraint; + .. code-block:: php-annotations - /** - * @Annotation - */ - class ContainsAlphanumeric extends Constraint - { - public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.'; - } + // src/Validator/ContainsAlphanumeric.php + namespace App\Validator; + + use Symfony\Component\Validator\Constraint; + + /** + * @Annotation + */ + class ContainsAlphanumeric extends Constraint + { + public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.'; + } + + .. code-block:: php-attributes + + // src/Validator/ContainsAlphanumeric.php + namespace App\Validator; + + use Symfony\Component\Validator\Constraint; + + #[\Attribute] + class ContainsAlphanumeric extends Constraint + { + public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.'; + } + +.. versionadded:: 5.2 + + The ability to use PHP attributes to configure constraints was introduced in + Symfony 5.2. Prior to this, Doctrine Annotations were the only way to + annotate constraints. .. note:: @@ -93,11 +116,6 @@ The validator class only has one required method ``validate()``:: } } -.. versionadded:: 4.4 - - The feature to allow passing an object as the ``buildViolation()`` argument - was introduced in Symfony 4.4. - Inside ``validate``, you don't need to return a value. Instead, you add violations to the validator's ``context`` property and a value will be considered valid if it causes no violations. The ``buildViolation()`` method takes the error @@ -133,6 +151,25 @@ You can use custom validators like the ones provided by Symfony itself: // ... } + .. code-block:: php-attributes + + // src/Entity/AcmeEntity.php + namespace App\Entity; + + use App\Validator as AcmeAssert; + use Symfony\Component\Validator\Constraints as Assert; + + class AcmeEntity + { + // ... + + #[Assert\NotBlank] + #[AcmeAssert\ContainsAlphanumeric] + protected $name; + + // ... + } + .. code-block:: yaml # config/validator/validation.yaml @@ -190,6 +227,16 @@ then your validator is already registered as a service and :doc:`tagged ` like any other service. +Create a Reusable Set of Constraints +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In case you need to apply some common set of constraints in different places +consistently across your application, you can extend the :doc:`Compound constraint `. + +.. versionadded:: 5.1 + + The ``Compound`` constraint was introduced in Symfony 5.1. + Class Constraint Validator ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -236,6 +283,14 @@ not to the property: // ... } + .. code-block:: php-attributes + + #[AcmeAssert\ProtocolClass] + class AcmeEntity + { + // ... + } + .. code-block:: yaml # config/validator/validation.yaml diff --git a/validation/sequence_provider.rst b/validation/sequence_provider.rst index 11edc43a7e0..503c50f67e5 100644 --- a/validation/sequence_provider.rst +++ b/validation/sequence_provider.rst @@ -358,3 +358,14 @@ provides a sequence of groups to be validated: // ... } } + +How to Sequentially Apply Constraints on a Single Property +---------------------------------------------------------- + +Sometimes, you may want to apply constraints sequentially on a single +property. The :doc:`Sequentially constraint ` +can solve this for you in a more straightforward way than using a ``GroupSequence``. + +.. versionadded:: 5.1 + + The ``Sequentially`` constraint was introduced in Symfony 5.1. diff --git a/workflow.rst b/workflow.rst index 7f7acffaa3a..750d6a2ca5e 100644 --- a/workflow.rst +++ b/workflow.rst @@ -232,6 +232,8 @@ what actions are allowed on a blog post:: // See all the available transitions for the post in the current state $transitions = $workflow->getEnabledTransitions($post); + // See a specific available transition for the post in the current state + $transition = $workflow->getEnabledTransition($post, 'publish'); Accessing the Workflow in a Class --------------------------------- @@ -383,11 +385,6 @@ order: The leaving and entering events are triggered even for transitions that stay in same place. -.. versionadded:: 4.3 - - Following events are also dispatched when the subject enters the workflow - for the first time: ``workflow.entered`` and ``workflow.[worflow name].entered``. - Here is an example of how to enable logging for every time a "blog_publishing" workflow leaves a place:: @@ -460,8 +457,7 @@ missing a title:: $title = $post->title; if (empty($title)) { - // Block the transition "to_review" if the post has no title - $event->setBlocked(true); + $event->setBlocked(true, 'This blog post cannot be marked as reviewed because it has no title.'); } } @@ -473,6 +469,119 @@ missing a title:: } } +.. versionadded:: 5.1 + + The optional second argument of ``setBlocked()`` was introduced in Symfony 5.1. + +Choosing which Events to Dispatch +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.2 + + Ability to choose which events to dispatch was introduced in Symfony 5.2. + +If you prefer to control which events are fired when performing each transition, +use the ``events_to_dispatch`` configuration option. This option does not apply +to :ref:`Guard events `, which are always fired: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/workflow.yaml + framework: + workflows: + blog_publishing: + # you can pass one or more event names + events_to_dispatch: ['workflow.leave', 'workflow.completed'] + + # pass an empty array to not dispatch any event + events_to_dispatch: [] + + # ... + + .. code-block:: xml + + + + + + + + workflow.leave + workflow.completed + + + + + + + + + + .. code-block:: php + + // config/packages/workflow.php + $container->loadFromExtension('framework', [ + // ... + 'workflows' => [ + 'blog_publishing' => [ + // you can pass one or more event names + 'events_to_dispatch' => [ + 'workflow.leave', + 'workflow.completed', + ], + + // pass an empty array to not dispatch any event + 'events_to_dispatch' => [], + + // ... + ], + ], + ]); + +You can also disable a specific event from being fired when applying a transition:: + + use App\Entity\BlogPost; + use Symfony\Component\Workflow\Exception\LogicException; + + $post = new BlogPost(); + + $workflow = $this->container->get('workflow.blog_publishing'); + + try { + $workflow->apply($post, 'to_review', [ + Workflow::DISABLE_ANNOUNCE_EVENT => true, + Workflow::DISABLE_LEAVE_EVENT => true, + ]); + } catch (LogicException $exception) { + // ... + } + +Disabling an event for a specific transition will take precedence over any +events specified in the workflow configuration. In the above example the +``workflow.leave`` event will not be fired, even if it has been specified as an +event to be dispatched for all transitions in the workflow configuration. + +.. versionadded:: 5.1 + + The ``Workflow::DISABLE_ANNOUNCE_EVENT`` constant was introduced in Symfony 5.1. + +.. versionadded:: 5.2 + + The constants for other events (as seen below) were introduced in Symfony 5.2. + + * ``Workflow::DISABLE_LEAVE_EVENT`` + * ``Workflow::DISABLE_TRANSITION_EVENT`` + * ``Workflow::DISABLE_ENTER_EVENT`` + * ``Workflow::DISABLE_ENTERED_EVENT`` + * ``Workflow::DISABLE_COMPLETED_EVENT`` + Event Methods ~~~~~~~~~~~~~ @@ -669,10 +778,6 @@ place:: } } -.. versionadded:: 4.1 - - The transition blockers were introduced in Symfony 4.1. - Usage in Twig ------------- @@ -685,6 +790,9 @@ of domain logic in your templates: ``workflow_transitions()`` Returns an array with all the transitions enabled for the given object. +``workflow_transition()`` + Returns a specific transition enabled for the given object and transition name. + ``workflow_marked_places()`` Returns an array with the place names of the given marking. @@ -734,10 +842,6 @@ The following example shows these functions in action: Storing Metadata ---------------- -.. versionadded:: 4.1 - - The feature to store metadata in workflows was introduced in Symfony 4.1. - In case you need it, you can store arbitrary metadata in workflows, their places, and their transitions using the ``metadata`` option. This metadata can be only the title of the workflow or very complex objects: @@ -932,6 +1036,15 @@ In Twig templates, metadata is available via the ``workflow_metadata()`` functio {% endfor %}

+

+ to_review Priority +

    +
  • + to_review: + {{ workflow_metadata(blog_post, 'priority', workflow_transition(blog_post, 'to_review')) }} +
  • +
+

Learn more ----------