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

api-platform / core / 9710836697

28 Jun 2024 09:35AM UTC coverage: 63.285% (+1.2%) from 62.122%
9710836697

push

github

soyuka
docs: changelog v3.3.7

11104 of 17546 relevant lines covered (63.29%)

52.26 hits per line

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

91.21
/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\Put;
34
use ApiPlatform\Metadata\Util\CamelCaseToSnakeCaseNameConverter;
35
use ApiPlatform\State\CreateProvider;
36
use Psr\Log\LoggerInterface;
37

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

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

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

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

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

61
                continue;
9✔
62
            }
63

64
            $currentValue = $operation->{$getter}();
14✔
65

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

70
            if (null !== $currentValue) {
14✔
71
                continue;
14✔
72
            }
73

74
            $operation = $operation->{'with'.$upperKey}($value);
14✔
75
        }
76

77
        return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties()));
26✔
78
    }
79

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

86
        return $this->addGlobalDefaults($resource);
26✔
87
    }
88

89
    private function getDefaultHttpOperations($resource): iterable
90
    {
91
        if (($defaultOperations = $this->defaults['operations'] ?? null) && null === $resource->getOperations()) {
4✔
92
            $operations = [];
×
93

94
            foreach ($defaultOperations as $defaultOperation) {
×
95
                $operations[] = new $defaultOperation();
×
96
            }
97

98
            return new Operations($operations);
×
99
        }
100

101
        $post = new Post();
4✔
102
        if ($resource->getUriTemplate() && !$resource->getProvider()) {
4✔
103
            $post = $post->withProvider(CreateProvider::class);
4✔
104
        }
105

106
        return [new Get(), new GetCollection(), $post, new Put(), new Patch(), new Delete()];
4✔
107
    }
108

109
    private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
110
    {
111
        $graphQlOperations = [];
4✔
112
        foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $operation) {
4✔
113
            [$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
4✔
114
            $graphQlOperations[$key] = $operation;
4✔
115
        }
116

117
        if ($resource->getMercure()) {
4✔
118
            [$key, $operation] = $this->getOperationWithDefaults($resource, (new Subscription())->withDescription("Subscribes to the update event of a {$operation->getShortName()}."));
4✔
119
            $graphQlOperations[$key] = $operation;
4✔
120
        }
121

122
        return $resource->withGraphQlOperations($graphQlOperations);
4✔
123
    }
124

125
    /**
126
     * Adds nested query operations if there are no existing query ones on the resource.
127
     * They are needed when the resource is queried inside a root query, using a relation.
128
     * Since the nested argument is used, root queries will not be generated for these operations.
129
     */
130
    private function completeGraphQlOperations(ApiResource $resource): ApiResource
131
    {
132
        $graphQlOperations = $resource->getGraphQlOperations();
9✔
133

134
        $hasQueryOperation = false;
9✔
135
        $hasQueryCollectionOperation = false;
9✔
136
        foreach ($graphQlOperations as $operation) {
9✔
137
            if ($operation instanceof Query && !$operation instanceof QueryCollection) {
8✔
138
                $hasQueryOperation = true;
4✔
139
            }
140
            if ($operation instanceof QueryCollection) {
8✔
141
                $hasQueryCollectionOperation = true;
8✔
142
            }
143
        }
144

145
        if (!$hasQueryOperation) {
9✔
146
            $queryOperation = (new Query())->withNested(true);
9✔
147
            $graphQlOperations[$queryOperation->getName()] = $queryOperation;
9✔
148
        }
149
        if (!$hasQueryCollectionOperation) {
9✔
150
            $queryCollectionOperation = (new QueryCollection())->withNested(true);
5✔
151
            $graphQlOperations[$queryCollectionOperation->getName()] = $queryCollectionOperation;
5✔
152
        }
153

154
        return $resource->withGraphQlOperations($graphQlOperations);
9✔
155
    }
156

157
    private function getOperationWithDefaults(ApiResource $resource, Operation $operation, bool $generated = false, array $ignoredOptions = []): array
158
    {
159
        // Inherit from resource defaults
160
        foreach (get_class_methods($resource) as $methodName) {
26✔
161
            if (!str_starts_with($methodName, 'get')) {
26✔
162
                continue;
26✔
163
            }
164

165
            if (\in_array(lcfirst(substr($methodName, 3)), $ignoredOptions, true)) {
26✔
166
                continue;
8✔
167
            }
168

169
            if (!method_exists($operation, $methodName) || null !== $operation->{$methodName}()) {
26✔
170
                continue;
26✔
171
            }
172

173
            if (null === ($value = $resource->{$methodName}())) {
26✔
174
                continue;
26✔
175
            }
176

177
            $operation = $operation->{'with'.substr($methodName, 3)}($value);
26✔
178
        }
179

180
        $operation = $operation->withExtraProperties(array_merge(
26✔
181
            $resource->getExtraProperties(),
26✔
182
            $operation->getExtraProperties(),
26✔
183
            $generated ? ['generated_operation' => true] : []
26✔
184
        ));
26✔
185

186
        // Add global defaults attributes to the operation
187
        $operation = $this->addGlobalDefaults($operation);
26✔
188

189
        if ($operation instanceof GraphQlOperation) {
26✔
190
            if (!$operation->getName()) {
9✔
191
                throw new RuntimeException('No GraphQL operation name.');
×
192
            }
193

194
            if ($operation instanceof Mutation) {
9✔
195
                $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
4✔
196
            }
197

198
            return [$operation->getName(), $operation];
9✔
199
        }
200

201
        if (!$operation instanceof HttpOperation) {
26✔
202
            throw new RuntimeException(sprintf('Operation should be an instance of "%s"', HttpOperation::class));
×
203
        }
204

205
        if (!$operation->getName() && $operation->getRouteName()) {
26✔
206
            /** @var HttpOperation $operation */
207
            $operation = $operation->withName($operation->getRouteName());
4✔
208
        }
209

210
        $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass());
26✔
211

212
        return [
26✔
213
            $operationName,
26✔
214
            $operation,
26✔
215
        ];
26✔
216
    }
217

218
    private function getDefaultShortname(string $resourceClass): string
219
    {
220
        return (false !== $pos = strrpos($resourceClass, '\\')) ? substr($resourceClass, $pos + 1) : $resourceClass;
×
221
    }
222

223
    private function getDefaultOperationName(HttpOperation $operation, string $resourceClass): string
224
    {
225
        $path = ($operation->getRoutePrefix() ?? '').($operation->getUriTemplate() ?? '');
25✔
226

227
        return sprintf(
25✔
228
            '_api_%s_%s%s',
25✔
229
            $path ?: ($operation->getShortName() ?? $this->getDefaultShortname($resourceClass)),
25✔
230
            strtolower($operation->getMethod()),
25✔
231
            $operation instanceof CollectionOperationInterface ? '_collection' : '');
25✔
232
    }
233
}
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