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

api-platform / core / 8447144713

27 Mar 2024 05:31AM UTC coverage: 56.853% (+0.3%) from 56.568%
8447144713

push

github

soyuka
feat(openapi): document parameter

17 of 43 new or added lines in 1 file covered. (39.53%)

31 existing lines in 2 files now uncovered.

9744 of 17139 relevant lines covered (56.85%)

41.78 hits per line

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

53.33
/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\Api\FilterInterface as LegacyFilterInterface;
17
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
18
use ApiPlatform\Doctrine\Odm\State\Options as ODMOptions;
19
use ApiPlatform\Doctrine\Orm\State\Options;
20
use ApiPlatform\Metadata\FilterInterface;
21
use ApiPlatform\Metadata\Parameter;
22
use ApiPlatform\Metadata\Parameters;
23
use ApiPlatform\Metadata\QueryParameterInterface;
24
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
25
use ApiPlatform\Metadata\ResourceClassResolverInterface;
26
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
27
use Psr\Container\ContainerInterface;
28
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
29
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
30
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface as BaseCacheableSupportsMethodInterface;
31
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
32
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
33
use Symfony\Component\Serializer\Serializer;
34

35
/**
36
 * Enhances the result of collection by adding the filters applied on collection.
37
 *
38
 * @author Samuel ROZE <samuel.roze@gmail.com>
39
 */
40
final class CollectionFiltersNormalizer implements NormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface
41
{
42
    private ?ContainerInterface $filterLocator = null;
43

44
    /**
45
     * @param ContainerInterface $filterLocator The new filter locator or the deprecated filter collection
46
     */
47
    public function __construct(private readonly NormalizerInterface $collectionNormalizer, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly LegacyResourceClassResolverInterface|ResourceClassResolverInterface $resourceClassResolver, ContainerInterface $filterLocator)
48
    {
49
        $this->filterLocator = $filterLocator;
160✔
50
    }
51

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

60
    public function getSupportedTypes($format): array
61
    {
62
        // @deprecated remove condition when support for symfony versions under 6.3 is dropped
63
        if (!method_exists($this->collectionNormalizer, 'getSupportedTypes')) {
88✔
UNCOV
64
            return ['*' => $this->collectionNormalizer instanceof BaseCacheableSupportsMethodInterface && $this->collectionNormalizer->hasCacheableSupportsMethod()];
×
65
        }
66

67
        return $this->collectionNormalizer->getSupportedTypes($format);
88✔
68
    }
69

70
    public function hasCacheableSupportsMethod(): bool
71
    {
72
        if (method_exists(Serializer::class, 'getSupportedTypes')) {
×
73
            trigger_deprecation(
×
74
                'api-platform/core',
×
75
                '3.1',
×
UNCOV
76
                'The "%s()" method is deprecated, use "getSupportedTypes()" instead.',
×
UNCOV
77
                __METHOD__
×
78
            );
×
79
        }
80

UNCOV
81
        return $this->collectionNormalizer instanceof BaseCacheableSupportsMethodInterface && $this->collectionNormalizer->hasCacheableSupportsMethod();
×
82
    }
83

84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
88
    {
89
        if (($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && $object instanceof \ArrayObject && !\count($object)) {
72✔
UNCOV
90
            return $object;
×
91
        }
92

93
        $data = $this->collectionNormalizer->normalize($object, $format, $context);
72✔
94
        if (!isset($context['resource_class']) || isset($context['api_sub_level'])) {
72✔
95
            return $data;
20✔
96
        }
97

98
        if (!\is_array($data)) {
52✔
UNCOV
99
            throw new UnexpectedValueException('Expected data to be an array');
×
100
        }
101
        $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']);
52✔
102
        $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null);
52✔
103

104
        $parameters = $operation->getParameters();
52✔
105
        $resourceFilters = $operation->getFilters();
52✔
106
        if (!$resourceFilters && !$parameters) {
52✔
107
            return $data;
4✔
108
        }
109

110
        $requestParts = parse_url($context['request_uri'] ?? '');
48✔
111
        if (!\is_array($requestParts)) {
48✔
UNCOV
112
            return $data;
×
113
        }
114
        $currentFilters = [];
48✔
115
        foreach ($resourceFilters as $filterId) {
48✔
116
            if ($filter = $this->getFilter($filterId)) {
16✔
117
                $currentFilters[] = $filter;
12✔
118
            }
119
        }
120

121
        if ($options = $operation->getStateOptions()) {
48✔
122
            if ($options instanceof Options && $options->getEntityClass()) {
36✔
UNCOV
123
                $resourceClass = $options->getEntityClass();
×
124
            }
125

126
            if ($options instanceof ODMOptions && $options->getDocumentClass()) {
36✔
UNCOV
127
                $resourceClass = $options->getDocumentClass();
×
128
            }
129
        }
130

131
        if ($currentFilters || ($parameters && \count($parameters))) {
48✔
132
            $data['hydra:search'] = $this->getSearch($resourceClass, $requestParts, $currentFilters, $parameters);
12✔
133
        }
134

135
        return $data;
48✔
136
    }
137

138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function setNormalizer(NormalizerInterface $normalizer): void
142
    {
143
        if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
128✔
144
            $this->collectionNormalizer->setNormalizer($normalizer);
128✔
145
        }
146
    }
147

148
    /**
149
     * Returns the content of the Hydra search property.
150
     *
151
     * @param LegacyFilterInterface[]|FilterInterface[] $filters
152
     * @param array<string, Parameter>                  $parameters
153
     */
154
    private function getSearch(string $resourceClass, array $parts, array $filters, array|Parameters|null $parameters): array
155
    {
156
        $variables = [];
12✔
157
        $mapping = [];
12✔
158
        foreach ($filters as $filter) {
12✔
159
            foreach ($filter->getDescription($resourceClass) as $variable => $data) {
12✔
160
                $variables[] = $variable;
12✔
161
                $mapping[] = ['@type' => 'IriTemplateMapping', 'variable' => $variable, 'property' => $data['property'], 'required' => $data['required']];
12✔
162
            }
163
        }
164

165
        foreach ($parameters ?? [] as $key => $parameter) {
12✔
166
            // Each IriTemplateMapping maps a variable used in the template to a property
UNCOV
167
            if (!$parameter instanceof QueryParameterInterface) {
×
UNCOV
168
                continue;
×
169
            }
170

UNCOV
171
            if (!($property = $parameter->getProperty()) && ($filterId = $parameter->getFilter()) && ($filter = $this->getFilter($filterId))) {
×
UNCOV
172
                foreach ($filter->getDescription($resourceClass) as $variable => $description) {
×
173
                    // This is a practice induced by PHP and is not necessary when implementing URI template
UNCOV
174
                    if (str_ends_with((string) $variable, '[]')) {
×
UNCOV
175
                        continue;
×
176
                    }
177

178
                    // :property is a pattern allowed when defining parameters
UNCOV
179
                    $k = str_replace(':property', $description['property'], $key);
×
UNCOV
180
                    $variable = str_replace($description['property'], $k, $variable);
×
UNCOV
181
                    $variables[] = $variable;
×
UNCOV
182
                    $m = ['@type' => 'IriTemplateMapping', 'variable' => $variable, 'property' => $description['property'], 'required' => $description['required']];
×
UNCOV
183
                    if (null !== ($required = $parameter->getRequired())) {
×
UNCOV
184
                        $m['required'] = $required;
×
185
                    }
UNCOV
186
                    $mapping[] = $m;
×
187
                }
188

UNCOV
189
                continue;
×
190
            }
191

UNCOV
192
            if (!$property) {
×
UNCOV
193
                continue;
×
194
            }
195

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

204
        return ['@type' => 'hydra:IriTemplate', 'hydra:template' => sprintf('%s{?%s}', $parts['path'], implode(',', $variables)), 'hydra:variableRepresentation' => 'BasicRepresentation', 'hydra:mapping' => $mapping];
12✔
205
    }
206

207
    /**
208
     * Gets a filter with a backward compatibility.
209
     */
210
    private function getFilter(string $filterId): LegacyFilterInterface|FilterInterface|null
211
    {
212
        if ($this->filterLocator && $this->filterLocator->has($filterId)) {
16✔
213
            return $this->filterLocator->get($filterId);
12✔
214
        }
215

216
        return null;
4✔
217
    }
218
}
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

© 2026 Coveralls, Inc