vendor/symfony/doctrine-bridge/ArgumentResolver/EntityValueResolver.php line 76

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bridge\Doctrine\ArgumentResolver;
  11. use Doctrine\DBAL\Types\ConversionException;
  12. use Doctrine\ORM\EntityManagerInterface;
  13. use Doctrine\ORM\NoResultException;
  14. use Doctrine\Persistence\ManagerRegistry;
  15. use Doctrine\Persistence\ObjectManager;
  16. use Symfony\Bridge\Doctrine\Attribute\MapEntity;
  17. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
  20. use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
  21. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  22. /**
  23.  * Yields the entity matching the criteria provided in the route.
  24.  *
  25.  * @author Fabien Potencier <fabien@symfony.com>
  26.  * @author Jérémy Derussé <jeremy@derusse.com>
  27.  */
  28. final class EntityValueResolver implements ValueResolverInterface
  29. {
  30.     public function __construct(
  31.         private ManagerRegistry $registry,
  32.         private ?ExpressionLanguage $expressionLanguage null,
  33.         private MapEntity $defaults = new MapEntity(),
  34.     ) {
  35.     }
  36.     public function resolve(Request $requestArgumentMetadata $argument): array
  37.     {
  38.         if (\is_object($request->attributes->get($argument->getName()))) {
  39.             return [];
  40.         }
  41.         $options $argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF);
  42.         $options = ($options[0] ?? $this->defaults)->withDefaults($this->defaults$argument->getType());
  43.         if (!$options->class || $options->disabled) {
  44.             return [];
  45.         }
  46.         if (!$manager $this->getManager($options->objectManager$options->class)) {
  47.             return [];
  48.         }
  49.         $message '';
  50.         if (null !== $options->expr) {
  51.             if (null === $object $this->findViaExpression($manager$request$options)) {
  52.                 $message sprintf(' The expression "%s" returned null.'$options->expr);
  53.             }
  54.         // find by identifier?
  55.         } elseif (false === $object $this->find($manager$request$options$argument->getName())) {
  56.             // find by criteria
  57.             if (!$criteria $this->getCriteria($request$options$manager)) {
  58.                 return [];
  59.             }
  60.             try {
  61.                 $object $manager->getRepository($options->class)->findOneBy($criteria);
  62.             } catch (NoResultException|ConversionException) {
  63.                 $object null;
  64.             }
  65.         }
  66.         if (null === $object && !$argument->isNullable()) {
  67.             throw new NotFoundHttpException(sprintf('"%s" object not found by "%s".'$options->classself::class).$message);
  68.         }
  69.         return [$object];
  70.     }
  71.     private function getManager(?string $namestring $class): ?ObjectManager
  72.     {
  73.         if (null === $name) {
  74.             return $this->registry->getManagerForClass($class);
  75.         }
  76.         try {
  77.             $manager $this->registry->getManager($name);
  78.         } catch (\InvalidArgumentException) {
  79.             return null;
  80.         }
  81.         return $manager->getMetadataFactory()->isTransient($class) ? null $manager;
  82.     }
  83.     private function find(ObjectManager $managerRequest $requestMapEntity $optionsstring $name): false|object|null
  84.     {
  85.         if ($options->mapping || $options->exclude) {
  86.             return false;
  87.         }
  88.         $id $this->getIdentifier($request$options$name);
  89.         if (false === $id || null === $id) {
  90.             return $id;
  91.         }
  92.         if ($options->evictCache && $manager instanceof EntityManagerInterface) {
  93.             $cacheProvider $manager->getCache();
  94.             if ($cacheProvider && $cacheProvider->containsEntity($options->class$id)) {
  95.                 $cacheProvider->evictEntity($options->class$id);
  96.             }
  97.         }
  98.         try {
  99.             return $manager->getRepository($options->class)->find($id);
  100.         } catch (NoResultException|ConversionException) {
  101.             return null;
  102.         }
  103.     }
  104.     private function getIdentifier(Request $requestMapEntity $optionsstring $name): mixed
  105.     {
  106.         if (\is_array($options->id)) {
  107.             $id = [];
  108.             foreach ($options->id as $field) {
  109.                 // Convert "%s_uuid" to "foobar_uuid"
  110.                 if (str_contains($field'%s')) {
  111.                     $field sprintf($field$name);
  112.                 }
  113.                 $id[$field] = $request->attributes->get($field);
  114.             }
  115.             return $id;
  116.         }
  117.         if (null !== $options->id) {
  118.             $name $options->id;
  119.         }
  120.         if ($request->attributes->has($name)) {
  121.             return $request->attributes->get($name) ?? ($options->stripNull false null);
  122.         }
  123.         if (!$options->id && $request->attributes->has('id')) {
  124.             return $request->attributes->get('id') ?? ($options->stripNull false null);
  125.         }
  126.         return false;
  127.     }
  128.     private function getCriteria(Request $requestMapEntity $optionsObjectManager $manager): array
  129.     {
  130.         if (null === $mapping $options->mapping) {
  131.             $mapping $request->attributes->keys();
  132.         }
  133.         if ($mapping && \is_array($mapping) && array_is_list($mapping)) {
  134.             $mapping array_combine($mapping$mapping);
  135.         }
  136.         foreach ($options->exclude as $exclude) {
  137.             unset($mapping[$exclude]);
  138.         }
  139.         if (!$mapping) {
  140.             return [];
  141.         }
  142.         // if a specific id has been defined in the options and there is no corresponding attribute
  143.         // return false in order to avoid a fallback to the id which might be of another object
  144.         if (\is_string($options->id) && null === $request->attributes->get($options->id)) {
  145.             return [];
  146.         }
  147.         $criteria = [];
  148.         $metadata $manager->getClassMetadata($options->class);
  149.         foreach ($mapping as $attribute => $field) {
  150.             if (!$metadata->hasField($field) && (!$metadata->hasAssociation($field) || !$metadata->isSingleValuedAssociation($field))) {
  151.                 continue;
  152.             }
  153.             $criteria[$field] = $request->attributes->get($attribute);
  154.         }
  155.         if ($options->stripNull) {
  156.             $criteria array_filter($criteria, static fn ($value) => null !== $value);
  157.         }
  158.         return $criteria;
  159.     }
  160.     private function findViaExpression(ObjectManager $managerRequest $requestMapEntity $options): ?object
  161.     {
  162.         if (!$this->expressionLanguage) {
  163.             throw new \LogicException(sprintf('You cannot use the "%s" if the ExpressionLanguage component is not available. Try running "composer require symfony/expression-language".'__CLASS__));
  164.         }
  165.         $repository $manager->getRepository($options->class);
  166.         $variables array_merge($request->attributes->all(), ['repository' => $repository]);
  167.         try {
  168.             return $this->expressionLanguage->evaluate($options->expr$variables);
  169.         } catch (NoResultException|ConversionException) {
  170.             return null;
  171.         }
  172.     }
  173. }