RequestMapper::map()   A
last analyzed

Complexity

Conditions 6
Paths 26

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 17
c 0
b 0
f 0
nc 26
nop 2
dl 0
loc 24
rs 9.0777
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\Normalizer\DenormalizerInterface as Denormalizer;
22
use Symfony\Component\Validator\Constraints\Collection;
23
use Symfony\Component\Validator\Validator\ValidatorInterface as Validator;
24
25
#[AsService]
26
class RequestMapper implements RequestMapperInterface
27
{
28
    private const DENORMALIZE_TYPE = 'array';
29
30
    public function __construct(
31
        private readonly Validator $validator,
32
        private readonly Denormalizer $denormalizer,
33
        private readonly ExceptionMapperLocator $exceptionMapperLocator,
34
        private readonly DiscriminatorMapExtractor $classExtractor,
35
        private readonly ConstraintExtractor $constraintExtractor,
36
        private readonly ParametersExtractor $parametersExtractor,
37
    ) {
38
    }
39
40
    public function map(Request $request, RequestBody $attribute): object
41
    {
42
        $requestPayload = $request->request->all();
43
        $requestClass = $attribute->requestClass ?? throw ConverterException::nullableParameterClass();
44
45
        try {
46
            $targetClass = $this->classExtractor->extract($requestClass, $requestPayload);
47
48
            if ($attribute->isValidationNeeded) {
49
                $requestPayload = $this->parametersExtractor->extract($requestClass, $requestPayload);
50
                $constraints = $this->constraintExtractor->extract($targetClass, $requestPayload);
51
52
                $this->validate($requestPayload, $attribute->validationGroups, $constraints);
53
            }
54
55
            return $this->denormalizer->denormalize($requestPayload, $targetClass, self::DENORMALIZE_TYPE, $attribute->serializerContext);
56
        } catch (ClassExtractorException $e) {
57
            throw ValidationException::fromErrors(ValidationError::fromTypeAndClassExtractorException(DiscriminatorMap::class, $e));
58
        } catch (ConstraintException $e) {
59
            throw ValidationException::fromConstraintViolationList($e->getViolations());
60
        } catch (MissingConstructorArgumentsException|\TypeError $e) {
61
            throw ValidationException::fromErrors($this->exceptionMapperLocator->get($e)($e, $requestPayload));
62
        } catch (\Throwable $e) {
63
            throw ConverterException::fromThrowable($e);
64
        }
65
    }
66
67
    /** @throws ValidationException */
68
    private function validate(mixed $target, array $validationGroups, ?Collection $constraints = null): void
69
    {
70
        if (($violations = $this->validator->validate($target, $constraints, $validationGroups))->count()) {
71
            throw ConstraintException::fromConstraintViolationList($violations);
72
        }
73
    }
74
}
75