- <?php
- /*
-  * This file is part of the Symfony package.
-  *
-  * (c) Fabien Potencier <fabien@symfony.com>
-  *
-  * For the full copyright and license information, please view the LICENSE
-  * file that was distributed with this source code.
-  */
- namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
- use Doctrine\DBAL\Types\ConversionException;
- use Doctrine\ORM\EntityManagerInterface;
- use Doctrine\ORM\NoResultException;
- use Doctrine\Persistence\ManagerRegistry;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
- use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
- use Symfony\Component\ExpressionLanguage\SyntaxError;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
- /**
-  * DoctrineParamConverter.
-  *
-  * @author Fabien Potencier <fabien@symfony.com>
-  */
- class DoctrineParamConverter implements ParamConverterInterface
- {
-     /**
-      * @var ManagerRegistry
-      */
-     private $registry;
-     /**
-      * @var ExpressionLanguage
-      */
-     private $language;
-     /**
-      * @var array
-      */
-     private $defaultOptions;
-     public function __construct(ManagerRegistry $registry = null, ExpressionLanguage $expressionLanguage = null, array $options = [])
-     {
-         $this->registry = $registry;
-         $this->language = $expressionLanguage;
-         $defaultValues = [
-             'entity_manager' => null,
-             'exclude' => [],
-             'mapping' => [],
-             'strip_null' => false,
-             'expr' => null,
-             'id' => null,
-             'repository_method' => null,
-             'map_method_signature' => false,
-             'evict_cache' => false,
-         ];
-         $this->defaultOptions = array_merge($defaultValues, $options);
-     }
-     /**
-      * {@inheritdoc}
-      *
-      * @throws \LogicException       When unable to guess how to get a Doctrine instance from the request information
-      * @throws NotFoundHttpException When object not found
-      */
-     public function apply(Request $request, ParamConverter $configuration)
-     {
-         $name = $configuration->getName();
-         $class = $configuration->getClass();
-         $options = $this->getOptions($configuration);
-         if (null === $request->attributes->get($name, false)) {
-             $configuration->setIsOptional(true);
-         }
-         $errorMessage = null;
-         if ($expr = $options['expr']) {
-             $object = $this->findViaExpression($class, $request, $expr, $options, $configuration);
-             if (null === $object) {
-                 $errorMessage = sprintf('The expression "%s" returned null', $expr);
-             }
-             // find by identifier?
-         } elseif (false === $object = $this->find($class, $request, $options, $name)) {
-             // find by criteria
-             if (false === $object = $this->findOneBy($class, $request, $options)) {
-                 if ($configuration->isOptional()) {
-                     $object = null;
-                 } else {
-                     throw new \LogicException(sprintf('Unable to guess how to get a Doctrine instance from the request information for parameter "%s".', $name));
-                 }
-             }
-         }
-         if (null === $object && false === $configuration->isOptional()) {
-             $message = sprintf('%s object not found by the @%s annotation.', $class, $this->getAnnotationName($configuration));
-             if ($errorMessage) {
-                 $message .= ' '.$errorMessage;
-             }
-             throw new NotFoundHttpException($message);
-         }
-         $request->attributes->set($name, $object);
-         return true;
-     }
-     private function find($class, Request $request, $options, $name)
-     {
-         if ($options['mapping'] || $options['exclude']) {
-             return false;
-         }
-         $id = $this->getIdentifier($request, $options, $name);
-         if (false === $id || null === $id) {
-             return false;
-         }
-         if ($options['repository_method']) {
-             $method = $options['repository_method'];
-         } else {
-             $method = 'find';
-         }
-         $om = $this->getManager($options['entity_manager'], $class);
-         if ($options['evict_cache'] && $om instanceof EntityManagerInterface) {
-             $cacheProvider = $om->getCache();
-             if ($cacheProvider && $cacheProvider->containsEntity($class, $id)) {
-                 $cacheProvider->evictEntity($class, $id);
-             }
-         }
-         try {
-             return $om->getRepository($class)->$method($id);
-         } catch (NoResultException $e) {
-             return;
-         } catch (ConversionException $e) {
-             return;
-         }
-     }
-     private function getIdentifier(Request $request, $options, $name)
-     {
-         if (null !== $options['id']) {
-             if (!\is_array($options['id'])) {
-                 $name = $options['id'];
-             } elseif (\is_array($options['id'])) {
-                 $id = [];
-                 foreach ($options['id'] as $field) {
-                     if (false !== strstr($field, '%s')) {
-                         // Convert "%s_uuid" to "foobar_uuid"
-                         $field = sprintf($field, $name);
-                     }
-                     $id[$field] = $request->attributes->get($field);
-                 }
-                 return $id;
-             }
-         }
-         if ($request->attributes->has($name)) {
-             return $request->attributes->get($name);
-         }
-         if ($request->attributes->has('id') && !$options['id']) {
-             return $request->attributes->get('id');
-         }
-         return false;
-     }
-     private function findOneBy($class, Request $request, $options)
-     {
-         if (!$options['mapping']) {
-             $keys = $request->attributes->keys();
-             $options['mapping'] = $keys ? array_combine($keys, $keys) : [];
-         }
-         foreach ($options['exclude'] as $exclude) {
-             unset($options['mapping'][$exclude]);
-         }
-         if (!$options['mapping']) {
-             return false;
-         }
-         // if a specific id has been defined in the options and there is no corresponding attribute
-         // return false in order to avoid a fallback to the id which might be of another object
-         if ($options['id'] && null === $request->attributes->get($options['id'])) {
-             return false;
-         }
-         $criteria = [];
-         $em = $this->getManager($options['entity_manager'], $class);
-         $metadata = $em->getClassMetadata($class);
-         $mapMethodSignature = $options['repository_method']
-             && $options['map_method_signature']
-             && true === $options['map_method_signature'];
-         foreach ($options['mapping'] as $attribute => $field) {
-             if ($metadata->hasField($field)
-                 || ($metadata->hasAssociation($field) && $metadata->isSingleValuedAssociation($field))
-                 || $mapMethodSignature) {
-                 $criteria[$field] = $request->attributes->get($attribute);
-             }
-         }
-         if ($options['strip_null']) {
-             $criteria = array_filter($criteria, function ($value) {
-                 return null !== $value;
-             });
-         }
-         if (!$criteria) {
-             return false;
-         }
-         if ($options['repository_method']) {
-             $repositoryMethod = $options['repository_method'];
-         } else {
-             $repositoryMethod = 'findOneBy';
-         }
-         try {
-             if ($mapMethodSignature) {
-                 return $this->findDataByMapMethodSignature($em, $class, $repositoryMethod, $criteria);
-             }
-             return $em->getRepository($class)->$repositoryMethod($criteria);
-         } catch (NoResultException $e) {
-             return;
-         } catch (ConversionException $e) {
-             return;
-         }
-     }
-     private function findDataByMapMethodSignature($em, $class, $repositoryMethod, $criteria)
-     {
-         $arguments = [];
-         $repository = $em->getRepository($class);
-         $ref = new \ReflectionMethod($repository, $repositoryMethod);
-         foreach ($ref->getParameters() as $parameter) {
-             if (\array_key_exists($parameter->name, $criteria)) {
-                 $arguments[] = $criteria[$parameter->name];
-             } elseif ($parameter->isDefaultValueAvailable()) {
-                 $arguments[] = $parameter->getDefaultValue();
-             } else {
-                 throw new \InvalidArgumentException(sprintf('Repository method "%s::%s" requires that you provide a value for the "$%s" argument.', \get_class($repository), $repositoryMethod, $parameter->name));
-             }
-         }
-         return $ref->invokeArgs($repository, $arguments);
-     }
-     private function findViaExpression($class, Request $request, $expression, $options, ParamConverter $configuration)
-     {
-         if (null === $this->language) {
-             throw new \LogicException(sprintf('To use the @%s tag with the "expr" option, you need to install the ExpressionLanguage component.', $this->getAnnotationName($configuration)));
-         }
-         $repository = $this->getManager($options['entity_manager'], $class)->getRepository($class);
-         $variables = array_merge($request->attributes->all(), ['repository' => $repository]);
-         try {
-             return $this->language->evaluate($expression, $variables);
-         } catch (NoResultException $e) {
-             return;
-         } catch (ConversionException $e) {
-             return;
-         } catch (SyntaxError $e) {
-             throw new \LogicException(sprintf('Error parsing expression -- "%s" -- (%s).', $expression, $e->getMessage()), 0, $e);
-         }
-     }
-     /**
-      * {@inheritdoc}
-      */
-     public function supports(ParamConverter $configuration)
-     {
-         // if there is no manager, this means that only Doctrine DBAL is configured
-         if (null === $this->registry || !\count($this->registry->getManagerNames())) {
-             return false;
-         }
-         if (null === $configuration->getClass()) {
-             return false;
-         }
-         $options = $this->getOptions($configuration, false);
-         // Doctrine Entity?
-         $em = $this->getManager($options['entity_manager'], $configuration->getClass());
-         if (null === $em) {
-             return false;
-         }
-         return !$em->getMetadataFactory()->isTransient($configuration->getClass());
-     }
-     private function getOptions(ParamConverter $configuration, $strict = true)
-     {
-         $passedOptions = $configuration->getOptions();
-         if (isset($passedOptions['repository_method'])) {
-             @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);
-         }
-         if (isset($passedOptions['map_method_signature'])) {
-             @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);
-         }
-         $extraKeys = array_diff(array_keys($passedOptions), array_keys($this->defaultOptions));
-         if ($extraKeys && $strict) {
-             throw new \InvalidArgumentException(sprintf('Invalid option(s) passed to @%s: "%s".', $this->getAnnotationName($configuration), implode(', ', $extraKeys)));
-         }
-         return array_replace($this->defaultOptions, $passedOptions);
-     }
-     private function getManager($name, $class)
-     {
-         if (null === $name) {
-             return $this->registry->getManagerForClass($class);
-         }
-         return $this->registry->getManager($name);
-     }
-     private function getAnnotationName(ParamConverter $configuration)
-     {
-         $r = new \ReflectionClass($configuration);
-         return $r->getShortName();
-     }
- }