Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.00% covered (warning)
84.00%
42 / 50
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
AddAcceptLanguageEventSubscriber
84.00% covered (warning)
84.00%
42 / 50
40.00% covered (danger)
40.00%
2 / 5
15.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSubscribedEvents
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 onOpenApiOperationAdded
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 onOpenApiSchemaGenerated
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
3.00
 getExamples
66.67% covered (warning)
66.67%
12 / 18
0.00% covered (danger)
0.00%
0 / 1
10.37
1<?php
2namespace Apie\RestApi\EventListeners;
3
4use Apie\Core\Attributes\ExampleValue;
5use Apie\Core\Identifiers\SnakeCaseSlug;
6use Apie\Core\Utils\ConverterUtils;
7use Apie\RestApi\Events\OpenApiOperationAddedEvent;
8use Apie\RestApi\Events\OpenApiSchemaGeneratedEvent;
9use Apie\SchemaGenerator\SchemaGenerator;
10use Apie\TypeConverter\ReflectionTypeFactory;
11use cebe\openapi\spec\Example;
12use cebe\openapi\spec\Reference;
13use Symfony\Component\EventDispatcher\EventSubscriberInterface;
14
15class AddAcceptLanguageEventSubscriber implements EventSubscriberInterface
16{
17    public function __construct(
18        private readonly ?string $languageTypehint,
19        private readonly SchemaGenerator $schemaFactory,
20    ) {
21    }
22    public static function getSubscribedEvents(): array
23    {
24        return [
25            OpenApiOperationAddedEvent::class => 'onOpenApiOperationAdded',
26            OpenApiSchemaGeneratedEvent::class => 'onOpenApiSchemaGenerated',
27        ];
28    }
29
30    public function onOpenApiOperationAdded(OpenApiOperationAddedEvent $event): void
31    {
32        if ($this->languageTypehint === null) {
33            return;
34        }
35        $ref = new Reference([
36            '$ref' => '#/components/parameters/AcceptLanguage',
37        ]);
38        $parameters = $event->operation->parameters ?? [];
39        $parameters[] = $ref;
40        $event->operation->parameters = $parameters;
41    }
42
43    public function onOpenApiSchemaGenerated(OpenApiSchemaGeneratedEvent $event): void
44    {
45        if ($this->languageTypehint === null) {
46            return;
47        }
48        $openApi = $event->openApi;
49
50        $parameters = $openApi->components->parameters ?? [];
51        $examples = $this->getExamples(ReflectionTypeFactory::createReflectionType($this->languageTypehint));
52        $parameter = new \cebe\openapi\spec\Parameter([
53            'name' => 'Accept-Language',
54            'in' => 'header',
55            'required' => false,
56            'description' => 'Preferred language for the response.',
57            'schema' => $this->schemaFactory->createSchema(
58                $this->languageTypehint,
59                display: false
60            ),
61        ]);
62        if ($examples) {
63            $parameter->examples = $examples;
64        }
65        $parameters['AcceptLanguage'] = $parameter;
66        $openApi->components->parameters = $parameters;
67    }
68
69    /**
70     * @return array<string, Example>
71     */
72    private function getExamples(\ReflectionType $input): array
73    {
74        $examples = [];
75        if ($input instanceof \ReflectionNamedType) {
76            $class = ConverterUtils::toReflectionClass($input);
77            if ($class === null) {
78                return [];
79            }
80            foreach ($class->getAttributes(ExampleValue::class) as $attribute) {
81                $exampleValue = $attribute->newInstance();
82                $id = SnakeCaseSlug::fromText($exampleValue->name)->toNative();
83                $examples[$id] = new Example([
84                    'summary' => $exampleValue->name,
85                    'value' => $exampleValue->toExample(),
86                ]);
87            }
88            return $examples;
89        }
90        assert($input instanceof \ReflectionIntersectionType || $input instanceof \ReflectionUnionType);
91        foreach ($input->getTypes() as $type) {
92            // only allow interfaces or union types. Merge examples of intersections make no sense otherwise.
93            if ($input instanceof \ReflectionUnionType || ConverterUtils::toReflectionClass($type)?->isInterface()) {
94                $examples = array_merge($examples, $this->getExamples($type));
95            }
96        }
97        return $examples;
98    }
99}