vendor/artyuum/request-dto-mapper-bundle/src/Mapper/DtoMapper.php line 153

Open in your IDE?
  1. <?php
  2. namespace Artyum\RequestDtoMapperBundle\Mapper;
  3. use Artyum\RequestDtoMapperBundle\Annotation\Dto;
  4. use Artyum\RequestDtoMapperBundle\Event\PostDtoMappingEvent;
  5. use Artyum\RequestDtoMapperBundle\Event\PreDtoMappingEvent;
  6. use Artyum\RequestDtoMapperBundle\Event\PreDtoValidationEvent;
  7. use Artyum\RequestDtoMapperBundle\Exception\DtoDefinitionException;
  8. use Artyum\RequestDtoMapperBundle\Exception\DtoMappingException;
  9. use Artyum\RequestDtoMapperBundle\Exception\DtoValidationException;
  10. use Doctrine\Common\Annotations\AnnotationReader;
  11. use ReflectionClass;
  12. use ReflectionException;
  13. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\Serializer\Exception\ExceptionInterface;
  16. use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
  17. use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
  18. use Symfony\Component\Serializer\SerializerInterface;
  19. use Symfony\Component\Validator\Validator\ValidatorInterface;
  20. use Throwable;
  21. class DtoMapper
  22. {
  23.     private EventDispatcherInterface $eventDispatcher;
  24.     private SerializerInterface $serializer;
  25.     private DenormalizerInterface $denormalizer;
  26.     private ValidatorInterface $validator;
  27.     private array $defaultMapperOptions = [
  28.         ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true,
  29.     ];
  30.     public function __construct(
  31.         EventDispatcherInterface $eventDispatcherSerializerInterface $serializerDenormalizerInterface $denormalizer,
  32.         ValidatorInterface $validator
  33.     ) {
  34.         $this->serializer $serializer;
  35.         $this->denormalizer $denormalizer;
  36.         $this->validator $validator;
  37.         $this->eventDispatcher $eventDispatcher;
  38.     }
  39.     /**
  40.      * Maps the passed array to the DTO.
  41.      *
  42.      * @throws ExceptionInterface
  43.      */
  44.     private function mapFromArray(array $arraystring $dto, array $options = []): DtoInterface
  45.     {
  46.         $options array_merge($this->defaultMapperOptions$options);
  47.         /** @var DtoInterface $dto */
  48.         $dto $this->denormalizer->denormalize($array$dtonull$options);
  49.         return $dto;
  50.     }
  51.     /**
  52.      * Maps the passed raw body (assuming its JSON) to the DTO.
  53.      *
  54.      * @throws DtoMappingException
  55.      */
  56.     private function mapFromJson(string $rawBodystring $dto, array $options = []): DtoInterface
  57.     {
  58.         $options array_merge($this->defaultMapperOptions$options);
  59.         try {
  60.             /** @var DtoInterface $dto */
  61.             $dto $this->serializer->deserialize($rawBody$dto'json'$options);
  62.         } catch (Throwable $throwable) {
  63.             throw new DtoMappingException($throwable->getMessage(), 0$throwable);
  64.         }
  65.         return $dto;
  66.     }
  67.     /**
  68.      * Gets the right Dto annotation class instance from the passed FQCN for the current HTTP method.
  69.      *
  70.      * @throws ReflectionException
  71.      */
  72.     private function getDtoAnnotation(string $dtostring $method): ?Dto
  73.     {
  74.         $reflectionClass = new ReflectionClass($dto);
  75.         $annotationReader = new AnnotationReader();
  76.         $annotations $annotationReader->getClassAnnotations($reflectionClass);
  77.         // loops through all annotations of the class and return the Dto annotation class instance if found
  78.         foreach ($annotations as $annotation) {
  79.             if (!$annotation instanceof Dto) {
  80.                 continue;
  81.             }
  82.             if (in_array($method$annotation->methodstrue)) {
  83.                 return $annotation;
  84.             }
  85.         }
  86.         // gets the Dto annotation class instance from the parent class if it wasn't found above
  87.         if ($reflectionClass->getParentClass() && $reflectionClass->getParentClass()->implementsInterface(DtoInterface::class)) {
  88.             return $this->getDtoAnnotation($reflectionClass->getParentClass()->getName(), $method);
  89.         }
  90.         return null;
  91.     }
  92.     /**
  93.      * @return array|bool[]
  94.      */
  95.     public function getDefaultMapperOptions()
  96.     {
  97.         return $this->defaultMapperOptions;
  98.     }
  99.     /**
  100.      * Maps the request data to the DTO.
  101.      *
  102.      * @throws DtoDefinitionException
  103.      * @throws DtoMappingException
  104.      * @throws DtoValidationException
  105.      * @throws ExceptionInterface
  106.      * @throws ReflectionException
  107.      */
  108.     public function map(Request $requeststring $dto): DtoInterface
  109.     {
  110.         $dtoAnnotation $this->getDtoAnnotation($dto$request->getMethod());
  111.         if (!$dtoAnnotation) {
  112.             throw new DtoDefinitionException('There is no context set for the current HTTP method: ' $request->getMethod());
  113.         }
  114.         /** @var PreDtoMappingEvent $preDtoMappingEvent */
  115.         $preDtoMappingEvent $this->eventDispatcher->dispatch(new PreDtoMappingEvent($request$dto$dtoAnnotation));
  116.         // calls the proper "mapper" method based on the passed source
  117.         if ($dtoAnnotation->source === 'query_strings') {
  118.             $dto $this->mapFromArray($preDtoMappingEvent->getRequest()->query->all(), $dto$preDtoMappingEvent->getOptions());
  119.         } elseif ($dtoAnnotation->source === 'body_parameters') {
  120.             $dto $this->mapFromArray($preDtoMappingEvent->getRequest()->request->all(), $dto$preDtoMappingEvent->getOptions());
  121.         } elseif ($dtoAnnotation->source === 'json') {
  122.             $dto $this->mapFromJson($preDtoMappingEvent->getRequest()->getContent(), $dto$preDtoMappingEvent->getOptions());
  123.         }
  124.         if ($dtoAnnotation->validation) {
  125.             /** @var PreDtoValidationEvent $preDtoValidationEvent */
  126.             $preDtoValidationEvent $this->eventDispatcher->dispatch(new PreDtoValidationEvent($request$dto$dtoAnnotation));
  127.             if (count($errors $this->validator->validate($preDtoValidationEvent->getDto(), null$dtoAnnotation->validationGroups))) {
  128.                 throw new DtoValidationException($errors);
  129.             }
  130.         }
  131.         /** @var PostDtoMappingEvent $postDtoMappingEvent */
  132.         $postDtoMappingEvent $this->eventDispatcher->dispatch(new PostDtoMappingEvent($dto));
  133.         return $postDtoMappingEvent->getDto();
  134.     }
  135. }