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

api-platform / core / 18089937549

29 Sep 2025 07:56AM UTC coverage: 21.764% (-0.3%) from 22.093%
18089937549

Pull #7416

github

web-flow
Merge 061bcc790 into abe0438be
Pull Request #7416: fix(laravel): serializer attributes on Eloquent methods

0 of 151 new or added lines in 11 files covered. (0.0%)

5028 existing lines in 173 files now uncovered.

11889 of 54626 relevant lines covered (21.76%)

25.32 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
            'jsonStream' => true,
167
            'mercure' => true,
168
            'stateOptions' => [
169
                'elasticsearchOptions' => [
170
                    'index' => 'foo_index',
171
                ],
172
            ],
173
            'graphQlOperations' => [
174
                [
175
                    'args' => [
176
                        'foo' => [
177
                            'type' => 'custom',
178
                            'bar' => 'baz',
179
                        ],
180
                    ],
181
                    'extraArgs' => [
182
                        'bar' => [
183
                            'type' => 'custom',
184
                            'baz' => 'qux',
185
                        ],
186
                    ],
187
                    'queryParameterValidationEnabled' => true,
188
                    'shortName' => self::SHORT_NAME,
189
                    'description' => 'Creates a Comment.',
190
                    'class' => Mutation::class,
191
                    'name' => 'create',
192
                    'urlGenerationStrategy' => 0,
193
                    'deprecationReason' => 'I don\'t know',
194
                    'normalizationContext' => [
195
                        'groups' => 'comment:read_collection',
196
                    ],
197
                    'denormalizationContext' => [
198
                        'groups' => ['comment:write'],
199
                    ],
200
                    'validationContext' => [
201
                        'foo' => 'bar',
202
                    ],
203
                    'filters' => ['comment.another_custom_filter'],
204
                    'mercure' => [
205
                        'private' => true,
206
                    ],
207
                    'messenger' => 'input',
208
                    'input' => 'App\Dto\CreateCommentInput',
209
                    'output' => 'App\Dto\CommentCollectionOutut',
210
                    'order' => ['userId'],
211
                    'fetchPartial' => false,
212
                    'forceEager' => false,
213
                    'paginationClientEnabled' => false,
214
                    'paginationClientItemsPerPage' => false,
215
                    'paginationClientPartial' => false,
216
                    'paginationEnabled' => false,
217
                    'paginationFetchJoinCollection' => false,
218
                    'paginationUseOutputWalkers' => false,
219
                    'paginationItemsPerPage' => 54,
220
                    'paginationMaximumItemsPerPage' => 200,
221
                    'paginationPartial' => false,
222
                    'paginationType' => 'page',
223
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
224
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
225
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
226
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
227
                    'read' => true,
228
                    'deserialize' => false,
229
                    'validate' => false,
230
                    'write' => false,
231
                    'serialize' => true,
232
                    'priority' => 200,
233
                    'extraProperties' => [
234
                        'custom_property' => 'Lorem ipsum dolor sit amet',
235
                        'another_custom_property' => [
236
                            'Lorem ipsum' => 'Dolor sit amet',
237
                        ],
238
                        'foo' => 'bar',
239
                        'route_prefix' => '/v1', // from defaults
240
                    ],
241
                    'stateOptions' => [
242
                        'elasticsearchOptions' => [
243
                            'index' => 'foo_index',
244
                        ],
245
                    ],
246
                ],
247
                [
248
                    'class' => Query::class,
249
                    'queryParameterValidationEnabled' => true,
250
                    'extraProperties' => [
251
                        'route_prefix' => '/v1',
252
                        'custom_property' => 'Lorem ipsum dolor sit amet',
253
                        'another_custom_property' => [
254
                            'Lorem ipsum' => 'Dolor sit amet',
255
                        ],
256
                    ],
257
                    'stateOptions' => [
258
                        'elasticsearchOptions' => [
259
                            'index' => 'foo_index',
260
                        ],
261
                    ],
262
                ],
263
                [
264
                    'class' => QueryCollection::class,
265
                    'queryParameterValidationEnabled' => true,
266
                    'extraProperties' => [
267
                        'route_prefix' => '/v1',
268
                        'custom_property' => 'Lorem ipsum dolor sit amet',
269
                        'another_custom_property' => [
270
                            'Lorem ipsum' => 'Dolor sit amet',
271
                        ],
272
                    ],
273
                    'stateOptions' => [
274
                        'elasticsearchOptions' => [
275
                            'index' => 'foo_index',
276
                        ],
277
                    ],
278
                ],
279
                [
280
                    'class' => Subscription::class,
281
                    'queryParameterValidationEnabled' => true,
282
                    'extraProperties' => [
283
                        'route_prefix' => '/v1',
284
                        'custom_property' => 'Lorem ipsum dolor sit amet',
285
                        'another_custom_property' => [
286
                            'Lorem ipsum' => 'Dolor sit amet',
287
                        ],
288
                    ],
289
                    'stateOptions' => [
290
                        'elasticsearchOptions' => [
291
                            'index' => 'foo_index',
292
                        ],
293
                    ],
294
                ],
295
            ],
296
            'operations' => [
297
                [
298
                    'name' => 'custom_operation_name',
299
                    'method' => 'GET',
300
                    'uriTemplate' => '/users/{userId}/comments{._format}',
301
                    'shortName' => self::SHORT_NAME,
302
                    'description' => 'A list of Comments',
303
                    'types' => ['Comment'],
304
                    'formats' => [
305
                        'json' => null,
306
                        'jsonld' => null,
307
                        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
308
                    ],
309
                    'inputFormats' => [
310
                        'jsonld' => 'application/merge-patch+json+ld',
311
                    ],
312
                    'outputFormats' => [
313
                        'jsonld' => 'application/merge-patch+json+ld',
314
                    ],
315
                    'uriVariables' => [
316
                        'userId' => [
317
                            'fromClass' => Comment::class,
318
                            'fromProperty' => 'author',
319
                            'compositeIdentifier' => true,
320
                        ],
321
                    ],
322
                    'routePrefix' => '/foo/api',
323
                    'defaults' => [
324
                        '_bar' => '_foo',
325
                    ],
326
                    'requirements' => [
327
                        'userId' => '\d+',
328
                    ],
329
                    'options' => [
330
                        'bar' => 'baz',
331
                    ],
332
                    'stateless' => false,
333
                    'sunset' => '2021-12-01',
334
                    'acceptPatch' => 'text/example;charset=utf-8',
335
                    'status' => 204,
336
                    'host' => 'api-platform.com',
337
                    'schemes' => ['https'],
338
                    'headers' => ['key' => 'value'],
339
                    'condition' => 'request.headers.has(\'Accept\')',
340
                    'controller' => 'App\Controller\CustomController',
341
                    'class' => GetCollection::class,
342
                    'urlGenerationStrategy' => 0,
343
                    'deprecationReason' => 'I don\'t know',
344
                    'cacheHeaders' => [
345
                        'max_age' => 60,
346
                        'shared_max_age' => 120,
347
                        'vary' => ['Authorization', 'Accept-Language', 'Accept'],
348
                    ],
349
                    'normalizationContext' => [
350
                        'groups' => 'comment:read_collection',
351
                    ],
352
                    'denormalizationContext' => [
353
                        'groups' => ['comment:write'],
354
                    ],
355
                    'hydraContext' => [
356
                        'foo' => ['bar' => 'baz'],
357
                    ],
358
                    'openapi' => [
359
                        'extensionProperties' => [
360
                            'bar' => 'baz',
361
                        ],
362
                    ],
363
                    'validationContext' => [
364
                        'foo' => 'bar',
365
                    ],
366
                    'filters' => ['comment.another_custom_filter'],
367
                    'mercure' => [
368
                        'private' => true,
369
                    ],
370
                    'messenger' => 'input',
371
                    'input' => 'App\Dto\CreateCommentInput',
372
                    'output' => 'App\Dto\CommentCollectionOutut',
373
                    'order' => ['userId'],
374
                    'fetchPartial' => false,
375
                    'forceEager' => false,
376
                    'paginationClientEnabled' => false,
377
                    'paginationClientItemsPerPage' => false,
378
                    'paginationClientPartial' => false,
379
                    'paginationViaCursor' => [
380
                        'userId' => 'DESC',
381
                    ],
382
                    'paginationEnabled' => false,
383
                    'paginationFetchJoinCollection' => false,
384
                    'paginationUseOutputWalkers' => false,
385
                    'paginationItemsPerPage' => 54,
386
                    'paginationMaximumItemsPerPage' => 200,
387
                    'paginationPartial' => false,
388
                    'paginationType' => 'page',
389
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
390
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
391
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
392
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
393
                    'exceptionToStatus' => [
394
                        'Symfony\Component\Serializer\Exception\ExceptionInterface' => 404,
395
                    ],
396
                    'queryParameterValidationEnabled' => false,
397
                    'strictQueryParameterValidation' => false,
398
                    'hideHydraOperation' => false,
399
                    'read' => true,
400
                    'deserialize' => false,
401
                    'validate' => false,
402
                    'write' => false,
403
                    'serialize' => true,
404
                    'priority' => 200,
405
                    'extraProperties' => [
406
                        'custom_property' => 'Lorem ipsum dolor sit amet',
407
                        'another_custom_property' => [
408
                            'Lorem ipsum' => 'Dolor sit amet',
409
                        ],
410
                        'foo' => 'bar',
411
                    ],
412
                    'links' => [
413
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
414
                    ],
415
                    'parameters' => [
416
                        'author' => ['key' => 'author', 'required' => true, 'schema' => ['type' => 'string']],
417
                    ],
418
                ],
419
                [
420
                    'uriTemplate' => '/users/{userId}/comments/{commentId}{._format}',
421
                    'class' => Get::class,
422
                    'uriVariables' => [
423
                        'userId' => [
424
                            'fromClass' => Comment::class,
425
                            'fromProperty' => 'author',
426
                            'compositeIdentifier' => true,
427
                        ],
428
                        'commentId' => [Comment::class, 'id'],
429
                    ],
430
                    'links' => [
431
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
432
                    ],
433
                    'parameters' => [
434
                        'date' => ['key' => 'date'],
435
                    ],
436
                ],
437
            ],
438
        ],
439
    ];
440
    private const BASE = [
441
        'shortName',
442
        'description',
443
        'urlGenerationStrategy',
444
        'deprecationReason',
445
        'messenger',
446
        'mercure',
447
        'input',
448
        'output',
449
        'fetchPartial',
450
        'forceEager',
451
        'paginationClientEnabled',
452
        'paginationClientItemsPerPage',
453
        'paginationClientPartial',
454
        'paginationEnabled',
455
        'paginationFetchJoinCollection',
456
        'paginationUseOutputWalkers',
457
        'paginationItemsPerPage',
458
        'paginationMaximumItemsPerPage',
459
        'paginationPartial',
460
        'paginationType',
461
        'processor',
462
        'provider',
463
        'security',
464
        'securityMessage',
465
        'securityPostDenormalize',
466
        'securityPostDenormalizeMessage',
467
        'securityPostValidation',
468
        'securityPostValidationMessage',
469
        'normalizationContext',
470
        'denormalizationContext',
471
        'collectDenormalizationErrors',
472
        'validationContext',
473
        'filters',
474
        'order',
475
        'extraProperties',
476
        'jsonStream',
477
    ];
478
    private const EXTENDED_BASE = [
479
        'uriTemplate',
480
        'routePrefix',
481
        'stateless',
482
        'sunset',
483
        'acceptPatch',
484
        'status',
485
        'host',
486
        'condition',
487
        'controller',
488
        'queryParameterValidationEnabled',
489
        'strictQueryParameterValidation',
490
        'hideHydraOperation',
491
        'exceptionToStatus',
492
        'types',
493
        'formats',
494
        'inputFormats',
495
        'outputFormats',
496
        'uriVariables',
497
        'defaults',
498
        'requirements',
499
        'options',
500
        'schemes',
501
        'cacheHeaders',
502
        'hydraContext',
503
        'openapi',
504
        'paginationViaCursor',
505
        'stateOptions',
506
        'links',
507
        'rules',
508
        'headers',
509
        'parameters',
510
    ];
511

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

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

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

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

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

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

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

UNCOV
558
                $resource = $resource->withOperations(new Operations($operations));
×
559

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

568
                $resources[] = $resource->withGraphQlOperations($graphQlOperations);
×
569

UNCOV
570
                continue;
×
571
            }
572

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

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

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

UNCOV
586
            $resources[] = $resource;
×
587
        }
588

UNCOV
589
        return $resources;
×
590
    }
591

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

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

UNCOV
607
            if (\in_array($key, $allowedProperties, true)) {
×
UNCOV
608
                continue;
×
609
            }
610

UNCOV
611
            $values['extensionProperties'][$key] = $value;
×
UNCOV
612
            unset($values[$key]);
×
613
        }
614

UNCOV
615
        return new OpenApiOperation(...$values);
×
616
    }
617

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

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

UNCOV
647
        return $uriVariables;
×
648
    }
649

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

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

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

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

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

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

UNCOV
681
        return new Operations($operations);
×
682
    }
683

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

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

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

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

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

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

UNCOV
715
        return $operations;
×
716
    }
717

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

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

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

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

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

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

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

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

UNCOV
757
        return $parameters;
×
758
    }
759

760
    private function withJsonStream(bool $value): bool
761
    {
UNCOV
762
        return $value;
×
763
    }
764
}
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