From 7fc8065113ed9462b6cc38023aba899097d90800 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Tue, 28 Feb 2023 11:17:49 +0100 Subject: [PATCH 1/7] Document https://github.com/symfony/symfony/pull/48992 --- controller/value_resolver.rst | 125 ++++++++++++++++++++++++++++++++-- reference/attributes.rst | 4 +- 2 files changed, 122 insertions(+), 7 deletions(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 0f30fd2d0f1..4c990ac59da 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -213,6 +213,83 @@ PSR-7 Objects Resolver: :class:`Psr\\Http\\Message\\RequestInterface` or :class:`Psr\\Http\\Message\\MessageInterface`. It requires installing :doc:`the PSR-7 Bridge ` component. +Managing Value Resolvers +------------------------ + +For each argument, every resolver tagged with ``controller.argument_value_resolver`` +will be called until one provides a value. The order in which they are called depends +on their priority. For example, the ``SessionValueResolver`` (priority 50) will be +called before the ``DefaultValueResolver`` (priority -100) which allows to write e.g. +``SessionInterface $session = null`` to get the session if there is one, or ``null`` +if there is none. + +But what if you *know* there will be a session? In that case every resolver running +before ``SessionValueResolver`` is useless. Worse, some of these could actually +provide a value before ``SessionValueResolver`` has a chance to (don't worry though, +this won't happen with built-in resolvers). Since Symfony 6.3, this kind of issue +can be resolved by leveraging the +:class:`Symfony\\Component\\HttpKernel\\Attribute\\ValueResolver` attribute:: + + // src/Controller/SessionController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\ValueResolver; + use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Component\HttpFoundation\Session\SessionInterface; + + class SessionController + { + #[Route('/')] + public function __invoke( + #[ValueResolver(SessionValueResolver::class)] + SessionInterface $session + ): Response + { + // ... + } + } + +.. versionadded:: 6.3 + + The ``ValueResolver`` attribute was introduced in Symfony 6.3. + +You can target a resolver by passing its name (more on that later) as ``ValueResolver``'s +first argument. For convenience, built-in resolvers' name are their FQCN. + +By default, a targeted resolver is "pinned" to the argument holding the +``ValueResolver`` attribute, meaning that only it will be called to provide a value, +and that it will have to. + +In the above example the ``DefaultValueResolver`` would never be called, so adding a +default value to ``$session`` would be useless. If we need one, then it is fine not +to use ``ValueResolver``. +But then, what if we want to prevent an hypothetic ``EagerValueResolver`` to provide a +value before ``SessionValueResolver``? Time to use ``ValueResolver``'s second argument! +By passing it to ``true``, you can disable the targeted resolver:: + + // src/Controller/SessionController.php + namespace App\Controller; + + use App\ArgumentResolver\EagerValueResolver; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\ValueResolver; + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Component\HttpFoundation\Session\SessionInterface; + + class SessionController + { + #[Route('/')] + public function __invoke( + #[ValueResolver(EagerValueResolver::class, disabled: true)] + SessionInterface $session = null + ): Response + { + // ... + } + } + Adding a Custom Value Resolver ------------------------------ @@ -297,8 +374,13 @@ When those requirements are met, the method creates a new instance of the custom value object and returns it as the value for this argument. That's it! Now all you have to do is add the configuration for the service -container. This can be done by tagging the service with ``controller.argument_value_resolver`` -and adding a priority: +container. This can be done by adding one of the following tags to your value resolver. + +``controller.argument_value_resolver`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This tag is automatically added to every service implementing ``ValueResolverInterface``, +but you can set it yourself to change its ``priority`` or ``name`` attributes. .. configuration-block:: @@ -313,7 +395,9 @@ and adding a priority: App\ValueResolver\BookingIdValueResolver: tags: - - { name: controller.argument_value_resolver, priority: 150 } + - controller.argument_value_resolver: + name: booking_id + priority: 150 .. code-block:: xml @@ -330,7 +414,7 @@ and adding a priority: - + controller.argument_value_resolver @@ -347,7 +431,7 @@ and adding a priority: $services = $containerConfigurator->services(); $services->set(BookingIdValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 150]) + ->tag('controller.argument_value_resolver', ['name' => 'booking_id', 'priority' => 150]) ; }; @@ -364,3 +448,34 @@ command to see which argument resolvers are present and in which order they run: .. code-block:: terminal $ php bin/console debug:container debug.argument_resolver.inner --show-arguments + +You can also configure the name passed to the ``ValueResolver`` attribute to target +your resolver. Otherwise it will default to the service's id. + +``controller.targeted_value_resolver`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Set this tag if you want your resolver to be called only if it is pinned by a +``ValueResolver`` attribute. Like ``controller.argument_value_resolver``, you +can customize the name by which your resolver can be targeted. + +As an alternative, you can add the +:class:`Symfony\\Component\\HttpKernel\\Attribute\\AsTargetedValueResolver` attribute +to your resolver and pass your custom name as its first argument:: + + // src/ValueResolver/IdentifierValueResolver.php + namespace App\ValueResolver; + + use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver; + use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; + + #[AsTargetedValueResolver('booking_id')] + class BookingIdValueResolver implements ValueResolverInterface + { + // ... + } + +.. versionadded:: 6.3 + + The ``controller.targeted_value_resolver`` tag and ``AsTargetedValueResolver`` + attribute were introduced in Symfony 6.3. diff --git a/reference/attributes.rst b/reference/attributes.rst index 2cabf9de7bd..e7507949e97 100644 --- a/reference/attributes.rst +++ b/reference/attributes.rst @@ -54,13 +54,13 @@ HttpKernel ~~~~~~~~~~ * :doc:`AsController ` -* :class:`Symfony\\Component\\HttpKernel\\Attribute\\AsPinnedValueResolver` +* :ref:`AsTargetedValueResolver ` * :ref:`Cache ` * :ref:`MapDateTime ` * :ref:`MapQueryParameter ` * :ref:`MapQueryString ` * :ref:`MapRequestPayload ` -* :class:`Symfony\\Component\\HttpKernel\\Attribute\\ValueResolver` +* :ref:`ValueResolver ` * :ref:`WithHttpStatus ` * :ref:`WithLogLevel ` From 50450a43aef07d7b6037db66f9e8c9fe85f14e1a Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Fri, 21 Apr 2023 11:11:12 +0200 Subject: [PATCH 2/7] Address https://github.com/symfony/symfony-docs/pull/17763#discussion_r1173363869 --- controller/value_resolver.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 4c990ac59da..8cab4a1cb2f 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -475,6 +475,23 @@ to your resolver and pass your custom name as its first argument:: // ... } +You can then pass this name as ``ValueResolver``'s first argument to pin your resolver:: + + // src/Controller/BookingController.php + namespace App\Controller; + + use App\Reservation\BookingId; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\ValueResolver; + + class BookingController + { + public function index(#[ValueResolver('booking_id')] BookingId $id): Response + { + // ... do something with $id + } + } + .. versionadded:: 6.3 The ``controller.targeted_value_resolver`` tag and ``AsTargetedValueResolver`` From 1009ca2d7ca11133377b8f254841a3e92ed901ef Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Sun, 4 Jun 2023 10:16:06 +0200 Subject: [PATCH 3/7] Do not hardcode priorities --- controller/value_resolver.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 8cab4a1cb2f..0adfe7074fc 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -218,8 +218,8 @@ Managing Value Resolvers For each argument, every resolver tagged with ``controller.argument_value_resolver`` will be called until one provides a value. The order in which they are called depends -on their priority. For example, the ``SessionValueResolver`` (priority 50) will be -called before the ``DefaultValueResolver`` (priority -100) which allows to write e.g. +on their priority. For example, the ``SessionValueResolver`` will be called before the +``DefaultValueResolver`` because its priority is higher. This allows to write e.g. ``SessionInterface $session = null`` to get the session if there is one, or ``null`` if there is none. From 31edf49e117350c8167fb151a4906ab130271378 Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Sun, 4 Jun 2023 10:18:12 +0200 Subject: [PATCH 4/7] Fix code samples --- controller/value_resolver.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 0adfe7074fc..3eae4ecf2af 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -234,10 +234,10 @@ can be resolved by leveraging the namespace App\Controller; use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Attribute\ValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; use Symfony\Component\Routing\Annotation\Route; - use Symfony\Component\HttpFoundation\Session\SessionInterface; class SessionController { @@ -274,9 +274,9 @@ By passing it to ``true``, you can disable the targeted resolver:: use App\ArgumentResolver\EagerValueResolver; use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Attribute\ValueResolver; use Symfony\Component\Routing\Annotation\Route; - use Symfony\Component\HttpFoundation\Session\SessionInterface; class SessionController { @@ -414,7 +414,7 @@ but you can set it yourself to change its ``priority`` or ``name`` attributes. - controller.argument_value_resolver + controller.argument_value_resolver From ac13dfc54c500e7195e499687b40dddc3541454b Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Sun, 4 Jun 2023 11:11:53 +0200 Subject: [PATCH 5/7] =?UTF-8?q?Update=20=E2=80=9CManaging=20Value=20Resolv?= =?UTF-8?q?ers=E2=80=9D=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/value_resolver.rst | 56 +++++++++++------------------------ 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 3eae4ecf2af..f605c93371b 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -223,12 +223,13 @@ on their priority. For example, the ``SessionValueResolver`` will be called befo ``SessionInterface $session = null`` to get the session if there is one, or ``null`` if there is none. -But what if you *know* there will be a session? In that case every resolver running -before ``SessionValueResolver`` is useless. Worse, some of these could actually -provide a value before ``SessionValueResolver`` has a chance to (don't worry though, -this won't happen with built-in resolvers). Since Symfony 6.3, this kind of issue -can be resolved by leveraging the -:class:`Symfony\\Component\\HttpKernel\\Attribute\\ValueResolver` attribute:: +In that specific case, you don't need any resolver running before +``SessionValueResolver``, so skipping them would not only improve performance, +but also prevent one of them providing a value before ``SessionValueResolver`` +has a chance to. + +The :class:`Symfony\\Component\\HttpKernel\\Attribute\\ValueResolver` attribute lets you +do this by "targeting" the resolver you want:: // src/Controller/SessionController.php namespace App\Controller; @@ -244,7 +245,7 @@ can be resolved by leveraging the #[Route('/')] public function __invoke( #[ValueResolver(SessionValueResolver::class)] - SessionInterface $session + SessionInterface $session = null ): Response { // ... @@ -255,40 +256,19 @@ can be resolved by leveraging the The ``ValueResolver`` attribute was introduced in Symfony 6.3. -You can target a resolver by passing its name (more on that later) as ``ValueResolver``'s -first argument. For convenience, built-in resolvers' name are their FQCN. - -By default, a targeted resolver is "pinned" to the argument holding the -``ValueResolver`` attribute, meaning that only it will be called to provide a value, -and that it will have to. +In the example above, the ``SessionValueResolver`` will be called first because it is +targeted. The ``DefaultValueResolver`` will be called next if no value has been provided; +that's why we can assign ``null`` as ``$session``'s default value. -In the above example the ``DefaultValueResolver`` would never be called, so adding a -default value to ``$session`` would be useless. If we need one, then it is fine not -to use ``ValueResolver``. -But then, what if we want to prevent an hypothetic ``EagerValueResolver`` to provide a -value before ``SessionValueResolver``? Time to use ``ValueResolver``'s second argument! -By passing it to ``true``, you can disable the targeted resolver:: - - // src/Controller/SessionController.php - namespace App\Controller; +We target a resolver by passing its name as ``ValueResolver``'s first argument. +For convenience, built-in resolvers' name are their FQCN. - use App\ArgumentResolver\EagerValueResolver; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\HttpFoundation\Session\SessionInterface; - use Symfony\Component\HttpKernel\Attribute\ValueResolver; - use Symfony\Component\Routing\Annotation\Route; +The ``ValueResolver`` attribute can also be used to disable the targeted resolver +by passing its ``$disabled`` argument to ``true``, in which case it won't be called. +This is how :ref:`MapEntity allows to disable the EntityValueResolver +for a specific controller `. +Yes, ``MapEntity`` extends ``ValueResolver``! - class SessionController - { - #[Route('/')] - public function __invoke( - #[ValueResolver(EagerValueResolver::class, disabled: true)] - SessionInterface $session = null - ): Response - { - // ... - } - } Adding a Custom Value Resolver ------------------------------ From 432c2ebbdcac7ff93d3e1577a9948c5f43524279 Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Sun, 4 Jun 2023 11:19:46 +0200 Subject: [PATCH 6/7] =?UTF-8?q?Remove=20=E2=80=9Cpin=E2=80=9D=20usage=20re?= =?UTF-8?q?mnants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/value_resolver.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index f605c93371b..736b8938201 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -435,7 +435,7 @@ your resolver. Otherwise it will default to the service's id. ``controller.targeted_value_resolver`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Set this tag if you want your resolver to be called only if it is pinned by a +Set this tag if you want your resolver to be called only if it is targeted by a ``ValueResolver`` attribute. Like ``controller.argument_value_resolver``, you can customize the name by which your resolver can be targeted. @@ -455,7 +455,7 @@ to your resolver and pass your custom name as its first argument:: // ... } -You can then pass this name as ``ValueResolver``'s first argument to pin your resolver:: +You can then pass this name as ``ValueResolver``'s first argument to target your resolver:: // src/Controller/BookingController.php namespace App\Controller; From b57df5476580bfcaf5ab4903c4545eb08a8e52b0 Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Sun, 4 Jun 2023 11:24:13 +0200 Subject: [PATCH 7/7] Reword disabled resolvers paragraph --- controller/value_resolver.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 736b8938201..0c017f8d1c1 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -263,13 +263,11 @@ that's why we can assign ``null`` as ``$session``'s default value. We target a resolver by passing its name as ``ValueResolver``'s first argument. For convenience, built-in resolvers' name are their FQCN. -The ``ValueResolver`` attribute can also be used to disable the targeted resolver -by passing its ``$disabled`` argument to ``true``, in which case it won't be called. -This is how :ref:`MapEntity allows to disable the EntityValueResolver -for a specific controller `. +A targeted resolver can also be disabled by passing ``ValueResolver``'s ``$disabled`` +argument to ``true``; this is how :ref:`MapEntity allows to disable the +EntityValueResolver for a specific controller `. Yes, ``MapEntity`` extends ``ValueResolver``! - Adding a Custom Value Resolver ------------------------------