diff --git a/bundles/configuration.rst b/bundles/configuration.rst index dedfada2ea2..eee7b9f7c37 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -477,6 +477,14 @@ the extension. You might want to change this to a more professional URL:: } } +.. deprecated:: 7.4 + + The ``getNamespace()`` method, together with XML support, is deprecated + since Symfony 7.4 and will be removed in Symfony 8.0. + + If your bundle needs to remain compatible with older Symfony versions that + still support XML, keep this method and add the ``@deprecated`` annotation to it. + Providing an XML Schema ~~~~~~~~~~~~~~~~~~~~~~~ @@ -510,6 +518,14 @@ can place it anywhere you like. You should return this path as the base path:: } } +.. deprecated:: 7.4 + + The ``getXsdValidationBasePath()`` method, together with XML support, is + deprecated since Symfony 7.4 and will be removed in Symfony 8.0. + + If your bundle needs to remain compatible with older Symfony versions that + still support XML, keep this method and add the ``@deprecated`` annotation to it. + Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be ``https://acme_company.com/schema/dic/hello/hello-1.0.xsd``: diff --git a/components/browser_kit.rst b/components/browser_kit.rst index 42d9d07af1c..f4fa0697f67 100644 --- a/components/browser_kit.rst +++ b/components/browser_kit.rst @@ -335,6 +335,20 @@ history:: // go forward to documentation page $crawler = $client->forward(); + // check if the history position is on the first page + if (!$client->getHistory()->isFirstPage()) { + $crawler = $client->back(); + } + + // check if the history position is on the last page + if (!$client->getHistory()->isLastPage()) { + $crawler = $client->forward(); + } + +.. versionadded:: 7.4 + + The ``isFirstPage()`` and ``isLastPage()`` methods were introduced in Symfony 7.4. + You can delete the client's history with the ``restart()`` method. This will also delete all the cookies:: diff --git a/components/clock.rst b/components/clock.rst index c4ac88e9092..83795278873 100644 --- a/components/clock.rst +++ b/components/clock.rst @@ -271,7 +271,17 @@ Storing DatePoints in the Database ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you :doc:`use Doctrine ` to work with databases, consider using the -``date_point`` Doctrine type, which converts to/from ``DatePoint`` objects automatically:: +new Doctrine types: + +======================= ====================== ===== +DatePoint Doctrine type Extends Doctrine type Class +======================= ====================== ===== +``date_point`` ``datetime_immutable`` :class:`Symfony\\Bridge\\Doctrine\\Types\\DatePointType` +``day_point`` ``date_immutable`` :class:`Symfony\\Bridge\\Doctrine\\Types\\DayPointType` +``time_point`` ``time_immutable`` :class:`Symfony\\Bridge\\Doctrine\\Types\\TimePointType` +======================= ====================== ===== + +They convert to/from ``DatePoint`` objects automatically:: // src/Entity/Product.php namespace App\Entity; @@ -282,7 +292,7 @@ If you :doc:`use Doctrine ` to work with databases, consider using th #[ORM\Entity] class Product { - // if you don't define the Doctrine type explicitly, Symfony will autodetect it: + // if you don't define the Doctrine type explicitly, Symfony will autodetect 'date_point': #[ORM\Column] private DatePoint $createdAt; @@ -290,6 +300,12 @@ If you :doc:`use Doctrine ` to work with databases, consider using th #[ORM\Column(type: 'date_point')] private DatePoint $updatedAt; + #[ORM\Column(type: 'day_point')] + public DatePoint $birthday; + + #[ORM\Column(type: 'time_point')] + public DatePoint $openAt; + // ... } @@ -297,6 +313,10 @@ If you :doc:`use Doctrine ` to work with databases, consider using th The ``DatePointType`` was introduced in Symfony 7.3. +.. versionadded:: 7.4 + + The ``DayPointType`` and ``TimePointType`` were introduced in Symfony 7.4. + .. _clock_writing-tests: Writing Time-Sensitive Tests diff --git a/components/config/definition.rst b/components/config/definition.rst index 2b1841bc24a..35ae35c468f 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -281,9 +281,9 @@ Before defining the children of an array node, you can provide options like: A basic prototyped array configuration can be defined as follows:: $node - ->fixXmlConfig('driver') ->children() - ->arrayNode('drivers') + // the arguments are the plural and singular variants of the option name + ->arrayNode('drivers', 'driver') ->scalarPrototype()->end() ->end() ->end() @@ -312,9 +312,8 @@ The processed configuration is:: A more complex example would be to define a prototyped array with children:: $node - ->fixXmlConfig('connection') ->children() - ->arrayNode('connections') + ->arrayNode('connections', 'connection') ->arrayPrototype() ->children() ->scalarNode('table')->end() @@ -385,9 +384,8 @@ the Symfony Config component treats arrays as lists by default. In order to maintain the array keys use the ``useAttributeAsKey()`` method:: $node - ->fixXmlConfig('connection') ->children() - ->arrayNode('connections') + ->arrayNode('connections', 'connection') ->useAttributeAsKey('name') ->arrayPrototype() ->children() @@ -734,14 +732,14 @@ normalization would make both of these ``auto_connect``. ``foo-bar_moo`` or if it already exists. Another difference between YAML and XML is in the way arrays of values may -be represented. In YAML you may have: +be represented. In YAML you may have an option called ``extensions`` (in plural): .. code-block:: yaml twig: extensions: ['twig.extension.foo', 'twig.extension.bar'] -and in XML: +and in XML you have a list of options called ``extension`` (in singular): .. code-block:: xml @@ -750,33 +748,24 @@ and in XML: twig.extension.bar -This difference can be removed in normalization by pluralizing the key used -in XML. You can specify that you want a key to be pluralized in this way -with ``fixXmlConfig()``:: +This difference can be removed in normalization by defining the singular variant +of the option name using the second argument of the ``arrayNode()`` method:: $rootNode - ->fixXmlConfig('extension') ->children() - ->arrayNode('extensions') + ->arrayNode('extensions', 'extension') ->scalarPrototype()->end() ->end() ->end() ; -If it is an irregular pluralization you can specify the plural to use as -a second argument:: +.. versionadded:: 7.4 - $rootNode - ->fixXmlConfig('child', 'children') - ->children() - ->arrayNode('children') - // ... - ->end() - ->end() - ; + The second argument of ``arrayNode()`` was introduced in Symfony 7.4. In prior + Symfony versions, you had to define the singular variant using the ``fixXmlConfig()`` + method on the root node (``$rootNode->fixXmlConfig('extension')``). -As well as fixing this, ``fixXmlConfig()`` ensures that single XML elements -are still turned into an array. So you may have: +This ensures that single XML elements are still turned into an array. So you may have: .. code-block:: xml @@ -791,7 +780,7 @@ and sometimes only: By default, ``connection`` would be an array in the first case and a string in the second, making it difficult to validate. You can ensure it is always -an array with ``fixXmlConfig()``. +an array with the second argument of ``arrayNode()``. You can further control the normalization process if you need to. For example, you may want to allow a string to be set and used as a particular key or diff --git a/components/console.rst b/components/console.rst index 14817240206..5a9c4e4a467 100644 --- a/components/console.rst +++ b/components/console.rst @@ -43,10 +43,15 @@ First, you need to create a PHP script to define the console application:: $application->run(); Then, you can register the commands using -:method:`Symfony\\Component\\Console\\Application::add`:: +:method:`Symfony\\Component\\Console\\Application::addCommand`:: // ... - $application->add(new GenerateAdminCommand()); + $application->addCommand(new GenerateAdminCommand()); + +.. versionadded:: 7.4 + + The ``addCommand()`` method was introduced in Symfony 7.4. In earlier + versions, you had to use the ``add()`` method of the same class. You can also register inline commands and define their behavior thanks to the ``Command::setCode()`` method:: diff --git a/components/console/helpers/cursor.rst b/components/console/helpers/cursor.rst index 63045f178ad..c139a3c75d9 100644 --- a/components/console/helpers/cursor.rst +++ b/components/console/helpers/cursor.rst @@ -22,12 +22,10 @@ of the output: { // ... - public function __invoke(OutputInterface $output): int + public function __invoke(Cursor $cursor, OutputInterface $output): int { // ... - $cursor = new Cursor($output); - // moves the cursor to a specific column (1st argument) and // row (2nd argument) position $cursor->moveToPosition(7, 11); @@ -39,6 +37,11 @@ of the output: } } +.. versionadded:: 7.4 + + Support for injecting the ``Cursor`` helper into the ``__invoke()`` method + was introduced in Symfony 7.4. + Using the cursor ---------------- diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index a0126ee5a71..64413b092b3 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -324,6 +324,63 @@ the response to a question should allow multiline answers by passing ``true`` to Multiline questions stop reading user input after receiving an end-of-transmission control character (``Ctrl-D`` on Unix systems or ``Ctrl-Z`` on Windows). +Setting a Timeout for User Input +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes, commands can hang if a user takes too long to respond. For example, +if interactive questions are used inside an open database transaction, a delayed +response could leave the transaction open for too long. + +You can prevent this by setting a maximum time limit for input using the +:method:`Symfony\\Component\\Console\\Question\\Question::setTimeout` method. +If the user doesn't respond within the specified timeout, a +:class:`Symfony\\Component\\Console\\Exception\\MissingInputException` will be thrown:: + + use Symfony\Component\Console\Exception\MissingInputException; + use Symfony\Component\Console\Question\Question; + + // ... + public function __invoke(InputInterface $input, OutputInterface $output): int + { + // ... + $helper = new QuestionHelper(); + + $question = new Question('Please enter your answer'); + $question->setTimeout(30); // 30 seconds + + try { + $answer = $helper->ask($input, $output, $question); + // ... do something with the answer + } catch (MissingInputException $exception) { + $output->writeln('No input received within timeout period.'); + // ... handle timeout + } + + return Command::SUCCESS; + } + +.. note:: + + The timeout only applies to interactive input streams. For non-interactive + streams (such as pipes or files), the timeout is ignored and the question + behaves normally. + +You can also use timeouts with other question types such as +:class:`Symfony\\Component\\Console\\Question\\ConfirmationQuestion` and +:class:`Symfony\\Component\\Console\\Question\\ChoiceQuestion`:: + + use Symfony\Component\Console\Question\ConfirmationQuestion; + + // ... + $question = new ConfirmationQuestion('Do you want to continue?', false); + $question->setTimeout(10); // 10 seconds + + $continue = $helper->ask($input, $output, $question); + +.. versionadded:: 7.4 + + The timeout functionality for questions was introduced in Symfony 7.4. + Hiding the User's Response ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst index 9c6b06537e2..d6b99b2c38e 100644 --- a/components/console/single_command_tool.rst +++ b/components/console/single_command_tool.rst @@ -36,7 +36,7 @@ You can still register a command as usual:: $application = new Application('echo', '1.0.0'); $command = new DefaultCommand(); - $application->add($command); + $application->addCommand($command); $application->setDefaultCommand($command->getName(), true); $application->run(); diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 62d1e92d89b..95dfd1e5344 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -639,7 +639,7 @@ else that can be used to create a working example:: $routes->add('hello', new Route('/hello/{name}', [ '_controller' => function (Request $request): Response { return new Response( - sprintf("Hello %s", $request->get('name')) + sprintf("Hello %s", $request->attributes->get('name')) ); }] )); diff --git a/components/intl.rst b/components/intl.rst index ba3cbdcb959..a0a1a245fa7 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -202,6 +202,16 @@ numeric country codes:: $exists = Countries::numericCodeExists('250'); // => true +.. note:: + + When the ``SYMFONY_INTL_WITH_USER_ASSIGNED`` environment variable is set, + the Symfony Intl component will also recognize user-assigned codes: ``XK``, ``XKK``, and ``983``. + This allows applications to handle these codes, which is useful for supporting regions that need to use them. + +.. versionadded:: 7.4 + + Support for ``SYMFONY_INTL_WITH_USER_ASSIGNED`` was introduced in Symfony 7.4. + Locales ~~~~~~~ @@ -301,6 +311,52 @@ to catching the exception, you can also check if a given currency code is valid: $isValidCurrency = Currencies::exists($currencyCode); +By default, the previous currency methods return all currencies, including +those that are no longer in use. Symfony provides several methods to filter +currencies so you can work only with those that are actually valid and in use +for a given country. + +These methods use ICU metadata (``tender``, ``from`` and ``to`` dates) to +determine whether a currency is `legal tender`_ and/or active at a specific +point in time:: + + use Symfony\Component\Intl\Currencies; + + // get the list of today's legal and active currencies for a country + $codes = Currencies::forCountry('FR'); + // ['EUR'] + + // include non-legal currencies too, and check them at a given date + $codesAll = Currencies::forCountry( + 'ES', + legalTender: null, + active: true, + date: new \DateTimeImmutable('1982-01-01') + ); + // ['ESP', 'ESB'] + + // check if a currency is valid today for a country + $isOk = Currencies::isValidInCountry('CH', 'CHF'); + // true + + // check if a currency is valid in any country on a specific date + $isGlobal = Currencies::isValidInAnyCountry( + 'USD', + legalTender: true, + active: true, + date: new \DateTimeImmutable('2005-01-01') + ); + // true + +Note that some currencies (especially non-legal-tender ones) do not have validity +ranges defined. In such cases, a ``RuntimeException`` will be thrown. In addition, +an ``InvalidArgumentException`` will be thrown if the specified currency is invalid. + +.. versionadded:: 7.4 + + The ``forCountry()``, ``isValidInCountry()`` and ``isValidInAnyCountry()`` + methods were introduced in Symfony 7.4. + .. _component-intl-timezones: Timezones @@ -419,6 +475,7 @@ Learn more .. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 .. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 .. _`ISO 3166-1 numeric`: https://en.wikipedia.org/wiki/ISO_3166-1_numeric +.. _`legal tender`: https://en.wikipedia.org/wiki/Legal_tender .. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets .. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time .. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1 diff --git a/components/json_path.rst b/components/json_path.rst index 9db8e48885e..6fa1e475eef 100644 --- a/components/json_path.rst +++ b/components/json_path.rst @@ -3,8 +3,7 @@ The JsonPath Component .. versionadded:: 7.3 - The JsonPath component was introduced in Symfony 7.3 as an - :doc:`experimental feature `. + The JsonPath component was introduced in Symfony 7.3. The JsonPath component lets you query and extract data from JSON structures. It implements the `RFC 9535 – JSONPath`_ standard, allowing you to navigate diff --git a/components/lock.rst b/components/lock.rst index a2ad15d52e0..f3069f4056f 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -128,6 +128,15 @@ example, the kernel will automatically release semaphores acquired by the store (see :ref:`lock stores ` for supported stores), an exception will be thrown when the application tries to serialize the key. +Locks can be serialized using both the native PHP serialization system +and its :phpfunction:`serialize` function, or using the Serializer +component. + +.. versionadded:: 7.4 + + The support for serializing locks with the Serializer component was + introduced in Symfony 7.4. + .. _lock-blocking-locks: Blocking Locks @@ -391,6 +400,7 @@ Store Scope Blocking Ex ========================================================== ====== ======== ======== ======= ============= :ref:`DoctrineDbalPostgreSqlStore ` remote yes no yes no :ref:`DoctrineDbalStore ` remote retry yes no yes +:ref:`DynamoDbStore ` remote retry yes no yes :ref:`FlockStore ` local yes no yes no :ref:`MemcachedStore ` remote retry yes no yes :ref:`MongoDbStore ` remote retry yes no yes @@ -415,6 +425,10 @@ the lock in a non-blocking way until the lock is acquired. The :class:`Symfony\\Component\\Lock\\Store\\NullStore` was introduced in Symfony 7.2. +.. versionadded:: 7.4 + + The ``DynamoDbStore`` was introduced in Symfony 7.4. + .. _lock-store-flock: FlockStore @@ -706,6 +720,32 @@ PHP process is terminated:: Zookeeper does not require a TTL as the nodes used for locking are ephemeral and die when the PHP process is terminated. +.. _lock-store-dynamodb: + +DynamoDbStore +~~~~~~~~~~~~~ + +The DynamoDbStore saves locks on a Amazon DynamoDB table. Install it by running: + +.. code-block:: terminal + + $ composer require symfony/amazon-dynamo-db-lock + +It requires a `DynamoDbClient`_ instance or a `Data Source Name (DSN)`_. +This store does not support blocking, and expects a TTL to avoid stalled locks:: + + use Symfony\Component\Lock\Bridge\DynamoDb\Store\DynamoDbStore; + + // a DynamoDbClient instance or DSN + $dynamoDbClientOrDSN = 'dynamodb://default/lock'; + $store = new DynamoDbStore($dynamoDbClientOrDSN); + +The table where values are stored is created automatically on the first call to +the :method:`Symfony\\Component\\Lock\\Bridge\\DynamoDb\\DynamoDbStore::save` method. +You can also create this table explicitly by calling the +:method:`Symfony\\Component\\Lock\\Bridge\\DynamoDb\\DynamoDbStore::createTable` method in +your code. + Reliability ----------- @@ -1057,3 +1097,4 @@ are still running. .. _`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 .. _`PCNTL`: https://www.php.net/manual/book.pcntl.php +.. _`DynamoDbClient`: https://async-aws.com/clients/dynamodb.html diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index e7334a3e658..5f2fb4ed960 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -566,9 +566,13 @@ Clock Mocking The :class:`Symfony\\Bridge\\PhpUnit\\ClockMock` class provided by this bridge allows you to mock the PHP's built-in time functions ``time()``, ``microtime()``, -``sleep()``, ``usleep()``, ``gmdate()``, and ``hrtime()``. Additionally the -function ``date()`` is mocked so it uses the mocked time if no timestamp is -specified. +``sleep()``, ``usleep()``, ``gmdate()``, ``hrtime()``, and ``strtotime()``. +Additionally the function ``date()`` is mocked so it uses the mocked time if no +timestamp is specified. + +.. versionadded:: 7.4 + + Support for mocking the ``strtotime()`` function was introduced in Symfony 7.4. Other functions with an optional timestamp parameter that defaults to ``time()`` will still use the system time instead of the mocked time. This means that you diff --git a/components/runtime.rst b/components/runtime.rst index 770ea102563..b401127eab9 100644 --- a/components/runtime.rst +++ b/components/runtime.rst @@ -302,16 +302,23 @@ Using Options ~~~~~~~~~~~~~ Some behavior of the Runtimes can be modified through runtime options. They -can be set using the ``APP_RUNTIME_OPTIONS`` environment variable:: +can be set using the ``APP_RUNTIME_OPTIONS`` environment variable as an array +or a JSON encoded string:: $_SERVER['APP_RUNTIME_OPTIONS'] = [ 'project_dir' => '/var/task', ]; + // same configuration using JSON: + // $_SERVER['APP_RUNTIME_OPTIONS'] = '{"project_dir":"\/var\/task"}'; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; // ... +.. versionadded:: 7.4 + + The support for JSON contents in ``APP_RUNTIME_OPTIONS`` was introduced in Symfony 7.4. + You can also configure ``extra.runtime`` in ``composer.json``: .. code-block:: json diff --git a/components/uid.rst b/components/uid.rst index b4083765436..4d705f13cf6 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -120,7 +120,7 @@ sortable (like :ref:`ULIDs `). It's more efficient for database indexing **UUID v7** (UNIX timestamp) Generates time-ordered UUIDs based on a high-resolution Unix Epoch timestamp -source (the number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds excluded) +source (the number of microseconds since midnight 1 Jan 1970 UTC, leap seconds excluded) (`read the UUIDv7 spec `__). It's recommended to use this version over UUIDv1 and UUIDv6 because it provides better entropy (and a more strict chronological order of UUID generation):: @@ -130,6 +130,10 @@ better entropy (and a more strict chronological order of UUID generation):: $uuid = Uuid::v7(); // $uuid is an instance of Symfony\Component\Uid\UuidV7 +.. versionadded:: 7.4 + + In Symfony 7.4, the precision was increased from milliseconds to microseconds. + **UUID v8** (custom) Provides an RFC-compatible format intended for experimental or vendor-specific use cases @@ -154,8 +158,42 @@ following methods to create a ``Uuid`` object from it:: $uuid = Uuid::fromBase58('TuetYWNHhmuSQ3xPoVLv9M'); $uuid = Uuid::fromRfc4122('d9e7a184-5d5b-11ea-a62a-3499710062d0'); -You can also use the ``UuidFactory`` to generate UUIDs. First, you may -configure the behavior of the factory using configuration files:: +You can also use the ``UuidFactory`` to generate UUIDs. Inject the factory in +your services and use it as follows: + + namespace App\Service; + + use Symfony\Component\Uid\Factory\UuidFactory; + + class FooService + { + public function __construct( + private UuidFactory $uuidFactory, + ) { + } + + public function generate(): void + { + $uuid = $this->uuidFactory->create(); + + $randomBasedUuid = $this->uuidFactory->randomBased()->create(); + // $namespace can be omitted if a default namespace is configured in the factory (see below) + $nameBasedUuid = $this->uuidFactory->nameBased($namespace)->create($name); + // $node can be omitted if a default node is configured in the factory (see below) + $timestampBased = $this->uuidFactory->timeBased($node)->create(); + + // ... + } + } + +By default, this factory generates the folllowing UUIDs: + +* Default and time-based UUIDs: UUIDv7 +* Name-based UUIDs: UUIDv5 +* Random-based UUIDs: UUIDv4 +* Time-based node and UUID namespace: ``null`` + +You can configure these default values:: .. configuration-block:: @@ -164,10 +202,10 @@ configure the behavior of the factory using configuration files:: # config/packages/uid.yaml framework: uid: - default_uuid_version: 7 - name_based_uuid_version: 5 + default_uuid_version: 6 + name_based_uuid_version: 3 name_based_uuid_namespace: 6ba7b810-9dad-11d1-80b4-00c04fd430c8 - time_based_uuid_version: 7 + time_based_uuid_version: 6 time_based_uuid_node: 121212121212 .. code-block:: xml @@ -183,10 +221,10 @@ configure the behavior of the factory using configuration files:: @@ -205,41 +243,19 @@ configure the behavior of the factory using configuration files:: $container->extension('framework', [ 'uid' => [ - 'default_uuid_version' => 7, - 'name_based_uuid_version' => 5, + 'default_uuid_version' => 6, + 'name_based_uuid_version' => 3, 'name_based_uuid_namespace' => '6ba7b810-9dad-11d1-80b4-00c04fd430c8', - 'time_based_uuid_version' => 7, + 'time_based_uuid_version' => 6, 'time_based_uuid_node' => 121212121212, ], ]); }; -Then, you can inject the factory in your services and use it to generate UUIDs based -on the configuration you defined:: - - namespace App\Service; - - use Symfony\Component\Uid\Factory\UuidFactory; +.. versionadded:: 7.4 - class FooService - { - public function __construct( - private UuidFactory $uuidFactory, - ) { - } - - public function generate(): void - { - // This creates a UUID of the version given in the configuration file (v7 by default) - $uuid = $this->uuidFactory->create(); - - $nameBasedUuid = $this->uuidFactory->nameBased(/** ... */); - $randomBasedUuid = $this->uuidFactory->randomBased(); - $timestampBased = $this->uuidFactory->timeBased(); - - // ... - } - } + Starting from Symfony 7.4, the default version for both UUIDs and time-based + UUIDs is v7. In previous versions, the default was v6. Converting UUIDs ~~~~~~~~~~~~~~~~ @@ -429,6 +445,65 @@ of the UUID parameters:: } } +Testing UUIDs +~~~~~~~~~~~~~ + +Many UUID versions include random or time-dependent parts, which makes them +hard to use in tests. The :class:`Symfony\\Component\\Uid\\Factory\\MockUuidFactory` +class allows you to control the UUIDs generated during your tests, making them +predictable and reproducible. + +First, make sure that your service generates UUID values using the +:class:`Symfony\\Component\\Uid\\Factory\\UuidFactory`:: + + use Symfony\Component\Uid\Factory\UuidFactory; + use Symfony\Component\Uid\Uuid; + + class UserService + { + public function __construct( + private UuidFactory $uuidFactory + ){ + } + + public function createUserId(): string + { + return $this->uuidFactory->create()->toRfc4122(); + } + } + +Then, in your tests, you can use ``MockUuidFactory`` to define a sequence of the +UUIDs that will be returned each time you generate one:: + + use PHPUnit\Framework\TestCase; + use Symfony\Component\Uid\Factory\MockUuidFactory; + use Symfony\Component\Uid\UuidV4; + + class UserServiceTest extends TestCase + { + public function testCreateUserId(): void + { + $factory = new MockUuidFactory([ + UuidV4::fromString('11111111-1111-4111-8111-111111111111'), + UuidV4::fromString('22222222-2222-4222-8222-222222222222'), + ]); + + $service = new UserService($factory); + + $this->assertSame('11111111-1111-4111-8111-111111111111', $service->createUserId()); + $this->assertSame('22222222-2222-4222-8222-222222222222', $service->createUserId()); + } + } + +In addition to the ``create()`` method, the ``MockUuidFactory`` also works for +the ``randomBased()``, ``timeBased()``, and ``nameBased()`` methods. You can even +mix different UUID versions in the same sequence. + +.. note:: + + ``MockUuidFactory`` throws an exception if the sequence is exhausted or the + available UUID types don't match the requested type. + .. _ulid: ULIDs diff --git a/configuration.rst b/configuration.rst index a2f6958d174..766b8f3d973 100644 --- a/configuration.rst +++ b/configuration.rst @@ -53,7 +53,7 @@ Configuration Formats ~~~~~~~~~~~~~~~~~~~~~ Unlike other frameworks, Symfony doesn't impose a specific format on you to -configure your applications, but lets you choose between YAML, XML and PHP. +configure your applications, but lets you choose between YAML and PHP. Throughout the Symfony documentation, all configuration examples will be shown in these three formats. @@ -66,20 +66,18 @@ readable. These are the main advantages and disadvantages of each format: * **YAML**: simple, clean and readable, but not all IDEs support autocompletion and validation for it. :doc:`Learn the YAML syntax `; -* **XML**: autocompleted/validated by most IDEs and is parsed natively by PHP, - but sometimes it generates configuration considered too verbose. `Learn the XML syntax`_; * **PHP**: very powerful and it allows you to create dynamic configuration with - arrays or a :ref:`ConfigBuilder `. + arrays, and benefits from auto completion and static analysis using + array shapes. -.. note:: +.. deprecated:: 7.4 + + The XML and Config Builder formats are deprecated in Symfony 7.4 and + will be removed in Symfony 8.0. - By default Symfony loads the configuration files defined in YAML and PHP - formats. If you define configuration in XML format, update the - :method:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait::configureContainer` - and/or - :method:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait::configureRoutes` - methods in the ``src/Kernel.php`` file to add support for the ``.xml`` file - extension. +.. versionadded:: 7.4 + + Array shapes were introduced in Symfony 7.4. Importing Configuration Files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -100,9 +98,9 @@ configuration files, even if they use a different format: - { resource: '/etc/myapp/*.yaml' } # ignore_errors: not_found silently discards errors if the loaded file doesn't exist - - { resource: 'my_config_file.xml', ignore_errors: not_found } + - { resource: 'my_config_file.php', ignore_errors: not_found } # ignore_errors: true silently discards all errors (including invalid code and not found) - - { resource: 'my_other_config_file.xml', ignore_errors: true } + - { resource: 'my_other_config_file.php', ignore_errors: true } # ... @@ -267,27 +265,6 @@ reusable configuration value. By convention, parameters are defined under the // ... -.. warning:: - - By default and when using XML configuration, the values between ```` - tags are not trimmed. This means that the value of the following parameter will be - ``'\n something@example.com\n'``: - - .. code-block:: xml - - - something@example.com - - - If you want to trim the value of your parameter, use the ``trim`` attribute. - When using it, the value of the following parameter will be ``something@example.com``: - - .. code-block:: xml - - - something@example.com - - Once defined, you can reference this parameter value from any other configuration file using a special syntax: wrap the parameter name in two ``%`` (e.g. ``%app.admin_email%``): @@ -331,7 +308,7 @@ configuration file using a special syntax: wrap the parameter name in two ``%`` 'email_address' => param('app.admin_email'), // ... but if you prefer it, you can also pass the name as a string - // surrounded by two % (same as in YAML and XML formats) and Symfony will + // surrounded by two % (same as in the YAML format) and Symfony will // replace it by that parameter value 'email_address' => '%app.admin_email%', ]); @@ -1302,52 +1279,6 @@ parameters at once by type-hinting any of its constructor arguments with the } } -.. _config-config-builder: - -Using PHP ConfigBuilders ------------------------- - -Writing PHP config is sometimes difficult because you end up with large nested -arrays and you have no autocompletion help from your favorite IDE. A way to -address this is to use "ConfigBuilders". They are objects that will help you -build these arrays. - -Symfony generates the ConfigBuilder classes automatically in the -:ref:`kernel build directory ` for all the -bundles installed in your application. By convention they all live in the -namespace ``Symfony\Config``:: - - // config/packages/security.php - use Symfony\Config\SecurityConfig; - - return static function (SecurityConfig $security): void { - $security->firewall('main') - ->pattern('^/*') - ->lazy(true) - ->security(false); - - $security - ->roleHierarchy('ROLE_ADMIN', ['ROLE_USER']) - ->roleHierarchy('ROLE_SUPER_ADMIN', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']) - ->accessControl() - ->path('^/user') - ->roles('ROLE_USER'); - - $security->accessControl(['path' => '^/admin', 'roles' => 'ROLE_ADMIN']); - }; - -.. note:: - - Only root classes in the namespace ``Symfony\Config`` are ConfigBuilders. - Nested configs (e.g. ``\Symfony\Config\Framework\CacheConfig``) are regular - PHP objects which aren't autowired when using them as an argument type. - -.. note:: - - In order to get ConfigBuilders autocompletion in your IDE/editor, make sure - to not exclude the directory where these classes are generated (by default, - in ``var/cache/dev/Symfony/Config/``). - Keep Going! ----------- @@ -1369,7 +1300,6 @@ And all the other topics related to configuration: configuration/* -.. _`Learn the XML syntax`: https://en.wikipedia.org/wiki/XML .. _`environment variables`: https://en.wikipedia.org/wiki/Environment_variable .. _`symbolic links`: https://en.wikipedia.org/wiki/Symbolic_link .. _`utilities to manage env vars`: https://symfony.com/doc/current/cloud/env.html diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst index e5dff35b6d0..21fd76ac39d 100644 --- a/configuration/override_dir_structure.rst +++ b/configuration/override_dir_structure.rst @@ -118,6 +118,19 @@ named ``APP_CACHE_DIR`` whose value is the full path of the cache folder. its own cached configuration files, and so each needs its own directory to store those cache files. +In case you have multiple frontend servers using the same shared filesystem, you +can make use of the :method:`Symfony\\Component\\HttpKernel\\Kernel::getShareDir` method to +get a shared directory for cache and shared data. The shared directory can be set +by overriding an environment variable named ``APP_SHARE_DIR`` whose value is the full +path of the shared folder. This directory is also accessible as a container parameter +named ``%kernel.share_dir%``. + +.. versionadded:: 7.4 + + The ``Kernel::getShareDir()`` method, the ``%kernel.share_dir`` parameter and + the support for the ``APP_SHARE_DIR`` environment variable were introduced + in Symfony 7.4. + .. _override-logs-dir: Override the Log Directory diff --git a/console.rst b/console.rst index eda37db0e0f..931fcbbb577 100644 --- a/console.rst +++ b/console.rst @@ -152,12 +152,17 @@ If you can't use PHP attributes, register the command as a service and :ref:`default services.yaml configuration `, this is already done for you, thanks to :ref:`autoconfiguration `. -You can also use ``#[AsCommand]`` to add a description and longer help text for the command:: +You can also use ``#[AsCommand]`` to add a description, usage exampless, and +longer help text for the command:: #[AsCommand( name: 'app:create-user', - description: 'Creates a new user.', // the command description shown when running "php bin/console list" - help: 'This command allows you to create a user...', // the command help shown when running the command with the "--help" option + // this short description is shown when running "php bin/console list" + description: 'Creates a new user.', + // this is shown when running the command with the "--help" option + help: 'This command allows you to create a user...', + // this allows you to show one or more usage examples (no need to add the command name) + usages: ['bob', 'alice --as-admin'], )] class CreateUserCommand { @@ -167,6 +172,11 @@ You can also use ``#[AsCommand]`` to add a description and longer help text for } } +.. versionadded:: 7.4 + + The feature to define usage examples in the ``#[AsCommand]`` attribute was + introduced in Symfony 7.4. + Additionally, you can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to leverage advanced features like lifecycle hooks (e.g. :method:`Symfony\\Component\\Console\\Command\\Command::initialize` and and :method:`Symfony\\Component\\Console\\Command\\Command::interact`):: @@ -207,6 +217,35 @@ After configuring and registering the command, you can run it in the terminal: As you might expect, this command will do nothing as you didn't write any logic yet. Add your own logic inside the ``__invoke()`` method. +.. _command-aliases: + +Command Aliases +~~~~~~~~~~~~~~~ + +You can define alternative names (aliases) for a command directly in its name +using a pipe (``|``) separator. The first name in the list becomes the actual +command name; the others are aliases that can also be used to run the command:: + + // src/Command/CreateUserCommand.php + namespace App\Command; + + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Command\Command; + + #[AsCommand( + name: 'app:create-user|app:add-user|app:new-user', + description: 'Creates a new user.', + )] + class CreateUserCommand extends Command + { + // ... + } + +.. versionadded:: 7.4 + + The ability to define aliases through the command name was introduced in + Symfony 7.4. + Console Output -------------- diff --git a/console/hide_commands.rst b/console/hide_commands.rst index 4ab9d3a6dad..aad4b6d45a4 100644 --- a/console/hide_commands.rst +++ b/console/hide_commands.rst @@ -22,8 +22,25 @@ the ``hidden`` property of the ``AsCommand`` attribute:: // ... } -Hidden commands behave the same as normal commands but they are no longer displayed -in command listings, so end-users are not aware of their existence. +You can also define a command as hidden using the pipe (``|``) syntax of +:ref:`command aliases `. To do this, use the command name as one +of the aliases and leave the main command name (the part before the ``|``) empty:: + + // src/Command/LegacyCommand.php + namespace App\Command; + + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Command\Command; + + #[AsCommand(name: '|app:legacy')] + class LegacyCommand extends Command + { + // ... + } + +.. versionadded:: 7.4 + + Support for hidding commands using the pipe syntax was introduced in Symfony 7.4. .. note:: diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst index 014a0ad0006..f7c7ec97007 100644 --- a/contributing/code/tests.rst +++ b/contributing/code/tests.rst @@ -64,6 +64,21 @@ what's going on and if the tests are broken because of the new code. On Windows, install the `Cmder`_, `ConEmu`_, `ANSICON`_ or `Mintty`_ free applications to see colored test results. +Testing Generated Code +---------------------- + +Some tests generate code on the fly and verify that it matches the expected +output stored in a file. To regenerate those files, run the tests with the +environment variable ``TEST_GENERATE_FIXTURES`` set to ``1``: + +.. code-block:: terminal + + $ TEST_GENERATE_FIXTURES=1 php ./phpunit src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php + +.. versionadded:: 7.4 + + The ``TEST_GENERATE_FIXTURES`` environment variable was introduced in Symfony 7.4. + .. _`install Composer`: https://getcomposer.org/download/ .. _Cmder: https://cmder.app/ .. _ConEmu: https://conemu.github.io/ diff --git a/controller.rst b/controller.rst index a3b14416f31..a31b79e5029 100644 --- a/controller.rst +++ b/controller.rst @@ -951,6 +951,79 @@ This way, browsers can start downloading the assets immediately; like the ``sendEarlyHints()`` method also returns the ``Response`` object, which you must use to create the full response sent from the controller action. +Decoupling Controllers from Symfony +----------------------------------- + +Extending the :ref:`AbstractController base class ` +simplifies controller development and is **recommended for most applications**. +However, some advanced users prefer to fully decouple your controllers from Symfony +(for example, to improve testability or to follow a more framework-agnostic design) +Symfony provides tools to help you do that. + +To decouple controllers, Symfony exposes all the helpers from ``AbstractController`` +through another class called :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerHelper`, +where each helper is available as a public method:: + + use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper; + use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf; + use Symfony\Component\HttpFoundation\Response; + + class MyController + { + public function __construct( + #[AutowireMethodOf(ControllerHelper::class)] + private \Closure $render, + #[AutowireMethodOf(ControllerHelper::class)] + private \Closure $redirectToRoute, + ) { + } + + public function showProduct(int $id): Response + { + if (!$id) { + return ($this->redirectToRoute)('product_list'); + } + + return ($this->render)('product/show.html.twig', ['product_id' => $id]); + } + } + +You can inject the entire ``ControllerHelper`` class if you prefer, but using the +:ref:`AutowireMethodOf ` attribute as in the previous example, +lets you inject only the exact helpers you need, making your code more efficient. + +Since ``#[AutowireMethodOf]`` also works with interfaces, you can define interfaces +for these helper methods:: + + interface RenderInterface + { + // this is the signature of the render() helper + public function __invoke(string $view, array $parameters = [], ?Response $response = null): Response; + } + +Then, update your controller to use the interface instead of a closure:: + + use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper; + use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf; + + class MyController + { + public function __construct( + #[AutowireMethodOf(ControllerHelper::class)] + private RenderInterface $render, + ) { + } + + // ... + } + +Using interfaces like in the previous example provides full static analysis and +autocompletion benefits with no extra boilerplate code. + +.. versionadded:: 7.4 + + The ``ControllerHelper`` class was introduced in Symfony 7.4. + Final Thoughts -------------- diff --git a/event_dispatcher.rst b/event_dispatcher.rst index 13a3bbb0297..e5953a6ecec 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -210,6 +210,12 @@ can also be applied to methods directly:: // ... } + #[AsEventListener] + public function onMultipleCustomEvent(CustomEvent|AnotherCustomEvent $event): void + { + // ... + } + #[AsEventListener(event: 'foo', priority: 42)] public function onFoo(): void { @@ -228,6 +234,11 @@ can also be applied to methods directly:: Note that the attribute doesn't require its ``event`` parameter to be set if the method already type-hints the expected event. +.. versionadded:: 7.4 + + Support for union types in the ``$event`` argument of methods using the + ``#[AsEventListener]`` attribute was introduced in Symfony 7.4. + .. _events-subscriber: Creating an Event Subscriber diff --git a/forms.rst b/forms.rst index 83065d7524b..2d2cb53a564 100644 --- a/forms.rst +++ b/forms.rst @@ -513,7 +513,8 @@ object. - NotBlank: ~ dueDate: - NotBlank: ~ - - Type: \DateTimeInterface + - Type: + type: \DateTimeInterface .. code-block:: xml @@ -530,7 +531,9 @@ object. - \DateTimeInterface + + + @@ -776,6 +779,10 @@ to the ``form()`` or the ``form_start()`` helper functions: ``DELETE`` request. The :ref:`http_method_override ` option must be enabled for this to work. + For security, you can restrict which HTTP methods can be overridden using the + :ref:`allowed_http_method_override ` + option. + Changing the Form Name ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/html_sanitizer.rst b/html_sanitizer.rst index 8e641c3672d..84639f72c10 100644 --- a/html_sanitizer.rst +++ b/html_sanitizer.rst @@ -29,6 +29,13 @@ You can install the HTML Sanitizer component with: $ composer require symfony/html-sanitizer +.. versionadded:: 7.4 + + Starting in Symfony 7.4, applications running on PHP 8.4 or higher will use + the native HTML5 parser provided by PHP. All other applications will continue + to use the third-party ``masterminds/html5`` parser, which is installed + automatically when installing the HTML Sanitizer package. + Basic Usage ----------- diff --git a/http_client.rst b/http_client.rst index a1c8f09bc1d..d3b4db6c424 100644 --- a/http_client.rst +++ b/http_client.rst @@ -740,6 +740,8 @@ making a request. Use the ``max_redirects`` setting to configure this behavior 'max_redirects' => 0, ]); +.. _http-client-retry-failed-requests: + Retry Failed Requests ~~~~~~~~~~~~~~~~~~~~~ @@ -1486,29 +1488,114 @@ in the foreach loop:: } } +.. _http-client_caching: + Caching Requests and Responses ------------------------------ This component provides a :class:`Symfony\\Component\\HttpClient\\CachingHttpClient` -decorator that allows caching responses and serving them from the local storage -for next requests. The implementation leverages the -:class:`Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache` class under the hood -so that the :doc:`HttpKernel component ` needs to be -installed in your application:: +decorator that enables caching of HTTP responses and serving them from cache +storage on subsequent requests, as described in `RFC 9111`_. - use Symfony\Component\HttpClient\CachingHttpClient; - use Symfony\Component\HttpClient\HttpClient; - use Symfony\Component\HttpKernel\HttpCache\Store; +Internally, it relies on a :class:`tag aware cache `, +so the :doc:`Cache component ` must be installed in your application. - $store = new Store('/path/to/cache/storage/'); - $client = HttpClient::create(); - $client = new CachingHttpClient($client, $store); +.. tip:: + + The caching mechanism is asynchronous. The response must be fully consumed + (for example, by calling ``getContent()`` or using a stream) for it to be + stored in the cache. + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + http_client: + scoped_clients: + example.client: + base_uri: 'https://example.com' + caching: + cache_pool: example_cache_pool + + cache: + pools: + example_cache_pool: + adapter: cache.adapter.redis_tag_aware + tags: true + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/framework.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $framework->httpClient()->scopedClient('example.client') + ->baseUri('https://example.com') + ->caching() + ->cachePool('example_cache_pool') + // ... + ; + + $framework->cache() + ->pool('example_cache_pool') + ->adapter('cache.adapter.redis_tag_aware') + ->tags(true) + ; + }; + + .. code-block:: php-standalone + + use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter; + use Symfony\Component\HttpClient\CachingHttpClient; + use Symfony\Component\HttpClient\HttpClient; - // this won't hit the network if the resource is already in the cache - $response = $client->request('GET', 'https://example.com/cacheable-resource'); + $cache = new FilesystemTagAwareAdapter(); -:class:`Symfony\\Component\\HttpClient\\CachingHttpClient` accepts a third argument -to set the options of the :class:`Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache`. + $client = HttpClient::createForBaseUri('https://example.com'); + $cachingClient = new CachingHttpClient($client, $cache); + +.. tip:: + + It is strongly recommended to configure a + :ref:`retry strategy ` to gracefully + handle temporary cache inconsistencies or validation failures. + +.. versionadded:: 7.4 + + In Symfony 7.4, caching was refactored to comply with `RFC 9111`_ and to + leverage the :doc:`Cache component `. In previous versions, + it relied on ``HttpCache`` from the HttpKernel component. Limit the Number of Requests ---------------------------- @@ -1789,6 +1876,28 @@ You can also pass a set of default options to your client thanks to the // ... +.. _auto-upgrade-http-version: + +You can use the ``auto_upgrade_http_version`` option to control whether the HTTP +protocol version is automatically upgraded:: + + $client = (new Psr18Client()) + ->withOptions([ + // set to false to always use the HTTP version defined in your request; + // set to true to allow the server to upgrade the HTTP version in its response + 'auto_upgrade_http_version' => false + // ... + ]); + +.. note:: + + The ``auto_upgrade_http_version`` option is ignored for HTTP/1.0 requests, + which always keep that protocol version. + +.. versionadded:: 7.4 + + The ``auto_upgrade_http_version`` option was introduced in Symfony 7.4. + HTTPlug ~~~~~~~ @@ -1890,6 +1999,10 @@ You can also pass a set of default options to your client thanks to the // ... + +See the :ref:`auto_upgrade_http_version ` option for +details about how the HTTP protocol version selection works. + Native PHP Streams ~~~~~~~~~~~~~~~~~~ @@ -2494,5 +2607,6 @@ body:: .. _`idempotent method`: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods .. _`SSRF`: https://portswigger.net/web-security/ssrf .. _`RFC 6570`: https://www.rfc-editor.org/rfc/rfc6570 +.. _`RFC 9111`: https://www.rfc-editor.org/rfc/rfc9111 .. _`HAR`: https://w3c.github.io/web-performance/specs/HAR/Overview.html .. _`the Cookie HTTP request header`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie diff --git a/lock.rst b/lock.rst index 5bb072cdf73..f37c5fdda9e 100644 --- a/lock.rst +++ b/lock.rst @@ -61,6 +61,7 @@ this behavior by using the ``lock`` key like: 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: 'dynamodb://127.0.0.1/lock' lock: '%env(LOCK_DSN)%' # using an existing service lock: 'snc_redis.default' @@ -119,6 +120,8 @@ this behavior by using the ``lock`` key like: mongodb://127.0.0.1/app?collection=lock + dynamodb://127.0.0.1/lock + %env(LOCK_DSN)% @@ -157,6 +160,7 @@ this behavior by using the ``lock`` key like: ->resource('default', ['sqlsrv:server=127.0.0.1;Database=app']) ->resource('default', ['oci:host=127.0.0.1;dbname=app']) ->resource('default', ['mongodb://127.0.0.1/app?collection=lock']) + ->resource('default', ['dynamodb://127.0.0.1/lock']) ->resource('default', [env('LOCK_DSN')]) // using an existing service ->resource('default', ['snc_redis.default']) @@ -315,7 +319,7 @@ For example, to inject the ``invoice`` package defined earlier:: When :ref:`dealing with multiple implementations of the same type ` the ``#[Target]`` attribute helps you select which one to inject. Symfony creates -a target called ``lock.`` + "lock name" + ``.factory``. +a target with the same name as the lock. For example, to select the ``invoice`` lock defined earlier:: @@ -325,8 +329,13 @@ For example, to select the ``invoice`` lock defined earlier:: class SomeService { public function __construct( - #[Target('lock.invoice.factory')] private LockFactory $lockFactory + #[Target('invoice')] private LockFactory $lockFactory ): void { // ... } } + +.. versionadded:: 7.4 + + Before Symfony 7.4, the target name had to follow the ``lock..factory`` + pattern (e.g. ``#[Target('lock.invoice.factory')]``). diff --git a/logging.rst b/logging.rst index 98a380b82a9..da4ade65b19 100644 --- a/logging.rst +++ b/logging.rst @@ -479,5 +479,5 @@ Learn more .. _`Monolog`: https://github.com/Seldaek/monolog .. _`LoggerInterface`: https://github.com/php-fig/log/blob/master/src/LoggerInterface.php .. _`logrotate`: https://github.com/logrotate/logrotate -.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/3.x/src/DependencyInjection/Configuration.php +.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/4.x/src/DependencyInjection/Configuration.php .. _`STDERR PHP stream`: https://www.php.net/manual/en/features.commandline.io-streams.php diff --git a/mailer.rst b/mailer.rst index a2bbe55953c..5354bbb5ef6 100644 --- a/mailer.rst +++ b/mailer.rst @@ -97,28 +97,29 @@ Using a 3rd Party Transport Instead of using your own SMTP server or sendmail binary, you can send emails via a third-party provider: -===================== =============================================== =============== -Service Install with Webhook support -===================== =============================================== =============== -`AhaSend`_ ``composer require symfony/aha-send-mailer`` yes +===================== =================================================== =============== +Service Install with Webhook support +===================== =================================================== =============== +`AhaSend`_ ``composer require symfony/aha-send-mailer`` yes `Amazon SES`_ ``composer require symfony/amazon-mailer`` `Azure`_ ``composer require symfony/azure-mailer`` -`Brevo`_ ``composer require symfony/brevo-mailer`` yes +`Brevo`_ ``composer require symfony/brevo-mailer`` yes `Infobip`_ ``composer require symfony/infobip-mailer`` -`Mailgun`_ ``composer require symfony/mailgun-mailer`` yes -`Mailjet`_ ``composer require symfony/mailjet-mailer`` yes -`Mailomat`_ ``composer require symfony/mailomat-mailer`` yes +`Mailgun`_ ``composer require symfony/mailgun-mailer`` yes +`Mailjet`_ ``composer require symfony/mailjet-mailer`` yes +`Mailomat`_ ``composer require symfony/mailomat-mailer`` yes `MailPace`_ ``composer require symfony/mail-pace-mailer`` -`MailerSend`_ ``composer require symfony/mailer-send-mailer`` yes -`Mailtrap`_ ``composer require symfony/mailtrap-mailer`` yes -`Mandrill`_ ``composer require symfony/mailchimp-mailer`` yes +`MailerSend`_ ``composer require symfony/mailer-send-mailer`` yes +`Mailtrap`_ ``composer require symfony/mailtrap-mailer`` yes +`Mandrill`_ ``composer require symfony/mailchimp-mailer`` yes +`Microsoft Graph`_ ``composer require symfony/microsoft-graph-mailer`` `Postal`_ ``composer require symfony/postal-mailer`` -`Postmark`_ ``composer require symfony/postmark-mailer`` yes -`Resend`_ ``composer require symfony/resend-mailer`` yes +`Postmark`_ ``composer require symfony/postmark-mailer`` yes +`Resend`_ ``composer require symfony/resend-mailer`` yes `Scaleway`_ ``composer require symfony/scaleway-mailer`` -`SendGrid`_ ``composer require symfony/sendgrid-mailer`` yes -`Sweego`_ ``composer require symfony/sweego-mailer`` yes -===================== =============================================== =============== +`SendGrid`_ ``composer require symfony/sendgrid-mailer`` yes +`Sweego`_ ``composer require symfony/sweego-mailer`` yes +===================== =================================================== =============== .. versionadded:: 7.1 @@ -132,6 +133,10 @@ Service Install with Webhook su The AhaSend integration was introduced in Symfony 7.3. +.. versionadded:: 7.4 + + The Microsoft Graph integration and support for Mailtrap's sandbox environment were introduced in Symfony 7.4. + .. note:: As a convenience, Symfony also provides support for Gmail (``composer @@ -177,83 +182,90 @@ transport, but you can force to use one: This table shows the full list of available DSN formats for each third party provider: -+------------------------+---------------------------------------------------------+ -| Provider | Formats | -+========================+=========================================================+ -| `AhaSend`_ | - SMTP ``ahasend+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP n/a | -| | - API ``ahasend+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Amazon SES`_ | - SMTP ``ses+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP ``ses+https://ACCESS_KEY:SECRET_KEY@default`` | -| | - API ``ses+api://ACCESS_KEY:SECRET_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Azure`_ | - API ``azure+api://ACS_RESOURCE_NAME:KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Brevo`_ | - SMTP ``brevo+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP n/a | -| | - API ``brevo+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Google Gmail`_ | - SMTP ``gmail+smtp://USERNAME:APP-PASSWORD@default`` | -| | - HTTP n/a | -| | - API n/a | -+------------------------+---------------------------------------------------------+ -| `Infobip`_ | - SMTP ``infobip+smtp://KEY@default`` | -| | - HTTP n/a | -| | - API ``infobip+api://KEY@BASE_URL`` | -+------------------------+---------------------------------------------------------+ -| `Mandrill`_ | - SMTP ``mandrill+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP ``mandrill+https://KEY@default`` | -| | - API ``mandrill+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `MailerSend`_ | - SMTP ``mailersend+smtp://KEY@default`` | -| | - HTTP n/a | -| | - API ``mailersend+api://KEY@BASE_URL`` | -+------------------------+---------------------------------------------------------+ -| `Mailgun`_ | - SMTP ``mailgun+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP ``mailgun+https://KEY:DOMAIN@default`` | -| | - API ``mailgun+api://KEY:DOMAIN@default`` | -+------------------------+---------------------------------------------------------+ -| `Mailjet`_ | - SMTP ``mailjet+smtp://ACCESS_KEY:SECRET_KEY@default`` | -| | - HTTP n/a | -| | - API ``mailjet+api://ACCESS_KEY:SECRET_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Mailomat`_ | - SMTP ``mailomat+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP n/a | -| | - API ``mailomat+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `MailPace`_ | - SMTP ``mailpace+api://API_TOKEN@default`` | -| | - HTTP n/a | -| | - API ``mailpace+api://API_TOKEN@default`` | -+------------------------+---------------------------------------------------------+ -| `Mailtrap`_ | - SMTP ``mailtrap+smtp://PASSWORD@default`` | -| | - HTTP n/a | -| | - API ``mailtrap+api://API_TOKEN@default`` | -+------------------------+---------------------------------------------------------+ -| `Postal`_ | - SMTP n/a | -| | - HTTP n/a | -| | - API ``postal+api://API_KEY@BASE_URL`` | -+------------------------+---------------------------------------------------------+ -| `Postmark`_ | - SMTP ``postmark+smtp://ID@default`` | -| | - HTTP n/a | -| | - API ``postmark+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Resend`_ | - SMTP ``resend+smtp://resend:API_KEY@default`` | -| | - HTTP n/a | -| | - API ``resend+api://API_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Scaleway`_ | - SMTP ``scaleway+smtp://PROJECT_ID:API_KEY@default`` | -| | - HTTP n/a | -| | - API ``scaleway+api://PROJECT_ID:API_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Sendgrid`_ | - SMTP ``sendgrid+smtp://KEY@default`` | -| | - HTTP n/a | -| | - API ``sendgrid+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Sweego`_ | - SMTP ``sweego+smtp://LOGIN:PASSWORD@HOST:PORT`` | -| | - HTTP n/a | -| | - API ``sweego+api://API_KEY@default`` | -+------------------------+---------------------------------------------------------+ ++------------------------+-------------------------------------------------------------------------------------------+ +| Provider | Formats | ++========================+===========================================================================================+ +| `AhaSend`_ | - SMTP ``ahasend+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP n/a | +| | - API ``ahasend+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Amazon SES`_ | - SMTP ``ses+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP ``ses+https://ACCESS_KEY:SECRET_KEY@default`` | +| | - API ``ses+api://ACCESS_KEY:SECRET_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Azure`_ | - SMTP n/a | +| | - HTTP n/a | +| | - API ``azure+api://ACS_RESOURCE_NAME:KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Brevo`_ | - SMTP ``brevo+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP n/a | +| | - API ``brevo+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Google Gmail`_ | - SMTP ``gmail+smtp://USERNAME:APP-PASSWORD@default`` | +| | - HTTP n/a | +| | - API n/a | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Infobip`_ | - SMTP ``infobip+smtp://KEY@default`` | +| | - HTTP n/a | +| | - API ``infobip+api://KEY@BASE_URL`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mandrill`_ | - SMTP ``mandrill+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP ``mandrill+https://KEY@default`` | +| | - API ``mandrill+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `MailerSend`_ | - SMTP ``mailersend+smtp://KEY@default`` | +| | - HTTP n/a | +| | - API ``mailersend+api://KEY@BASE_URL`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailgun`_ | - SMTP ``mailgun+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP ``mailgun+https://KEY:DOMAIN@default`` | +| | - API ``mailgun+api://KEY:DOMAIN@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailjet`_ | - SMTP ``mailjet+smtp://ACCESS_KEY:SECRET_KEY@default`` | +| | - HTTP n/a | +| | - API ``mailjet+api://ACCESS_KEY:SECRET_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailomat`_ | - SMTP ``mailomat+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP n/a | +| | - API ``mailomat+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `MailPace`_ | - SMTP ``mailpace+api://API_TOKEN@default`` | +| | - HTTP n/a | +| | - API ``mailpace+api://API_TOKEN@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailtrap`_ | - SMTP ``mailtrap+smtp://PASSWORD@default`` | +| | - HTTP n/a | +| | - API (Live) ``mailtrap+api://API_TOKEN@default`` | +| | - API (Sandbox) ``mailtrap+sandbox://API_TOKEN@default/?inboxId=INBOX_ID`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Microsoft Graph`_ | - SMTP n/a | +| | - HTTP n/a | +| | - API ``microsoftgraph+api://CLIENT_APP_ID:CLIENT_APP_SECRET@default?tenantId=TENANT_ID`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Postal`_ | - SMTP n/a | +| | - HTTP n/a | +| | - API ``postal+api://API_KEY@BASE_URL`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Postmark`_ | - SMTP ``postmark+smtp://ID@default`` | +| | - HTTP n/a | +| | - API ``postmark+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Resend`_ | - SMTP ``resend+smtp://resend:API_KEY@default`` | +| | - HTTP n/a | +| | - API ``resend+api://API_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Scaleway`_ | - SMTP ``scaleway+smtp://PROJECT_ID:API_KEY@default`` | +| | - HTTP n/a | +| | - API ``scaleway+api://PROJECT_ID:API_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Sendgrid`_ | - SMTP ``sendgrid+smtp://KEY@default`` | +| | - HTTP n/a | +| | - API ``sendgrid+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Sweego`_ | - SMTP ``sweego+smtp://LOGIN:PASSWORD@HOST:PORT`` | +| | - HTTP n/a | +| | - API ``sweego+api://API_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ .. warning:: @@ -2294,6 +2306,7 @@ the :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait`:: .. _`Markdown syntax`: https://commonmark.org/ .. _`Mailomat`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Mailomat/README.md .. _`MailPace`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/MailPace/README.md +.. _`Microsoft Graph`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/README.md .. _`OpenSSL PHP extension`: https://www.php.net/manual/en/book.openssl.php .. _`PEM encoded`: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail .. _`Postal`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Postal/README.md diff --git a/messenger.rst b/messenger.rst index 0bd57cfdf5b..7df8612d93d 100644 --- a/messenger.rst +++ b/messenger.rst @@ -543,6 +543,22 @@ command with the ``--all`` option: The ``--all`` option was introduced in Symfony 7.1. +When using ``--all``, you can exclude specific receivers using the ``--exclude-receivers`` +option (shortcut ``-eq``): + +.. code-block:: terminal + + $ php bin/console messenger:consume --all --exclude-receivers=async_priority_low --exclude-receivers=failed + +.. versionadded:: 7.4 + + The ``--exclude-receivers`` option was introduced in Symfony 7.4. + +.. note:: + + The ``--exclude-receivers`` option can only be used together with ``--all``. + Also, you cannot exclude all receivers. + Messages that take a long time to process may be redelivered prematurely because some transports assume that an unacknowledged message is lost. To prevent this issue, use the ``--keepalive`` command option to specify an interval (in seconds; @@ -2115,6 +2131,10 @@ The transport has a number of options: ``debug`` (default: ``false``) If ``true`` it logs all HTTP requests and responses (it impacts performance) +``delete_on_rejection`` (default: ``true``) + If set to ``false``, the message will not be deleted when rejected. Instead, + its visibility will be changed so that SQS can handle retries + ``endpoint`` (default: ``https://sqs.eu-west-1.amazonaws.com``) Absolute URL to the SQS service @@ -2133,6 +2153,10 @@ The transport has a number of options: ``region`` (default: ``eu-west-1``) Name of the AWS region +``retry_delay`` (default: ``0``) + Used only when ``delete_on_rejection`` is ``false``. Defines the visibility + timeout (in seconds) that SQS should apply when a message is rejected + ``secret_key`` AWS secret key (must be urlencoded) @@ -2149,6 +2173,10 @@ The transport has a number of options: The ``queue_attributes`` and ``queue_tags`` options were introduced in Symfony 7.3. +.. versionadded:: 7.4 + + The ``delete_on_rejection`` and ``retry_delay`` options were introduced in Symfony 7.4. + .. note:: The ``wait_time`` parameter defines the maximum duration Amazon SQS should @@ -2389,6 +2417,88 @@ contains many useful information such as the exit code or the output of the process. You can refer to the page dedicated on :ref:`handler results ` for more information. +Securing Messages with Signatures +--------------------------------- + +When messages are sent to message queues, there is a potential security risk +if an attacker injects forged payloads into the queue. Although message queues +should be properly secured to prevent unauthorized access, Symfony adds an extra +layer of protection by supporting message signing. + +This is particularly important for handlers that execute commands or processes, +which is why the ``RunProcessHandler`` has message signing **enabled by default**. + +Enabling Message Signing +~~~~~~~~~~~~~~~~~~~~~~~~ + +To enable message signing for your handler, set the ``sign`` option to ``true``: + +.. configuration-block:: + + .. code-block:: php-attributes + + // src/MessageHandler/SmsNotificationHandler.php + namespace App\MessageHandler; + + use App\Message\SmsNotification; + use Symfony\Component\Messenger\Attribute\AsMessageHandler; + + #[AsMessageHandler(sign: true)] + class SmsNotificationHandler + { + public function __invoke(SmsNotification $message): void + { + // ... handle message + } + } + + .. code-block:: yaml + + # config/services.yaml + services: + App\MessageHandler\SmsNotificationHandler: + tags: + - { name: messenger.message_handler, sign: true } + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + use App\MessageHandler\SmsNotificationHandler; + + $container->register(SmsNotificationHandler::class) + ->addTag('messenger.message_handler', ['sign' => true]); + +When signing is enabled: + +1. Messages are signed using an HMAC signature computed with your application's + secret key (``kernel.secret`` parameter). +2. The signature is added to the message headers (``Body-Sign`` and ``Sign-Algo``) + when the message is sent to a transport. +3. When the message is received and decoded, the signature is automatically verified. +4. If the signature is missing or invalid, an + :class:`Symfony\\Component\\Messenger\\Exception\\InvalidMessageSignatureException` + is thrown, and the message will not be handled. + +.. versionadded:: 7.4 + + Message signing support was introduced in Symfony 7.4. + Pinging A Webservice -------------------- @@ -2635,6 +2745,15 @@ Possible options to configure with tags are: Defines the order in which the handler is executed when multiple handlers can process the same message; those with higher priority run first. +``sign`` + Whether messages handled by this handler should be cryptographically signed + to prevent tampering. When enabled, messages are signed using HMAC with the + application's secret key. Default: ``false``. + + .. versionadded:: 7.4 + + The ``sign`` option was introduced in Symfony 7.4. + .. _handler-subscriber-options: Handling Multiple Messages @@ -3275,6 +3394,7 @@ In addition to middleware, Messenger also dispatches several events. You can of the process. For each, the event class is the event name: * :class:`Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent` +* :class:`Symfony\\Component\\Messenger\\Event\\MessageSentToTransportsEvent` * :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent` * :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent` * :class:`Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent` @@ -3284,6 +3404,16 @@ of the process. For each, the event class is the event name: * :class:`Symfony\\Component\\Messenger\\Event\\WorkerStartedEvent` * :class:`Symfony\\Component\\Messenger\\Event\\WorkerStoppedEvent` +.. versionadded:: 7.4 + + The ``MessageSentToTransportsEvent`` event was introduced in Symfony 7.4. + +.. note:: + + The ``MessageSentToTransportsEvent`` event is dispatched **only** after a + message was sent to at least one transport. If the message was sent to + multiple transports, the event is dispatched only once. + Additional Handler Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/object_mapper.rst b/object_mapper.rst index 625466ffefc..19b5032e2d4 100644 --- a/object_mapper.rst +++ b/object_mapper.rst @@ -3,8 +3,7 @@ Object Mapper .. versionadded:: 7.3 - The ObjectMapper component was introduced in Symfony 7.3 as an - :doc:`experimental feature `. + The ObjectMapper component was introduced in Symfony 7.3. This component transforms one object into another, simplifying tasks such as converting DTOs (Data Transfer Objects) into entities or vice versa. It can also @@ -429,6 +428,33 @@ And the related target object must define the ``createFromLegacy()`` method:: } } +Mapping Collections +------------------- + +By default, ObjectMapper does not map arrays or traversable collections. To map +each item in a collection (for example, mapping an array of DTOs to an array of +entities), you **must** explicitly use the ``MapCollection`` transformer: + +Example:: + + use Symfony\Component\ObjectMapper\Attribute\Map; + use Symfony\Component\ObjectMapper\Transform\MapCollection; + + class ProductListInput + { + #[Map(transform: new MapCollection())] + /** @var ProductInput[] */ + public array $products; + } + +With this configuration, ObjectMapper maps each item in the ``products`` array +according to the usual mapping rules. Without ``transform: new MapCollection()``, +the array is left unchanged. + +.. versionadded:: 7.4 + + The ``MapCollection`` transformer was introduced in Symfony 7.4. + Mapping Multiple Targets ------------------------ @@ -590,6 +616,51 @@ Using it in practice:: // $employeeDto->manager->name === 'Alice' // $employeeDto->manager->manager === $employeeDto +Decorating the ObjectMapper +--------------------------- + +The ``object_mapper`` service can be decorated to add custom logic or manage +state around the mapping process. + +You can use the :class:`Symfony\\Component\\ObjectMapper\\ObjectMapperAwareInterface` +to enable the decorated service to access the outermost decorator. If the +decorated service implements this interface, the decorator can pass itself to +it. This allows underlying services, like the ``ObjectMapper``, to call the +decorator's ``map()`` method during recursive mapping, ensuring that the +decorator's state is used consistently throughout the process. + +Here's an example of a decorator that preserves object identity across calls. +It uses the ``AsDecorator`` attribute to automatically configure itself as a +decorator of the ``object_mapper`` service:: + + // src/ObjectMapper/StatefulObjectMapper.php + namespace App\ObjectMapper; + + use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + use Symfony\Component\ObjectMapper\ObjectMapperAwareInterface; + use Symfony\Component\ObjectMapper\ObjectMapperInterface; + + #[AsDecorator(decorates: ObjectMapperInterface::class)] + final class StatefulObjectMapper implements ObjectMapperInterface + { + public function __construct(private ObjectMapperInterface $decorated) + { + // pass this decorator to the decorated service if it's aware + if ($this->decorated instanceof ObjectMapperAwareInterface) { + $this->decorated = $this->decorated->withObjectMapper($this); + } + } + + public function map(object $source, object|string|null $target = null): object + { + return $this->decorated->map($source, $target); + } + } + +.. versionadded:: 7.4 + + The feature to decorate the ObjetMapper was introduced in Symfony 7.4. + .. _objectmapper-custom-mapping-logic: Custom Mapping Logic diff --git a/page_creation.rst b/page_creation.rst index 0e2fd78e180..c4aa0cfc856 100644 --- a/page_creation.rst +++ b/page_creation.rst @@ -87,7 +87,7 @@ try it out by going to: http://localhost:8000/lucky/number Symfony recommends defining routes as attributes to have the controller code and its route configuration at the same location. However, if you prefer, you can - :doc:`define routes in separate files ` using YAML, XML and PHP formats. + :doc:`define routes in separate files ` using the YAML or PHP formats. If you see a lucky number being printed back to you, congratulations! But before you run off to play the lottery, check out how this works. Remember the two steps @@ -125,11 +125,11 @@ You should see your ``app_lucky_number`` route in the list: .. code-block:: terminal - ---------------- ------- ------- ----- -------------- - Name Method Scheme Host Path - ---------------- ------- ------- ----- -------------- - app_lucky_number ANY ANY ANY /lucky/number - ---------------- ------- ------- ----- -------------- + ---------------- ------- -------------- + Name Method Path + ---------------- ------- -------------- + app_lucky_number ANY /lucky/number + ---------------- ------- -------------- You will also see debugging routes besides ``app_lucky_number`` -- more on the debugging routes in the next section. diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index 3b66570b3d3..c942c917a01 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -243,29 +243,6 @@ using the special ``when@`` keyword: router: strict_requirements: null - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php // config/packages/framework.php diff --git a/rate_limiter.rst b/rate_limiter.rst index 4a072dbac89..db1b6d6eed8 100644 --- a/rate_limiter.rst +++ b/rate_limiter.rst @@ -262,7 +262,7 @@ argument named ``$anonymousApiLimiter``:: When :ref:`dealing with multiple implementations of the same type ` the ``#[Target]`` attribute helps you select which one to inject. Symfony creates -a target called "rate limiter name" + ``.limiter`` suffix. +a target with the same name as the rate limiter. For example, to select the ``anonymous_api`` limiter defined earlier, use ``anonymous_api.limiter`` as the target:: @@ -273,7 +273,7 @@ For example, to select the ``anonymous_api`` limiter defined earlier, use class ApiController extends AbstractController { public function index( - #[Target('anonymous_api.limiter')] RateLimiterFactoryInterface $rateLimiter + #[Target('anonymous_api')] RateLimiterFactoryInterface $rateLimiter ): Response { // ... @@ -286,6 +286,11 @@ For example, to select the ``anonymous_api`` limiter defined earlier, use added and should now be used for autowiring instead of :class:`Symfony\\Component\\RateLimiter\\RateLimiterFactory`. +.. versionadded:: 7.4 + + Before Symfony 7.4, the target name had to include the ``.limiter`` + suffix (e.g. ``#[Target('anonymous_api.limiter')]``). + Using the Rate Limiter Service ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 5eef19de7f2..447b1328985 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -301,7 +301,7 @@ to inject the ``foo_package`` package defined earlier:: When :ref:`dealing with multiple implementations of the same type ` the ``#[Target]`` attribute helps you select which one to inject. Symfony creates -a target called "asset package name" + ``.package`` suffix. +a target with the same name as the asset package. For example, to select the ``foo_package`` package defined earlier:: @@ -311,12 +311,17 @@ For example, to select the ``foo_package`` package defined earlier:: class SomeService { public function __construct( - #[Target('foo_package.package')] private PackageInterface $package + #[Target('foo_package')] private PackageInterface $package ): void { // ... } } +.. versionadded:: 7.4 + + Before Symfony 7.4, the target name had to include the ``.package`` + suffix (e.g. ``#[Target('foo_package.package')]``). + .. _reference-framework-assets-packages: packages @@ -1656,6 +1661,88 @@ 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). +caching +....... + +**type**: ``array`` + +This option configures the behavior of the :ref:`HTTP client caching `, +including which types of requests to cache and how many times. The behavior is +defined with the following options: + +* :ref:`cache_pool ` +* :ref:`shared ` +* :ref:`max_ttl ` + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + # ... + default_options: + caching: + cache_pool: cache.app + shared: true + max_ttl: 86400 + + scoped_clients: + my_api.client: + # ... + caching: + cache_pool: my_taggable_pool + +.. versionadded:: 7.4 + + The ``caching`` option was introduced in Symfony 7.4. + +.. _reference-http-client-caching-cache-pool: + +cache_pool +"""""""""" + +**type**: ``string`` + +The service ID of the cache pool used to store the cached responses. The service +must implement the :class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface`. + +By default, it uses an instance of :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` +wrapping the ``cache.app`` pool. + +.. versionadded:: 7.4 + + The ``cache_pool`` option was introduced in Symfony 7.4. + +.. _reference-http-client-caching-shared: + +shared +"""""" + +**type**: ``boolean`` **default**: ``true`` + +If ``true``, it uses a `shared cache`_ so cached responses can be reused across +users. Set it to ``false`` to use a `private cache`_. + +.. versionadded:: 7.4 + + The ``shared`` option was introduced in Symfony 7.4. + +.. _reference-http-client-caching-max-ttl: + +max_ttl +""""""" + +**type**: ``integer`` **default**: ``null`` + +The maximum time-to-live (in seconds) for cached responses. By default, responses +are cached for as long as the TTL specified by the server. When this option is +set, server-provided TTLs are capped to this value. + +.. versionadded:: 7.4 + + The ``max_ttl`` option was introduced in Symfony 7.4. + cafile ...... @@ -2034,6 +2121,85 @@ named ``kernel.http_method_override``. $request = Request::createFromGlobals(); // ... +.. _configuration-framework-allowed_http_method_override: + +allowed_http_method_override +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 7.4 + + The ``allowed_http_method_override`` option was introduced in Symfony 7.4. + +**type**: ``array`` **default**: ``null`` + +This option controls which HTTP methods can be overridden via the ``_method`` +request parameter or the ``X-HTTP-METHOD-OVERRIDE`` header when +:ref:`http_method_override ` is enabled. + +When set to ``null`` (the default), all HTTP methods can be overridden. When set +to an empty array (``[]``), HTTP method overriding is completely disabled. When +set to a specific list of methods, only those methods will be allowed as overrides: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + http_method_override: true + # Only allow PUT, PATCH, and DELETE to be overridden + allowed_http_method_override: ['PUT', 'PATCH', 'DELETE'] + + .. code-block:: xml + + + + + + + PUT + PATCH + DELETE + + + + .. code-block:: php + + // config/packages/framework.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $framework + ->httpMethodOverride(true) + ->allowedHttpMethodOverride(['PUT', 'PATCH', 'DELETE']) + ; + }; + +This security feature is useful for hardening your application by explicitly +defining which methods can be tunneled through POST requests. For example, if +your application only needs to override POST requests to PUT and DELETE, you +can restrict the allowed methods accordingly. + +You can also configure this programmatically using the +:method:`Request::setAllowedHttpMethodOverride ` +method:: + + // public/index.php + + // ... + $kernel = new CacheKernel($kernel); + + Request::enableHttpMethodParameterOverride(); + Request::setAllowedHttpMethodOverride(['PUT', 'PATCH', 'DELETE']); + $request = Request::createFromGlobals(); + // ... + .. _reference-framework-ide: ide @@ -4073,3 +4239,5 @@ to know their differences. .. _`Link HTTP header`: https://tools.ietf.org/html/rfc5988 .. _`SMTP session`: https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_transport_example .. _`PHP attributes`: https://www.php.net/manual/en/language.attributes.overview.php +.. _`shared cache`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching#shared_cache +.. _`private cache`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching#private_caches diff --git a/reference/configuration/kernel.rst b/reference/configuration/kernel.rst index b7596182906..443fb98ef31 100644 --- a/reference/configuration/kernel.rst +++ b/reference/configuration/kernel.rst @@ -347,6 +347,22 @@ servers support it, and you have to use a long-running web server like `FrankenP This parameter stores the value of :ref:`the framework.secret parameter `. +``kernel.share_dir`` +-------------------- + +**type**: ``string`` **default**: ``$this->getCacheDir()`` + +This parameter stores the absolute path of the shared cache directory of your Symfony +application. The default value is the current cache directory. + +This value is also exposed via the :method:`Symfony\\Component\\HttpKernel\\Kernel::getShareDir` +method of the kernel class, which you can override to return a different value. + +.. versionadded:: 7.4 + + The ``Kernel::getShareDir()`` method and the ``%kernel.share_dir`` parameter + were introduced in Symfony 7.4. + ``kernel.trust_x_sendfile_type_header`` --------------------------------------- diff --git a/reference/configuration/monolog.rst b/reference/configuration/monolog.rst index 3e1ffab21a8..9c781f4e1e4 100644 --- a/reference/configuration/monolog.rst +++ b/reference/configuration/monolog.rst @@ -30,4 +30,4 @@ in your application configuration. messages in the profiler. The profiler uses the name "debug" so it is reserved and cannot be used in the configuration. -.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/3.x/src/DependencyInjection/Configuration.php +.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/4.x/src/DependencyInjection/Configuration.php diff --git a/reference/constraints/All.rst b/reference/constraints/All.rst index 43ff4d6ac9d..127b005a01c 100644 --- a/reference/constraints/All.rst +++ b/reference/constraints/All.rst @@ -41,9 +41,10 @@ entry in that array: properties: favoriteColors: - All: - - NotBlank: ~ - - Length: - min: 5 + constraints: + - NotBlank: ~ + - Length: + min: 5 .. code-block:: xml diff --git a/reference/constraints/AtLeastOneOf.rst b/reference/constraints/AtLeastOneOf.rst index fecbe617f5a..c4be1657dfc 100644 --- a/reference/constraints/AtLeastOneOf.rst +++ b/reference/constraints/AtLeastOneOf.rst @@ -53,7 +53,8 @@ The following constraints ensure that: properties: password: - AtLeastOneOf: - - Regex: '/#/' + - Regex: + pattern: '/#/' - Length: min: 10 grades: @@ -61,7 +62,9 @@ The following constraints ensure that: - Count: min: 3 - All: - - GreaterThanOrEqual: 5 + constraints: + - GreaterThanOrEqual: + value: 5 .. code-block:: xml @@ -93,7 +96,7 @@ The following constraints ensure that: diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst index 017b9435cff..197312f1812 100644 --- a/reference/constraints/Callback.rst +++ b/reference/constraints/Callback.rst @@ -50,7 +50,8 @@ Configuration # config/validator/validation.yaml App\Entity\Author: constraints: - - Callback: validate + - Callback: + callback: validate .. code-block:: xml @@ -61,7 +62,9 @@ Configuration xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - validate + + + @@ -177,7 +180,8 @@ You can then use the following configuration to invoke this validator: # config/validator/validation.yaml App\Entity\Author: constraints: - - Callback: [Acme\Validator, validate] + - Callback: + callback: [Acme\Validator, validate] .. code-block:: xml @@ -189,8 +193,10 @@ You can then use the following configuration to invoke this validator: - Acme\Validator - validate + diff --git a/reference/constraints/Charset.rst b/reference/constraints/Charset.rst index 084f24cdf76..746d0e44aa7 100644 --- a/reference/constraints/Charset.rst +++ b/reference/constraints/Charset.rst @@ -41,7 +41,8 @@ class uses UTF-8, you could do the following: App\Entity\FileDTO: properties: content: - - Charset: 'UTF-8' + - Charset: + charset: 'UTF-8' .. code-block:: xml diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst index 6378ed67cfb..5f0e76d681a 100644 --- a/reference/constraints/Choice.rst +++ b/reference/constraints/Choice.rst @@ -34,7 +34,7 @@ If your valid choice list is simple, you can pass them in directly via the { public const GENRES = ['fiction', 'non-fiction']; - #[Assert\Choice(['New York', 'Berlin', 'Tokyo'])] + #[Assert\Choice(choices: ['New York', 'Berlin', 'Tokyo'])] protected string $city; #[Assert\Choice(choices: Author::GENRES, message: 'Choose a valid genre.')] @@ -47,7 +47,8 @@ If your valid choice list is simple, you can pass them in directly via the App\Entity\Author: properties: city: - - Choice: [New York, Berlin, Tokyo] + - Choice: + choices: [New York, Berlin, Tokyo] genre: - Choice: choices: [fiction, non-fiction] @@ -64,9 +65,11 @@ If your valid choice list is simple, you can pass them in directly via the - New York - Berlin - Tokyo + @@ -97,7 +100,7 @@ If your valid choice list is simple, you can pass them in directly via the { $metadata->addPropertyConstraint( 'city', - new Assert\Choice(['New York', 'Berlin', 'Tokyo']) + new Assert\Choice(choices: ['New York', 'Berlin', 'Tokyo']) ); $metadata->addPropertyConstraint('genre', new Assert\Choice( @@ -107,6 +110,12 @@ If your valid choice list is simple, you can pass them in directly via the } } +.. deprecated:: 7.4 + + Passing an array of choices as the first argument of the ``Choice`` constraint + is deprecated and will stop working in Symfony 8.0. Instead, pass the choices + using the ``choices:`` named argument. + Supplying the Choices with a Callback Function ---------------------------------------------- diff --git a/reference/constraints/DivisibleBy.rst b/reference/constraints/DivisibleBy.rst index 23b36023cff..9685f46fe82 100644 --- a/reference/constraints/DivisibleBy.rst +++ b/reference/constraints/DivisibleBy.rst @@ -49,7 +49,8 @@ The following constraints ensure that: App\Entity\Item: properties: weight: - - DivisibleBy: 0.25 + - DivisibleBy: + value: 0.25 quantity: - DivisibleBy: value: 5 @@ -65,7 +66,7 @@ The following constraints ensure that: - 0.25 + diff --git a/reference/constraints/EqualTo.rst b/reference/constraints/EqualTo.rst index fdc402b1a97..d0b9c2b2c75 100644 --- a/reference/constraints/EqualTo.rst +++ b/reference/constraints/EqualTo.rst @@ -48,7 +48,8 @@ and that the ``age`` is ``20``, you could do the following: App\Entity\Person: properties: firstName: - - EqualTo: Mary + - EqualTo: + value: Mary age: - EqualTo: value: 20 @@ -64,7 +65,7 @@ and that the ``age`` is ``20``, you could do the following: - Mary + diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst index 62efa6cc08e..c773237af41 100644 --- a/reference/constraints/File.rst +++ b/reference/constraints/File.rst @@ -237,6 +237,8 @@ Parameter Description ``{{ suffix }}`` Suffix for the used file size unit (see above) ================ ============================================================= +.. _reference-constraints-file-mime-types: + ``mimeTypes`` ~~~~~~~~~~~~~ diff --git a/reference/constraints/GreaterThan.rst b/reference/constraints/GreaterThan.rst index d1b79028acd..c8b72851068 100644 --- a/reference/constraints/GreaterThan.rst +++ b/reference/constraints/GreaterThan.rst @@ -46,7 +46,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - GreaterThan: 5 + - GreaterThan: + value: 5 age: - GreaterThan: value: 18 @@ -62,7 +63,7 @@ The following constraints ensure that: - 5 + @@ -182,7 +183,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Order: properties: deliveryDate: - - GreaterThan: today UTC + - GreaterThan: + value: today UTC .. code-block:: xml @@ -194,7 +196,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -242,7 +246,8 @@ current time: App\Entity\Order: properties: deliveryDate: - - GreaterThan: +5 hours + - GreaterThan: + value: +5 hours .. code-block:: xml @@ -254,7 +259,9 @@ current time: - +5 hours + + + diff --git a/reference/constraints/GreaterThanOrEqual.rst b/reference/constraints/GreaterThanOrEqual.rst index 63c2ade6197..39eb81e917b 100644 --- a/reference/constraints/GreaterThanOrEqual.rst +++ b/reference/constraints/GreaterThanOrEqual.rst @@ -45,7 +45,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - GreaterThanOrEqual: 5 + - GreaterThanOrEqual: + value: 5 age: - GreaterThanOrEqual: value: 18 @@ -61,7 +62,7 @@ The following constraints ensure that: - 5 + @@ -122,7 +123,8 @@ that a date must at least be the current day: App\Entity\Order: properties: deliveryDate: - - GreaterThanOrEqual: today + - GreaterThanOrEqual: + value: today .. code-block:: xml @@ -134,7 +136,9 @@ that a date must at least be the current day: - today + + + @@ -181,7 +185,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Order: properties: deliveryDate: - - GreaterThanOrEqual: today UTC + - GreaterThanOrEqual: + value: today UTC .. code-block:: xml @@ -193,7 +198,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -241,7 +248,8 @@ current time: App\Entity\Order: properties: deliveryDate: - - GreaterThanOrEqual: +5 hours + - GreaterThanOrEqual: + value: +5 hours .. code-block:: xml @@ -253,7 +261,9 @@ current time: - +5 hours + + + diff --git a/reference/constraints/IdenticalTo.rst b/reference/constraints/IdenticalTo.rst index f8844f90a72..024ad7ad2a1 100644 --- a/reference/constraints/IdenticalTo.rst +++ b/reference/constraints/IdenticalTo.rst @@ -51,7 +51,8 @@ The following constraints ensure that: App\Entity\Person: properties: firstName: - - IdenticalTo: Mary + - IdenticalTo: + value: Mary age: - IdenticalTo: value: 20 @@ -67,7 +68,7 @@ The following constraints ensure that: - Mary + diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst index c1a8575070b..dde07bacd0a 100644 --- a/reference/constraints/Length.rst +++ b/reference/constraints/Length.rst @@ -201,10 +201,16 @@ You can use the following parameters in this message: Parameter Description ====================== ============================================================ ``{{ limit }}`` The expected maximum length +``{{ min }}`` The expected minimum length +``{{ max }}`` The expected maximum length ``{{ value }}`` The current (invalid) value ``{{ value_length }}`` The current value's length ====================== ============================================================ +.. versionadded:: 7.4 + + The ``{{ min }}`` and ``{{ max }}`` parameters were introduced in Symfony 7.4. + ``min`` ~~~~~~~ @@ -233,10 +239,16 @@ You can use the following parameters in this message: Parameter Description ====================== ============================================================ ``{{ limit }}`` The expected minimum length +``{{ min }}`` The expected minimum length +``{{ max }}`` The expected maximum length ``{{ value }}`` The current (invalid) value ``{{ value_length }}`` The current value's length ====================== ============================================================ +.. versionadded:: 7.4 + + The ``{{ min }}`` and ``{{ max }}`` parameters were introduced in Symfony 7.4. + .. include:: /reference/constraints/_normalizer-option.rst.inc .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/LessThan.rst b/reference/constraints/LessThan.rst index 3d23bcda445..859d7bae83f 100644 --- a/reference/constraints/LessThan.rst +++ b/reference/constraints/LessThan.rst @@ -46,7 +46,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - LessThan: 5 + - LessThan: + value: 5 age: - LessThan: value: 80 @@ -62,7 +63,7 @@ The following constraints ensure that: - 5 + @@ -123,7 +124,8 @@ that a date must be in the past like this: App\Entity\Person: properties: dateOfBirth: - - LessThan: today + - LessThan: + value: today .. code-block:: xml @@ -135,7 +137,9 @@ that a date must be in the past like this: - today + + + @@ -182,7 +186,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Person: properties: dateOfBirth: - - LessThan: today UTC + - LessThan: + value: today UTC .. code-block:: xml @@ -194,7 +199,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -241,7 +248,8 @@ can check that a person must be at least 18 years old like this: App\Entity\Person: properties: dateOfBirth: - - LessThan: -18 years + - LessThan: + value: -18 years .. code-block:: xml @@ -253,7 +261,9 @@ can check that a person must be at least 18 years old like this: - -18 years + + + diff --git a/reference/constraints/LessThanOrEqual.rst b/reference/constraints/LessThanOrEqual.rst index ac66c62d7d0..40bdbebc739 100644 --- a/reference/constraints/LessThanOrEqual.rst +++ b/reference/constraints/LessThanOrEqual.rst @@ -45,7 +45,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - LessThanOrEqual: 5 + - LessThanOrEqual: + value: 5 age: - LessThanOrEqual: value: 80 @@ -61,7 +62,7 @@ The following constraints ensure that: - 5 + @@ -122,7 +123,8 @@ that a date must be today or in the past like this: App\Entity\Person: properties: dateOfBirth: - - LessThanOrEqual: today + - LessThanOrEqual: + value: today .. code-block:: xml @@ -134,7 +136,9 @@ that a date must be today or in the past like this: - today + + + @@ -181,7 +185,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Person: properties: dateOfBirth: - - LessThanOrEqual: today UTC + - LessThanOrEqual: + value: today UTC .. code-block:: xml @@ -193,7 +198,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -240,7 +247,8 @@ can check that a person must be at least 18 years old like this: App\Entity\Person: properties: dateOfBirth: - - LessThanOrEqual: -18 years + - LessThanOrEqual: + value: -18 years .. code-block:: xml @@ -252,7 +260,9 @@ can check that a person must be at least 18 years old like this: - -18 years + + + diff --git a/reference/constraints/Negative.rst b/reference/constraints/Negative.rst index 0d043ee8f6e..b47806f5291 100644 --- a/reference/constraints/Negative.rst +++ b/reference/constraints/Negative.rst @@ -50,7 +50,7 @@ The following constraint ensures that the ``withdraw`` of a bank account - + diff --git a/reference/constraints/NegativeOrZero.rst b/reference/constraints/NegativeOrZero.rst index 5f221950528..f6d94af84ee 100644 --- a/reference/constraints/NegativeOrZero.rst +++ b/reference/constraints/NegativeOrZero.rst @@ -49,7 +49,7 @@ is a negative number or equal to zero: - + diff --git a/reference/constraints/NotCompromisedPassword.rst b/reference/constraints/NotCompromisedPassword.rst index 6641f9d8cb2..b2ae775b0ce 100644 --- a/reference/constraints/NotCompromisedPassword.rst +++ b/reference/constraints/NotCompromisedPassword.rst @@ -49,7 +49,7 @@ The following constraint ensures that the ``rawPassword`` property of the - + diff --git a/reference/constraints/NotEqualTo.rst b/reference/constraints/NotEqualTo.rst index dd3f633b4a1..ce8ecb7dea5 100644 --- a/reference/constraints/NotEqualTo.rst +++ b/reference/constraints/NotEqualTo.rst @@ -50,7 +50,8 @@ the following: App\Entity\Person: properties: firstName: - - NotEqualTo: Mary + - NotEqualTo: + value: Mary age: - NotEqualTo: value: 15 @@ -66,7 +67,7 @@ the following: - Mary + diff --git a/reference/constraints/NotIdenticalTo.rst b/reference/constraints/NotIdenticalTo.rst index b2c20027292..a45731fa2e3 100644 --- a/reference/constraints/NotIdenticalTo.rst +++ b/reference/constraints/NotIdenticalTo.rst @@ -51,7 +51,8 @@ The following constraints ensure that: App\Entity\Person: properties: firstName: - - NotIdenticalTo: Mary + - NotIdenticalTo: + value: Mary age: - NotIdenticalTo: value: 15 @@ -67,7 +68,7 @@ The following constraints ensure that: - Mary + diff --git a/reference/constraints/PasswordStrength.rst b/reference/constraints/PasswordStrength.rst index 0b242cacf08..d1489938236 100644 --- a/reference/constraints/PasswordStrength.rst +++ b/reference/constraints/PasswordStrength.rst @@ -53,7 +53,7 @@ By default, the minimum required score is ``2``. - + diff --git a/reference/constraints/Positive.rst b/reference/constraints/Positive.rst index b43fdde67d8..84c3bc11099 100644 --- a/reference/constraints/Positive.rst +++ b/reference/constraints/Positive.rst @@ -50,7 +50,7 @@ positive number (greater than zero): - + diff --git a/reference/constraints/PositiveOrZero.rst b/reference/constraints/PositiveOrZero.rst index 4aa8420993c..f3c54c85394 100644 --- a/reference/constraints/PositiveOrZero.rst +++ b/reference/constraints/PositiveOrZero.rst @@ -49,7 +49,7 @@ is positive or zero: - + diff --git a/reference/constraints/Regex.rst b/reference/constraints/Regex.rst index e3b4d4711b2..1b48eba1dff 100644 --- a/reference/constraints/Regex.rst +++ b/reference/constraints/Regex.rst @@ -38,7 +38,8 @@ more word characters at the beginning of your string: App\Entity\Author: properties: description: - - Regex: '/^\w+/' + - Regex: + pattern: '/^\w+/' .. code-block:: xml diff --git a/reference/constraints/Sequentially.rst b/reference/constraints/Sequentially.rst index 6d0be1863d0..afb45a72d09 100644 --- a/reference/constraints/Sequentially.rst +++ b/reference/constraints/Sequentially.rst @@ -68,9 +68,11 @@ You can validate each of these constraints sequentially to solve these issues: address: - Sequentially: - NotNull: ~ - - Type: string + - Type: + value: string - Length: { min: 10 } - - Regex: !php/const App\Localization\Place::ADDRESS_REGEX + - Regex: + pattern: !php/const App\Localization\Place::ADDRESS_REGEX - App\Validator\Constraints\Geolocalizable: ~ .. code-block:: xml @@ -85,7 +87,9 @@ You can validate each of these constraints sequentially to solve these issues: - string + + + diff --git a/reference/constraints/Traverse.rst b/reference/constraints/Traverse.rst index 56d400fb964..d0e1254884d 100644 --- a/reference/constraints/Traverse.rst +++ b/reference/constraints/Traverse.rst @@ -167,7 +167,8 @@ disable validating: # config/validator/validation.yaml App\Entity\BookCollection: constraints: - - Traverse: false + - Traverse: + traverse: false .. code-block:: xml @@ -178,7 +179,9 @@ disable validating: xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - false + + + diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst index b49536dff8b..7d2b4ffaa89 100644 --- a/reference/constraints/Type.rst +++ b/reference/constraints/Type.rst @@ -58,10 +58,12 @@ The following example checks if ``emailAddress`` is an instance of ``Symfony\Com App\Entity\Author: properties: emailAddress: - - Type: Symfony\Component\Mime\Address + - Type: + type: Symfony\Component\Mime\Address firstName: - - Type: string + - Type: + type: string age: - Type: diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index 219ce4ed4ef..1ba0bcd27f6 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -56,7 +56,8 @@ between all of the rows in your user table: # config/validator/validation.yaml App\Entity\User: constraints: - - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: email + - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: + fields: email properties: email: - Email: ~ diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst index c3fac520f96..eccee53e05a 100644 --- a/reference/constraints/Url.rst +++ b/reference/constraints/Url.rst @@ -165,7 +165,7 @@ Parameter Description ``protocols`` ~~~~~~~~~~~~~ -**type**: ``array`` **default**: ``['http', 'https']`` +**type**: ``array|string`` **default**: ``['http', 'https']`` The protocols considered to be valid for the URL. For example, if you also consider the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing @@ -237,6 +237,16 @@ the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing } } +The value of this option can also be an asterisk (``*``) to allow all protocols:: + + // allows all protocols whose names are RFC 3986 compliant + // (e.g. 'https://', 'git+ssh://', 'file://', 'custom://') + protocols: '*' + +.. versionadded:: 7.4 + + Support for ``*`` in the ``protocols`` option was introduced in Symfony 7.4. + ``relativeProtocol`` ~~~~~~~~~~~~~~~~~~~~ diff --git a/reference/constraints/Video.rst b/reference/constraints/Video.rst new file mode 100644 index 00000000000..a7b9e284700 --- /dev/null +++ b/reference/constraints/Video.rst @@ -0,0 +1,320 @@ +Video +===== + +Validates that a file is a valid video that meets certain constraints (width, height, +aspect ratio, etc.). It extends the :doc:`File ` constraint +and adds video-specific validation options. + +.. versionadded:: 7.4 + + The ``Video`` constraint was introduced in Symfony 7.4. + +========== =================================================================== +Applies to :ref:`property or method ` +Class :class:`Symfony\\Component\\Validator\\Constraints\\Video` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\VideoValidator` +========== =================================================================== + +Basic Usage +----------- + +This constraint is most commonly used on a property that stores a video file as a +:class:`Symfony\\Component\\HttpFoundation\\File\\File` object. For example, suppose +you're creating a video platform and want to validate uploaded video files:: + + // src/Entity/VideoContent.php + namespace App\Entity; + + use Symfony\Component\HttpFoundation\File\File; + use Symfony\Component\Validator\Constraints as Assert; + + class VideoContent + { + #[Assert\Video( + maxWidth: 1920, + maxHeight: 1080, + maxSize: '100M', + mimeTypes: ['video/mp4', 'video/webm'], + )] + private File $videoFile; + } + +.. warning:: + + This constraint requires the ``ffprobe`` binary to be installed and accessible + on your system. The constraint uses ``ffprobe`` to extract video metadata. + You can install it as part of the `FFmpeg package`_. + +Options +------- + +``allowedCodecs`` +~~~~~~~~~~~~~~~~~ + +**type**: ``array`` **default**: ``['h264', 'hevc', 'h265', 'vp9', 'av1', 'mpeg4', 'mpeg2video']`` + +Defines which `video codecs`_ are allowed. If the video uses a codec not in this +list, validation will fail:: + + // src/Entity/VideoContent.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class VideoContent + { + #[Assert\Video( + allowedCodecs: ['h264', 'hevc'], + )] + private $videoFile; + } + +``allowedContainers`` +~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``array`` **default**: ``['mp4', 'mov', 'mkv', 'webm', 'avi']`` + +Defines which `video container formats`_ are allowed:: + + // src/Entity/VideoContent.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class VideoContent + { + #[Assert\Video( + allowedContainers: ['mp4', 'webm'], + )] + private $videoFile; + } + +``allowLandscape`` +~~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If set to ``false``, the video cannot be landscape oriented (i.e., the width +cannot be greater than the height). + +``allowPortrait`` +~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If set to ``false``, the video cannot be portrait oriented (i.e., the width +cannot be less than the height). + +``allowSquare`` +~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If set to ``false``, the video cannot be a square (i.e., the width and height +cannot be equal). + +``maxHeight`` +~~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the height of the video file must be less than or equal to this value +in pixels. + +``maxPixels`` +~~~~~~~~~~~~~ + +**type**: ``integer`` | ``float`` + +If set, the total number of pixels (``width * height``) of the video file must be less +than or equal to this value. + +``maxRatio`` +~~~~~~~~~~~~ + +**type**: ``integer`` | ``float`` + +If set, the aspect ratio (``width / height``) of the video file must be less +than or equal to this value. For example, a square video has a ratio of 1, +a 16:9 video has a ratio of 1.78, and a 4:3 video has a ratio of 1.33. + +``maxWidth`` +~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the width of the video file must be less than or equal to this value +in pixels. + +``mimeTypes`` +~~~~~~~~~~~~~ + +**type**: ``array`` or ``string`` **default**: ``video/*`` + +See the :ref:`File mimeTypes option ` for a +description of this option and a list of common video MIME types. + +``minHeight`` +~~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the height of the video file must be greater than or equal to this +value in pixels. + +``minPixels`` +~~~~~~~~~~~~~ + +**type**: ``integer`` | ``float`` + +If set, the total number of pixels (``width * height``) of the video file must be greater +than or equal to this value. + +``minRatio`` +~~~~~~~~~~~~ + +**type**: ``integer`` | ``float`` + +If set, the aspect ratio (``width / height``) of the video file must be greater +than or equal to this value. + +``minWidth`` +~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the width of the video file must be greater than or equal to this value +in pixels. + +.. include:: /reference/constraints/_groups-option.rst.inc + +.. include:: /reference/constraints/_payload-option.rst.inc + +Messages +-------- + +``allowLandscapeMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented videos are not allowed.`` + +The message displayed if the video is landscape oriented and landscape videos +are not allowed. + +``allowPortraitMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented videos are not allowed.`` + +The message displayed if the video is portrait oriented and portrait videos are not allowed. + +``allowSquareMessage`` +~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video is square ({{ width }}x{{ height }}px). Square videos are not allowed.`` + +The message displayed if the video is square and square videos are not allowed. + +``corruptedMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video file is corrupted.`` + +The message displayed if the video file is corrupted and cannot be read by ``ffprobe``. + +``maxHeightMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.`` + +The message displayed if the height of the video exceeds ``maxHeight``. + +``maxPixelsMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.`` + +The message displayed if the total number of pixels of the video exceeds ``maxPixels``. + +``maxRatioMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.`` + +The message displayed if the aspect ratio of the video exceeds ``maxRatio``. + +``maxWidthMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.`` + +The message displayed if the width of the video exceeds ``maxWidth``. + +``mimeTypesMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This file is not a valid video.`` + +The message displayed if the media type of the video is not a valid media type +per the ``mimeTypes`` option. + +``minHeightMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.`` + +The message displayed if the height of the video is less than ``minHeight``. + +``minPixelsMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.`` + +The message displayed if the total number of pixels of the video is less than ``minPixels``. + +``minRatioMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.`` + +The message displayed if the aspect ratio of the video is less than ``minRatio``. + +``minWidthMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.`` + +The message displayed if the width of the video is less than ``minWidth``. + +``multipleVideoStreamsMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video contains multiple streams. Only one stream is allowed.`` + +The message displayed if the video file contains multiple video streams. + +``sizeNotDetectedMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The size of the video could not be detected.`` + +The message displayed if ``ffprobe`` cannot detect the dimensions of the video. + +``unsupportedCodecMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``Unsupported video codec "{{ codec }}".`` + +The message displayed if the video uses a codec that is not in the ``allowedCodecs`` list. + +``unsupportedContainerMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``Unsupported video container "{{ container }}".`` + +The message displayed if the video uses a container format that is not in the +``allowedContainers`` list. + +.. _`FFmpeg package`: https://ffmpeg.org/ +.. _`video codecs`: https://en.wikipedia.org/wiki/Comparison_of_video_codecs +.. _`video container formats`: https://en.wikipedia.org/wiki/Comparison_of_video_container_formats diff --git a/reference/constraints/When.rst b/reference/constraints/When.rst index 4b2e8eb7590..1c1292b8f17 100644 --- a/reference/constraints/When.rst +++ b/reference/constraints/When.rst @@ -85,7 +85,8 @@ One way to accomplish this is with the When constraint: App\Model\Discount: properties: value: - - GreaterThan: 0 + - GreaterThan: + value: 0 - When: expression: "this.getType() == 'percent'" constraints: @@ -106,7 +107,9 @@ One way to accomplish this is with the When constraint: xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - 0 + + +