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

api-platform / core / 14273749539

04 Apr 2025 08:37PM UTC coverage: 8.52%. Remained the same
14273749539

Pull #7066

github

web-flow
Merge e2763ae0f into 5e47ca394
Pull Request #7066: fix(subresource): Link Security YAML

1 of 3 new or added lines in 2 files covered. (33.33%)

2091 existing lines in 148 files now uncovered.

13395 of 157214 relevant lines covered (8.52%)

22.94 hits per line

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

91.92
/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
    {
45
        $extraProperties = $this->defaults['extra_properties'] ?? [];
134✔
46

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

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

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

60
                continue;
109✔
61
            }
62

63
            $currentValue = $operation->{$getter}();
128✔
64

65
            if (\is_array($currentValue) && $currentValue) {
128✔
66
                if (\is_string($value)) {
128✔
67
                    $value = [$value];
×
68
                }
69

70
                $operation = $operation->{'with'.$upperKey}(array_merge($value, $currentValue));
128✔
71
            }
72

73
            if (null !== $currentValue || null === $value) {
128✔
74
                continue;
128✔
75
            }
76

77
            $operation = $operation->{'with'.$upperKey}($value);
128✔
78
        }
79

80
        return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties()));
134✔
81
    }
82

83
    private function getResourceWithDefaults(string $resourceClass, string $shortName, ApiResource $resource): ApiResource
84
    {
85
        $resource = $resource
134✔
86
            ->withShortName($resource->getShortName() ?? $shortName)
134✔
87
            ->withClass($resourceClass);
134✔
88

89
        return $this->addGlobalDefaults($resource);
134✔
90
    }
91

92
    private function getDefaultHttpOperations($resource): iterable
93
    {
94
        if (enum_exists($resource->getClass())) {
57✔
95
            return new Operations([new GetCollection(paginationEnabled: false), new Get()]);
7✔
96
        }
97

98
        if (($defaultOperations = $this->defaults['operations'] ?? null) && null === $resource->getOperations()) {
53✔
99
            $operations = [];
53✔
100

101
            foreach ($defaultOperations as $defaultOperation) {
53✔
102
                $operation = new $defaultOperation();
53✔
103

104
                if ($operation instanceof Post && $resource->getUriTemplate() && !$resource->getProvider()) {
53✔
UNCOV
105
                    $operation = $operation->withProvider(CreateProvider::class);
2✔
106
                }
107

108
                $operations[] = $operation;
53✔
109
            }
110

111
            return new Operations($operations);
53✔
112
        }
113

114
        $post = new Post();
×
115
        if ($resource->getUriTemplate() && !$resource->getProvider()) {
×
116
            $post = $post->withProvider(CreateProvider::class);
×
117
        }
118

119
        return [new Get(), new GetCollection(), $post, new Patch(), new Delete()];
×
120
    }
121

122
    private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
123
    {
124
        $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')];
90✔
125
        $graphQlOperations = [];
90✔
126
        foreach ($operations as $operation) {
90✔
127
            [$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
90✔
128
            $graphQlOperations[$key] = $operation;
90✔
129
        }
130

131
        if ($resource->getMercure()) {
90✔
132
            [$key, $operation] = $this->getOperationWithDefaults($resource, (new Subscription())->withDescription("Subscribes to the update event of a {$operation->getShortName()}."));
4✔
133
            $graphQlOperations[$key] = $operation;
4✔
134
        }
135

136
        return $resource->withGraphQlOperations($graphQlOperations);
90✔
137
    }
138

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

148
        $hasQueryOperation = false;
24✔
149
        $hasQueryCollectionOperation = false;
24✔
150
        foreach ($graphQlOperations as $operation) {
24✔
151
            if ($operation instanceof Query && !$operation instanceof QueryCollection) {
17✔
152
                $hasQueryOperation = true;
15✔
153
            }
154
            if ($operation instanceof QueryCollection) {
17✔
155
                $hasQueryCollectionOperation = true;
10✔
156
            }
157
        }
158

159
        if (!$hasQueryOperation) {
24✔
160
            $queryOperation = (new Query())->withNested(true);
11✔
161
            $graphQlOperations[$queryOperation->getName()] = $queryOperation;
11✔
162
        }
163
        if (!$hasQueryCollectionOperation) {
24✔
164
            $queryCollectionOperation = (new QueryCollection())->withNested(true);
18✔
165
            $graphQlOperations[$queryCollectionOperation->getName()] = $queryCollectionOperation;
18✔
166
        }
167

168
        return $resource->withGraphQlOperations($graphQlOperations);
24✔
169
    }
170

171
    private function getOperationWithDefaults(ApiResource $resource, Operation $operation, bool $generated = false, array $ignoredOptions = []): array
172
    {
173
        // Inherit from resource defaults
174
        foreach (get_class_methods($resource) as $methodName) {
134✔
175
            if (!str_starts_with($methodName, 'get')) {
134✔
176
                continue;
134✔
177
            }
178

179
            if (\in_array(lcfirst(substr($methodName, 3)), $ignoredOptions, true)) {
134✔
180
                continue;
34✔
181
            }
182

183
            if (!method_exists($operation, $methodName) || null !== $operation->{$methodName}()) {
134✔
184
                continue;
134✔
185
            }
186

187
            if (null === ($value = $resource->{$methodName}())) {
134✔
188
                continue;
134✔
189
            }
190

191
            $operation = $operation->{'with'.substr($methodName, 3)}($value);
134✔
192
        }
193

194
        $operation = $operation->withExtraProperties(array_merge(
134✔
195
            $resource->getExtraProperties(),
134✔
196
            $operation->getExtraProperties(),
134✔
197
            $generated ? ['generated_operation' => true] : []
134✔
198
        ));
134✔
199

200
        // Add global defaults attributes to the operation
201
        $operation = $this->addGlobalDefaults($operation);
134✔
202

203
        if ($operation instanceof GraphQlOperation) {
134✔
204
            if (!$operation->getName()) {
109✔
205
                throw new RuntimeException('No GraphQL operation name.');
×
206
            }
207

208
            if ($operation instanceof Mutation) {
109✔
209
                $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
97✔
210
            }
211

212
            return [$operation->getName(), $operation];
109✔
213
        }
214

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

219
        if (!$operation->getName() && $operation->getRouteName()) {
134✔
220
            /** @var HttpOperation $operation */
UNCOV
221
            $operation = $operation->withName($operation->getRouteName());
2✔
222
        }
223

224
        $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass());
134✔
225

226
        return [
134✔
227
            $operationName,
134✔
228
            $operation,
134✔
229
        ];
134✔
230
    }
231

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

237
    private function getDefaultOperationName(HttpOperation $operation, string $resourceClass): string
238
    {
239
        $path = ($operation->getRoutePrefix() ?? '').($operation->getUriTemplate() ?? '');
131✔
240

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