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

api-platform / core / 15775135891

20 Jun 2025 08:42AM UTC coverage: 22.065% (+0.2%) from 21.876%
15775135891

push

github

soyuka
Merge 4.1

13 of 103 new or added lines in 10 files covered. (12.62%)

868 existing lines in 35 files now uncovered.

11487 of 52060 relevant lines covered (22.06%)

21.72 hits per line

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

0.0
/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.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\Tests\Extractor;
15

16
use ApiPlatform\Metadata\ApiResource;
17
use ApiPlatform\Metadata\Delete;
18
use ApiPlatform\Metadata\Extractor\XmlResourceExtractor;
19
use ApiPlatform\Metadata\Extractor\YamlResourceExtractor;
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\Query;
25
use ApiPlatform\Metadata\GraphQl\QueryCollection;
26
use ApiPlatform\Metadata\GraphQl\Subscription;
27
use ApiPlatform\Metadata\HttpOperation;
28
use ApiPlatform\Metadata\Operations;
29
use ApiPlatform\Metadata\Patch;
30
use ApiPlatform\Metadata\Post;
31
use ApiPlatform\Metadata\QueryParameter;
32
use ApiPlatform\Metadata\Resource\Factory\ExtractorResourceMetadataCollectionFactory;
33
use ApiPlatform\Metadata\Resource\Factory\OperationDefaultsTrait;
34
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
35
use ApiPlatform\Metadata\Tests\Extractor\Adapter\ResourceAdapterInterface;
36
use ApiPlatform\Metadata\Tests\Extractor\Adapter\XmlResourceAdapter;
37
use ApiPlatform\Metadata\Tests\Extractor\Adapter\YamlResourceAdapter;
38
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\Comment;
39
use ApiPlatform\Metadata\Util\CamelCaseToSnakeCaseNameConverter;
40
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
41
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
42
use ApiPlatform\OpenApi\Model\RequestBody;
43
use PHPUnit\Framework\AssertionFailedError;
44
use PHPUnit\Framework\TestCase;
45
use Symfony\Component\WebLink\Link;
46

47
/**
48
 * Ensures XML and YAML mappings are fully compatible with ApiResource.
49
 *
50
 * @author Vincent Chalamon <vincentchalamon@gmail.com>
51
 */
52
final class ResourceMetadataCompatibilityTest extends TestCase
53
{
54
    use OperationDefaultsTrait;
55
    private const RESOURCE_CLASS = Comment::class;
56
    private const SHORT_NAME = 'Comment';
57
    private const DEFAULTS = [
58
        'route_prefix' => '/v1',
59
    ];
60
    private const FIXTURES = [
61
        null,
62
        [
63
            'uriTemplate' => '/users/{userId}/comments',
64
            'shortName' => self::SHORT_NAME,
65
            'description' => 'A list of Comments from User',
66
            'routePrefix' => '/api',
67
            'stateless' => true,
68
            'sunset' => '2021-01-01',
69
            'acceptPatch' => 'application/merge-patch+json',
70
            'status' => 200,
71
            'host' => 'example.com',
72
            'condition' => 'request.headers.get(\'User-Agent\') matches \{/firefox/i\'',
73
            'controller' => 'App\Controller\CommentController',
74
            'urlGenerationStrategy' => 1,
75
            'deprecationReason' => 'This resource is deprecated',
76
            'messenger' => true,
77
            'input' => 'App\Dto\CommentInput',
78
            'output' => 'App\Dto\CommentOutut',
79
            'fetchPartial' => true,
80
            'forceEager' => true,
81
            'paginationClientEnabled' => true,
82
            'paginationClientItemsPerPage' => true,
83
            'paginationClientPartial' => true,
84
            'paginationEnabled' => true,
85
            'paginationFetchJoinCollection' => true,
86
            'paginationUseOutputWalkers' => true,
87
            'paginationItemsPerPage' => 42,
88
            'paginationMaximumItemsPerPage' => 200,
89
            'paginationPartial' => true,
90
            'paginationType' => 'page',
91
            'security' => 'is_granted(\'ROLE_USER\')',
92
            'securityMessage' => 'Sorry, you can\'t access this resource.',
93
            'securityPostDenormalize' => 'is_granted(\'ROLE_ADMIN\')',
94
            'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this resource.',
95
            'securityPostValidation' => 'is_granted(\'ROLE_OWNER\')',
96
            'securityPostValidationMessage' => 'Sorry, you must the owner of this resource to access it.',
97
            'queryParameterValidationEnabled' => true,
98
            'strictQueryParameterValidation' => false,
99
            'hideHydraOperation' => false,
100
            'types' => ['someirischema', 'anotheririschema'],
101
            'formats' => [
102
                'json' => null,
103
                'jsonld' => null,
104
                'xls' => 'application/vnd.ms-excel',
105
            ],
106
            'inputFormats' => [
107
                'json' => 'application/merge-patch+json',
108
            ],
109
            'outputFormats' => [
110
                'json' => 'application/merge-patch+json',
111
            ],
112
            'uriVariables' => [
113
                'userId' => [
114
                    'fromClass' => Comment::class,
115
                    'fromProperty' => 'author',
116
                    'compositeIdentifier' => true,
117
                ],
118
            ],
119
            'defaults' => [
120
                'prout' => 'pouet',
121
            ],
122
            'requirements' => [
123
                'id' => '\d+',
124
            ],
125
            'options' => [
126
                'foo' => 'bar',
127
            ],
128
            'schemes' => ['http', 'https'],
129
            'cacheHeaders' => [
130
                'max_age' => 60,
131
                'shared_max_age' => 120,
132
                'vary' => ['Authorization', 'Accept-Language'],
133
            ],
134
            'normalizationContext' => [
135
                'groups' => 'comment:read',
136
            ],
137
            'denormalizationContext' => [
138
                'groups' => ['comment:write', 'comment:custom'],
139
            ],
140
            'collectDenormalizationErrors' => true,
141
            'hydraContext' => [
142
                'foo' => ['bar' => 'baz'],
143
            ],
144
            'openapi' => [
145
                'extensionProperties' => [
146
                    'bar' => 'baz',
147
                ],
148
            ],
149
            'validationContext' => [
150
                'foo' => 'bar',
151
            ],
152
            'filters' => ['comment.custom_filter'],
153
            'order' => ['foo', 'bar'],
154
            'paginationViaCursor' => [
155
                'id' => 'DESC',
156
            ],
157
            'exceptionToStatus' => [
158
                'Symfony\Component\Serializer\Exception\ExceptionInterface' => 400,
159
            ],
160
            'extraProperties' => [
161
                'custom_property' => 'Lorem ipsum dolor sit amet',
162
                'another_custom_property' => [
163
                    'Lorem ipsum' => 'Dolor sit amet',
164
                ],
165
            ],
166
            'mercure' => true,
167
            'stateOptions' => [
168
                'elasticsearchOptions' => [
169
                    'index' => 'foo_index',
170
                ],
171
            ],
172
            'graphQlOperations' => [
173
                [
174
                    'args' => [
175
                        'foo' => [
176
                            'type' => 'custom',
177
                            'bar' => 'baz',
178
                        ],
179
                    ],
180
                    'extraArgs' => [
181
                        'bar' => [
182
                            'type' => 'custom',
183
                            'baz' => 'qux',
184
                        ],
185
                    ],
186
                    'queryParameterValidationEnabled' => true,
187
                    'shortName' => self::SHORT_NAME,
188
                    'description' => 'Creates a Comment.',
189
                    'class' => Mutation::class,
190
                    'name' => 'create',
191
                    'urlGenerationStrategy' => 0,
192
                    'deprecationReason' => 'I don\'t know',
193
                    'normalizationContext' => [
194
                        'groups' => 'comment:read_collection',
195
                    ],
196
                    'denormalizationContext' => [
197
                        'groups' => ['comment:write'],
198
                    ],
199
                    'validationContext' => [
200
                        'foo' => 'bar',
201
                    ],
202
                    'filters' => ['comment.another_custom_filter'],
203
                    'mercure' => [
204
                        'private' => true,
205
                    ],
206
                    'messenger' => 'input',
207
                    'input' => 'App\Dto\CreateCommentInput',
208
                    'output' => 'App\Dto\CommentCollectionOutut',
209
                    'order' => ['userId'],
210
                    'fetchPartial' => false,
211
                    'forceEager' => false,
212
                    'paginationClientEnabled' => false,
213
                    'paginationClientItemsPerPage' => false,
214
                    'paginationClientPartial' => false,
215
                    'paginationEnabled' => false,
216
                    'paginationFetchJoinCollection' => false,
217
                    'paginationUseOutputWalkers' => false,
218
                    'paginationItemsPerPage' => 54,
219
                    'paginationMaximumItemsPerPage' => 200,
220
                    'paginationPartial' => false,
221
                    'paginationType' => 'page',
222
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
223
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
224
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
225
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
226
                    'read' => true,
227
                    'deserialize' => false,
228
                    'validate' => false,
229
                    'write' => false,
230
                    'serialize' => true,
231
                    'priority' => 200,
232
                    'extraProperties' => [
233
                        'custom_property' => 'Lorem ipsum dolor sit amet',
234
                        'another_custom_property' => [
235
                            'Lorem ipsum' => 'Dolor sit amet',
236
                        ],
237
                        'foo' => 'bar',
238
                        'route_prefix' => '/v1', // from defaults
239
                    ],
240
                    'stateOptions' => [
241
                        'elasticsearchOptions' => [
242
                            'index' => 'foo_index',
243
                        ],
244
                    ],
245
                ],
246
                [
247
                    'class' => Query::class,
248
                    'queryParameterValidationEnabled' => true,
249
                    'extraProperties' => [
250
                        'route_prefix' => '/v1',
251
                        'custom_property' => 'Lorem ipsum dolor sit amet',
252
                        'another_custom_property' => [
253
                            'Lorem ipsum' => 'Dolor sit amet',
254
                        ],
255
                    ],
256
                    'stateOptions' => [
257
                        'elasticsearchOptions' => [
258
                            'index' => 'foo_index',
259
                        ],
260
                    ],
261
                ],
262
                [
263
                    'class' => QueryCollection::class,
264
                    'queryParameterValidationEnabled' => true,
265
                    'extraProperties' => [
266
                        'route_prefix' => '/v1',
267
                        'custom_property' => 'Lorem ipsum dolor sit amet',
268
                        'another_custom_property' => [
269
                            'Lorem ipsum' => 'Dolor sit amet',
270
                        ],
271
                    ],
272
                    'stateOptions' => [
273
                        'elasticsearchOptions' => [
274
                            'index' => 'foo_index',
275
                        ],
276
                    ],
277
                ],
278
                [
279
                    'class' => Subscription::class,
280
                    'queryParameterValidationEnabled' => true,
281
                    'extraProperties' => [
282
                        'route_prefix' => '/v1',
283
                        'custom_property' => 'Lorem ipsum dolor sit amet',
284
                        'another_custom_property' => [
285
                            'Lorem ipsum' => 'Dolor sit amet',
286
                        ],
287
                    ],
288
                    'stateOptions' => [
289
                        'elasticsearchOptions' => [
290
                            'index' => 'foo_index',
291
                        ],
292
                    ],
293
                ],
294
            ],
295
            'operations' => [
296
                [
297
                    'name' => 'custom_operation_name',
298
                    'method' => 'GET',
299
                    'uriTemplate' => '/users/{userId}/comments{._format}',
300
                    'shortName' => self::SHORT_NAME,
301
                    'description' => 'A list of Comments',
302
                    'types' => ['Comment'],
303
                    'formats' => [
304
                        'json' => null,
305
                        'jsonld' => null,
306
                        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
307
                    ],
308
                    'inputFormats' => [
309
                        'jsonld' => 'application/merge-patch+json+ld',
310
                    ],
311
                    'outputFormats' => [
312
                        'jsonld' => 'application/merge-patch+json+ld',
313
                    ],
314
                    'uriVariables' => [
315
                        'userId' => [
316
                            'fromClass' => Comment::class,
317
                            'fromProperty' => 'author',
318
                            'compositeIdentifier' => true,
319
                        ],
320
                    ],
321
                    'routePrefix' => '/foo/api',
322
                    'defaults' => [
323
                        '_bar' => '_foo',
324
                    ],
325
                    'requirements' => [
326
                        'userId' => '\d+',
327
                    ],
328
                    'options' => [
329
                        'bar' => 'baz',
330
                    ],
331
                    'stateless' => false,
332
                    'sunset' => '2021-12-01',
333
                    'acceptPatch' => 'text/example;charset=utf-8',
334
                    'status' => 204,
335
                    'host' => 'api-platform.com',
336
                    'schemes' => ['https'],
337
                    'headers' => ['key' => 'value'],
338
                    'condition' => 'request.headers.has(\'Accept\')',
339
                    'controller' => 'App\Controller\CustomController',
340
                    'class' => GetCollection::class,
341
                    'urlGenerationStrategy' => 0,
342
                    'deprecationReason' => 'I don\'t know',
343
                    'cacheHeaders' => [
344
                        'max_age' => 60,
345
                        'shared_max_age' => 120,
346
                        'vary' => ['Authorization', 'Accept-Language', 'Accept'],
347
                    ],
348
                    'normalizationContext' => [
349
                        'groups' => 'comment:read_collection',
350
                    ],
351
                    'denormalizationContext' => [
352
                        'groups' => ['comment:write'],
353
                    ],
354
                    'hydraContext' => [
355
                        'foo' => ['bar' => 'baz'],
356
                    ],
357
                    'openapi' => [
358
                        'extensionProperties' => [
359
                            'bar' => 'baz',
360
                        ],
361
                    ],
362
                    'validationContext' => [
363
                        'foo' => 'bar',
364
                    ],
365
                    'filters' => ['comment.another_custom_filter'],
366
                    'mercure' => [
367
                        'private' => true,
368
                    ],
369
                    'messenger' => 'input',
370
                    'input' => 'App\Dto\CreateCommentInput',
371
                    'output' => 'App\Dto\CommentCollectionOutut',
372
                    'order' => ['userId'],
373
                    'fetchPartial' => false,
374
                    'forceEager' => false,
375
                    'paginationClientEnabled' => false,
376
                    'paginationClientItemsPerPage' => false,
377
                    'paginationClientPartial' => false,
378
                    'paginationViaCursor' => [
379
                        'userId' => 'DESC',
380
                    ],
381
                    'paginationEnabled' => false,
382
                    'paginationFetchJoinCollection' => false,
383
                    'paginationUseOutputWalkers' => false,
384
                    'paginationItemsPerPage' => 54,
385
                    'paginationMaximumItemsPerPage' => 200,
386
                    'paginationPartial' => false,
387
                    'paginationType' => 'page',
388
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
389
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
390
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
391
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
392
                    'exceptionToStatus' => [
393
                        'Symfony\Component\Serializer\Exception\ExceptionInterface' => 404,
394
                    ],
395
                    'queryParameterValidationEnabled' => false,
396
                    'strictQueryParameterValidation' => false,
397
                    'hideHydraOperation' => false,
398
                    'read' => true,
399
                    'deserialize' => false,
400
                    'validate' => false,
401
                    'write' => false,
402
                    'serialize' => true,
403
                    'priority' => 200,
404
                    'extraProperties' => [
405
                        'custom_property' => 'Lorem ipsum dolor sit amet',
406
                        'another_custom_property' => [
407
                            'Lorem ipsum' => 'Dolor sit amet',
408
                        ],
409
                        'foo' => 'bar',
410
                    ],
411
                    'links' => [
412
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
413
                    ],
414
                    'parameters' => [
415
                        'author' => ['key' => 'author', 'required' => true, 'schema' => ['type' => 'string']],
416
                    ],
417
                ],
418
                [
419
                    'uriTemplate' => '/users/{userId}/comments/{commentId}{._format}',
420
                    'class' => Get::class,
421
                    'uriVariables' => [
422
                        'userId' => [
423
                            'fromClass' => Comment::class,
424
                            'fromProperty' => 'author',
425
                            'compositeIdentifier' => true,
426
                        ],
427
                        'commentId' => [Comment::class, 'id'],
428
                    ],
429
                    'links' => [
430
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
431
                    ],
432
                    'parameters' => [
433
                        'date' => ['key' => 'date'],
434
                    ],
435
                ],
436
            ],
437
        ],
438
    ];
439
    private const BASE = [
440
        'shortName',
441
        'description',
442
        'urlGenerationStrategy',
443
        'deprecationReason',
444
        'messenger',
445
        'mercure',
446
        'input',
447
        'output',
448
        'fetchPartial',
449
        'forceEager',
450
        'paginationClientEnabled',
451
        'paginationClientItemsPerPage',
452
        'paginationClientPartial',
453
        'paginationEnabled',
454
        'paginationFetchJoinCollection',
455
        'paginationUseOutputWalkers',
456
        'paginationItemsPerPage',
457
        'paginationMaximumItemsPerPage',
458
        'paginationPartial',
459
        'paginationType',
460
        'processor',
461
        'provider',
462
        'security',
463
        'securityMessage',
464
        'securityPostDenormalize',
465
        'securityPostDenormalizeMessage',
466
        'securityPostValidation',
467
        'securityPostValidationMessage',
468
        'normalizationContext',
469
        'denormalizationContext',
470
        'collectDenormalizationErrors',
471
        'validationContext',
472
        'filters',
473
        'order',
474
        'extraProperties',
475
    ];
476
    private const EXTENDED_BASE = [
477
        'uriTemplate',
478
        'routePrefix',
479
        'stateless',
480
        'sunset',
481
        'acceptPatch',
482
        'status',
483
        'host',
484
        'condition',
485
        'controller',
486
        'queryParameterValidationEnabled',
487
        'strictQueryParameterValidation',
488
        'hideHydraOperation',
489
        'exceptionToStatus',
490
        'types',
491
        'formats',
492
        'inputFormats',
493
        'outputFormats',
494
        'uriVariables',
495
        'defaults',
496
        'requirements',
497
        'options',
498
        'schemes',
499
        'cacheHeaders',
500
        'hydraContext',
501
        'openapi',
502
        'paginationViaCursor',
503
        'stateOptions',
504
        'links',
505
        'rules',
506
        'headers',
507
        'parameters',
508
    ];
509

510
    #[\PHPUnit\Framework\Attributes\DataProvider('getExtractors')]
511
    public function testValidMetadata(string $extractorClass, ResourceAdapterInterface $adapter): void
512
    {
UNCOV
513
        $reflClass = new \ReflectionClass(ApiResource::class);
×
UNCOV
514
        $parameters = $reflClass->getConstructor()->getParameters();
×
UNCOV
515
        $this->defaults = self::DEFAULTS;
×
UNCOV
516
        $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();
×
517

518
        try {
UNCOV
519
            $extractor = new $extractorClass($adapter(self::RESOURCE_CLASS, $parameters, self::FIXTURES));
×
UNCOV
520
            $factory = new ExtractorResourceMetadataCollectionFactory($extractor, null, self::DEFAULTS, null, true);
×
UNCOV
521
            $collection = $factory->create(self::RESOURCE_CLASS);
×
522
        } catch (\Exception $exception) {
×
523
            throw new AssertionFailedError('Failed asserting that the schema is valid according to '.ApiResource::class, 0, $exception);
×
524
        }
525

UNCOV
526
        $resources = $this->buildApiResources();
×
UNCOV
527
        $this->assertEquals(new ResourceMetadataCollection(self::RESOURCE_CLASS, $resources), $collection);
×
528
    }
529

530
    public static function getExtractors(): array
531
    {
532
        return [
×
UNCOV
533
            [XmlResourceExtractor::class, new XmlResourceAdapter()],
×
UNCOV
534
            [YamlResourceExtractor::class, new YamlResourceAdapter()],
×
535
        ];
×
536
    }
537

538
    /**
539
     * @return ApiResource[]
540
     */
541
    private function buildApiResources(): array
542
    {
543
        $resources = [];
×
544

UNCOV
545
        foreach (self::FIXTURES as $fixtures) {
×
UNCOV
546
            $resource = (new ApiResource())->withClass(self::RESOURCE_CLASS)->withShortName(self::SHORT_NAME);
×
547

UNCOV
548
            if (null === $fixtures) {
×
549
                // Build default operations
UNCOV
550
                $operations = [];
×
UNCOV
551
                foreach ([new Get(), new GetCollection(), new Post(), new Patch(), new Delete()] as $operation) {
×
552
                    [$name, $operation] = $this->getOperationWithDefaults($resource, $operation);
×
UNCOV
553
                    $operations[$name] = $operation;
×
554
                }
555

UNCOV
556
                $resource = $resource->withOperations(new Operations($operations));
×
557

558
                // Build default GraphQL operations
559
                $graphQlOperations = [];
×
560
                foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $graphQlOperation) {
×
561
                    $description = $graphQlOperation instanceof Mutation ? ucfirst("{$graphQlOperation->getName()}s a {$resource->getShortName()}.") : null;
×
562
                    [$name, $operation] = $this->getOperationWithDefaults($resource, $graphQlOperation);
×
UNCOV
563
                    $graphQlOperations[$name] = $operation->withDescription($description);
×
564
                }
565

UNCOV
566
                $resources[] = $resource->withGraphQlOperations($graphQlOperations);
×
567

568
                continue;
×
569
            }
570

571
            foreach ($fixtures as $parameter => $value) {
×
572
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
UNCOV
573
                    $value = $this->{'with'.ucfirst($parameter)}($value, $fixtures);
×
574
                }
575

UNCOV
576
                if (method_exists($resource, 'with'.ucfirst($parameter))) {
×
577
                    $resource = $resource->{'with'.ucfirst($parameter)}($value, $fixtures);
×
UNCOV
578
                    continue;
×
579
                }
580

581
                throw new \RuntimeException(\sprintf('Unknown ApiResource parameter "%s".', $parameter));
×
582
            }
583

UNCOV
584
            $resources[] = $resource;
×
585
        }
586

587
        return $resources;
×
588
    }
589

590
    private function withOpenapi(array|bool $values): bool|OpenApiOperation
591
    {
UNCOV
592
        if (\is_bool($values)) {
×
593
            return $values;
×
594
        }
595

596
        $allowedProperties = array_map(fn (\ReflectionProperty $reflProperty): string => $reflProperty->getName(), (new \ReflectionClass(OpenApiOperation::class))->getProperties());
×
UNCOV
597
        foreach ($values as $key => $value) {
×
UNCOV
598
            $values[$key] = match ($key) {
×
UNCOV
599
                'externalDocs' => new ExternalDocumentation(description: $value['description'] ?? '', url: $value['url'] ?? ''),
×
UNCOV
600
                'requestBody' => new RequestBody(description: $value['description'] ?? '', content: isset($value['content']) ? new \ArrayObject($value['content']) : null, required: $value['required'] ?? false),
×
601
                'callbacks' => new \ArrayObject($value),
×
602
                default => $value,
×
UNCOV
603
            };
×
604

605
            if (\in_array($key, $allowedProperties, true)) {
×
606
                continue;
×
607
            }
608

609
            $values['extensionProperties'][$key] = $value;
×
610
            unset($values[$key]);
×
611
        }
612

UNCOV
613
        return new OpenApiOperation(...$values);
×
614
    }
615

616
    private function withUriVariables(array $values): array
617
    {
618
        $uriVariables = [];
×
619
        foreach ($values as $parameterName => $value) {
×
UNCOV
620
            if (\is_string($value)) {
×
UNCOV
621
                $uriVariables[$value] = $value;
×
622
                continue;
×
623
            }
624

UNCOV
625
            if (isset($value['fromClass']) || isset($value[0])) {
×
UNCOV
626
                $uriVariables[$parameterName]['from_class'] = $value['fromClass'] ?? $value[0];
×
627
            }
628
            if (isset($value['fromProperty']) || isset($value[1])) {
×
629
                $uriVariables[$parameterName]['from_property'] = $value['fromProperty'] ?? $value[1];
×
630
            }
631
            if (isset($value['toClass'])) {
×
UNCOV
632
                $uriVariables[$parameterName]['to_class'] = $value['toClass'];
×
633
            }
634
            if (isset($value['toProperty'])) {
×
635
                $uriVariables[$parameterName]['to_property'] = $value['toProperty'];
×
636
            }
637
            if (isset($value['identifiers'])) {
×
638
                $uriVariables[$parameterName]['identifiers'] = $value['identifiers'];
×
639
            }
640
            if (isset($value['compositeIdentifier'])) {
×
641
                $uriVariables[$parameterName]['composite_identifier'] = $value['compositeIdentifier'];
×
642
            }
643
        }
644

UNCOV
645
        return $uriVariables;
×
646
    }
647

648
    private function withOperations(array $values, ?array $fixtures): Operations
649
    {
650
        $operations = [];
×
UNCOV
651
        foreach ($values as $value) {
×
UNCOV
652
            $class = $value['class'] ?? HttpOperation::class;
×
UNCOV
653
            unset($value['class']);
×
654
            $operation = (new $class())->withClass(self::RESOURCE_CLASS);
×
655

UNCOV
656
            foreach (array_merge(self::BASE, self::EXTENDED_BASE) as $parameter) {
×
UNCOV
657
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
UNCOV
658
                    $value[$parameter] = $fixtures[$parameter];
×
659
                }
660
            }
661

662
            foreach ($value as $parameter => $parameterValue) {
×
663
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
UNCOV
664
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
665
                }
666

667
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
UNCOV
668
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
UNCOV
669
                    continue;
×
670
                }
671

672
                throw new \RuntimeException(\sprintf('Unknown Operation parameter "%s".', $parameter));
×
673
            }
674

UNCOV
675
            $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, self::RESOURCE_CLASS);
×
676
            $operations[$operationName] = $operation;
×
677
        }
678

UNCOV
679
        return new Operations($operations);
×
680
    }
681

682
    private function withGraphQlOperations(array $values, ?array $fixtures): array
683
    {
684
        $operations = [];
×
685
        foreach ($values as $value) {
×
UNCOV
686
            $class = $value['class'];
×
UNCOV
687
            unset($value['class']);
×
688
            $operation = (new $class())->withClass(self::RESOURCE_CLASS);
×
689

UNCOV
690
            foreach (self::BASE as $parameter) {
×
UNCOV
691
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
UNCOV
692
                    $value[$parameter] = $fixtures[$parameter];
×
693
                }
694
            }
695

696
            foreach ($value as $parameter => $parameterValue) {
×
697
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
UNCOV
698
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
699
                }
700

701
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
UNCOV
702
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
UNCOV
703
                    continue;
×
704
                }
705

706
                throw new \RuntimeException(\sprintf('Unknown GraphQlOperation parameter "%s".', $parameter));
×
707
            }
708

UNCOV
709
            $operationName = $operation->getName();
×
710
            $operations[$operationName] = $operation;
×
711
        }
712

UNCOV
713
        return $operations;
×
714
    }
715

716
    private function withStateOptions(array $values)
717
    {
718
        if (!$values) {
×
719
            return null;
×
720
        }
721

722
        if (1 !== \count($values)) {
×
UNCOV
723
            throw new \InvalidArgumentException('Only one options can be configured at a time.');
×
724
        }
725

UNCOV
726
        $configuration = reset($values);
×
727
        switch (key($values)) {
×
728
            case 'elasticsearchOptions':
×
UNCOV
729
                return null;
×
730
        }
731

732
        throw new \LogicException(\sprintf('Unsupported "%s" state options.', key($values)));
×
733
    }
734

735
    private function withLinks(array $values): ?array
736
    {
737
        if (!$values) {
×
738
            return null;
×
739
        }
740

741
        return [new Link($values[0]['rel'] ?? null, $values[0]['href'] ?? null)];
×
742
    }
743

744
    private function withParameters(array $values): ?array
745
    {
746
        if (!$values) {
×
747
            return null;
×
748
        }
749

750
        $parameters = [];
×
UNCOV
751
        foreach ($values as $k => $value) {
×
UNCOV
752
            $parameters[$k] = new QueryParameter(key: $value['key'] ?? $k, required: $value['required'] ?? null, schema: $value['schema'] ?? null);
×
753
        }
754

755
        return $parameters;
×
756
    }
757
}
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