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

api-platform / core / 13200284839

07 Feb 2025 12:56PM UTC coverage: 0.0% (-8.2%) from 8.164%
13200284839

Pull #6952

github

web-flow
Merge 519fbf8cc into 62377f880
Pull Request #6952: fix: errors retrieval and documentation

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

10757 existing lines in 366 files now uncovered.

0 of 47781 relevant lines covered (0.0%)

0.0 hits per line

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

0.0
/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\CreateProvider;
35
use Psr\Log\LoggerInterface;
36

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

43
    private function addGlobalDefaults(ApiResource|Operation $operation): ApiResource|Operation
44
    {
UNCOV
45
        $extraProperties = $this->defaults['extra_properties'] ?? [];
×
46

UNCOV
47
        foreach ($this->defaults as $key => $value) {
×
UNCOV
48
            if ('operations' === $key) {
×
UNCOV
49
                continue;
×
50
            }
51

UNCOV
52
            $upperKey = ucfirst($this->camelCaseToSnakeCaseNameConverter->denormalize($key));
×
UNCOV
53
            $getter = 'get'.$upperKey;
×
54

UNCOV
55
            if (!method_exists($operation, $getter)) {
×
UNCOV
56
                if (!isset($extraProperties[$key])) {
×
UNCOV
57
                    $extraProperties[$key] = $value;
×
58
                }
59

UNCOV
60
                continue;
×
61
            }
62

UNCOV
63
            $currentValue = $operation->{$getter}();
×
64

UNCOV
65
            if (\is_array($currentValue) && $currentValue) {
×
UNCOV
66
                $operation = $operation->{'with'.$upperKey}(array_merge($value, $currentValue));
×
67
            }
68

UNCOV
69
            if (null !== $currentValue || null === $value) {
×
UNCOV
70
                continue;
×
71
            }
72

UNCOV
73
            $operation = $operation->{'with'.$upperKey}($value);
×
74
        }
75

UNCOV
76
        return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties()));
×
77
    }
78

79
    private function getResourceWithDefaults(string $resourceClass, string $shortName, ApiResource $resource): ApiResource
80
    {
UNCOV
81
        $resource = $resource
×
UNCOV
82
            ->withShortName($resource->getShortName() ?? $shortName)
×
UNCOV
83
            ->withClass($resourceClass);
×
84

UNCOV
85
        return $this->addGlobalDefaults($resource);
×
86
    }
87

88
    private function getDefaultHttpOperations($resource): iterable
89
    {
UNCOV
90
        if (enum_exists($resource->getClass())) {
×
UNCOV
91
            return new Operations([new GetCollection(paginationEnabled: false), new Get()]);
×
92
        }
93

UNCOV
94
        if (($defaultOperations = $this->defaults['operations'] ?? null) && null === $resource->getOperations()) {
×
UNCOV
95
            $operations = [];
×
96

UNCOV
97
            foreach ($defaultOperations as $defaultOperation) {
×
UNCOV
98
                $operation = new $defaultOperation();
×
99

UNCOV
100
                if ($operation instanceof Post && $resource->getUriTemplate() && !$resource->getProvider()) {
×
101
                    $operation = $operation->withProvider(CreateProvider::class);
×
102
                }
103

UNCOV
104
                $operations[] = $operation;
×
105
            }
106

UNCOV
107
            return new Operations($operations);
×
108
        }
109

110
        $post = new Post();
×
111
        if ($resource->getUriTemplate() && !$resource->getProvider()) {
×
112
            $post = $post->withProvider(CreateProvider::class);
×
113
        }
114

115
        return [new Get(), new GetCollection(), $post, new Patch(), new Delete()];
×
116
    }
117

118
    private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
119
    {
UNCOV
120
        $operations = enum_exists($resource->getClass()) ? [new QueryCollection(paginationEnabled: false), new Query()] : [new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')];
×
UNCOV
121
        $graphQlOperations = [];
×
UNCOV
122
        foreach ($operations as $operation) {
×
UNCOV
123
            [$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
×
UNCOV
124
            $graphQlOperations[$key] = $operation;
×
125
        }
126

UNCOV
127
        if ($resource->getMercure()) {
×
UNCOV
128
            [$key, $operation] = $this->getOperationWithDefaults($resource, (new Subscription())->withDescription("Subscribes to the update event of a {$operation->getShortName()}."));
×
UNCOV
129
            $graphQlOperations[$key] = $operation;
×
130
        }
131

UNCOV
132
        return $resource->withGraphQlOperations($graphQlOperations);
×
133
    }
134

135
    /**
136
     * Adds nested query operations if there are no existing query ones on the resource.
137
     * They are needed when the resource is queried inside a root query, using a relation.
138
     * Since the nested argument is used, root queries will not be generated for these operations.
139
     */
140
    private function completeGraphQlOperations(ApiResource $resource): ApiResource
141
    {
UNCOV
142
        $graphQlOperations = $resource->getGraphQlOperations();
×
143

UNCOV
144
        $hasQueryOperation = false;
×
UNCOV
145
        $hasQueryCollectionOperation = false;
×
UNCOV
146
        foreach ($graphQlOperations as $operation) {
×
UNCOV
147
            if ($operation instanceof Query && !$operation instanceof QueryCollection) {
×
UNCOV
148
                $hasQueryOperation = true;
×
149
            }
UNCOV
150
            if ($operation instanceof QueryCollection) {
×
UNCOV
151
                $hasQueryCollectionOperation = true;
×
152
            }
153
        }
154

UNCOV
155
        if (!$hasQueryOperation) {
×
UNCOV
156
            $queryOperation = (new Query())->withNested(true);
×
UNCOV
157
            $graphQlOperations[$queryOperation->getName()] = $queryOperation;
×
158
        }
UNCOV
159
        if (!$hasQueryCollectionOperation) {
×
UNCOV
160
            $queryCollectionOperation = (new QueryCollection())->withNested(true);
×
UNCOV
161
            $graphQlOperations[$queryCollectionOperation->getName()] = $queryCollectionOperation;
×
162
        }
163

UNCOV
164
        return $resource->withGraphQlOperations($graphQlOperations);
×
165
    }
166

167
    private function getOperationWithDefaults(ApiResource $resource, Operation $operation, bool $generated = false, array $ignoredOptions = []): array
168
    {
169
        // Inherit from resource defaults
UNCOV
170
        foreach (get_class_methods($resource) as $methodName) {
×
UNCOV
171
            if (!str_starts_with($methodName, 'get')) {
×
UNCOV
172
                continue;
×
173
            }
174

UNCOV
175
            if (\in_array(lcfirst(substr($methodName, 3)), $ignoredOptions, true)) {
×
UNCOV
176
                continue;
×
177
            }
178

UNCOV
179
            if (!method_exists($operation, $methodName) || null !== $operation->{$methodName}()) {
×
UNCOV
180
                continue;
×
181
            }
182

UNCOV
183
            if (null === ($value = $resource->{$methodName}())) {
×
UNCOV
184
                continue;
×
185
            }
186

UNCOV
187
            $operation = $operation->{'with'.substr($methodName, 3)}($value);
×
188
        }
189

UNCOV
190
        $operation = $operation->withExtraProperties(array_merge(
×
UNCOV
191
            $resource->getExtraProperties(),
×
UNCOV
192
            $operation->getExtraProperties(),
×
UNCOV
193
            $generated ? ['generated_operation' => true] : []
×
UNCOV
194
        ));
×
195

196
        // Add global defaults attributes to the operation
UNCOV
197
        $operation = $this->addGlobalDefaults($operation);
×
198

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

UNCOV
204
            if ($operation instanceof Mutation) {
×
UNCOV
205
                $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
×
206
            }
207

UNCOV
208
            return [$operation->getName(), $operation];
×
209
        }
210

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

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

UNCOV
220
        $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass());
×
221

UNCOV
222
        return [
×
UNCOV
223
            $operationName,
×
UNCOV
224
            $operation,
×
UNCOV
225
        ];
×
226
    }
227

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

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

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