OpenApiPathBuilder::getDetailPath()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 2
nop 1
dl 0
loc 20
rs 9.7998
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Framework\Api\ApiDefinition\Generator\OpenApi;
4
5
use OpenApi\Annotations\Delete;
6
use OpenApi\Annotations\Get;
7
use OpenApi\Annotations\Parameter;
8
use OpenApi\Annotations\Patch;
9
use OpenApi\Annotations\PathItem;
10
use OpenApi\Annotations\Post;
11
use OpenApi\Annotations\Response as OpenApiResponse;
12
use OpenApi\Annotations\Tag;
13
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
14
use Shopware\Core\Framework\Log\Package;
15
use Shopware\Core\System\SalesChannel\Entity\SalesChannelDefinitionInterface;
16
use Symfony\Component\HttpFoundation\Response;
17
18
#[Package('core')]
19
class OpenApiPathBuilder
20
{
21
    private const EXPERIMENTAL_ANNOTATION_NAME = 'experimental';
22
23
    /**
24
     * @return PathItem[]
25
     */
26
    public function getPathActions(EntityDefinition $definition, string $path): array
27
    {
28
        $paths = [];
29
        $paths[$path] = new PathItem([
30
            'path' => $path,
31
        ]);
32
        $paths[$path]->get = $this->getListingPath($definition, $path);
33
34
        $paths[$path . '/{id}'] = new PathItem([
35
            'path' => $path . '/{id}',
36
        ]);
37
        $paths[$path . '/{id}']->get = $this->getDetailPath($definition);
38
39
        if (is_subclass_of($definition, SalesChannelDefinitionInterface::class)) {
40
            return $paths;
41
        }
42
43
        $paths[$path]->post = $this->getCreatePath($definition);
44
        $paths[$path . '/{id}']->patch = $this->getUpdatePath($definition);
45
        $paths[$path . '/{id}']->delete = $this->getDeletePath($definition);
46
47
        return $paths;
48
    }
49
50
    public function getTag(EntityDefinition $definition): Tag
51
    {
52
        $humanReadableName = $this->convertToHumanReadable($definition->getEntityName());
53
54
        return new Tag(['name' => $humanReadableName, 'description' => 'The endpoint for operations on ' . $humanReadableName]);
55
    }
56
57
    private function getListingPath(EntityDefinition $definition, string $path): Get
58
    {
59
        $humanReadableName = $this->convertToHumanReadable($definition->getEntityName());
60
        $tags = [$humanReadableName];
61
62
        if ($experimental = $this->isExperimental($definition)) {
63
            $tags[] = 'Experimental';
64
        }
65
66
        $schemaName = $this->snakeCaseToCamelCase($definition->getEntityName());
67
68
        return new Get([
69
            'summary' => 'List with basic information of ' . $humanReadableName . ' resources.' . ($experimental ? ' Experimental API, not part of our backwards compatibility promise, thus this API can introduce breaking changes at any time.' : ''),
70
            'description' => $definition->since() ? 'Available since: ' . $definition->since() : '',
0 ignored issues
show
Bug introduced by
Are you sure $definition->since() of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

70
            'description' => $definition->since() ? 'Available since: ' . /** @scrutinizer ignore-type */ $definition->since() : '',
Loading history...
Bug introduced by
Are you sure the usage of $definition->since() targeting Shopware\Core\Framework\...tityDefinition::since() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
71
            'tags' => $tags,
72
            'parameters' => $this->getDefaultListingParameter(),
73
            'operationId' => 'get' . $this->convertToOperationId($definition->getEntityName()) . 'List',
74
            'responses' => [
75
                Response::HTTP_OK => new OpenApiResponse([
76
                    'response' => Response::HTTP_OK,
77
                    'description' => 'List of ' . $humanReadableName . ' resources.',
78
                    'content' => [
79
                        'application/vnd.api+json' => [
80
                            'schema' => [
81
                                'allOf' => [
82
                                    ['$ref' => '#/components/schemas/success'],
83
                                    [
84
                                        'type' => 'object',
85
                                        'properties' => [
86
                                            'data' => [
87
                                                'allOf' => [
88
                                                    ['$ref' => '#/components/schemas/data'],
89
                                                    [
90
                                                        'type' => 'array',
91
                                                        'items' => [
92
                                                            '$ref' => '#/components/schemas/' . $schemaName,
93
                                                        ],
94
                                                    ],
95
                                                ],
96
                                            ],
97
                                            'links' => [
98
                                                'allOf' => [
99
                                                    ['$ref' => '#/components/schemas/pagination'],
100
                                                    [
101
                                                        'type' => 'object',
102
                                                        'properties' => [
103
                                                            'first' => ['example' => $path . '?limit=25'],
104
                                                            'last' => ['example' => $path . '?limit=25&page=11'],
105
                                                            'next' => ['example' => $path . '?limit=25&page=4'],
106
                                                            'prev' => ['example' => $path . '?limit=25&page=2'],
107
                                                        ],
108
                                                    ],
109
                                                ],
110
                                            ],
111
                                        ],
112
                                    ],
113
                                ],
114
                            ],
115
                        ],
116
                        'application/json' => [
117
                            'schema' => [
118
                                'type' => 'object',
119
                                'properties' => [
120
                                    'total' => ['type' => 'integer'],
121
                                    'data' => [
122
                                        'type' => 'array',
123
                                        'items' => [
124
                                            '$ref' => '#/components/schemas/' . $schemaName,
125
                                        ],
126
                                    ],
127
                                ],
128
                            ],
129
                        ],
130
                    ],
131
                ]),
132
                Response::HTTP_UNAUTHORIZED => $this->getResponseRef((string) Response::HTTP_UNAUTHORIZED),
133
            ],
134
        ]);
135
    }
136
137
    private function getDetailPath(EntityDefinition $definition): Get
138
    {
139
        $schemaName = $this->snakeCaseToCamelCase($definition->getEntityName());
140
141
        $tags = [$this->convertToHumanReadable($definition->getEntityName())];
142
143
        if ($experimental = $this->isExperimental($definition)) {
144
            $tags[] = 'Experimental';
145
        }
146
147
        return new Get([
148
            'summary' => 'Detailed information about a ' . $this->convertToHumanReadable($definition->getEntityName()) . ' resource.' . ($experimental ? ' Experimental API, not part of our backwards compatibility promise, thus this API can introduce breaking changes at any time.' : ''),
149
            'description' => $definition->since() ? 'Available since: ' . $definition->since() : '',
0 ignored issues
show
Bug introduced by
Are you sure the usage of $definition->since() targeting Shopware\Core\Framework\...tityDefinition::since() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $definition->since() of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
            'description' => $definition->since() ? 'Available since: ' . /** @scrutinizer ignore-type */ $definition->since() : '',
Loading history...
150
            'operationId' => 'get' . $this->convertToOperationId($definition->getEntityName()),
151
            'tags' => $tags,
152
            'parameters' => [$this->getIdParameter($definition)],
153
            'responses' => [
154
                Response::HTTP_OK => $this->getDetailResponse($schemaName),
155
                Response::HTTP_NOT_FOUND => $this->getResponseRef((string) Response::HTTP_NOT_FOUND),
156
                Response::HTTP_UNAUTHORIZED => $this->getResponseRef((string) Response::HTTP_UNAUTHORIZED),
157
            ],
158
        ]);
159
    }
160
161
    private function getCreatePath(EntityDefinition $definition): Post
162
    {
163
        $schemaName = $this->snakeCaseToCamelCase($definition->getEntityName());
164
165
        $tags = [$this->convertToHumanReadable($definition->getEntityName())];
166
167
        if ($experimental = $this->isExperimental($definition)) {
168
            $tags[] = 'Experimental';
169
        }
170
171
        return new Post([
172
            'summary' => 'Create a new ' . $this->convertToHumanReadable($definition->getEntityName()) . ' resources.' . ($experimental ? ' Experimental API, not part of our backwards compatibility promise, thus this API can introduce breaking changes at any time.' : ''),
173
            'description' => $definition->since() ? 'Available since: ' . $definition->since() : '',
0 ignored issues
show
Bug introduced by
Are you sure the usage of $definition->since() targeting Shopware\Core\Framework\...tityDefinition::since() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $definition->since() of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

173
            'description' => $definition->since() ? 'Available since: ' . /** @scrutinizer ignore-type */ $definition->since() : '',
Loading history...
174
            'tags' => $tags,
175
            'operationId' => 'create' . $this->convertToOperationId($definition->getEntityName()),
176
            'parameters' => [
177
                new Parameter([
178
                    'name' => '_response',
179
                    'in' => 'query',
180
                    'schema' => ['type' => 'string', 'enum' => ['basic', 'detail']],
181
                    'description' => 'Data format for response. Empty if none is provided.',
182
                ]),
183
            ],
184
            'requestBody' => [
185
                'content' => [
186
                    'application/vnd.api+json' => [
187
                        'schema' => [
188
                            'type' => 'object',
189
                            'properties' => [
190
                                'data' => [
191
                                    '$ref' => '#/components/schemas/' . $schemaName,
192
                                ],
193
                                'included' => [
194
                                    'type' => 'array',
195
                                    'items' => ['$ref' => '#/components/schemas/resource'],
196
                                    'uniqueItems' => true,
197
                                ],
198
                            ],
199
                        ],
200
                    ],
201
                    'application/json' => [
202
                        'schema' => [
203
                            '$ref' => '#/components/schemas/' . $schemaName,
204
                        ],
205
                    ],
206
                ],
207
            ],
208
            'responses' => [
209
                Response::HTTP_CREATED => $this->getDetailResponse($schemaName),
210
                Response::HTTP_BAD_REQUEST => $this->getResponseRef((string) Response::HTTP_BAD_REQUEST),
211
                Response::HTTP_UNAUTHORIZED => $this->getResponseRef((string) Response::HTTP_UNAUTHORIZED),
212
            ],
213
        ]);
214
    }
215
216
    private function getUpdatePath(EntityDefinition $definition): Patch
217
    {
218
        $schemaName = $this->snakeCaseToCamelCase($definition->getEntityName());
219
220
        $tags = [$this->convertToHumanReadable($definition->getEntityName())];
221
222
        if ($experimental = $this->isExperimental($definition)) {
223
            $tags[] = 'Experimental';
224
        }
225
226
        return new Patch([
227
            'summary' => 'Partially update information about a ' . $this->convertToHumanReadable($definition->getEntityName()) . ' resource.' . ($experimental ? ' Experimental API, not part of our backwards compatibility promise, thus this API can introduce breaking changes at any time.' : ''),
228
            'description' => $definition->since() ? 'Available since: ' . $definition->since() : '',
0 ignored issues
show
Bug introduced by
Are you sure the usage of $definition->since() targeting Shopware\Core\Framework\...tityDefinition::since() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $definition->since() of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

228
            'description' => $definition->since() ? 'Available since: ' . /** @scrutinizer ignore-type */ $definition->since() : '',
Loading history...
229
            'operationId' => 'update' . $this->convertToOperationId($definition->getEntityName()),
230
            'tags' => $tags,
231
            'parameters' => [$this->getIdParameter($definition), $this->getResponseDataParameter()],
232
            'requestBody' => [
233
                'description' => 'Partially update information about a ' . $this->convertToHumanReadable($definition->getEntityName()) . ' resource.',
234
                'content' => [
235
                    'application/vnd.api+json' => [
236
                        'schema' => [
237
                            'type' => 'object',
238
                            'properties' => [
239
                                'data' => [
240
                                    '$ref' => '#/components/schemas/' . $schemaName,
241
                                ],
242
                                'included' => [
243
                                    'type' => 'array',
244
                                    'items' => ['$ref' => '#/components/schemas/resource'],
245
                                    'uniqueItems' => true,
246
                                ],
247
                            ],
248
                        ],
249
                    ],
250
                    'application/json' => [
251
                        'schema' => [
252
                            '$ref' => '#/components/schemas/' . $schemaName,
253
                        ],
254
                    ],
255
                ],
256
            ],
257
            'responses' => [
258
                Response::HTTP_OK => $this->getDetailResponse($schemaName),
259
                Response::HTTP_BAD_REQUEST => $this->getResponseRef((string) Response::HTTP_BAD_REQUEST),
260
                Response::HTTP_NOT_FOUND => $this->getResponseRef((string) Response::HTTP_NOT_FOUND),
261
                Response::HTTP_UNAUTHORIZED => $this->getResponseRef((string) Response::HTTP_UNAUTHORIZED),
262
            ],
263
        ]);
264
    }
265
266
    private function getDeletePath(EntityDefinition $definition): Delete
267
    {
268
        $tags = [$this->convertToHumanReadable($definition->getEntityName())];
269
270
        if ($experimental = $this->isExperimental($definition)) {
271
            $tags[] = 'Experimental';
272
        }
273
274
        return new Delete([
275
            'operationId' => 'delete' . $this->convertToOperationId($definition->getEntityName()),
276
            'description' => $definition->since() ? 'Available since: ' . $definition->since() : '',
0 ignored issues
show
Bug introduced by
Are you sure the usage of $definition->since() targeting Shopware\Core\Framework\...tityDefinition::since() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $definition->since() of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

276
            'description' => $definition->since() ? 'Available since: ' . /** @scrutinizer ignore-type */ $definition->since() : '',
Loading history...
277
            'summary' => 'Delete a ' . $this->convertToHumanReadable($definition->getEntityName()) . ' resource.' . ($experimental ? ' Experimental API, not part of our backwards compatibility promise, thus this API can introduce breaking changes at any time.' : ''),
278
            'tags' => $tags,
279
            'parameters' => [$this->getIdParameter($definition), $this->getResponseDataParameter()],
280
            'responses' => [
281
                Response::HTTP_NO_CONTENT => $this->getResponseRef((string) Response::HTTP_NO_CONTENT),
282
                Response::HTTP_NOT_FOUND => $this->getResponseRef((string) Response::HTTP_NOT_FOUND),
283
                Response::HTTP_UNAUTHORIZED => $this->getResponseRef((string) Response::HTTP_UNAUTHORIZED),
284
            ],
285
        ]);
286
    }
287
288
    private function convertToHumanReadable(string $name): string
289
    {
290
        $nameParts = array_map('ucfirst', explode('_', $name));
291
292
        return implode(' ', $nameParts);
293
    }
294
295
    private function convertToOperationId(string $name): string
296
    {
297
        $name = ucfirst($this->convertToHumanReadable($name));
298
299
        return str_replace(' ', '', $name);
300
    }
301
302
    /**
303
     * @return list<Parameter>
0 ignored issues
show
Bug introduced by
The type Shopware\Core\Framework\...\Generator\OpenApi\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
304
     */
305
    private function getDefaultListingParameter(): array
306
    {
307
        return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(new OpenApi...oded SwagQL in JSON'))) returns the type array<integer,OpenApi\Annotations\Parameter> which is incompatible with the documented return type Shopware\Core\Framework\...\Generator\OpenApi\list.
Loading history...
308
            new Parameter([
309
                'name' => 'limit',
310
                'in' => 'query',
311
                'schema' => [
312
                    'type' => 'integer',
313
                ],
314
                'description' => 'Max amount of resources to be returned in a page',
315
            ]),
316
            new Parameter([
317
                'name' => 'page',
318
                'in' => 'query',
319
                'schema' => [
320
                    'type' => 'integer',
321
                ],
322
                'description' => 'The page to be returned',
323
            ]),
324
            new Parameter([
325
                'name' => 'query',
326
                'in' => 'query',
327
                'schema' => [
328
                    'type' => 'string',
329
                ],
330
                'description' => 'Encoded SwagQL in JSON',
331
            ]),
332
        ];
333
    }
334
335
    private function getDetailResponse(string $schemaName): OpenApiResponse
336
    {
337
        return new OpenApiResponse([
338
            'response' => Response::HTTP_OK,
339
            'description' => 'Detail of ' . $schemaName,
340
            'content' => [
341
                'application/vnd.api+json' => [
342
                    'schema' => [
343
                        'allOf' => [
344
                            ['$ref' => '#/components/schemas/success'],
345
                            [
346
                                'type' => 'object',
347
                                'properties' => [
348
                                    'data' => [
349
                                        '$ref' => '#/components/schemas/' . $schemaName,
350
                                    ],
351
                                ],
352
                            ],
353
                        ],
354
                    ],
355
                ],
356
                'application/json' => [
357
                    'schema' => [
358
                        '$ref' => '#/components/schemas/' . $schemaName,
359
                    ],
360
                ],
361
            ],
362
        ]);
363
    }
364
365
    private function getResponseRef(string $responseName): OpenApiResponse
366
    {
367
        return new OpenApiResponse([
368
            'response' => $responseName,
369
            'ref' => '#/components/responses/' . $responseName,
370
        ]);
371
    }
372
373
    private function getResponseDataParameter(): Parameter
374
    {
375
        $responseDataParameter = new Parameter([
376
            'name' => '_response',
377
            'in' => 'query',
378
            'schema' => [
379
                'type' => 'string',
380
            ],
381
            'allowEmptyValue' => true,
382
            'description' => 'Data format for response. Empty if none is provided.',
383
        ]);
384
385
        return $responseDataParameter;
386
    }
387
388
    private function getIdParameter(EntityDefinition $definition): Parameter
389
    {
390
        $idParameter = new Parameter([
391
            'name' => 'id',
392
            'in' => 'path',
393
            'schema' => ['type' => 'string', 'pattern' => '^[0-9a-f]{32}$'],
394
            'description' => 'Identifier for the ' . $definition->getEntityName(),
395
            'required' => true,
396
        ]);
397
398
        return $idParameter;
399
    }
400
401
    private function snakeCaseToCamelCase(string $input): string
402
    {
403
        return str_replace('_', '', ucwords($input, '_'));
404
    }
405
406
    private function isExperimental(EntityDefinition $definition): bool
407
    {
408
        $reflection = new \ReflectionClass($definition);
409
410
        return str_contains($reflection->getDocComment() ?: '', '@' . self::EXPERIMENTAL_ANNOTATION_NAME);
411
    }
412
}
413