diff --git a/components/browser_kit.rst b/components/browser_kit.rst index 8cf0772298c..ddbbd0f704d 100644 --- a/components/browser_kit.rst +++ b/components/browser_kit.rst @@ -329,6 +329,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/phpunit_bridge.rst b/components/phpunit_bridge.rst index 5ce4c003a11..999e9626b40 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/uid.rst b/components/uid.rst index b4083765436..46c710a0fd5 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 diff --git a/console.rst b/console.rst index be9292f92a5..a9457c1620f 100644 --- a/console.rst +++ b/console.rst @@ -202,6 +202,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/object_mapper.rst b/object_mapper.rst index 625466ffefc..74cbb5756e7 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 diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index 5fda8e9a14f..035bdee7c7e 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -110,9 +110,14 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``2011-06-05 12:15:00``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) +* ``date_point`` (a :ref:`DatePoint ` object) * ``array`` (e.g. ``[2011, 06, 05, 12, 15, 0]``) * ``timestamp`` (e.g. ``1307276100``) +.. versionadded:: 7.4 + + Support for ``date_point`` values was introduced in Symfony 7.4. + The value that comes back from the form will also be normalized back into this format. diff --git a/reference/forms/types/options/date_input.rst.inc b/reference/forms/types/options/date_input.rst.inc index dafd7c483ad..b4dc263cac1 100644 --- a/reference/forms/types/options/date_input.rst.inc +++ b/reference/forms/types/options/date_input.rst.inc @@ -9,9 +9,14 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``2011-06-05``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) +* ``date_point`` (a :ref:`DatePoint ` object) * ``array`` (e.g. ``['year' => 2011, 'month' => 06, 'day' => 05]``) * ``timestamp`` (e.g. ``1307232000``) +.. versionadded:: 7.4 + + Support for ``date_point`` values was introduced in Symfony 7.4. + The value that comes back from the form will also be normalized back into this format. diff --git a/reference/forms/types/time.rst b/reference/forms/types/time.rst index a3378f948cd..968907efd5b 100644 --- a/reference/forms/types/time.rst +++ b/reference/forms/types/time.rst @@ -99,9 +99,14 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``12:17:26``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) +* ``date_point`` (a :ref:`DatePoint ` object) * ``array`` (e.g. ``['hour' => 12, 'minute' => 17, 'second' => 26]``) * ``timestamp`` (e.g. ``1307232000``) +.. versionadded:: 7.4 + + Support for ``date_point`` values was introduced in Symfony 7.4. + The value that comes back from the form will also be normalized back into this format. diff --git a/routing.rst b/routing.rst index 4f31e70da64..03bafff82fd 100644 --- a/routing.rst +++ b/routing.rst @@ -1158,6 +1158,18 @@ special parameters created by Symfony: ``_locale`` Used to set the :ref:`locale ` on the request. +``_query`` + Used to add query parameters to the generated URL. + + .. versionadded:: 7.4 + + The ``_query`` parameter was introduced in Symfony 7.4. + + .. deprecated:: 7.4 + + Passing a value other than an array as the ``_query`` parameter was + deprecated in Symfony 7.4. + You can include these attributes (except ``_fragment``) both in individual routes and in route imports. Symfony defines some special attributes with the same name (except for the leading underscore) so you can define them easier: @@ -1176,6 +1188,7 @@ and in route imports. Symfony defines some special attributes with the same name path: '/articles/{_locale}/search.{_format}', locale: 'en', format: 'html', + query: ['page' => 1], requirements: [ '_locale' => 'en|fr', '_format' => 'html|xml', @@ -1194,6 +1207,8 @@ and in route imports. Symfony defines some special attributes with the same name controller: App\Controller\ArticleController::search locale: en format: html + query: + page: 1 requirements: _locale: en|fr _format: html|xml @@ -1231,6 +1246,7 @@ and in route imports. Symfony defines some special attributes with the same name ->controller([ArticleController::class, 'search']) ->locale('en') ->format('html') + ->query(['page' => 1]) ->requirements([ '_locale' => 'en|fr', '_format' => 'html|xml', diff --git a/scheduler.rst b/scheduler.rst index ed6ada8b5ed..5b19a81026a 100644 --- a/scheduler.rst +++ b/scheduler.rst @@ -133,9 +133,14 @@ on a particular schedule:: .. tip:: - By default, the schedule name is ``default`` and the transport name follows + The schedule name must be unique and by default, it is ``default``. The transport name follows the syntax: ``scheduler_nameofyourschedule`` (e.g. ``scheduler_default``). +.. versionadded:: 7.4 + + Throwing an exception for duplicate schedule names instead of replacing the existing schedule + was introduced with Symfony 7.4. + .. tip:: `Memoizing`_ your schedule is a good practice to prevent unnecessary reconstruction diff --git a/security.rst b/security.rst index 9d2df6165d0..ede0bc39825 100644 --- a/security.rst +++ b/security.rst @@ -461,8 +461,8 @@ You can also manually hash a password by running: $ php bin/console security:hash-password -Read more about all available hashers and password migration in -:doc:`security/passwords`. +Read more about all available hashers (including specific hashers) and password +migration in :doc:`security/passwords`. .. _firewalls-authentication: .. _a-authentication-firewalls: diff --git a/security/passwords.rst b/security/passwords.rst index 7f05bc3acb9..5de5d4b7b24 100644 --- a/security/passwords.rst +++ b/security/passwords.rst @@ -256,6 +256,64 @@ You can customize the reset password bundle's behavior by updating the ``reset_password.yaml`` file. For more information on the configuration, check out the `SymfonyCastsResetPasswordBundle`_ guide. +Injecting a Specific Password Hasher +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases, you may define a password hasher in your configuration that is +not tied to a user class. For example, you might use a separate hasher for +password recovery codes or API tokens. + +With the following configuration: + +.. code-block:: yaml + + # config/packages/security.yaml + security: + password_hashers: + recovery_code: 'auto' + + firewalls: + main: + # ... + +You can inject the ``recovery_code`` password hasher into any service. However, +you can't rely on standard autowiring, as Symfony doesn't know which specific +hasher to provide. + +Instead, use the ``#[Target]`` attribute to explicitly request the hasher by +its configuration key:: + + // src/Controller/HomepageController.php + namespace App\Controller; + + use Symfony\Component\DependencyInjection\Attribute\Target; + use Symfony\Component\PasswordHasher\PasswordHasherInterface; + + class HomepageController extends AbstractController + { + public function __construct( + #[Target('recovery_code')] + private readonly PasswordHasherInterface $passwordHasher, + ) { + } + + #[Route('/')] + public function index(): Response + { + $plaintextToken = 'some-secret-token'; + + // Note: use hash(), not hashPassword(), as we are not using a UserInterface object + $hashedToken = $this->passwordHasher->hash($plaintextToken); + } + } + +When injecting a specific hasher by its name, you should type-hint the generic +:class:`Symfony\\Component\\PasswordHasher\\PasswordHasherInterface`. + +.. versionadded:: 7.4 + + The feature to inject specific password hashers was introduced in Symfony 7.4. + .. _security-password-migration: Password Migration diff --git a/setup.rst b/setup.rst index 20d71d112eb..c652658427f 100644 --- a/setup.rst +++ b/setup.rst @@ -48,10 +48,10 @@ application: .. code-block:: terminal # run this if you are building a traditional web application - $ symfony new my_project_directory --version="7.3.x-dev" --webapp + $ symfony new my_project_directory --version="7.4.x-dev" --webapp # run this if you are building a microservice, console application or API - $ symfony new my_project_directory --version="7.3.x-dev" + $ symfony new my_project_directory --version="7.4.x-dev" The only difference between these two commands is the number of packages installed by default. The ``--webapp`` option installs extra packages to give @@ -63,12 +63,12 @@ Symfony application using Composer: .. code-block:: terminal # run this if you are building a traditional web application - $ composer create-project symfony/skeleton:"7.3.x-dev" my_project_directory + $ composer create-project symfony/skeleton:"7.4.x-dev" my_project_directory $ cd my_project_directory $ composer require webapp # run this if you are building a microservice, console application or API - $ composer create-project symfony/skeleton:"7.3.x-dev" my_project_directory + $ composer create-project symfony/skeleton:"7.4.x-dev" my_project_directory No matter which command you run to create the Symfony application. All of them will create a new ``my_project_directory/`` directory, download some dependencies diff --git a/testing.rst b/testing.rst index 09cddfa55bb..120e0fd4fa3 100644 --- a/testing.rst +++ b/testing.rst @@ -986,11 +986,11 @@ However, Symfony provides useful shortcut methods for the most common cases: Response Assertions ................... -``assertResponseIsSuccessful(string $message = '', bool $verbose = true)`` +``assertResponseIsSuccessful(string $message = '', ?bool $verbose = null)`` Asserts that the response was successful (HTTP status is 2xx). -``assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true)`` +``assertResponseStatusCodeSame(int $expectedCode, string $message = '', ?bool $verbose = null)`` Asserts a specific HTTP status code. -``assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true)`` +``assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', ?bool $verbose = null)`` Asserts the response is a redirect response (optionally, you can check the target location and status code). The excepted location can be either an absolute or a relative path. @@ -1008,13 +1008,25 @@ Response Assertions Asserts the response format returned by the :method:`Symfony\\Component\\HttpFoundation\\Response::getFormat` method is the same as the expected value. -``assertResponseIsUnprocessable(string $message = '', bool $verbose = true)`` +``assertResponseIsUnprocessable(string $message = '', bool ?$verbose = null)`` Asserts the response is unprocessable (HTTP status is 422) +By default, these assert methods provide detailed error messages when they fail. +You can control the verbosity level using the optional ``verbose`` argument in +each assert method. To set this verbosity level globally, use the +``setBrowserKitAssertionsAsVerbose()`` method from the +:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait`:: + + BrowserKitAssertionsTrait::setBrowserKitAssertionsAsVerbose(false); + .. versionadded:: 7.1 The ``$verbose`` parameters were introduced in Symfony 7.1. +.. versionadded:: 7.4 + + The ``setBrowserKitAssertionsAsVerbose()`` method was introduced in Symfony 7.4. + Request Assertions .................. @@ -1105,14 +1117,18 @@ Mailer Assertions ``assertEmailHeaderSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '')``/``assertEmailHeaderNotSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '')`` Asserts that the given email does (not) have the expected header set to the expected value. -``assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '')`` - Asserts that the given address header equals the expected e-mail +``assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '')``/``assertEmailAddressNotContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '')`` + Asserts that the given address header does (not) equal the expected e-mail address. This assertion normalizes addresses like ``Jane Smith `` into ``jane@example.com``. ``assertEmailSubjectContains(RawMessage $email, string $expectedValue, string $message = '')``/``assertEmailSubjectNotContains(RawMessage $email, string $expectedValue, string $message = '')`` Asserts that the subject of the given email does (not) contain the expected subject. +.. versionadded:: 7.4 + + The ``assertEmailAddressNotContains()`` assertion was introduced in Symfony 7.4. + Notifier Assertions ................... diff --git a/web_link.rst b/web_link.rst index 79fd51b8d02..3fe2e349f50 100644 --- a/web_link.rst +++ b/web_link.rst @@ -195,6 +195,30 @@ You can also add links to the HTTP response directly from controllers and servic } } +Parsing Link Headers +-------------------- + +Some third-party APIs provide resources such as pagination URLs using the +``Link`` HTTP header. The WebLink component provides the +:class:`Symfony\\Component\\WebLink\\HttpHeaderParser` utility class to parse +those headers and transform them into :class:`Symfony\\Component\\WebLink\\Link` +instances:: + + use Symfony\Component\WebLink\HttpHeaderParser; + + $parser = new HttpHeaderParser(); + // get the value of the Link header from the Request + $linkHeader = '; rel="prerender",; rel="dns-prefetch"; pr="0.7",; rel="preload"; as="script"'; + + $links = $parser->parse($linkHeader)->getLinks(); + $links[0]->getRels(); // ['prerender'] + $links[1]->getAttributes(); // ['pr' => '0.7'] + $links[2]->getHref(); // '/baz.js' + +.. versionadded:: 7.4 + + The ``HttpHeaderParser`` class was introduced in Symfony 7.4. + .. _`WebLink`: https://github.com/symfony/web-link .. _`HTTP/2 Server Push`: https://tools.ietf.org/html/rfc7540#section-8.2 .. _`Resource Hints`: https://www.w3.org/TR/resource-hints/