@@ -598,15 +598,59 @@ 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+ Automatically Fetching Objects (EntityValueResolver )
602+ ----------------------------------------------------
603603
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 :
604+ In many cases, you can use the `EntityValueResolver `_ to do the query for you
605+ automatically! First, enable the feature :
606606
607- .. code-block :: terminal
607+ .. configuration-block ::
608+
609+ .. code-block :: yaml
610+
611+ # config/packages/doctrine.yaml
612+ doctrine :
613+ orm :
614+ controller_resolver :
615+ enabled : true
616+ auto_mapping : true
617+ evict_cache : false
618+
619+ .. code-block :: xml
620+
621+ <!-- config/packages/doctrine.xml -->
622+ <?xml version =" 1.0" encoding =" UTF-8" ?>
623+ <container xmlns =" http://symfony.com/schema/dic/services"
624+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
625+ xmlns : doctrine =" http://symfony.com/schema/dic/doctrine"
626+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
627+ https://symfony.com/schema/dic/services/services-1.0.xsd
628+ http://symfony.com/schema/dic/doctrine
629+ https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd" >
608630
609- $ composer require sensio/framework-extra-bundle
631+ <doctrine : config >
632+ <!-- by convention the env var names are always uppercase -->
633+ <doctrine : orm >
634+ <doctrine : controller_resolver auto_mapping =" true" evict_cache =" false" />
635+ </doctrine : orm >
636+ </doctrine : config >
637+
638+ </container >
639+
640+ .. code-block :: php
641+
642+ // config/packages/doctrine.php
643+ use Symfony\Config\DoctrineConfig;
644+
645+ return static function (DoctrineConfig $doctrine) {
646+ $controllerResolver = $doctrine->orm()
647+ ->entityManager('default')
648+ // ...
649+ ->controllerResolver();
650+
651+ $controllerResolver->autoMapping(true);
652+ $controllerResolver->evictCache(true);
653+ };
610654
611655 Now, simplify your controller::
612656
@@ -621,7 +665,7 @@ Now, simplify your controller::
621665
622666 class ProductController extends AbstractController
623667 {
624- #[Route('/product/{id}', name: 'product_show' )]
668+ #[Route('/product/{id}')]
625669 public function show(Product $product): Response
626670 {
627671 // use the Product!
@@ -632,7 +676,212 @@ Now, simplify your controller::
632676That's it! The bundle uses the ``{id} `` from the route to query for the ``Product ``
633677by the ``id `` column. If it's not found, a 404 page is generated.
634678
635- There are many more options you can use. Read more about the `ParamConverter `_.
679+ This behavior can be enabled on all your controllers, by setting the ``auto_mapping ``
680+ parameter to ``true ``. Or individually on the desired controllers by using the
681+ ``MapEntity `` attribute:
682+
683+ // src/Controller/ProductController.php
684+ namespace App\C ontroller;
685+
686+ use App\E ntity\P roduct;
687+ use App\R epository\P roductRepository;
688+ use Symfony\B ridge\D octrine\A ttribute\M apEntity;
689+ use Symfony\C omponent\H ttpFoundation\R esponse;
690+ use Symfony\C omponent\R outing\A nnotation\R oute;
691+ // ...
692+
693+ class ProductController extends AbstractController
694+ {
695+ #[Route('/product/{id}')]
696+ public function show(
697+ #[MapEntity]
698+ Product $product
699+ ): Response {
700+ // use the Product!
701+ // ...
702+ }
703+ }
704+
705+ Fetch Automatically
706+ ~~~~~~~~~~~~~~~~~~~
707+
708+ If your route wildcards match properties on your entity, then the resolver
709+ will automatically fetch them:
710+
711+ .. configuration-block ::
712+
713+ .. code-block :: php-attributes
714+
715+ /**
716+ * Fetch via primary key because {id} is in the route.
717+ */
718+ #[Route('/product/{id}')]
719+ public function showByPk(Post $post): Response
720+ {
721+ }
722+
723+ /**
724+ * Perform a findOneBy() where the slug property matches {slug}.
725+ */
726+ #[Route('/product/{slug}')]
727+ public function showBySlug(Post $post): Response
728+ {
729+ }
730+
731+ Automatic fetching works in these situations:
732+
733+ * If ``{id} `` is in your route, then this is used to fetch by
734+ primary key via the ``find() `` method.
735+
736+ * The resolver will attempt to do a ``findOneBy() `` fetch by using
737+ *all * of the wildcards in your route that are actually properties
738+ on your entity (non-properties are ignored).
739+
740+ You can control this behavior by actually *adding * the ``MapEntity ``
741+ attribute and using the `MapEntity options `_.
742+
743+ Fetch via an Expression
744+ ~~~~~~~~~~~~~~~~~~~~~~~
745+
746+ If automatic fetching doesn't work, use an expression:
747+
748+ .. configuration-block ::
749+
750+ .. code-block :: php-attributes
751+
752+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
753+
754+ #[Route('/product/{product_id}')]
755+ public function show(
756+ #[MapEntity(expr: 'repository.find(product_id)')]
757+ Product $product
758+ ): Response {
759+ }
760+
761+ Use the special ``MapEntity `` attribute with an ``expr `` option to
762+ fetch the object by calling a method on your repository. The
763+ ``repository `` method will be your entity's Repository class and
764+ any route wildcards - like ``{product_id} `` are available as variables.
765+
766+ This can also be used to help resolve multiple arguments:
767+
768+ .. configuration-block ::
769+
770+ .. code-block :: php-attributes
771+
772+ #[Route('/product/{id}/comments/{comment_id}')]
773+ public function show(
774+ Product $product
775+ #[MapEntity(expr: 'repository.find(comment_id)')]
776+ Comment $comment
777+ ): Response {
778+ }
779+
780+ In the example above, the ``$product `` argument is handled automatically,
781+ but ``$comment `` is configured with the attribute since they cannot both follow
782+ the default convention.
783+
784+ .. _`MapEntity options` :
785+
786+
787+ MapEntity Options
788+ ~~~~~~~~~~~~~~~~~
789+
790+ A number of ``options `` are available on the ``MapEntity `` annotation to
791+ control behavior:
792+
793+ * ``id ``: If an ``id `` option is configured and matches a route parameter, then
794+ the resolver will find by the primary key:
795+
796+ .. configuration-block ::
797+
798+ .. code-block :: php-attributes
799+
800+ #[Route('/product/{product_id}')]
801+ public function show(
802+ Product $product
803+ #[MapEntity(id: 'product_id')]
804+ Comment $comment
805+ ): Response {
806+ }
807+
808+ * ``mapping ``: Configures the properties and values to use with the ``findOneBy() ``
809+ method: the key is the route placeholder name and the value is the Doctrine
810+ property name:
811+
812+ .. configuration-block ::
813+
814+ .. code-block :: php-attributes
815+
816+ #[Route('/product/{category}/{slug}/comments/{comment_slug}')]
817+ public function show(
818+ #[MapEntity(mapping: ['date' => 'date', 'slug' => 'slug'])]
819+ Product $product
820+ #[MapEntity(mapping: ['comment_slug' => 'slug'])]
821+ Comment $comment
822+ ): Response {
823+ }
824+
825+ * ``exclude `` Configures the properties that should be used in the ``findOneBy() ``
826+ method by *excluding * one or more properties so that not *all * are used:
827+
828+ .. configuration-block ::
829+
830+ .. code-block :: php-attributes
831+
832+ #[Route('/product/{slug}/{date}')]
833+ public function show(
834+ #[MapEntity(exclude: ['date'])]
835+ Product $product
836+ \DateTime $date
837+ ): Response {
838+ }
839+
840+ * ``stripNull `` If true, then when ``findOneBy() `` is used, any values that
841+ are ``null `` will not be used for the query.
842+
843+ * ``entityManager `` By default, the ``EntityValueResolver `` uses the *default *
844+ entity manager, but you can configure this:
845+
846+ .. configuration-block ::
847+
848+ .. code-block :: php-attributes
849+
850+ #[Route('/product/{id}')]
851+ public function show(
852+ #[MapEntity(entityManager: ['foo'])]
853+ Product $product
854+ ): Response {
855+ }
856+
857+ * ``evictCache `` If true, forces Doctrine to always fetch the entity from the
858+ database instead of cache.
859+
860+ * ``disabled `` If true, the ``EntityValueResolver `` will not try to replace
861+ the argument.
862+
863+ .. tip ::
864+
865+ When enabled globally, it's possible to disabled the behavior on a specific
866+ controller, by using the ``MapEntity `` set to ``disabled ``.
867+
868+ public function show(
869+ #[CurrentUser]
870+ #[MapEntity(disabled: true)]
871+ User $user
872+ ): Response {
873+ // User is not resolved by the EntityValueResolver
874+ // ...
875+ }
876+
877+ .. versionadded :: 6.2
878+
879+ Entity Value Resolver was introduced in Symfony 6.2.
880+
881+ .. versionadded :: 2.7.1
882+
883+ Autowiring of the ``EntityValueResolver `` was introduced in DoctrineBundle
884+ 2.7.1.
636885
637886Updating an Object
638887------------------
@@ -896,7 +1145,6 @@ Learn more
8961145.. _`DoctrineMigrationsBundle` : https://github.com/doctrine/DoctrineMigrationsBundle
8971146.. _`NativeQuery` : https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/native-sql.html
8981147.. _`SensioFrameworkExtraBundle` : https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
899- .. _`ParamConverter` : https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
9001148.. _`limit of 767 bytes for the index key prefix` : https://dev.mysql.com/doc/refman/5.6/en/innodb-limits.html
9011149.. _`Doctrine screencast series` : https://symfonycasts.com/screencast/symfony-doctrine
9021150.. _`API Platform` : https://api-platform.com/docs/core/validation/
0 commit comments