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

api-platform / core / 11180438725

04 Oct 2024 01:02PM UTC coverage: 7.836% (+0.003%) from 7.833%
11180438725

push

github

soyuka
fix(metadata): graphql can be disabled but with an existing operation

2 of 9 new or added lines in 5 files covered. (22.22%)

720 existing lines in 47 files now uncovered.

12939 of 165112 relevant lines covered (7.84%)

27.02 hits per line

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

92.05
/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.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\Metadata\Resource\Factory;
15

16
use ApiPlatform\Metadata\ApiProperty;
17
use ApiPlatform\Metadata\FilterInterface;
18
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
19
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
20
use ApiPlatform\Metadata\Operation;
21
use ApiPlatform\Metadata\Parameter;
22
use ApiPlatform\Metadata\Parameters;
23
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
24
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
25
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
26
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
27
use ApiPlatform\Serializer\Filter\FilterInterface as SerializerFilterInterface;
28
use Psr\Container\ContainerInterface;
29
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
30

31
/**
32
 * Prepares Parameters documentation by reading its filter details and declaring an OpenApi parameter.
33
 *
34
 * @experimental
35
 */
36
final class ParameterResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
37
{
38
    private array $localPropertyCache;
39

40
    public function __construct(
41
        private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
42
        private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory,
43
        private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
44
        private readonly ?ContainerInterface $filterLocator = null,
45
        private readonly ?NameConverterInterface $nameConverter = null,
46
    ) {
47
    }
2,684✔
48

49
    public function create(string $resourceClass): ResourceMetadataCollection
50
    {
51
        $resourceMetadataCollection = $this->decorated?->create($resourceClass) ?? new ResourceMetadataCollection($resourceClass);
111✔
52

53
        foreach ($resourceMetadataCollection as $i => $resource) {
111✔
54
            $operations = $resource->getOperations();
100✔
55

56
            $internalPriority = -1;
100✔
57
            foreach ($operations as $operationName => $operation) {
100✔
58
                $parameters = $this->getDefaultParameters($operation, $resourceClass, $internalPriority);
100✔
59
                if (\count($parameters) > 0) {
100✔
60
                    $operations->add($operationName, $operation->withParameters($parameters));
15✔
61
                }
62
            }
63

64
            $resourceMetadataCollection[$i] = $resource->withOperations($operations->sort());
100✔
65

66
            if (!$graphQlOperations = $resource->getGraphQlOperations()) {
100✔
67
                continue;
49✔
68
            }
69

70
            $internalPriority = -1;
82✔
71
            foreach ($graphQlOperations as $operationName => $operation) {
82✔
72
                $parameters = $this->getDefaultParameters($operation, $resourceClass, $internalPriority);
82✔
73
                if (\count($parameters) > 0) {
82✔
74
                    $graphQlOperations[$operationName] = $operation->withParameters($parameters);
6✔
75
                }
76
            }
77

78
            $resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
82✔
79
        }
80

81
        return $resourceMetadataCollection;
111✔
82
    }
83

84
    /**
85
     * @return array{propertyNames: string[], properties: array<string, ApiProperty>}
86
     */
87
    private function getProperties(string $resourceClass): array
88
    {
89
        if (isset($this->localPropertyCache[$resourceClass])) {
100✔
90
            return $this->localPropertyCache[$resourceClass];
85✔
91
        }
92

93
        $propertyNames = [];
100✔
94
        $properties = [];
100✔
95
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
100✔
96
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
85✔
97
            if ($propertyMetadata->isReadable()) {
85✔
98
                $propertyNames[] = $property;
85✔
99
                $properties[$property] = $propertyMetadata;
85✔
100
            }
101
        }
102

103
        $this->localPropertyCache = [$resourceClass => ['propertyNames' => $propertyNames, 'properties' => $properties]];
100✔
104

105
        return $this->localPropertyCache[$resourceClass];
100✔
106
    }
107

108
    private function getDefaultParameters(Operation $operation, string $resourceClass, int &$internalPriority): Parameters
109
    {
110
        ['propertyNames' => $propertyNames, 'properties' => $properties] = $this->getProperties($resourceClass);
100✔
111
        $parameters = $operation->getParameters() ?? new Parameters();
100✔
112
        foreach ($parameters as $key => $parameter) {
100✔
113
            if (':property' === $key) {
15✔
114
                foreach ($propertyNames as $property) {
6✔
115
                    $converted = $this->nameConverter?->denormalize($property) ?? $property;
6✔
116
                    $propertyParameter = $this->setDefaults($converted, $parameter, $resourceClass, $properties);
6✔
117
                    $priority = $propertyParameter->getPriority() ?? $internalPriority--;
6✔
118
                    $parameters->add($converted, $propertyParameter->withPriority($priority)->withKey($converted));
6✔
119
                }
120

121
                $parameters->remove($key, $parameter::class);
6✔
122
                continue;
6✔
123
            }
124

125
            $key = $parameter->getKey() ?? $key;
15✔
126

127
            if (str_contains($key, ':property')) {
15✔
128
                $p = [];
9✔
129
                foreach ($propertyNames as $prop) {
9✔
130
                    $p[$this->nameConverter?->denormalize($prop) ?? $prop] = $prop;
6✔
131
                }
132

133
                $parameter = $parameter->withExtraProperties($parameter->getExtraProperties() + ['_properties' => $p]);
9✔
134
            }
135

136
            $parameter = $this->setDefaults($key, $parameter, $resourceClass, $properties);
15✔
137
            $priority = $parameter->getPriority() ?? $internalPriority--;
15✔
138
            $parameters->add($key, $parameter->withPriority($priority));
15✔
139
        }
140

141
        return $parameters;
100✔
142
    }
143

144
    private function addFilterMetadata(Parameter $parameter): Parameter
145
    {
146
        if (!($filterId = $parameter->getFilter())) {
15✔
147
            return $parameter;
15✔
148
        }
149

150
        $filter = \is_object($filterId) ? $filterId : $this->filterLocator->get($filterId);
9✔
151

152
        if (!$filter) {
9✔
UNCOV
153
            return $parameter;
×
154
        }
155

156
        if (null === $parameter->getSchema() && $filter instanceof JsonSchemaFilterInterface) {
9✔
157
            if ($schema = $filter->getSchema($parameter)) {
6✔
158
                $parameter = $parameter->withSchema($schema);
6✔
159
            }
160
        }
161

162
        if (null === $parameter->getOpenApi() && $filter instanceof OpenApiParameterFilterInterface) {
9✔
163
            if ($openApiParameter = $filter->getOpenApiParameters($parameter)) {
6✔
164
                $parameter = $parameter->withOpenApi($openApiParameter);
6✔
165
            }
166
        }
167

168
        return $parameter;
9✔
169
    }
170

171
    /**
172
     * @param array<string, ApiProperty> $properties
173
     */
174
    private function setDefaults(string $key, Parameter $parameter, string $resourceClass, array $properties): Parameter
175
    {
176
        if (null === $parameter->getKey()) {
15✔
177
            $parameter = $parameter->withKey($key);
15✔
178
        }
179

180
        $filter = $parameter->getFilter();
15✔
181
        if (\is_string($filter) && $this->filterLocator->has($filter)) {
15✔
182
            $filter = $this->filterLocator->get($filter);
9✔
183
        }
184

185
        if ($filter instanceof SerializerFilterInterface && null === $parameter->getProvider()) {
15✔
186
            $parameter = $parameter->withProvider('api_platform.serializer.filter_parameter_provider');
6✔
187
        }
188

189
        // Read filter description to populate the Parameter
190
        $description = $filter instanceof FilterInterface ? $filter->getDescription($resourceClass) : [];
15✔
191
        if (($schema = $description[$key]['schema'] ?? null) && null === $parameter->getSchema()) {
15✔
UNCOV
192
            $parameter = $parameter->withSchema($schema);
×
193
        }
194

195
        if (null === $parameter->getProperty() && ($property = $description[$key]['property'] ?? null)) {
15✔
196
            $parameter = $parameter->withProperty($property);
6✔
197
        }
198

199
        $currentKey = $key;
15✔
200
        if (null === $parameter->getProperty() && isset($properties[$key])) {
15✔
201
            $parameter = $parameter->withProperty($key);
6✔
202
        }
203

204
        if (null === $parameter->getProperty() && $this->nameConverter && ($nameConvertedKey = $this->nameConverter->normalize($key)) && isset($properties[$nameConvertedKey])) {
15✔
UNCOV
205
            $parameter = $parameter->withProperty($key)->withExtraProperties(['_query_property' => $nameConvertedKey] + $parameter->getExtraProperties());
×
UNCOV
206
            $currentKey = $nameConvertedKey;
×
207
        }
208

209
        if (isset($properties[$currentKey]) && ($eloquentRelation = ($properties[$currentKey]->getExtraProperties()['eloquent_relation'] ?? null)) && isset($eloquentRelation['foreign_key'])) {
15✔
UNCOV
210
            $parameter = $parameter->withExtraProperties(['_query_property' => $eloquentRelation['foreign_key']] + $parameter->getExtraProperties());
×
211
        }
212

213
        if (null === $parameter->getRequired() && ($required = $description[$key]['required'] ?? null)) {
15✔
UNCOV
214
            $parameter = $parameter->withRequired($required);
×
215
        }
216

217
        if (null === $parameter->getOpenApi() && ($openApi = $description[$key]['openapi'] ?? null) && $openApi instanceof OpenApiParameter) {
15✔
UNCOV
218
            $parameter = $parameter->withOpenApi($openApi);
×
219
        }
220

221
        return $this->addFilterMetadata($parameter);
15✔
222
    }
223
}
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