Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.84% covered (success)
91.84%
45 / 49
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
PruneUnusedComponentsSubscriber
91.84% covered (success)
91.84%
45 / 49
60.00% covered (warning)
60.00%
3 / 5
23.29
0.00% covered (danger)
0.00%
0 / 1
 getSubscribedEvents
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 onOpenApiSchemaGenerated
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 collectUsedSchemaRefs
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
3.01
 collectRefsFromRoot
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
7.99
 collectRefsFromObject
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2namespace Apie\RestApi\EventListeners;
3
4use Apie\RestApi\Events\OpenApiSchemaGeneratedEvent;
5use cebe\openapi\spec\OpenApi;
6use cebe\openapi\SpecBaseObject;
7use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8
9class PruneUnusedComponentsSubscriber implements EventSubscriberInterface
10{
11    public static function getSubscribedEvents(): array
12    {
13        return [
14            OpenApiSchemaGeneratedEvent::class => 'onOpenApiSchemaGenerated',
15        ];
16    }
17
18    public function onOpenApiSchemaGenerated(OpenApiSchemaGeneratedEvent $event): void
19    {
20        $openApi = $event->openApi;
21
22        do {
23            $before = count($openApi->components->schemas ?? []);
24
25            $usedSchemas = $this->collectUsedSchemaRefs($openApi);
26            $schemas = $openApi->components->schemas ?? [];
27
28            foreach ($schemas as $name => $_schema) {
29                if (!isset($usedSchemas[$name])) {
30                    unset($schemas[$name]);
31                }
32            }
33
34            $openApi->components->schemas = $schemas;
35
36            $after = count($openApi->components->schemas ?? []);
37        } while ($after !== $before);
38    }
39
40    /**
41     * @return array<string, bool>
42     */
43    private function collectUsedSchemaRefs(OpenApi $openApi): array
44    {
45        $used = [];
46        $queue = [];
47
48        $this->collectRefsFromRoot($openApi, $queue);
49
50        while (!empty($queue)) {
51            $ref = array_pop($queue);
52
53            if (!str_starts_with($ref, '#/components/schemas/')) {
54                continue;
55            }
56
57            $name = substr($ref, strlen('#/components/schemas/'));
58            $used[$name] = true;
59        }
60
61        return $used;
62    }
63
64    /**
65     * @param array<int, string> $queue
66     */
67    private function collectRefsFromRoot(OpenApi $openApi, array &$queue): void
68    {
69        foreach ($openApi->paths ?? [] as $path) {
70            foreach ($path->getOperations() as $op) {
71                $this->collectRefsFromObject($op, $queue);
72            }
73        }
74
75        foreach ($openApi->components->parameters ?? [] as $p) {
76            $this->collectRefsFromObject($p, $queue);
77        }
78
79        foreach ($openApi->components->requestBodies ?? [] as $rb) {
80            $this->collectRefsFromObject($rb, $queue);
81        }
82
83        foreach ($openApi->components->responses ?? [] as $r) {
84            $this->collectRefsFromObject($r, $queue);
85        }
86
87        foreach ($openApi->components->schemas ?? [] as $r) {
88            $this->collectRefsFromObject($r, $queue);
89        }
90    }
91
92    /**
93     * @param array<int, string> $queue
94     */
95    private function collectRefsFromObject(mixed $node, array &$queue): void
96    {
97        if (is_array($node)) {
98            foreach ($node as $v) {
99                $this->collectRefsFromObject($v, $queue);
100            }
101            return;
102        }
103        if ($node instanceof SpecBaseObject) {
104            foreach ($node->getSerializableData() as $prop) {
105                $this->collectRefsFromObject($prop, $queue);
106            }
107            return;
108        }
109
110        if (is_object($node)) {
111            foreach (get_object_vars($node) as $v) {
112                $this->collectRefsFromObject($v, $queue);
113            }
114            return;
115        }
116
117        if (is_string($node)) {
118            if (str_starts_with($node, '#/components/schemas/')) {
119                $queue[] = $node;
120            }
121        }
122    }
123}