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

api-platform / core / 7582976395

19 Jan 2024 11:09AM UTC coverage: 61.988% (+0.03%) from 61.96%
7582976395

push

github

web-flow
feat(metadata): headers configuration (#6074)

27 of 40 new or added lines in 14 files covered. (67.5%)

46 existing lines in 8 files now uncovered.

17483 of 28204 relevant lines covered (61.99%)

32.43 hits per line

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

61.35
/src/Metadata/Extractor/YamlResourceExtractor.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\Extractor;
15

16
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
17
use ApiPlatform\Metadata\GetCollection;
18
use ApiPlatform\Metadata\Post;
19
use ApiPlatform\Metadata\Tests\Fixtures\StateOptions;
20
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
21
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
22
use ApiPlatform\OpenApi\Model\Parameter;
23
use ApiPlatform\OpenApi\Model\RequestBody;
24
use ApiPlatform\State\OptionsInterface;
25
use Symfony\Component\WebLink\Link;
26
use Symfony\Component\Yaml\Exception\ParseException;
27
use Symfony\Component\Yaml\Yaml;
28

29
/**
30
 * Extracts an array of metadata from a list of YAML files.
31
 *
32
 * @author Antoine Bluchet <soyuka@gmail.com>
33
 * @author Baptiste Meyer <baptiste.meyer@gmail.com>
34
 * @author Kévin Dunglas <dunglas@gmail.com>
35
 * @author Vincent Chalamon <vincentchalamon@gmail.com>
36
 */
37
final class YamlResourceExtractor extends AbstractResourceExtractor
38
{
39
    use ResourceExtractorTrait;
40

41
    /**
42
     * {@inheritdoc}
43
     */
44
    protected function extractPath(string $path): void
45
    {
46
        try {
47
            $resourcesYaml = Yaml::parse((string) file_get_contents($path), Yaml::PARSE_CONSTANT);
32✔
48
        } catch (ParseException $e) {
×
49
            $e->setParsedFile($path);
×
50

51
            throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
×
52
        }
53

54
        if (null === $resourcesYaml = $resourcesYaml['resources'] ?? $resourcesYaml) {
32✔
55
            return;
×
56
        }
57

58
        if (!\is_array($resourcesYaml)) {
32✔
59
            throw new InvalidArgumentException(sprintf('"resources" setting is expected to be null or an array, %s given in "%s".', \gettype($resourcesYaml), $path));
×
60
        }
61

62
        $this->buildResources($resourcesYaml, $path);
32✔
63
    }
64

65
    private function buildResources(array $resourcesYaml, string $path): void
66
    {
67
        foreach ($resourcesYaml as $resourceName => $resourceYaml) {
32✔
68
            $resourceName = $this->resolve($resourceName);
32✔
69

70
            if (null === $resourceYaml) {
32✔
71
                $resourceYaml = [[]];
32✔
72
            }
73

74
            if (!\array_key_exists(0, $resourceYaml)) {
32✔
75
                $resourceYaml = [$resourceYaml];
32✔
76
            }
77

78
            foreach ($resourceYaml as $key => $resourceYamlDatum) {
32✔
79
                if (null === $resourceYamlDatum) {
32✔
80
                    $resourceYamlDatum = [];
32✔
81
                }
82

83
                try {
84
                    $base = $this->buildExtendedBase($resourceYamlDatum);
32✔
85
                    $this->resources[$resourceName][$key] = array_merge($base, [
32✔
86
                        'operations' => $this->buildOperations($resourceYamlDatum, $base),
32✔
87
                        'graphQlOperations' => $this->buildGraphQlOperations($resourceYamlDatum, $base),
32✔
88
                    ]);
32✔
89
                } catch (InvalidArgumentException $exception) {
×
90
                    throw new InvalidArgumentException(sprintf('%s in "%s" (%s).', $exception->getMessage(), $resourceName, $path));
×
91
                }
92
            }
93
        }
94
    }
95

96
    private function buildExtendedBase(array $resource): array
97
    {
98
        return array_merge($this->buildBase($resource), [
32✔
99
            'uriTemplate' => $this->phpize($resource, 'uriTemplate', 'string'),
32✔
100
            'routePrefix' => $this->phpize($resource, 'routePrefix', 'string'),
32✔
101
            'stateless' => $this->phpize($resource, 'stateless', 'bool'),
32✔
102
            'sunset' => $this->phpize($resource, 'sunset', 'string'),
32✔
103
            'acceptPatch' => $this->phpize($resource, 'acceptPatch', 'string'),
32✔
104
            'host' => $this->phpize($resource, 'host', 'string'),
32✔
105
            'condition' => $this->phpize($resource, 'condition', 'string'),
32✔
106
            'controller' => $this->phpize($resource, 'controller', 'string'),
32✔
107
            'queryParameterValidationEnabled' => $this->phpize($resource, 'queryParameterValidationEnabled', 'bool'),
32✔
108
            'types' => $this->buildArrayValue($resource, 'types'),
32✔
109
            'cacheHeaders' => $this->buildArrayValue($resource, 'cacheHeaders'),
32✔
110
            'hydraContext' => $this->buildArrayValue($resource, 'hydraContext'),
32✔
111
            'openapiContext' => $this->buildArrayValue($resource, 'openapiContext'), // TODO Remove in 4.0
32✔
112
            'openapi' => $this->buildOpenapi($resource),
32✔
113
            'paginationViaCursor' => $this->buildArrayValue($resource, 'paginationViaCursor'),
32✔
114
            'exceptionToStatus' => $this->buildArrayValue($resource, 'exceptionToStatus'),
32✔
115
            'defaults' => $this->buildArrayValue($resource, 'defaults'),
32✔
116
            'requirements' => $this->buildArrayValue($resource, 'requirements'),
32✔
117
            'options' => $this->buildArrayValue($resource, 'options'),
32✔
118
            'status' => $this->phpize($resource, 'status', 'integer'),
32✔
119
            'schemes' => $this->buildArrayValue($resource, 'schemes'),
32✔
120
            'formats' => $this->buildArrayValue($resource, 'formats'),
32✔
121
            'uriVariables' => $this->buildUriVariables($resource),
32✔
122
            'inputFormats' => $this->buildArrayValue($resource, 'inputFormats'),
32✔
123
            'outputFormats' => $this->buildArrayValue($resource, 'outputFormats'),
32✔
124
            'stateOptions' => $this->buildStateOptions($resource),
32✔
125
            'links' => $this->buildLinks($resource),
32✔
126
            'headers' => $this->buildHeaders($resource),
32✔
127
        ]);
32✔
128
    }
129

130
    private function buildBase(array $resource): array
131
    {
132
        return [
32✔
133
            'shortName' => $this->phpize($resource, 'shortName', 'string'),
32✔
134
            'description' => $this->phpize($resource, 'description', 'string'),
32✔
135
            'urlGenerationStrategy' => $this->phpize($resource, 'urlGenerationStrategy', 'integer'),
32✔
136
            'deprecationReason' => $this->phpize($resource, 'deprecationReason', 'string'),
32✔
137
            'elasticsearch' => $this->phpize($resource, 'elasticsearch', 'bool'),
32✔
138
            'fetchPartial' => $this->phpize($resource, 'fetchPartial', 'bool'),
32✔
139
            'forceEager' => $this->phpize($resource, 'forceEager', 'bool'),
32✔
140
            'paginationClientEnabled' => $this->phpize($resource, 'paginationClientEnabled', 'bool'),
32✔
141
            'paginationClientItemsPerPage' => $this->phpize($resource, 'paginationClientItemsPerPage', 'bool'),
32✔
142
            'paginationClientPartial' => $this->phpize($resource, 'paginationClientPartial', 'bool'),
32✔
143
            'paginationEnabled' => $this->phpize($resource, 'paginationEnabled', 'bool'),
32✔
144
            'paginationFetchJoinCollection' => $this->phpize($resource, 'paginationFetchJoinCollection', 'bool'),
32✔
145
            'paginationUseOutputWalkers' => $this->phpize($resource, 'paginationUseOutputWalkers', 'bool'),
32✔
146
            'paginationItemsPerPage' => $this->phpize($resource, 'paginationItemsPerPage', 'integer'),
32✔
147
            'paginationMaximumItemsPerPage' => $this->phpize($resource, 'paginationMaximumItemsPerPage', 'integer'),
32✔
148
            'paginationPartial' => $this->phpize($resource, 'paginationPartial', 'bool'),
32✔
149
            'paginationType' => $this->phpize($resource, 'paginationType', 'string'),
32✔
150
            'processor' => $this->phpize($resource, 'processor', 'string'),
32✔
151
            'provider' => $this->phpize($resource, 'provider', 'string'),
32✔
152
            'security' => $this->phpize($resource, 'security', 'string'),
32✔
153
            'securityMessage' => $this->phpize($resource, 'securityMessage', 'string'),
32✔
154
            'securityPostDenormalize' => $this->phpize($resource, 'securityPostDenormalize', 'string'),
32✔
155
            'securityPostDenormalizeMessage' => $this->phpize($resource, 'securityPostDenormalizeMessage', 'string'),
32✔
156
            'securityPostValidation' => $this->phpize($resource, 'securityPostValidation', 'string'),
32✔
157
            'securityPostValidationMessage' => $this->phpize($resource, 'securityPostValidationMessage', 'string'),
32✔
158
            'input' => $this->phpize($resource, 'input', 'bool|string'),
32✔
159
            'output' => $this->phpize($resource, 'output', 'bool|string'),
32✔
160
            'normalizationContext' => $this->buildArrayValue($resource, 'normalizationContext'),
32✔
161
            'denormalizationContext' => $this->buildArrayValue($resource, 'denormalizationContext'),
32✔
162
            'collectDenormalizationErrors' => $this->phpize($resource, 'collectDenormalizationErrors', 'bool'),
32✔
163
            'validationContext' => $this->buildArrayValue($resource, 'validationContext'),
32✔
164
            'filters' => $this->buildArrayValue($resource, 'filters'),
32✔
165
            'order' => $this->buildArrayValue($resource, 'order'),
32✔
166
            'extraProperties' => $this->buildArrayValue($resource, 'extraProperties'),
32✔
167
            'mercure' => $this->buildMercure($resource),
32✔
168
            'messenger' => $this->buildMessenger($resource),
32✔
169
            'read' => $this->phpize($resource, 'read', 'bool'),
32✔
170
            'write' => $this->phpize($resource, 'write', 'bool'),
32✔
171
        ];
32✔
172
    }
173

174
    private function buildUriVariables(array $resource): ?array
175
    {
176
        if (!\array_key_exists('uriVariables', $resource)) {
32✔
177
            return null;
32✔
178
        }
179

180
        $uriVariables = [];
32✔
181
        foreach ($resource['uriVariables'] as $parameterName => $data) {
32✔
182
            if (\is_string($data)) {
32✔
183
                $uriVariables[$data] = $data;
×
184
                continue;
×
185
            }
186

187
            if (2 === (is_countable($data) ? \count($data) : 0) && isset($data[0]) && isset($data[1])) {
32✔
188
                $data['fromClass'] = $data[0];
32✔
189
                $data['fromProperty'] = $data[1];
32✔
190
                unset($data[0], $data[1]);
32✔
191
            }
192
            if (isset($data['fromClass'])) {
32✔
193
                $uriVariables[$parameterName]['from_class'] = $data['fromClass'];
32✔
194
            }
195
            if (isset($data['fromProperty'])) {
32✔
196
                $uriVariables[$parameterName]['from_property'] = $data['fromProperty'];
32✔
197
            }
198
            if (isset($data['toClass'])) {
32✔
199
                $uriVariables[$parameterName]['to_class'] = $data['toClass'];
×
200
            }
201
            if (isset($data['toProperty'])) {
32✔
202
                $uriVariables[$parameterName]['to_property'] = $data['toProperty'];
32✔
203
            }
204
            if (isset($data['identifiers'])) {
32✔
205
                $uriVariables[$parameterName]['identifiers'] = $data['identifiers'];
×
206
            }
207
            if (isset($data['compositeIdentifier'])) {
32✔
208
                $uriVariables[$parameterName]['composite_identifier'] = $data['compositeIdentifier'];
×
209
            }
210
        }
211

212
        return $uriVariables;
32✔
213
    }
214

215
    private function buildOpenapi(array $resource): bool|OpenApiOperation|null
216
    {
217
        if (!\array_key_exists('openapi', $resource)) {
32✔
218
            return null;
32✔
219
        }
220

221
        if (!\is_array($resource['openapi'])) {
×
222
            return $this->phpize($resource, 'openapi', 'bool');
×
223
        }
224

225
        $allowedProperties = array_map(fn (\ReflectionProperty $reflProperty): string => $reflProperty->getName(), (new \ReflectionClass(OpenApiOperation::class))->getProperties());
×
226
        foreach ($resource['openapi'] as $key => $value) {
×
227
            $resource['openapi'][$key] = match ($key) {
×
228
                'externalDocs' => new ExternalDocumentation(description: $value['description'] ?? '', url: $value['url'] ?? ''),
×
229
                'requestBody' => new RequestBody(description: $value['description'] ?? '', content: isset($value['content']) ? new \ArrayObject($value['content'] ?? []) : null, required: $value['required'] ?? false),
×
230
                'callbacks' => new \ArrayObject($value ?? []),
×
231
                default => $value,
×
232
            };
×
233

234
            if (\in_array($key, $allowedProperties, true)) {
×
235
                continue;
×
236
            }
237

238
            $resource['openapi']['extensionProperties'][$key] = $value;
×
239
            unset($resource['openapi'][$key]);
×
240
        }
241

242
        if (\array_key_exists('parameters', $resource['openapi']) && \is_array($openapiParameters = $resource['openapi']['parameters'] ?? [])) {
×
243
            $parameters = [];
×
244
            foreach ($openapiParameters as $parameter) {
×
245
                $parameters[] = new Parameter(
×
246
                    name: $parameter['name'],
×
247
                    in: $parameter['in'],
×
248
                    description: $parameter['description'] ?? '',
×
249
                    required: $parameter['required'] ?? false,
×
250
                    deprecated: $parameter['deprecated'] ?? false,
×
251
                    allowEmptyValue: $parameter['allowEmptyValue'] ?? false,
×
252
                    schema: $parameter['schema'] ?? [],
×
253
                    style: $parameter['style'] ?? null,
×
254
                    explode: $parameter['explode'] ?? false,
×
255
                    allowReserved: $parameter['allowReserved '] ?? false,
×
256
                    example: $parameter['example'] ?? null,
×
257
                    examples: isset($parameter['examples']) ? new \ArrayObject($parameter['examples']) : null,
×
258
                    content: isset($parameter['content']) ? new \ArrayObject($parameter['content']) : null
×
259
                );
×
260
            }
261
            $resource['openapi']['parameters'] = $parameters;
×
262
        }
263

264
        return new OpenApiOperation(...$resource['openapi']);
×
265
    }
266

267
    /**
268
     * @return bool|string|string[]|null
269
     */
270
    private function buildMercure(array $resource): array|bool|string|null
271
    {
272
        if (!\array_key_exists('mercure', $resource)) {
32✔
273
            return null;
32✔
274
        }
275

276
        if (\is_string($resource['mercure'])) {
×
277
            return $this->phpize($resource, 'mercure', 'bool|string');
×
278
        }
279

280
        return $resource['mercure'];
×
281
    }
282

283
    private function buildMessenger(array $resource): bool|array|string|null
284
    {
285
        if (!\array_key_exists('messenger', $resource)) {
32✔
286
            return null;
32✔
287
        }
288

289
        return $this->phpize($resource, 'messenger', 'bool|string');
×
290
    }
291

292
    private function buildOperations(array $resource, array $root): ?array
293
    {
294
        if (!\array_key_exists('operations', $resource)) {
32✔
295
            return null;
32✔
296
        }
297

298
        $data = [];
32✔
299
        foreach ($resource['operations'] as $class => $operation) {
32✔
300
            if (null === $operation) {
32✔
301
                $operation = [];
32✔
302
            }
303

304
            if (\array_key_exists('class', $operation)) {
32✔
305
                if (!\array_key_exists('name', $operation) && \is_string($class)) {
×
306
                    $operation['name'] = $class;
×
307
                }
308
                $class = $operation['class'];
×
309
            }
310

311
            if (empty($class)) {
32✔
312
                throw new InvalidArgumentException('Missing "class" attribute');
×
313
            }
314

315
            if (!class_exists($class)) {
32✔
316
                throw new InvalidArgumentException(sprintf('Operation class "%s" does not exist', $class));
×
317
            }
318

319
            $datum = $this->buildExtendedBase($operation);
32✔
320
            foreach ($datum as $key => $value) {
32✔
321
                if (null === $value) {
32✔
322
                    $datum[$key] = $root[$key];
32✔
323
                }
324
            }
325

326
            if (\in_array((string) $class, [GetCollection::class, Post::class], true)) {
32✔
327
                $datum['itemUriTemplate'] = $this->phpize($operation, 'itemUriTemplate', 'string');
32✔
328
            } elseif (isset($operation['itemUriTemplate'])) {
32✔
329
                throw new InvalidArgumentException(sprintf('"itemUriTemplate" option is not allowed on a %s operation.', $class));
×
330
            }
331

332
            $data[] = array_merge($datum, [
32✔
333
                'read' => $this->phpize($operation, 'read', 'bool'),
32✔
334
                'deserialize' => $this->phpize($operation, 'deserialize', 'bool'),
32✔
335
                'validate' => $this->phpize($operation, 'validate', 'bool'),
32✔
336
                'write' => $this->phpize($operation, 'write', 'bool'),
32✔
337
                'serialize' => $this->phpize($operation, 'serialize', 'bool'),
32✔
338
                'queryParameterValidate' => $this->phpize($operation, 'queryParameterValidate', 'bool'),
32✔
339
                'priority' => $this->phpize($operation, 'priority', 'integer'),
32✔
340
                'name' => $this->phpize($operation, 'name', 'string'),
32✔
341
                'class' => (string) $class,
32✔
342
            ]);
32✔
343
        }
344

345
        return $data;
32✔
346
    }
347

348
    private function buildGraphQlOperations(array $resource, array $root): ?array
349
    {
350
        if (!\array_key_exists('graphQlOperations', $resource) || !\is_array($resource['graphQlOperations'])) {
32✔
351
            return null;
32✔
352
        }
353

354
        $data = [];
32✔
355
        foreach ($resource['graphQlOperations'] as $class => $operation) {
32✔
356
            if (null === $operation) {
×
357
                $operation = [];
×
358
            }
359

360
            if (\array_key_exists('class', $operation)) {
×
361
                if (!\array_key_exists('name', $operation) && \is_string($class)) {
×
362
                    $operation['name'] = $class;
×
363
                }
364
                $class = $operation['class'];
×
365
            }
366

367
            if (empty($class)) {
×
368
                throw new InvalidArgumentException('Missing "class" attribute');
×
369
            }
370

371
            if (!class_exists($class)) {
×
372
                throw new InvalidArgumentException(sprintf('Operation class "%s" does not exist', $class));
×
373
            }
374

375
            $datum = $this->buildBase($operation);
×
376
            foreach ($datum as $key => $value) {
×
377
                if (null === $value) {
×
378
                    $datum[$key] = $root[$key];
×
379
                }
380
            }
381

382
            $data[] = array_merge($datum, [
×
383
                'resolver' => $this->phpize($operation, 'resolver', 'string'),
×
384
                'args' => $operation['args'] ?? null,
×
385
                'extraArgs' => $operation['extraArgs'] ?? null,
×
386
                'class' => (string) $class,
×
387
                'read' => $this->phpize($operation, 'read', 'bool'),
×
388
                'deserialize' => $this->phpize($operation, 'deserialize', 'bool'),
×
389
                'validate' => $this->phpize($operation, 'validate', 'bool'),
×
390
                'write' => $this->phpize($operation, 'write', 'bool'),
×
391
                'serialize' => $this->phpize($operation, 'serialize', 'bool'),
×
392
                'priority' => $this->phpize($operation, 'priority', 'integer'),
×
393
                'name' => $this->phpize($operation, 'name', 'string'),
×
394
            ]);
×
395
        }
396

397
        return $data ?: null;
32✔
398
    }
399

400
    private function buildStateOptions(array $resource): ?OptionsInterface
401
    {
402
        $stateOptions = $resource['stateOptions'] ?? [];
32✔
403
        if (!\is_array($stateOptions)) {
32✔
404
            return null;
×
405
        }
406

407
        if (!$stateOptions) {
32✔
408
            return null;
32✔
409
        }
410

411
        $configuration = reset($stateOptions);
×
412
        switch (key($stateOptions)) {
×
413
            case 'elasticsearchOptions':
×
414
                return new StateOptions($configuration['index'] ?? null, $configuration['type'] ?? null);
×
415
        }
416

417
        return null;
×
418
    }
419

420
    /**
421
     * @return Link[]
422
     */
423
    private function buildLinks(array $resource): ?array
424
    {
425
        if (!isset($resource['links']) || !\is_array($resource['links'])) {
32✔
426
            return null;
32✔
427
        }
428

429
        $links = [];
×
430
        foreach ($resource['links'] as $link) {
×
431
            $links[] = new Link(rel: $link['rel'], href: $link['href']);
×
432
        }
433

434
        return $links;
×
435
    }
436

437
    /**
438
     * @return array<string, string>
439
     */
440
    private function buildHeaders(array $resource): ?array
441
    {
442
        if (!isset($resource['headers']) || !\is_array($resource['headers'])) {
32✔
443
            return null;
32✔
444
        }
445

NEW
446
        $headers = [];
×
NEW
447
        foreach ($resource['headers'] as $key => $value) {
×
NEW
448
            $headers[$key] = $value;
×
449
        }
450

NEW
451
        return $headers;
×
452
    }
453
}
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