• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

0 of 2 new or added lines in 1 file covered. (0.0%)

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.11
/src/Hydra/Serializer/CollectionFiltersNormalizer.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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\Hydra\Serializer;
15

16
use ApiPlatform\JsonLd\Serializer\HydraPrefixTrait;
17
use ApiPlatform\Metadata\FilterInterface;
18
use ApiPlatform\Metadata\Parameters;
19
use ApiPlatform\Metadata\QueryParameterInterface;
20
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
21
use ApiPlatform\Metadata\ResourceClassResolverInterface;
22
use ApiPlatform\State\Util\StateOptionsTrait;
23
use Psr\Container\ContainerInterface;
24
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
25
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
26
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
27
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
28

29
/**
30
 * Enhances the result of collection by adding the filters applied on collection.
31
 *
32
 * @author Samuel ROZE <samuel.roze@gmail.com>
33
 */
34
final class CollectionFiltersNormalizer implements NormalizerInterface, NormalizerAwareInterface
35
{
36
    use HydraPrefixTrait;
37
    use StateOptionsTrait;
38
    private ?ContainerInterface $filterLocator = null;
39

40
    /**
41
     * @param ContainerInterface   $filterLocator  The new filter locator or the deprecated filter collection
42
     * @param array<string, mixed> $defaultContext
43
     */
44
    public function __construct(
45
        private readonly NormalizerInterface $collectionNormalizer,
46
        private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
47
        private readonly ResourceClassResolverInterface $resourceClassResolver,
48
        ?ContainerInterface $filterLocator = null,
49
        private readonly array $defaultContext = [],
50
    ) {
UNCOV
51
        $this->filterLocator = $filterLocator;
950✔
52
    }
53

54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
58
    {
UNCOV
59
        return $this->collectionNormalizer->supportsNormalization($data, $format, $context);
433✔
60
    }
61

62
    public function getSupportedTypes($format): array
63
    {
UNCOV
64
        return $this->collectionNormalizer->getSupportedTypes($format);
839✔
65
    }
66

67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
71
    {
UNCOV
72
        if (($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && $object instanceof \ArrayObject && !\count($object)) {
433✔
73
            return $object;
1✔
74
        }
75

UNCOV
76
        $data = $this->collectionNormalizer->normalize($object, $format, $context);
433✔
UNCOV
77
        if (!isset($context['resource_class']) || isset($context['api_sub_level'])) {
433✔
UNCOV
78
            return $data;
232✔
79
        }
80

UNCOV
81
        if (!\is_array($data)) {
286✔
82
            throw new UnexpectedValueException('Expected data to be an array');
×
83
        }
UNCOV
84
        $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']);
286✔
UNCOV
85
        $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null);
286✔
86

UNCOV
87
        $parameters = $operation->getParameters();
286✔
UNCOV
88
        $resourceFilters = $operation->getFilters();
286✔
UNCOV
89
        if (!$resourceFilters && !$parameters) {
286✔
UNCOV
90
            return $data;
71✔
91
        }
92

UNCOV
93
        $requestParts = parse_url($context['request_uri'] ?? '');
215✔
UNCOV
94
        if (!\is_array($requestParts)) {
215✔
95
            return $data;
×
96
        }
UNCOV
97
        $currentFilters = [];
215✔
UNCOV
98
        foreach ($resourceFilters as $filterId) {
215✔
UNCOV
99
            if ($filter = $this->getFilter($filterId)) {
116✔
UNCOV
100
                $currentFilters[] = $filter;
116✔
101
            }
102
        }
103

UNCOV
104
        $resourceClass = $this->getStateOptionsClass($operation, $resourceClass);
215✔
105

UNCOV
106
        if ($currentFilters || ($parameters && \count($parameters))) {
215✔
UNCOV
107
            $hydraPrefix = $this->getHydraPrefix($context + $this->defaultContext);
215✔
UNCOV
108
            $data[$hydraPrefix.'search'] = $this->getSearch($resourceClass, $requestParts, $currentFilters, $parameters, $hydraPrefix);
215✔
109
        }
110

UNCOV
111
        return $data;
215✔
112
    }
113

114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function setNormalizer(NormalizerInterface $normalizer): void
118
    {
UNCOV
119
        if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
950✔
UNCOV
120
            $this->collectionNormalizer->setNormalizer($normalizer);
950✔
121
        }
122
    }
123

124
    /**
125
     * Returns the content of the Hydra search property.
126
     *
127
     * @param FilterInterface[] $filters
128
     */
129
    private function getSearch(string $resourceClass, array $parts, array $filters, ?Parameters $parameters, string $hydraPrefix): array
130
    {
UNCOV
131
        $variables = [];
215✔
UNCOV
132
        $mapping = [];
215✔
UNCOV
133
        foreach ($filters as $filter) {
215✔
UNCOV
134
            foreach ($filter->getDescription($resourceClass) as $variable => $data) {
116✔
UNCOV
135
                $variables[] = $variable;
116✔
UNCOV
136
                $mapping[] = ['@type' => 'IriTemplateMapping', 'variable' => $variable, 'property' => $data['property'] ?? null, 'required' => $data['required'] ?? false];
116✔
137
            }
138
        }
139

UNCOV
140
        foreach ($parameters ?? [] as $key => $parameter) {
215✔
141
            // Each IriTemplateMapping maps a variable used in the template to a property
UNCOV
142
            if (!$parameter instanceof QueryParameterInterface || false === $parameter->getHydra()) {
198✔
UNCOV
143
                continue;
103✔
144
            }
145

UNCOV
146
            if (($filterId = $parameter->getFilter()) && \is_string($filterId) && ($filter = $this->getFilter($filterId))) {
99✔
UNCOV
147
                $filterDescription = $filter->getDescription($resourceClass);
4✔
148

UNCOV
149
                foreach ($filterDescription as $variable => $description) {
4✔
150
                    // // This is a practice induced by PHP and is not necessary when implementing URI template
UNCOV
151
                    if (str_ends_with((string) $variable, '[]')) {
4✔
UNCOV
152
                        continue;
2✔
153
                    }
154

UNCOV
155
                    if (($prop = $parameter->getProperty()) && ($description['property'] ?? null) !== $prop) {
4✔
156
                        continue;
×
157
                    }
158

159
                    // :property is a pattern allowed when defining parameters
UNCOV
160
                    $k = str_replace(':property', $description['property'], $key);
4✔
UNCOV
161
                    $variable = str_replace($description['property'], $k, $variable);
4✔
UNCOV
162
                    $variables[] = $variable;
4✔
UNCOV
163
                    $m = ['@type' => 'IriTemplateMapping', 'variable' => $variable, 'property' => $description['property'], 'required' => $description['required']];
4✔
UNCOV
164
                    if (null !== ($required = $parameter->getRequired())) {
4✔
165
                        $m['required'] = $required;
×
166
                    }
UNCOV
167
                    $mapping[] = $m;
4✔
168
                }
169

UNCOV
170
                if ($filterDescription) {
4✔
UNCOV
171
                    continue;
4✔
172
                }
173
            }
174

UNCOV
175
            if (str_contains($key, ':property') && $parameter->getProperties()) {
97✔
UNCOV
176
                $required = $parameter->getRequired();
1✔
UNCOV
177
                foreach ($parameter->getProperties() as $prop) {
1✔
UNCOV
178
                    $k = str_replace(':property', $prop, $key);
1✔
UNCOV
179
                    $m = ['@type' => 'IriTemplateMapping', 'variable' => $k, 'property' => $prop];
1✔
UNCOV
180
                    $variables[] = $k;
1✔
UNCOV
181
                    if (null !== $required) {
1✔
182
                        $m['required'] = $required;
×
183
                    }
UNCOV
184
                    $mapping[] = $m;
1✔
185
                }
186

UNCOV
187
                continue;
1✔
188
            }
189

UNCOV
190
            if (!($property = $parameter->getProperty())) {
96✔
UNCOV
191
                continue;
28✔
192
            }
193

UNCOV
194
            $m = ['@type' => 'IriTemplateMapping', 'variable' => $key, 'property' => $property];
88✔
UNCOV
195
            $variables[] = $key;
88✔
UNCOV
196
            if (null !== ($required = $parameter->getRequired())) {
88✔
UNCOV
197
                $m['required'] = $required;
2✔
198
            }
UNCOV
199
            $mapping[] = $m;
88✔
200
        }
201

UNCOV
202
        return ['@type' => $hydraPrefix.'IriTemplate', $hydraPrefix.'template' => \sprintf('%s{?%s}', $parts['path'], implode(',', $variables)), $hydraPrefix.'variableRepresentation' => 'BasicRepresentation', $hydraPrefix.'mapping' => $mapping];
215✔
203
    }
204

205
    /**
206
     * Gets a filter with a backward compatibility.
207
     */
208
    private function getFilter(string $filterId): ?FilterInterface
209
    {
UNCOV
210
        if ($this->filterLocator && $this->filterLocator->has($filterId)) {
120✔
UNCOV
211
            return $this->filterLocator->get($filterId);
120✔
212
        }
213

214
        return null;
×
215
    }
216
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc