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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

0 of 2 new or added lines in 1 file covered. (0.0%)

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

92.08
/src/Metadata/Resource/Factory/OperationDefaultsTrait.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\ApiResource;
17
use ApiPlatform\Metadata\CollectionOperationInterface;
18
use ApiPlatform\Metadata\Delete;
19
use ApiPlatform\Metadata\Exception\RuntimeException;
20
use ApiPlatform\Metadata\Get;
21
use ApiPlatform\Metadata\GetCollection;
22
use ApiPlatform\Metadata\GraphQl\DeleteMutation;
23
use ApiPlatform\Metadata\GraphQl\Mutation;
24
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
25
use ApiPlatform\Metadata\GraphQl\Query;
26
use ApiPlatform\Metadata\GraphQl\QueryCollection;
27
use ApiPlatform\Metadata\GraphQl\Subscription;
28
use ApiPlatform\Metadata\HttpOperation;
29
use ApiPlatform\Metadata\Operation;
30
use ApiPlatform\Metadata\Operations;
31
use ApiPlatform\Metadata\Patch;
32
use ApiPlatform\Metadata\Post;
33
use ApiPlatform\Metadata\Util\CamelCaseToSnakeCaseNameConverter;
34
use ApiPlatform\State\ApiResource\Error;
35
use ApiPlatform\State\CreateProvider;
36
use ApiPlatform\Validator\Exception\ValidationException;
37
use Psr\Log\LoggerInterface;
38

39
trait OperationDefaultsTrait
40
{
41
    private CamelCaseToSnakeCaseNameConverter $camelCaseToSnakeCaseNameConverter;
42
    private array $defaults = [];
43
    private LoggerInterface $logger;
44

45
    private function addGlobalDefaults(ApiResource|Operation $operation): ApiResource|Operation
46
    {
47
        // Do not add global defaults for internal resources:
UNCOV
48
        if (\in_array($operation->getClass(), [Error::class, ValidationException::class], true)) {
68✔
UNCOV
49
            return $operation;
3✔
50
        }
51

UNCOV
52
        $extraProperties = $this->defaults['extra_properties'] ?? [];
66✔
53

UNCOV
54
        foreach ($this->defaults as $key => $value) {
66✔
UNCOV
55
            if ('operations' === $key) {
63✔
UNCOV
56
                continue;
63✔
57
            }
58

UNCOV
59
            $upperKey = ucfirst($this->camelCaseToSnakeCaseNameConverter->denormalize($key));
63✔
UNCOV
60
            $getter = 'get'.$upperKey;
63✔
61

UNCOV
62
            if (!method_exists($operation, $getter)) {
63✔
UNCOV
63
                if (!isset($extraProperties[$key])) {
50✔
UNCOV
64
                    $extraProperties[$key] = $value;
50✔
65
                }
66

UNCOV
67
                continue;
50✔
68
            }
69

UNCOV
70
            $currentValue = $operation->{$getter}();
63✔
71

UNCOV
72
            if (\is_array($currentValue) && $currentValue) {
63✔
UNCOV
73
                if (\is_string($value)) {
63✔
74
                    $value = [$value];
×
75
                }
76

UNCOV
77
                $operation = $operation->{'with'.$upperKey}(array_merge($value, $currentValue));
63✔
78
            }
79

UNCOV
80
            if (null !== $currentValue || null === $value) {
63✔
UNCOV
81
                continue;
63✔
82
            }
83

UNCOV
84
            $operation = $operation->{'with'.$upperKey}($value);
63✔
85
        }
86

UNCOV
87
        return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties()));
66✔
88
    }
89

90
    private function getResourceWithDefaults(string $resourceClass, string $shortName, ApiResource $resource): ApiResource
91
    {
UNCOV
92
        $resource = $resource
68✔
UNCOV
93
            ->withShortName($resource->getShortName() ?? $shortName)
68✔
UNCOV
94
            ->withClass($resourceClass);
68✔
95

UNCOV
96
        return $this->addGlobalDefaults($resource);
68✔
97
    }
98

99
    private function getDefaultHttpOperations($resource): iterable
100
    {
UNCOV
101
        if (enum_exists($resource->getClass())) {
28✔
UNCOV
102
            return new Operations([new GetCollection(paginationEnabled: false), new Get()]);
3✔
103
        }
104

UNCOV
105
        if (($defaultOperations = $this->defaults['operations'] ?? null) && null === $resource->getOperations()) {
26✔
UNCOV
106
            $operations = [];
26✔
107

UNCOV
108
            foreach ($defaultOperations as $defaultOperation) {
26✔
UNCOV
109
                $operation = new $defaultOperation();
26✔
110

UNCOV
111
                if ($operation instanceof Post && $resource->getUriTemplate() && !$resource->getProvider()) {
26✔
112
                    $operation = $operation->withProvider(CreateProvider::class);
1✔
113
                }
114

UNCOV
115
                $operations[] = $operation;
26✔
116
            }
117

UNCOV
118
            return new Operations($operations);
26✔
119
        }
120

121
        $post = new Post();
×
122
        if ($resource->getUriTemplate() && !$resource->getProvider()) {
×
123
            $post = $post->withProvider(CreateProvider::class);
×
124
        }
125

126
        return [new Get(), new GetCollection(), $post, new Patch(), new Delete()];
×
127
    }
128

129
    private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
130
    {
UNCOV
131
        $operations = enum_exists($resource->getClass()) ? [new Query(), new QueryCollection(paginationEnabled: false)] : [new Query(), new QueryCollection(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')];
44✔
UNCOV
132
        $graphQlOperations = [];
44✔
UNCOV
133
        foreach ($operations as $operation) {
44✔
UNCOV
134
            [$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
44✔
UNCOV
135
            $graphQlOperations[$key] = $operation;
44✔
136
        }
137

UNCOV
138
        if ($resource->getMercure()) {
44✔
UNCOV
139
            [$key, $operation] = $this->getOperationWithDefaults($resource, (new Subscription())->withDescription("Subscribes to the update event of a {$operation->getShortName()}."));
2✔
UNCOV
140
            $graphQlOperations[$key] = $operation;
2✔
141
        }
142

UNCOV
143
        return $resource->withGraphQlOperations($graphQlOperations);
44✔
144
    }
145

146
    /**
147
     * Adds nested query operations if there are no existing query ones on the resource.
148
     * They are needed when the resource is queried inside a root query, using a relation.
149
     * Since the nested argument is used, root queries will not be generated for these operations.
150
     */
151
    private function completeGraphQlOperations(ApiResource $resource): ApiResource
152
    {
UNCOV
153
        $graphQlOperations = $resource->getGraphQlOperations();
11✔
154

UNCOV
155
        $hasQueryOperation = false;
11✔
UNCOV
156
        $hasQueryCollectionOperation = false;
11✔
UNCOV
157
        foreach ($graphQlOperations as $operation) {
11✔
UNCOV
158
            if ($operation instanceof Query && !$operation instanceof QueryCollection) {
8✔
UNCOV
159
                $hasQueryOperation = true;
7✔
160
            }
UNCOV
161
            if ($operation instanceof QueryCollection) {
8✔
UNCOV
162
                $hasQueryCollectionOperation = true;
5✔
163
            }
164
        }
165

UNCOV
166
        if (!$hasQueryOperation) {
11✔
UNCOV
167
            $queryOperation = (new Query())->withNested(true);
5✔
UNCOV
168
            $graphQlOperations[$queryOperation->getName()] = $queryOperation;
5✔
169
        }
UNCOV
170
        if (!$hasQueryCollectionOperation) {
11✔
UNCOV
171
            $queryCollectionOperation = (new QueryCollection())->withNested(true);
9✔
UNCOV
172
            $graphQlOperations[$queryCollectionOperation->getName()] = $queryCollectionOperation;
9✔
173
        }
174

UNCOV
175
        return $resource->withGraphQlOperations($graphQlOperations);
11✔
176
    }
177

178
    private function getOperationWithDefaults(ApiResource $resource, Operation $operation, bool $generated = false, array $ignoredOptions = []): array
179
    {
180
        // Inherit from resource defaults
UNCOV
181
        foreach (get_class_methods($resource) as $methodName) {
68✔
UNCOV
182
            if (!str_starts_with($methodName, 'get')) {
68✔
UNCOV
183
                continue;
68✔
184
            }
185

UNCOV
186
            if (\in_array(lcfirst(substr($methodName, 3)), $ignoredOptions, true)) {
68✔
UNCOV
187
                continue;
19✔
188
            }
189

UNCOV
190
            if (!method_exists($operation, $methodName) || null !== $operation->{$methodName}()) {
68✔
UNCOV
191
                continue;
68✔
192
            }
193

UNCOV
194
            if (null === ($value = $resource->{$methodName}())) {
68✔
UNCOV
195
                continue;
68✔
196
            }
197

UNCOV
198
            $operation = $operation->{'with'.substr($methodName, 3)}($value);
68✔
199
        }
200

UNCOV
201
        $operation = $operation->withExtraProperties(array_merge(
68✔
UNCOV
202
            $resource->getExtraProperties(),
68✔
UNCOV
203
            $operation->getExtraProperties(),
68✔
UNCOV
204
            $generated ? ['generated_operation' => true] : []
68✔
UNCOV
205
        ));
68✔
206

207
        // Add global defaults attributes to the operation
UNCOV
208
        $operation = $this->addGlobalDefaults($operation);
68✔
209

UNCOV
210
        if ($operation instanceof GraphQlOperation) {
68✔
UNCOV
211
            if (!$operation->getName()) {
53✔
212
                throw new RuntimeException('No GraphQL operation name.');
×
213
            }
214

UNCOV
215
            if ($operation instanceof Mutation) {
53✔
UNCOV
216
                $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
47✔
217
            }
218

UNCOV
219
            return [$operation->getName(), $operation];
53✔
220
        }
221

UNCOV
222
        if (!$operation instanceof HttpOperation) {
68✔
223
            throw new RuntimeException(\sprintf('Operation should be an instance of "%s"', HttpOperation::class));
×
224
        }
225

UNCOV
226
        if (!$operation->getName() && $operation->getRouteName()) {
68✔
227
            /** @var HttpOperation $operation */
228
            $operation = $operation->withName($operation->getRouteName());
1✔
229
        }
230

UNCOV
231
        $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass());
68✔
232

UNCOV
233
        return [
68✔
UNCOV
234
            $operationName,
68✔
UNCOV
235
            $operation,
68✔
UNCOV
236
        ];
68✔
237
    }
238

239
    private function getDefaultShortname(string $resourceClass): string
240
    {
241
        return (false !== $pos = strrpos($resourceClass, '\\')) ? substr($resourceClass, $pos + 1) : $resourceClass;
×
242
    }
243

244
    private function getDefaultOperationName(HttpOperation $operation, string $resourceClass): string
245
    {
UNCOV
246
        $path = ($operation->getRoutePrefix() ?? '').($operation->getUriTemplate() ?? '');
66✔
247

UNCOV
248
        return \sprintf(
66✔
UNCOV
249
            '_api_%s_%s%s',
66✔
UNCOV
250
            $path ?: ($operation->getShortName() ?? $this->getDefaultShortname($resourceClass)),
66✔
UNCOV
251
            strtolower($operation->getMethod()),
66✔
UNCOV
252
            $operation instanceof CollectionOperationInterface ? '_collection' : '');
66✔
253
    }
254
}
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