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

api-platform / core / 10508505187

22 Aug 2024 12:55PM UTC coverage: 7.704%. Remained the same
10508505187

push

github

soyuka
Merge 3.4

0 of 4 new or added lines in 4 files covered. (0.0%)

8840 existing lines in 284 files now uncovered.

12477 of 161949 relevant lines covered (7.7%)

22.99 hits per line

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

66.15
/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
    {
UNCOV
44
        $this->filterLocator = $filterLocator;
2,255✔
45
    }
46

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

55
    public function getSupportedTypes($format): array
56
    {
UNCOV
57
        return $this->collectionNormalizer->getSupportedTypes($format);
2,012✔
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
    {
UNCOV
65
        if (($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && $object instanceof \ArrayObject && !\count($object)) {
938✔
UNCOV
66
            return $object;
3✔
67
        }
68

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

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

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

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

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

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

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

UNCOV
111
        return $data;
346✔
112
    }
113

114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function setNormalizer(NormalizerInterface $normalizer): void
118
    {
UNCOV
119
        if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
2,255✔
UNCOV
120
            $this->collectionNormalizer->setNormalizer($normalizer);
2,255✔
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
    {
UNCOV
132
        $variables = [];
346✔
UNCOV
133
        $mapping = [];
346✔
UNCOV
134
        foreach ($filters as $filter) {
346✔
UNCOV
135
            foreach ($filter->getDescription($resourceClass) as $variable => $data) {
346✔
UNCOV
136
                $variables[] = $variable;
345✔
UNCOV
137
                $mapping[] = ['@type' => 'IriTemplateMapping', 'variable' => $variable, 'property' => $data['property'] ?? null, 'required' => $data['required'] ?? false];
345✔
138
            }
139
        }
140

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

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

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

165
                continue;
×
166
            }
167

168
            if (!$property) {
×
169
                continue;
×
170
            }
171

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

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

183
    /**
184
     * Gets a filter with a backward compatibility.
185
     */
186
    private function getFilter(string $filterId): ?FilterInterface
187
    {
UNCOV
188
        if ($this->filterLocator && $this->filterLocator->has($filterId)) {
346✔
UNCOV
189
            return $this->filterLocator->get($filterId);
346✔
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