vendor/sensio/framework-extra-bundle/src/Request/ParamConverter/DoctrineParamConverter.php line 91

  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 Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
  11. use Doctrine\DBAL\Types\ConversionException;
  12. use Doctrine\ORM\EntityManagerInterface;
  13. use Doctrine\ORM\NoResultException;
  14. use Doctrine\Persistence\ManagerRegistry;
  15. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  16. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  17. use Symfony\Component\ExpressionLanguage\SyntaxError;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  20. /**
  21.  * DoctrineParamConverter.
  22.  *
  23.  * @author Fabien Potencier <fabien@symfony.com>
  24.  */
  25. class DoctrineParamConverter implements ParamConverterInterface
  26. {
  27.     /**
  28.      * @var ManagerRegistry
  29.      */
  30.     private $registry;
  31.     /**
  32.      * @var ExpressionLanguage
  33.      */
  34.     private $language;
  35.     /**
  36.      * @var array
  37.      */
  38.     private $defaultOptions;
  39.     public function __construct(ManagerRegistry $registry nullExpressionLanguage $expressionLanguage null, array $options = [])
  40.     {
  41.         $this->registry $registry;
  42.         $this->language $expressionLanguage;
  43.         $defaultValues = [
  44.             'entity_manager' => null,
  45.             'exclude' => [],
  46.             'mapping' => [],
  47.             'strip_null' => false,
  48.             'expr' => null,
  49.             'id' => null,
  50.             'repository_method' => null,
  51.             'map_method_signature' => false,
  52.             'evict_cache' => false,
  53.         ];
  54.         $this->defaultOptions array_merge($defaultValues$options);
  55.     }
  56.     /**
  57.      * {@inheritdoc}
  58.      *
  59.      * @throws \LogicException       When unable to guess how to get a Doctrine instance from the request information
  60.      * @throws NotFoundHttpException When object not found
  61.      */
  62.     public function apply(Request $requestParamConverter $configuration)
  63.     {
  64.         $name $configuration->getName();
  65.         $class $configuration->getClass();
  66.         $options $this->getOptions($configuration);
  67.         if (null === $request->attributes->get($namefalse)) {
  68.             $configuration->setIsOptional(true);
  69.         }
  70.         $errorMessage null;
  71.         if ($expr $options['expr']) {
  72.             $object $this->findViaExpression($class$request$expr$options$configuration);
  73.             if (null === $object) {
  74.                 $errorMessage sprintf('The expression "%s" returned null'$expr);
  75.             }
  76.             // find by identifier?
  77.         } elseif (false === $object $this->find($class$request$options$name)) {
  78.             // find by criteria
  79.             if (false === $object $this->findOneBy($class$request$options)) {
  80.                 if ($configuration->isOptional()) {
  81.                     $object null;
  82.                 } else {
  83.                     throw new \LogicException(sprintf('Unable to guess how to get a Doctrine instance from the request information for parameter "%s".'$name));
  84.                 }
  85.             }
  86.         }
  87.         if (null === $object && false === $configuration->isOptional()) {
  88.             $message sprintf('%s object not found by the @%s annotation.'$class$this->getAnnotationName($configuration));
  89.             if ($errorMessage) {
  90.                 $message .= ' '.$errorMessage;
  91.             }
  92.             throw new NotFoundHttpException($message);
  93.         }
  94.         $request->attributes->set($name$object);
  95.         return true;
  96.     }
  97.     private function find($classRequest $request$options$name)
  98.     {
  99.         if ($options['mapping'] || $options['exclude']) {
  100.             return false;
  101.         }
  102.         $id $this->getIdentifier($request$options$name);
  103.         if (false === $id || null === $id) {
  104.             return false;
  105.         }
  106.         if ($options['repository_method']) {
  107.             $method $options['repository_method'];
  108.         } else {
  109.             $method 'find';
  110.         }
  111.         $om $this->getManager($options['entity_manager'], $class);
  112.         if ($options['evict_cache'] && $om instanceof EntityManagerInterface) {
  113.             $cacheProvider $om->getCache();
  114.             if ($cacheProvider && $cacheProvider->containsEntity($class$id)) {
  115.                 $cacheProvider->evictEntity($class$id);
  116.             }
  117.         }
  118.         try {
  119.             return $om->getRepository($class)->$method($id);
  120.         } catch (NoResultException $e) {
  121.             return;
  122.         } catch (ConversionException $e) {
  123.             return;
  124.         }
  125.     }
  126.     private function getIdentifier(Request $request$options$name)
  127.     {
  128.         if (null !== $options['id']) {
  129.             if (!\is_array($options['id'])) {
  130.                 $name $options['id'];
  131.             } elseif (\is_array($options['id'])) {
  132.                 $id = [];
  133.                 foreach ($options['id'] as $field) {
  134.                     if (false !== strstr($field'%s')) {
  135.                         // Convert "%s_uuid" to "foobar_uuid"
  136.                         $field sprintf($field$name);
  137.                     }
  138.                     $id[$field] = $request->attributes->get($field);
  139.                 }
  140.                 return $id;
  141.             }
  142.         }
  143.         if ($request->attributes->has($name)) {
  144.             return $request->attributes->get($name);
  145.         }
  146.         if ($request->attributes->has('id') && !$options['id']) {
  147.             return $request->attributes->get('id');
  148.         }
  149.         return false;
  150.     }
  151.     private function findOneBy($classRequest $request$options)
  152.     {
  153.         if (!$options['mapping']) {
  154.             $keys $request->attributes->keys();
  155.             $options['mapping'] = $keys array_combine($keys$keys) : [];
  156.         }
  157.         foreach ($options['exclude'] as $exclude) {
  158.             unset($options['mapping'][$exclude]);
  159.         }
  160.         if (!$options['mapping']) {
  161.             return false;
  162.         }
  163.         // if a specific id has been defined in the options and there is no corresponding attribute
  164.         // return false in order to avoid a fallback to the id which might be of another object
  165.         if ($options['id'] && null === $request->attributes->get($options['id'])) {
  166.             return false;
  167.         }
  168.         $criteria = [];
  169.         $em $this->getManager($options['entity_manager'], $class);
  170.         $metadata $em->getClassMetadata($class);
  171.         $mapMethodSignature $options['repository_method']
  172.             && $options['map_method_signature']
  173.             && true === $options['map_method_signature'];
  174.         foreach ($options['mapping'] as $attribute => $field) {
  175.             if ($metadata->hasField($field)
  176.                 || ($metadata->hasAssociation($field) && $metadata->isSingleValuedAssociation($field))
  177.                 || $mapMethodSignature) {
  178.                 $criteria[$field] = $request->attributes->get($attribute);
  179.             }
  180.         }
  181.         if ($options['strip_null']) {
  182.             $criteria array_filter($criteria, function ($value) {
  183.                 return null !== $value;
  184.             });
  185.         }
  186.         if (!$criteria) {
  187.             return false;
  188.         }
  189.         if ($options['repository_method']) {
  190.             $repositoryMethod $options['repository_method'];
  191.         } else {
  192.             $repositoryMethod 'findOneBy';
  193.         }
  194.         try {
  195.             if ($mapMethodSignature) {
  196.                 return $this->findDataByMapMethodSignature($em$class$repositoryMethod$criteria);
  197.             }
  198.             return $em->getRepository($class)->$repositoryMethod($criteria);
  199.         } catch (NoResultException $e) {
  200.             return;
  201.         } catch (ConversionException $e) {
  202.             return;
  203.         }
  204.     }
  205.     private function findDataByMapMethodSignature($em$class$repositoryMethod$criteria)
  206.     {
  207.         $arguments = [];
  208.         $repository $em->getRepository($class);
  209.         $ref = new \ReflectionMethod($repository$repositoryMethod);
  210.         foreach ($ref->getParameters() as $parameter) {
  211.             if (\array_key_exists($parameter->name$criteria)) {
  212.                 $arguments[] = $criteria[$parameter->name];
  213.             } elseif ($parameter->isDefaultValueAvailable()) {
  214.                 $arguments[] = $parameter->getDefaultValue();
  215.             } else {
  216.                 throw new \InvalidArgumentException(sprintf('Repository method "%s::%s" requires that you provide a value for the "$%s" argument.'\get_class($repository), $repositoryMethod$parameter->name));
  217.             }
  218.         }
  219.         return $ref->invokeArgs($repository$arguments);
  220.     }
  221.     private function findViaExpression($classRequest $request$expression$optionsParamConverter $configuration)
  222.     {
  223.         if (null === $this->language) {
  224.             throw new \LogicException(sprintf('To use the @%s tag with the "expr" option, you need to install the ExpressionLanguage component.'$this->getAnnotationName($configuration)));
  225.         }
  226.         $repository $this->getManager($options['entity_manager'], $class)->getRepository($class);
  227.         $variables array_merge($request->attributes->all(), ['repository' => $repository]);
  228.         try {
  229.             return $this->language->evaluate($expression$variables);
  230.         } catch (NoResultException $e) {
  231.             return;
  232.         } catch (ConversionException $e) {
  233.             return;
  234.         } catch (SyntaxError $e) {
  235.             throw new \LogicException(sprintf('Error parsing expression -- "%s" -- (%s).'$expression$e->getMessage()), 0$e);
  236.         }
  237.     }
  238.     /**
  239.      * {@inheritdoc}
  240.      */
  241.     public function supports(ParamConverter $configuration)
  242.     {
  243.         // if there is no manager, this means that only Doctrine DBAL is configured
  244.         if (null === $this->registry || !\count($this->registry->getManagerNames())) {
  245.             return false;
  246.         }
  247.         if (null === $configuration->getClass()) {
  248.             return false;
  249.         }
  250.         $options $this->getOptions($configurationfalse);
  251.         // Doctrine Entity?
  252.         $em $this->getManager($options['entity_manager'], $configuration->getClass());
  253.         if (null === $em) {
  254.             return false;
  255.         }
  256.         return !$em->getMetadataFactory()->isTransient($configuration->getClass());
  257.     }
  258.     private function getOptions(ParamConverter $configuration$strict true)
  259.     {
  260.         $passedOptions $configuration->getOptions();
  261.         if (isset($passedOptions['repository_method'])) {
  262.             @trigger_error('The repository_method option of @ParamConverter is deprecated and will be removed in 6.0. Use the expr option or @Entity.'\E_USER_DEPRECATED);
  263.         }
  264.         if (isset($passedOptions['map_method_signature'])) {
  265.             @trigger_error('The map_method_signature option of @ParamConverter is deprecated and will be removed in 6.0. Use the expr option or @Entity.'\E_USER_DEPRECATED);
  266.         }
  267.         $extraKeys array_diff(array_keys($passedOptions), array_keys($this->defaultOptions));
  268.         if ($extraKeys && $strict) {
  269.             throw new \InvalidArgumentException(sprintf('Invalid option(s) passed to @%s: "%s".'$this->getAnnotationName($configuration), implode(', '$extraKeys)));
  270.         }
  271.         return array_replace($this->defaultOptions$passedOptions);
  272.     }
  273.     private function getManager($name$class)
  274.     {
  275.         if (null === $name) {
  276.             return $this->registry->getManagerForClass($class);
  277.         }
  278.         return $this->registry->getManager($name);
  279.     }
  280.     private function getAnnotationName(ParamConverter $configuration)
  281.     {
  282.         $r = new \ReflectionClass($configuration);
  283.         return $r->getShortName();
  284.     }
  285. }