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

api-platform / core / 4498909317

pending completion
4498909317

push

github

soyuka
Merge 3.1

364 of 364 new or added lines in 61 files covered. (100.0%)

10781 of 17989 relevant lines covered (59.93%)

11.36 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\Put;
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\Tests\Fixtures\StateOptions;
40
use ApiPlatform\Metadata\Util\CamelCaseToSnakeCaseNameConverter;
41
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
42
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
43
use ApiPlatform\OpenApi\Model\RequestBody;
44
use ApiPlatform\State\OptionsInterface;
45
use PHPUnit\Framework\AssertionFailedError;
46
use PHPUnit\Framework\TestCase;
47

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

503
    /**
504
     * @dataProvider getExtractors
505
     */
506
    public function testValidMetadata(string $extractorClass, ResourceAdapterInterface $adapter): void
507
    {
508
        $reflClass = new \ReflectionClass(ApiResource::class);
×
509
        $parameters = $reflClass->getConstructor()->getParameters();
×
510
        $this->defaults = self::DEFAULTS;
×
511
        $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();
×
512

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

521
        $this->assertEquals(new ResourceMetadataCollection(self::RESOURCE_CLASS, $this->buildApiResources()), $collection);
×
522
    }
523

524
    public function getExtractors(): array
525
    {
526
        return [
×
527
            [XmlResourceExtractor::class, new XmlResourceAdapter()],
×
528
            [YamlResourceExtractor::class, new YamlResourceAdapter()],
×
529
        ];
×
530
    }
531

532
    /**
533
     * @return ApiResource[]
534
     */
535
    private function buildApiResources(): array
536
    {
537
        $resources = [];
×
538

539
        foreach (self::FIXTURES as $fixtures) {
×
540
            $resource = (new ApiResource())->withClass(self::RESOURCE_CLASS)->withShortName(self::SHORT_NAME);
×
541

542
            if (null === $fixtures) {
×
543
                // Build default operations
544
                $operations = [];
×
545
                foreach ([new Get(), new GetCollection(), new Post(), new Put(), new Patch(), new Delete()] as $operation) {
×
546
                    [$name, $operation] = $this->getOperationWithDefaults($resource, $operation);
×
547
                    $operations[$name] = $operation;
×
548
                }
549

550
                $resource = $resource->withOperations(new Operations($operations));
×
551

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

560
                $resources[] = $resource->withGraphQlOperations($graphQlOperations);
×
561

562
                continue;
×
563
            }
564

565
            foreach ($fixtures as $parameter => $value) {
×
566
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
567
                    $value = $this->{'with'.ucfirst($parameter)}($value, $fixtures);
×
568
                }
569

570
                if (method_exists($resource, 'with'.ucfirst($parameter))) {
×
571
                    $resource = $resource->{'with'.ucfirst($parameter)}($value, $fixtures);
×
572
                    continue;
×
573
                }
574

575
                throw new \RuntimeException(sprintf('Unknown ApiResource parameter "%s".', $parameter));
×
576
            }
577

578
            $resources[] = $resource;
×
579
        }
580

581
        return $resources;
×
582
    }
583

584
    private function withOpenapi(array|bool $values): bool|OpenApiOperation
585
    {
586
        if (\is_bool($values)) {
×
587
            return $values;
×
588
        }
589

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

599
            if (\in_array($key, $allowedProperties, true)) {
×
600
                continue;
×
601
            }
602

603
            $values['extensionProperties'][$key] = $value;
×
604
            unset($values[$key]);
×
605
        }
606

607
        return new OpenApiOperation(...$values);
×
608
    }
609

610
    private function withUriVariables(array $values): array
611
    {
612
        $uriVariables = [];
×
613
        foreach ($values as $parameterName => $value) {
×
614
            if (\is_string($value)) {
×
615
                $uriVariables[$value] = $value;
×
616
                continue;
×
617
            }
618

619
            if (isset($value['fromClass']) || isset($value[0])) {
×
620
                $uriVariables[$parameterName]['from_class'] = $value['fromClass'] ?? $value[0];
×
621
            }
622
            if (isset($value['fromProperty']) || isset($value[1])) {
×
623
                $uriVariables[$parameterName]['from_property'] = $value['fromProperty'] ?? $value[1];
×
624
            }
625
            if (isset($value['toClass'])) {
×
626
                $uriVariables[$parameterName]['to_class'] = $value['toClass'];
×
627
            }
628
            if (isset($value['toProperty'])) {
×
629
                $uriVariables[$parameterName]['to_property'] = $value['toProperty'];
×
630
            }
631
            if (isset($value['identifiers'])) {
×
632
                $uriVariables[$parameterName]['identifiers'] = $value['identifiers'];
×
633
            }
634
            if (isset($value['compositeIdentifier'])) {
×
635
                $uriVariables[$parameterName]['composite_identifier'] = $value['compositeIdentifier'];
×
636
            }
637
        }
638

639
        return $uriVariables;
×
640
    }
641

642
    private function withOperations(array $values, ?array $fixtures): Operations
643
    {
644
        $operations = [];
×
645
        foreach ($values as $value) {
×
646
            $class = $value['class'] ?? HttpOperation::class;
×
647
            unset($value['class']);
×
648
            $operation = (new $class())->withClass(self::RESOURCE_CLASS);
×
649

650
            foreach (array_merge(self::BASE, self::EXTENDED_BASE) as $parameter) {
×
651
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
652
                    $value[$parameter] = $fixtures[$parameter];
×
653
                }
654
            }
655

656
            foreach ($value as $parameter => $parameterValue) {
×
657
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
658
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
659
                }
660

661
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
662
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
663
                    continue;
×
664
                }
665

666
                throw new \RuntimeException(sprintf('Unknown Operation parameter "%s".', $parameter));
×
667
            }
668

669
            $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, self::RESOURCE_CLASS);
×
670
            $operations[$operationName] = $operation;
×
671
        }
672

673
        return new Operations($operations);
×
674
    }
675

676
    private function withGraphQlOperations(array $values, ?array $fixtures): array
677
    {
678
        $operations = [];
×
679
        foreach ($values as $value) {
×
680
            $class = $value['class'];
×
681
            unset($value['class']);
×
682
            $operation = (new $class())->withClass(self::RESOURCE_CLASS);
×
683

684
            foreach (self::BASE as $parameter) {
×
685
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
686
                    $value[$parameter] = $fixtures[$parameter];
×
687
                }
688
            }
689

690
            foreach ($value as $parameter => $parameterValue) {
×
691
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
692
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
693
                }
694

695
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
696
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
697
                    continue;
×
698
                }
699

700
                throw new \RuntimeException(sprintf('Unknown GraphQlOperation parameter "%s".', $parameter));
×
701
            }
702

703
            $operationName = $operation->getName();
×
704
            $operations[$operationName] = $operation;
×
705
        }
706

707
        return $operations;
×
708
    }
709

710
    private function withStateOptions(array $values): ?OptionsInterface
711
    {
712
        if (!$values) {
×
713
            return null;
×
714
        }
715

716
        if (1 !== \count($values)) {
×
717
            throw new \InvalidArgumentException('Only one options can be configured at a time.');
×
718
        }
719

720
        $configuration = reset($values);
×
721
        switch (key($values)) {
×
722
            case 'elasticsearchOptions':
×
723
                return new StateOptions($configuration['index'] ?? null, $configuration['type'] ?? null);
×
724
        }
725

726
        throw new \LogicException(sprintf('Unsupported "%s" state options.', key($values)));
×
727
    }
728
}
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

© 2026 Coveralls, Inc