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

api-platform / core / 9710836697

28 Jun 2024 09:35AM UTC coverage: 63.285% (+1.2%) from 62.122%
9710836697

push

github

soyuka
docs: changelog v3.3.7

11104 of 17546 relevant lines covered (63.29%)

52.26 hits per line

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

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

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

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

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

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

66
        return $this->collectionNormalizer->getSupportedTypes($format);
211✔
67
    }
68

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

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

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

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

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

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

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

120
        if ($options = $operation->getStateOptions()) {
64✔
121
            if ($options instanceof Options && $options->getEntityClass()) {
40✔
122
                $resourceClass = $options->getEntityClass();
×
123
            }
124

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

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

134
        return $data;
64✔
135
    }
136

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

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

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

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

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

188
                continue;
4✔
189
            }
190

191
            if (!$property) {
16✔
192
                continue;
12✔
193
            }
194

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

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

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

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