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

api-platform / core / 16050929464

03 Jul 2025 12:51PM UTC coverage: 22.065% (+0.2%) from 21.821%
16050929464

push

github

soyuka
chore: todo improvement

11516 of 52192 relevant lines covered (22.06%)

22.08 hits per line

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

90.22
/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
/**
40
 * @internal since api-platform 4.2
41
 */
42
trait OperationDefaultsTrait
43
{
44
    private CamelCaseToSnakeCaseNameConverter $camelCaseToSnakeCaseNameConverter;
45
    private array $defaults = [];
46
    private LoggerInterface $logger;
47

48
    private function addGlobalDefaults(ApiResource|Operation $operation): ApiResource|Operation
49
    {
50
        // Do not add global defaults for internal resources:
51
        if (\in_array($operation->getClass(), [Error::class, ValidationException::class], true)) {
106✔
52
            return $operation;
2✔
53
        }
54

55
        $extraProperties = $this->defaults['extra_properties'] ?? [];
106✔
56

57
        foreach ($this->defaults as $key => $value) {
106✔
58
            if ('operations' === $key) {
100✔
59
                continue;
100✔
60
            }
61

62
            $upperKey = ucfirst($this->camelCaseToSnakeCaseNameConverter->denormalize($key));
100✔
63
            $getter = 'get'.$upperKey;
100✔
64

65
            if (!method_exists($operation, $getter)) {
100✔
66
                if (!isset($extraProperties[$key])) {
70✔
67
                    $extraProperties[$key] = $value;
70✔
68
                }
69

70
                continue;
70✔
71
            }
72

73
            $currentValue = $operation->{$getter}();
100✔
74

75
            if (\is_array($currentValue) && $currentValue) {
100✔
76
                if (\is_string($value)) {
100✔
77
                    $value = [$value];
×
78
                }
79

80
                $operation = $operation->{'with'.$upperKey}(array_merge($value, $currentValue));
100✔
81
            }
82

83
            if (null !== $currentValue || null === $value) {
100✔
84
                continue;
100✔
85
            }
86

87
            $operation = $operation->{'with'.$upperKey}($value);
100✔
88
        }
89

90
        return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties()));
106✔
91
    }
92

93
    private function getResourceWithDefaults(string $resourceClass, string $shortName, ApiResource $resource): ApiResource
94
    {
95
        $resource = $resource
106✔
96
            ->withShortName($resource->getShortName() ?? $shortName)
106✔
97
            ->withClass($resourceClass);
106✔
98

99
        return $this->addGlobalDefaults($resource);
106✔
100
    }
101

102
    /**
103
     * @return Operations<HttpOperation>|array<int,HttpOperation>
104
     */
105
    private function getDefaultHttpOperations(ApiResource $resource): iterable
106
    {
107
        if (enum_exists($resource->getClass())) {
32✔
108
            return new Operations([new GetCollection(paginationEnabled: false), new Get()]);
4✔
109
        }
110

111
        if (($defaultOperations = $this->defaults['operations'] ?? null) && null === $resource->getOperations()) {
28✔
112
            $operations = [];
28✔
113

114
            foreach ($defaultOperations as $defaultOperation) {
28✔
115
                $operation = new $defaultOperation();
28✔
116

117
                if ($operation instanceof Post && $resource->getUriTemplate() && !$resource->getProvider()) {
28✔
118
                    $operation = $operation->withProvider(CreateProvider::class);
2✔
119
                }
120

121
                $operations[] = $operation;
28✔
122
            }
123

124
            return new Operations($operations);
28✔
125
        }
126

127
        $post = new Post();
×
128
        if ($resource->getUriTemplate() && !$resource->getProvider()) {
×
129
            $post = $post->withProvider(CreateProvider::class);
×
130
        }
131

132
        return [new Get(), new GetCollection(), $post, new Patch(), new Delete()];
×
133
    }
134

135
    private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
136
    {
137
        $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')];
66✔
138
        $graphQlOperations = [];
66✔
139
        foreach ($operations as $operation) {
66✔
140
            [$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
66✔
141
            $graphQlOperations[$key] = $operation;
66✔
142
        }
143

144
        if ($resource->getMercure()) {
66✔
145
            [$key, $operation] = $this->getOperationWithDefaults($resource, (new Subscription())->withDescription("Subscribes to the update event of a {$operation->getShortName()}."));
2✔
146
            $graphQlOperations[$key] = $operation;
2✔
147
        }
148

149
        return $resource->withGraphQlOperations($graphQlOperations);
66✔
150
    }
151

152
    /**
153
     * Adds nested query operations if there are no existing query ones on the resource.
154
     * They are needed when the resource is queried inside a root query, using a relation.
155
     * Since the nested argument is used, root queries will not be generated for these operations.
156
     */
157
    private function completeGraphQlOperations(ApiResource $resource): ApiResource
158
    {
159
        $graphQlOperations = $resource->getGraphQlOperations();
8✔
160

161
        $hasQueryOperation = false;
8✔
162
        $hasQueryCollectionOperation = false;
8✔
163
        foreach ($graphQlOperations as $operation) {
8✔
164
            if ($operation instanceof Query && !$operation instanceof QueryCollection) {
6✔
165
                $hasQueryOperation = true;
4✔
166
            }
167
            if ($operation instanceof QueryCollection) {
6✔
168
                $hasQueryCollectionOperation = true;
4✔
169
            }
170
        }
171

172
        if (!$hasQueryOperation) {
8✔
173
            $queryOperation = (new Query())->withNested(true);
4✔
174
            $graphQlOperations[$queryOperation->getName()] = $queryOperation;
4✔
175
        }
176
        if (!$hasQueryCollectionOperation) {
8✔
177
            $queryCollectionOperation = (new QueryCollection())->withNested(true);
6✔
178
            $graphQlOperations[$queryCollectionOperation->getName()] = $queryCollectionOperation;
6✔
179
        }
180

181
        return $resource->withGraphQlOperations($graphQlOperations);
8✔
182
    }
183

184
    /**
185
     * @param list<string> $ignoredOptions
186
     *
187
     * @return array<int,mixed>
188
     */
189
    private function getOperationWithDefaults(ApiResource $resource, Operation $operation, bool $generated = false, array $ignoredOptions = []): array
190
    {
191
        $operation = $operation->cascadeFromResource($resource, $ignoredOptions)->withExtraProperties(array_merge(
106✔
192
            $resource->getExtraProperties(),
106✔
193
            $operation->getExtraProperties(),
106✔
194
            $generated ? ['generated_operation' => true] : []
106✔
195
        ));
106✔
196

197
        // Add global defaults attributes to the operation
198
        $operation = $this->addGlobalDefaults($operation);
106✔
199

200
        if ($operation instanceof GraphQlOperation) {
106✔
201
            if (!$operation->getName()) {
72✔
202
                throw new RuntimeException('No GraphQL operation name.');
×
203
            }
204

205
            if ($operation instanceof Mutation) {
72✔
206
                $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
64✔
207
            }
208

209
            return [$operation->getName(), $operation];
72✔
210
        }
211

212
        if (!$operation instanceof HttpOperation) {
106✔
213
            throw new RuntimeException(\sprintf('Operation should be an instance of "%s"', HttpOperation::class));
×
214
        }
215

216
        if (!$operation->getName() && $operation->getRouteName()) {
106✔
217
            /** @var HttpOperation $operation */
218
            $operation = $operation->withName($operation->getRouteName());
×
219
        }
220

221
        $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass());
106✔
222

223
        return [
106✔
224
            $operationName,
106✔
225
            $operation,
106✔
226
        ];
106✔
227
    }
228

229
    private function getDefaultShortname(string $resourceClass): string
230
    {
231
        return (false !== $pos = strrpos($resourceClass, '\\')) ? substr($resourceClass, $pos + 1) : $resourceClass;
×
232
    }
233

234
    private function getDefaultOperationName(HttpOperation $operation, string $resourceClass): string
235
    {
236
        $path = ($operation->getRoutePrefix() ?? '').($operation->getUriTemplate() ?? '');
106✔
237

238
        return \sprintf(
106✔
239
            '_api_%s_%s%s',
106✔
240
            $path ?: ($operation->getShortName() ?? $this->getDefaultShortname($resourceClass)),
106✔
241
            strtolower($operation->getMethod()),
106✔
242
            $operation instanceof CollectionOperationInterface ? '_collection' : ''
106✔
243
        );
106✔
244
    }
245
}
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