Test Failed
Push — main ( 8bbfd5...5b42e4 )
by Fractal
03:27
created

RequestMapper   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 47
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 47
rs 10
c 0
b 0
f 0
wmc 9

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A validate() 0 4 2
A convert() 0 24 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace FRZB\Component\RequestMapper\RequestMapper;
6
7
use FRZB\Component\DependencyInjection\Attribute\AsService;
8
use FRZB\Component\RequestMapper\Attribute\RequestBody;
9
use FRZB\Component\RequestMapper\Data\ValidationError;
10
use FRZB\Component\RequestMapper\Exception\ClassExtractorException;
11
use FRZB\Component\RequestMapper\Exception\ConstraintException;
12
use FRZB\Component\RequestMapper\Exception\ConverterException;
13
use FRZB\Component\RequestMapper\Exception\ValidationException;
14
use FRZB\Component\RequestMapper\ExceptionMapper\ExceptionMapperLocatorInterface as ExceptionMapperLocator;
15
use FRZB\Component\RequestMapper\Extractor\ConstraintExtractor;
16
use FRZB\Component\RequestMapper\Extractor\DiscriminatorMapExtractor;
17
use FRZB\Component\RequestMapper\Extractor\ParametersExtractor;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
20
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
21
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
22
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface as Denormalizer;
23
use Symfony\Component\Validator\Constraints\Collection;
24
use Symfony\Component\Validator\Validator\ValidatorInterface as Validator;
25
26
#[AsService]
27
class RequestMapper implements RequestMapperInterface
28
{
29
    private const DENORMALIZE_TYPE = 'array';
30
31
    public function __construct(
32
        private readonly Validator $validator,
33
        private readonly Denormalizer $denormalizer,
34
        private readonly ExceptionMapperLocator $exceptionMapperLocator,
35
        private readonly DiscriminatorMapExtractor $classExtractor,
36
        private readonly ConstraintExtractor $constraintExtractor,
37
        private readonly ParametersExtractor $parametersExtractor,
38
    ) {
39
    }
40
41
    public function convert(Request $request, RequestBody $attribute): object
42
    {
43
        $requestPayload = $request->request->all();
44
        $requestClass = $attribute->requestClass ?? throw ConverterException::nullableParameterClass();
45
46
        try {
47
            $targetClass = $this->classExtractor->extract($requestClass, $requestPayload);
48
49
            if ($attribute->isValidationNeeded) {
50
                $requestPayload = $this->parametersExtractor->extract($requestClass, $requestPayload);
51
                $constraints = $this->constraintExtractor->extract($targetClass, $requestPayload);
52
53
                $this->validate($requestPayload, $attribute->validationGroups, $constraints);
54
            }
55
56
            return $this->denormalizer->denormalize($requestPayload, $targetClass, self::DENORMALIZE_TYPE, $attribute->serializerContext);
57
        } catch (ClassExtractorException $e) {
58
            throw ValidationException::fromErrors(ValidationError::fromTypeAndClassExtractorException(DiscriminatorMap::class, $e));
59
        } catch (ConstraintException $e) {
60
            throw ValidationException::fromConstraintViolationList($e->getViolations());
61
        } catch (MissingConstructorArgumentsException|NotNormalizableValueException|\TypeError $e) {
62
            throw ValidationException::fromErrors($this->exceptionMapperLocator->get($e)($e, $requestPayload));
63
        } catch (\Throwable $e) {
64
            throw ConverterException::fromThrowable($e);
65
        }
66
    }
67
68
    /** @throws ValidationException */
69
    private function validate(mixed $target, array $validationGroups, ?Collection $constraints = null): void
70
    {
71
        if (($violations = $this->validator->validate($target, $constraints, $validationGroups))->count()) {
72
            throw ConstraintException::fromConstraintViolationList($violations);
73
        }
74
    }
75
}
76