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

api-platform / core / 17487610263

05 Sep 2025 08:12AM UTC coverage: 22.608% (+0.3%) from 22.319%
17487610263

push

github

web-flow
chore: remove @experimental flag from parameters (#7357)

12049 of 53296 relevant lines covered (22.61%)

26.21 hits per line

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

92.0
/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\Hydra\IriTemplateMapping;
17
use ApiPlatform\Hydra\State\Util\SearchHelperTrait;
18
use ApiPlatform\JsonLd\Serializer\HydraPrefixTrait;
19
use ApiPlatform\Metadata\FilterInterface;
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 SearchHelperTrait;
38
    use StateOptionsTrait;
39
    private ?ContainerInterface $filterLocator = null;
40

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

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

63
    /**
64
     * @param string|null $format
65
     */
66
    public function getSupportedTypes($format): array
67
    {
68
        return $this->collectionNormalizer->getSupportedTypes($format);
536✔
69
    }
70

71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
75
    {
76
        if (($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && $object instanceof \ArrayObject && !\count($object)) {
380✔
77
            return $object;
×
78
        }
79

80
        $data = $this->collectionNormalizer->normalize($object, $format, $context);
380✔
81
        if (!isset($context['resource_class']) || isset($context['api_sub_level'])) {
380✔
82
            return $data;
116✔
83
        }
84

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

91
        $parameters = $operation->getParameters();
274✔
92
        $resourceFilters = $operation->getFilters();
274✔
93
        if (!$resourceFilters && !$parameters) {
274✔
94
            return $data;
28✔
95
        }
96

97
        $requestParts = parse_url($context['request_uri'] ?? '');
246✔
98
        if (!\is_array($requestParts)) {
246✔
99
            return $data;
×
100
        }
101
        $currentFilters = [];
246✔
102
        foreach ($resourceFilters as $filterId) {
246✔
103
            if ($filter = $this->getFilter($filterId)) {
8✔
104
                $currentFilters[] = $filter;
8✔
105
            }
106
        }
107

108
        $resourceClass = $this->getStateOptionsClass($operation, $resourceClass);
246✔
109

110
        if ($currentFilters || ($parameters && \count($parameters))) {
246✔
111
            $hydraPrefix = $this->getHydraPrefix($context + $this->defaultContext);
246✔
112
            ['mapping' => $mapping, 'keys' => $keys] = $this->getSearchMappingAndKeys($operation, $resourceClass, $currentFilters, $parameters, [$this, 'getFilter']);
246✔
113
            $data[$hydraPrefix.'search'] = [
246✔
114
                '@type' => $hydraPrefix.'IriTemplate',
246✔
115
                $hydraPrefix.'template' => \sprintf('%s{?%s}', $requestParts['path'], implode(',', $keys)),
246✔
116
                $hydraPrefix.'variableRepresentation' => 'BasicRepresentation',
246✔
117
                $hydraPrefix.'mapping' => $this->convertMappingToArray($mapping),
246✔
118
            ];
246✔
119
        }
120

121
        return $data;
246✔
122
    }
123

124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function setNormalizer(NormalizerInterface $normalizer): void
128
    {
129
        if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
654✔
130
            $this->collectionNormalizer->setNormalizer($normalizer);
654✔
131
        }
132
    }
133

134
    /**
135
     * @param list<IriTemplateMapping> $mapping
136
     *
137
     * @return array<array<string, mixed>>
138
     */
139
    private function convertMappingToArray(array $mapping): array
140
    {
141
        $convertedMapping = [];
246✔
142
        foreach ($mapping as $m) {
246✔
143
            $converted = [
224✔
144
                '@type' => 'IriTemplateMapping',
224✔
145
                'variable' => $m->variable,
224✔
146
                'property' => $m->property,
224✔
147
            ];
224✔
148

149
            if (null !== ($r = $m->required)) {
224✔
150
                $converted['required'] = $r;
224✔
151
            }
152

153
            $convertedMapping[] = $converted;
224✔
154
        }
155

156
        return $convertedMapping;
246✔
157
    }
158

159
    /**
160
     * Gets a filter with a backward compatibility.
161
     */
162
    private function getFilter(string $filterId): ?FilterInterface
163
    {
164
        if ($this->filterLocator && $this->filterLocator->has($filterId)) {
18✔
165
            return $this->filterLocator->get($filterId);
18✔
166
        }
167

168
        return null;
×
169
    }
170
}
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