Skip to content

Commit b51821f

Browse files
committed
feature #17393 Add documentation about the Doctrine EntityValueResolver (jderusse)
This PR was merged into the 6.2 branch. Discussion ---------- Add documentation about the Doctrine `EntityValueResolver` fixes #43854 This PR replaces the documentation about the Doctrine ParamterConverter by the new Entity ValueResolver. It does not cover all the behaviors and possibilities. This was previously documented I the SensioFrameworkExtraBundle (https://symfony.com/bundles/SensioFrameworkExtraBundle/current/annotations/converters.html). I wonder where should I put such documentation for the EntityArgumentResolver ? Commits ------- dc7c6a1 [#17393] Finish entity value resolver docs 36d91fe Add documentation about the Doctrine Entity Value Resolver
2 parents f4d4197 + dc7c6a1 commit b51821f

File tree

3 files changed

+193
-18
lines changed

3 files changed

+193
-18
lines changed

.doctor-rst.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ whitelist:
8888
- '.. versionadded:: 1.11' # Messenger (Middleware / DoctrineBundle)
8989
- '.. versionadded:: 1.18' # Flex in setup/upgrade_minor.rst
9090
- '.. versionadded:: 1.0.0' # Encore
91+
- '.. versionadded:: 2.7.1' # Doctrine
9192
- '0 => 123' # assertion for var_dumper - components/var_dumper.rst
9293
- '1 => "foo"' # assertion for var_dumper - components/var_dumper.rst
9394
- '123,' # assertion for var_dumper - components/var_dumper.rst

best_practices.rst

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,16 +246,17 @@ Instead, you must use dependency injection to fetch services by
246246
:ref:`type-hinting action method arguments <controller-accessing-services>` or
247247
constructor arguments.
248248

249-
Use ParamConverters If They Are Convenient
250-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
249+
Use Entity Value Resolvers If They Are Convenient
250+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
251251

252-
If you're using :doc:`Doctrine </doctrine>`, then you can *optionally* use the
253-
`ParamConverter`_ to automatically query for an entity and pass it as an argument
254-
to your controller. It will also show a 404 page if no entity can be found.
252+
If you're using :doc:`Doctrine </doctrine>`, then you can *optionally* use
253+
the :ref:`EntityValueResolver <doctrine-entity-value-resolver>` to
254+
automatically query for an entity and pass it as an argument to your
255+
controller. It will also show a 404 page if no entity can be found.
255256

256257
If the logic to get an entity from a route variable is more complex, instead of
257-
configuring the ParamConverter, it's better to make the Doctrine query inside
258-
the controller (e.g. by calling to a :doc:`Doctrine repository method </doctrine>`).
258+
configuring the EntityValueResolver, it's better to make the Doctrine query
259+
inside the controller (e.g. by calling to a :doc:`Doctrine repository method </doctrine>`).
259260

260261
Templates
261262
---------
@@ -450,7 +451,6 @@ you must set up a redirection.
450451
.. _`Symfony Demo`: https://github.com/symfony/demo
451452
.. _`download Symfony`: https://symfony.com/download
452453
.. _`Composer`: https://getcomposer.org/
453-
.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
454454
.. _`feature toggles`: https://en.wikipedia.org/wiki/Feature_toggle
455455
.. _`smoke testing`: https://en.wikipedia.org/wiki/Smoke_testing_(software)
456456
.. _`Webpack`: https://webpack.js.org/

doctrine.rst

Lines changed: 184 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -598,17 +598,21 @@ the :ref:`doctrine-queries` section.
598598
see the web debug toolbar, install the ``profiler`` :ref:`Symfony pack <symfony-packs>`
599599
by running this command: ``composer require --dev symfony/profiler-pack``.
600600

601-
Automatically Fetching Objects (ParamConverter)
602-
-----------------------------------------------
601+
.. _doctrine-entity-value-resolver:
603602

604-
In many cases, you can use the `SensioFrameworkExtraBundle`_ to do the query
605-
for you automatically! First, install the bundle in case you don't have it:
603+
Automatically Fetching Objects (EntityValueResolver)
604+
----------------------------------------------------
606605

607-
.. code-block:: terminal
606+
.. versionadded:: 6.2
607+
608+
Entity Value Resolver was introduced in Symfony 6.2.
608609

609-
$ composer require sensio/framework-extra-bundle
610+
.. versionadded:: 2.7.1
610611

611-
Now, simplify your controller::
612+
Autowiring of the ``EntityValueResolver`` was introduced in DoctrineBundle 2.7.1.
613+
614+
In many cases, you can use the ``EntityValueResolver`` to do the query for you
615+
automatically! You can simplify the controller to::
612616

613617
// src/Controller/ProductController.php
614618
namespace App\Controller;
@@ -621,7 +625,7 @@ Now, simplify your controller::
621625

622626
class ProductController extends AbstractController
623627
{
624-
#[Route('/product/{id}', name: 'product_show')]
628+
#[Route('/product/{id}')]
625629
public function show(Product $product): Response
626630
{
627631
// use the Product!
@@ -632,7 +636,178 @@ Now, simplify your controller::
632636
That's it! The bundle uses the ``{id}`` from the route to query for the ``Product``
633637
by the ``id`` column. If it's not found, a 404 page is generated.
634638

635-
There are many more options you can use. Read more about the `ParamConverter`_.
639+
This behavior is enabled by default on all your controllers. You can
640+
disable it by setting the ``doctrine.orm.controller_resolver.auto_mapping``
641+
config option to ``false``.
642+
643+
When disabled, you can enable it individually on the desired controllers by
644+
using the ``MapEntity`` attribute::
645+
646+
// src/Controller/ProductController.php
647+
namespace App\Controller;
648+
649+
use App\Entity\Product;
650+
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
651+
use Symfony\Component\HttpFoundation\Response;
652+
use Symfony\Component\Routing\Annotation\Route;
653+
// ...
654+
655+
class ProductController extends AbstractController
656+
{
657+
#[Route('/product/{id}')]
658+
public function show(
659+
#[MapEntity]
660+
Product $product
661+
): Response {
662+
// use the Product!
663+
// ...
664+
}
665+
}
666+
667+
.. tip::
668+
669+
When enabled globally, it's possible to disabled the behavior on a specific
670+
controller, by using the ``MapEntity`` set to ``disabled``.
671+
672+
public function show(
673+
#[CurrentUser]
674+
#[MapEntity(disabled: true)]
675+
User $user
676+
): Response {
677+
// User is not resolved by the EntityValueResolver
678+
// ...
679+
}
680+
681+
Fetch Automatically
682+
~~~~~~~~~~~~~~~~~~~
683+
684+
If your route wildcards match properties on your entity, then the resolver
685+
will automatically fetch them::
686+
687+
/**
688+
* Fetch via primary key because {id} is in the route.
689+
*/
690+
#[Route('/product/{id}')]
691+
public function showByPk(Post $post): Response
692+
{
693+
}
694+
695+
/**
696+
* Perform a findOneBy() where the slug property matches {slug}.
697+
*/
698+
#[Route('/product/{slug}')]
699+
public function showBySlug(Post $post): Response
700+
{
701+
}
702+
703+
Automatic fetching works in these situations:
704+
705+
* If ``{id}`` is in your route, then this is used to fetch by
706+
primary key via the ``find()`` method.
707+
708+
* The resolver will attempt to do a ``findOneBy()`` fetch by using
709+
*all* of the wildcards in your route that are actually properties
710+
on your entity (non-properties are ignored).
711+
712+
You can control this behavior by actually *adding* the ``MapEntity``
713+
attribute and using the `MapEntity options`_.
714+
715+
Fetch via an Expression
716+
~~~~~~~~~~~~~~~~~~~~~~~
717+
718+
If automatic fetching doesn't work, you can write an expression using the
719+
:doc:`ExpressionLanguage component </components/expression_language>`::
720+
721+
#[Route('/product/{product_id}')]
722+
public function show(
723+
#[MapEntity(expr: 'repository.find(product_id)')]
724+
Product $product
725+
): Response {
726+
}
727+
728+
In the expression, the ``repository`` variable will be your entity's
729+
Repository class and any route wildcards - like ``{product_id}`` are
730+
available as variables.
731+
732+
This can also be used to help resolve multiple arguments::
733+
734+
#[Route('/product/{id}/comments/{comment_id}')]
735+
public function show(
736+
Product $product
737+
#[MapEntity(expr: 'repository.find(comment_id)')]
738+
Comment $comment
739+
): Response {
740+
}
741+
742+
In the example above, the ``$product`` argument is handled automatically,
743+
but ``$comment`` is configured with the attribute since they cannot both follow
744+
the default convention.
745+
746+
MapEntity Options
747+
~~~~~~~~~~~~~~~~~
748+
749+
A number of options are available on the ``MapEntity`` annotation to
750+
control behavior:
751+
752+
``id``
753+
If an ``id`` option is configured and matches a route parameter, then
754+
the resolver will find by the primary key::
755+
756+
#[Route('/product/{product_id}')]
757+
public function show(
758+
Product $product
759+
#[MapEntity(id: 'product_id')]
760+
Comment $comment
761+
): Response {
762+
}
763+
764+
``mapping``
765+
Configures the properties and values to use with the ``findOneBy()``
766+
method: the key is the route placeholder name and the value is the Doctrine
767+
property name::
768+
769+
#[Route('/product/{category}/{slug}/comments/{comment_slug}')]
770+
public function show(
771+
#[MapEntity(mapping: ['date' => 'date', 'slug' => 'slug'])]
772+
Product $product
773+
#[MapEntity(mapping: ['comment_slug' => 'slug'])]
774+
Comment $comment
775+
): Response {
776+
}
777+
778+
``exclude``
779+
Configures the properties that should be used in the ``findOneBy()``
780+
method by *excluding* one or more properties so that not *all* are used:
781+
782+
#[Route('/product/{slug}/{date}')]
783+
public function show(
784+
#[MapEntity(exclude: ['date'])]
785+
Product $product
786+
\DateTime $date
787+
): Response {
788+
}
789+
790+
``stripNull``
791+
If true, then when ``findOneBy()`` is used, any values that are
792+
``null`` will not be used for the query.
793+
794+
``entityManager``
795+
By default, the ``EntityValueResolver`` uses the *default*
796+
entity manager, but you can configure this::
797+
798+
#[Route('/product/{id}')]
799+
public function show(
800+
#[MapEntity(entityManager: ['foo'])]
801+
Product $product
802+
): Response {
803+
}
804+
805+
``evictCache``
806+
If true, forces Doctrine to always fetch the entity from the database
807+
instead of cache.
808+
809+
``disabled``
810+
If true, the ``EntityValueResolver`` will not try to replace the argument.
636811

637812
Updating an Object
638813
------------------
@@ -896,7 +1071,6 @@ Learn more
8961071
.. _`DoctrineMigrationsBundle`: https://github.com/doctrine/DoctrineMigrationsBundle
8971072
.. _`NativeQuery`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/native-sql.html
8981073
.. _`SensioFrameworkExtraBundle`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
899-
.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
9001074
.. _`limit of 767 bytes for the index key prefix`: https://dev.mysql.com/doc/refman/5.6/en/innodb-limits.html
9011075
.. _`Doctrine screencast series`: https://symfonycasts.com/screencast/symfony-doctrine
9021076
.. _`API Platform`: https://api-platform.com/docs/core/validation/

0 commit comments

Comments
 (0)