Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
22.50% covered (danger)
22.50%
9 / 40
34.48% covered (danger)
34.48%
10 / 29
8.00% covered (danger)
8.00%
2 / 25
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CustomRequestQueryValueResolver
22.50% covered (danger)
22.50%
9 / 40
34.48% covered (danger)
34.48%
10 / 29
8.00% covered (danger)
8.00%
2 / 25
25.00% covered (danger)
25.00%
1 / 4
144.60
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 enumConversionExceptionToViolation
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 typeErrorToViolation
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 resolve
53.33% covered (warning)
53.33%
8 / 15
45.00% covered (danger)
45.00%
9 / 20
5.26% covered (danger)
5.26%
1 / 19
0.00% covered (danger)
0.00%
0 / 1
62.42
1<?php
2
3namespace App\Dto\Request;
4
5use App\Exception\ConversionException;
6use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
8use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
9use Symfony\Component\HttpKernel\Exception\HttpException;
10use Symfony\Component\Validator\ConstraintViolation;
11use Symfony\Component\Validator\ConstraintViolationList;
12use Symfony\Component\Validator\Exception\ValidationFailedException;
13use Symfony\Component\Validator\Validator\ValidatorInterface;
14
15/**
16 * @author Wilhelm Zwertvaegher
17 */
18class CustomRequestQueryValueResolver implements ValueResolverInterface
19{
20    public function __construct(
21        private readonly RequestFactory $requestFactory,
22        private readonly ValidatorInterface $validator,
23    ) {
24    }
25
26    private function enumConversionExceptionToViolation(ConversionException $e): ConstraintViolation
27    {
28        $message = $e->getMessage();
29
30        return new ConstraintViolation(
31            message: "Invalid enum value. {$message}",
32            messageTemplate: '{{ message }}',
33            parameters: [],
34            root: null,
35            propertyPath: $e->getPath(),
36            invalidValue: $e->getValue(),
37        );
38    }
39
40    private function typeErrorToViolation(\TypeError $e): ConstraintViolation
41    {
42        $message = $e->getMessage();
43
44        if (preg_match('/\$\w+/', $message, $matches)) {
45            $property = ltrim($matches[0], '$');
46        } else {
47            $property = 'unknown';
48        }
49
50        if (preg_match('/,\s*(.+)\s+given/', $message, $matches)) {
51            $invalidValue = $matches[1];
52        } else {
53            $invalidValue = null;
54        }
55
56        return new ConstraintViolation(
57            message: 'Invalid type.',
58            messageTemplate: null,
59            parameters: [],
60            root: null,
61            propertyPath: $property,
62            invalidValue: $invalidValue,
63        );
64    }
65
66    /**
67     * @return iterable<\App\Dto\Request\Request>
68     */
69    public function resolve(Request $request, ArgumentMetadata $argument): iterable
70    {
71        if (!$argument->getAttributes(RequestFromQuery::class)) {
72            return [];
73        }
74
75        if (!$argument->getType() || !is_a($argument->getType(), \App\Dto\Request\Request::class, allow_string: true)) {
76            return [];
77        }
78
79        $dto = null;
80        $violations = new ConstraintViolationList();
81        try {
82            $dto = $this->requestFactory->fromParameters($argument->getType(), $request->query->all());
83            $violations = $this->validator->validate($dto);
84        } catch (ConversionException $e) {
85            $violations->add($this->enumConversionExceptionToViolation($e));
86        } catch (\TypeError $e) {
87            $violations->add($this->typeErrorToViolation($e));
88        }
89
90        if ($violations->count() > 0) {
91            throw HttpException::fromStatusCode(422, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException(null, $violations));
92        }
93
94        return $dto ? [$dto] : [];
95    }
96}