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

api-platform / core / 10027319583

21 Jul 2024 09:36AM UTC coverage: 7.847%. Remained the same
10027319583

push

github

web-flow
ci: fix naming of Windows Behat matrix line (#6489)

12688 of 161690 relevant lines covered (7.85%)

26.88 hits per line

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

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

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

39
    /**
40
     * @param ContainerInterface $filterLocator The new filter locator or the deprecated filter collection
41
     */
42
    public function __construct(private readonly NormalizerInterface $collectionNormalizer, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, ContainerInterface $filterLocator)
43
    {
44
        $this->filterLocator = $filterLocator;
2,612✔
45
    }
46

47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
51
    {
52
        return $this->collectionNormalizer->supportsNormalization($data, $format, $context);
1,052✔
53
    }
54

55
    public function getSupportedTypes($format): array
56
    {
57
        return $this->collectionNormalizer->getSupportedTypes($format);
2,312✔
58
    }
59

60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
64
    {
65
        if (($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && $object instanceof \ArrayObject && !\count($object)) {
1,052✔
66
            return $object;
3✔
67
        }
68

69
        $data = $this->collectionNormalizer->normalize($object, $format, $context);
1,052✔
70
        if (!isset($context['resource_class']) || isset($context['api_sub_level'])) {
1,052✔
71
            return $data;
707✔
72
        }
73

74
        if (!\is_array($data)) {
611✔
75
            throw new UnexpectedValueException('Expected data to be an array');
×
76
        }
77
        $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']);
611✔
78
        $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null);
611✔
79

80
        $parameters = $operation->getParameters();
611✔
81
        $resourceFilters = $operation->getFilters();
611✔
82
        if (!$resourceFilters && !$parameters) {
611✔
83
            return $data;
232✔
84
        }
85

86
        $requestParts = parse_url($context['request_uri'] ?? '');
379✔
87
        if (!\is_array($requestParts)) {
379✔
88
            return $data;
×
89
        }
90
        $currentFilters = [];
379✔
91
        foreach ($resourceFilters as $filterId) {
379✔
92
            if ($filter = $this->getFilter($filterId)) {
352✔
93
                $currentFilters[] = $filter;
352✔
94
            }
95
        }
96

97
        if ($options = $operation->getStateOptions()) {
379✔
98
            if ($options instanceof Options && $options->getEntityClass()) {
355✔
99
                $resourceClass = $options->getEntityClass();
4✔
100
            }
101

102
            if ($options instanceof ODMOptions && $options->getDocumentClass()) {
355✔
103
                $resourceClass = $options->getDocumentClass();
4✔
104
            }
105
        }
106

107
        if ($currentFilters || ($parameters && \count($parameters))) {
379✔
108
            $data['hydra:search'] = $this->getSearch($resourceClass, $requestParts, $currentFilters, $parameters);
379✔
109
        }
110

111
        return $data;
379✔
112
    }
113

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

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

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

147
            if (!($property = $parameter->getProperty()) && ($filterId = $parameter->getFilter()) && ($filter = $this->getFilter($filterId))) {
27✔
148
                foreach ($filter->getDescription($resourceClass) as $variable => $description) {
3✔
149
                    // This is a practice induced by PHP and is not necessary when implementing URI template
150
                    if (str_ends_with((string) $variable, '[]')) {
3✔
151
                        continue;
3✔
152
                    }
153

154
                    // :property is a pattern allowed when defining parameters
155
                    $k = str_replace(':property', $description['property'], $key);
3✔
156
                    $variable = str_replace($description['property'], $k, $variable);
3✔
157
                    $variables[] = $variable;
3✔
158
                    $m = ['@type' => 'IriTemplateMapping', 'variable' => $variable, 'property' => $description['property'], 'required' => $description['required']];
3✔
159
                    if (null !== ($required = $parameter->getRequired())) {
3✔
160
                        $m['required'] = $required;
×
161
                    }
162
                    $mapping[] = $m;
3✔
163
                }
164

165
                continue;
3✔
166
            }
167

168
            if (!$property) {
27✔
169
                continue;
24✔
170
            }
171

172
            $m = ['@type' => 'IriTemplateMapping', 'variable' => $key, 'property' => $property];
9✔
173
            $variables[] = $key;
9✔
174
            if (null !== ($required = $parameter->getRequired())) {
9✔
175
                $m['required'] = $required;
6✔
176
            }
177
            $mapping[] = $m;
9✔
178
        }
179

180
        return ['@type' => 'hydra:IriTemplate', 'hydra:template' => sprintf('%s{?%s}', $parts['path'], implode(',', $variables)), 'hydra:variableRepresentation' => 'BasicRepresentation', 'hydra:mapping' => $mapping];
379✔
181
    }
182

183
    /**
184
     * Gets a filter with a backward compatibility.
185
     */
186
    private function getFilter(string $filterId): ?FilterInterface
187
    {
188
        if ($this->filterLocator && $this->filterLocator->has($filterId)) {
355✔
189
            return $this->filterLocator->get($filterId);
355✔
190
        }
191

192
        return null;
×
193
    }
194
}
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