Completed
Push — master ( 23a8df...d2250c )
by Antoine
26s queued 20s
created

isRequiredFilterValid()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 12
nc 5
nop 2
1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace ApiPlatform\Core\Filter;
15
16
use ApiPlatform\Core\Api\FilterLocatorTrait;
17
use ApiPlatform\Core\Exception\FilterValidationException;
18
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
19
use ApiPlatform\Core\Util\RequestAttributesExtractor;
20
use Psr\Container\ContainerInterface;
21
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
22
23
/**
24
 * Validates query parameters depending on filter description.
25
 *
26
 * @author Julien Deniau <[email protected]>
27
 */
28
final class QueryParameterValidateListener
29
{
30
    use FilterLocatorTrait;
31
32
    private $resourceMetadataFactory;
33
34
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ContainerInterface $filterLocator)
35
    {
36
        $this->resourceMetadataFactory = $resourceMetadataFactory;
37
        $this->setFilterLocator($filterLocator);
38
    }
39
40
    public function onKernelRequest(GetResponseEvent $event)
41
    {
42
        $request = $event->getRequest();
43
        if (
44
            !$request->isMethodSafe(false)
45
            || !($attributes = RequestAttributesExtractor::extractAttributes($request))
46
            || !isset($attributes['collection_operation_name'])
47
            || 'get' !== ($operationName = $attributes['collection_operation_name'])
48
        ) {
49
            return;
50
        }
51
52
        $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
53
        $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
54
55
        $errorList = [];
56
        foreach ($resourceFilters as $filterId) {
57
            if (!$filter = $this->getFilter($filterId)) {
58
                continue;
59
            }
60
61
            foreach ($filter->getDescription($attributes['resource_class']) as $name => $data) {
62
                if (!($data['required'] ?? false)) { // property is not required
63
                    continue;
64
                }
65
66
                if (!$this->isRequiredFilterValid($name, $request)) {
67
                    $errorList[] = sprintf('Query parameter "%s" is required', $name);
68
                }
69
            }
70
        }
71
72
        if ($errorList) {
73
            throw new FilterValidationException($errorList);
74
        }
75
    }
76
77
    /**
78
     * Test if required filter is valid. It validates array notation too like "required[bar]".
79
     */
80
    private function isRequiredFilterValid($name, $request): bool
81
    {
82
        $matches = [];
83
        parse_str($name, $matches);
84
        if (!$matches) {
85
            return false;
86
        }
87
88
        $rootName = array_keys($matches)[0] ?? '';
89
        if (!$rootName) {
90
            return false;
91
        }
92
93
        if (\is_array($matches[$rootName])) {
94
            $keyName = array_keys($matches[$rootName])[0];
95
96
            $queryParameter = $request->query->get($rootName);
97
98
            return \is_array($queryParameter) && isset($queryParameter[$keyName]);
99
        }
100
101
        return null !== $request->query->get($rootName);
102
    }
103
}
104