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

DerManoMann / openapi-extras / 27990977581

22 Jun 2026 11:26PM UTC coverage: 86.395% (-2.4%) from 88.806%
27990977581

push

github

web-flow
Bump actions/checkout from 6 to 7 (#56)

Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

254 of 294 relevant lines covered (86.39%)

11.46 hits per line

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

96.84
/src/Processors/MergeControllerDefaults.php
1
<?php declare(strict_types=1);
2

3
namespace Radebatz\OpenApi\Extras\Processors;
4

5
use OpenApi\Analysis;
6
use OpenApi\Annotations as OA;
7
use OpenApi\Context;
8
use OpenApi\Generator;
9
use Radebatz\OpenApi\Extras\Annotations as OAX;
10

11
class MergeControllerDefaults
12
{
13
    public function __invoke(Analysis $analysis): void
14
    {
15
        $controllers = $analysis->getAnnotationsOfType(OAX\Controller::class);
29✔
16
        $operations = $analysis->getAnnotationsOfType(OA\Operation::class);
29✔
17

18
        $controllerMap = $this->buildControllerMap($controllers);
29✔
19

20
        foreach ($operations as $operation) {
29✔
21
            $chain = $this->resolveControllerChain($operation->_context, $controllerMap, $analysis);
27✔
22
            if ($chain !== []) {
27✔
23
                $this->applyChain($operation, $chain);
27✔
24
            }
25
        }
26

27
        foreach ($controllers as $controller) {
29✔
28
            $this->clearMerged($analysis, $controller->headers);
27✔
29
            $this->clearMerged($analysis, $controller->responses);
27✔
30
            $this->clearMerged($analysis, $controller);
27✔
31
        }
32
    }
33

34
    /**
35
     * @param  OAX\Controller[]              $controllers
36
     * @return array<string, OAX\Controller>
37
     */
38
    protected function buildControllerMap(array $controllers): array
39
    {
40
        $map = [];
29✔
41
        foreach ($controllers as $controller) {
29✔
42
            $fqcn = $controller->_context->fullyQualifiedName($controller->_context->class);
27✔
43
            if ($fqcn) {
27✔
44
                $map[$fqcn] = $controller;
27✔
45
            }
46
        }
47

48
        return $map;
29✔
49
    }
50

51
    /**
52
     * @param  array<string, OAX\Controller> $controllerMap
53
     * @return OAX\Controller[]
54
     */
55
    protected function resolveControllerChain(?Context $operationContext, array $controllerMap, Analysis $analysis): array
56
    {
57
        if (!$operationContext || !$operationContext->class) {
27✔
58
            return [];
×
59
        }
60

61
        $fqcn = $operationContext->fullyQualifiedName($operationContext->class);
27✔
62
        if (!$fqcn) {
27✔
63
            return [];
×
64
        }
65

66
        $chain = [];
27✔
67

68
        // Direct class controller
69
        $directController = $controllerMap[$fqcn] ?? null;
27✔
70
        if ($directController) {
27✔
71
            $chain[] = $directController;
27✔
72

73
            if (!$directController->inherit) {
27✔
74
                return $chain;
26✔
75
            }
76
        }
77

78
        // Walk up the inheritance chain
79
        $superClasses = $analysis->getSuperClasses($fqcn);
27✔
80
        foreach (array_keys($superClasses) as $parentFqcn) {
27✔
81
            $parentController = $controllerMap[$parentFqcn] ?? null;
26✔
82
            if ($parentController) {
26✔
83
                $chain[] = $parentController;
26✔
84

85
                if (!$parentController->inherit) {
26✔
86
                    break;
×
87
                }
88
            }
89
        }
90

91
        // Reverse so most-distant ancestor is first, direct class is last
92
        return array_reverse($chain);
27✔
93
    }
94

95
    /**
96
     * @param OAX\Controller[] $chain
97
     */
98
    protected function applyChain(OA\Operation $operation, array $chain): void
99
    {
100
        $mergedPrefix = '';
27✔
101
        $mergedTags = [];
27✔
102
        $mergedResponses = [];
27✔
103
        $mergedHeaders = [];
27✔
104
        $mergedMiddlewares = [];
27✔
105

106
        foreach ($chain as $controller) {
27✔
107
            if ($controller->prefix && !Generator::isDefault($controller->prefix)) {
27✔
108
                $mergedPrefix .= '/' . trim($controller->prefix, '/');
27✔
109
            }
110

111
            if ($controller->tags) {
27✔
112
                foreach ($controller->tags as $tag) {
26✔
113
                    $mergedTags[$tag] = $tag;
26✔
114
                }
115
            }
116

117
            if ($controller->responses) {
27✔
118
                foreach ($controller->responses as $response) {
26✔
119
                    $mergedResponses[$response->response] = $response;
26✔
120
                }
121
            }
122

123
            if ($controller->headers) {
27✔
124
                foreach ($controller->headers as $header) {
26✔
125
                    $mergedHeaders[$header->header] = $header;
26✔
126
                }
127
            }
128

129
            $mergedMiddlewares = $this->collectMiddlewares($controller->attachables, $mergedMiddlewares);
27✔
130
        }
131

132
        $mergedMiddlewares = $this->collectMiddlewares($operation->attachables, $mergedMiddlewares);
27✔
133

134
        $this->applyPrefix($operation, $mergedPrefix);
27✔
135
        $this->applyTags($operation, $mergedTags);
27✔
136
        $this->applyResponses($operation, $mergedResponses);
27✔
137
        $this->applyHeaders($operation, $mergedHeaders);
27✔
138
        $this->applyMiddlewares($operation, $mergedMiddlewares);
27✔
139
    }
140

141
    protected function applyPrefix(OA\Operation $operation, string $mergedPrefix): void
142
    {
143
        if ($mergedPrefix !== '') {
27✔
144
            $path = $mergedPrefix . '/' . ltrim($operation->path, '/');
27✔
145
            $operation->path = str_replace('//', '/', $path);
27✔
146
        }
147
    }
148

149
    /**
150
     * @param array<string, string> $mergedTags
151
     */
152
    protected function applyTags(OA\Operation $operation, array $mergedTags): void
153
    {
154
        if ($mergedTags === []) {
27✔
155
            return;
27✔
156
        }
157

158
        $operationTags = Generator::isDefault($operation->tags) ? [] : $operation->tags;
26✔
159
        $operation->tags = array_values(array_unique([...$mergedTags, ...$operationTags]));
26✔
160
    }
161

162
    /**
163
     * @param array<string|int, OA\Response> $mergedResponses
164
     */
165
    protected function applyResponses(OA\Operation $operation, array $mergedResponses): void
166
    {
167
        if ($mergedResponses !== []) {
27✔
168
            $operation->merge(array_values($mergedResponses), true);
26✔
169
        }
170
    }
171

172
    /**
173
     * @param array<string, OA\Header> $mergedHeaders
174
     */
175
    protected function applyHeaders(OA\Operation $operation, array $mergedHeaders): void
176
    {
177
        if ($mergedHeaders && !Generator::isDefault($operation->responses)) {
27✔
178
            foreach ($operation->responses as $response) {
26✔
179
                foreach ($mergedHeaders as $header) {
26✔
180
                    if (Generator::isDefault($response->headers) || !in_array($header, $response->headers, true)) {
26✔
181
                        $response->merge([$header], true);
26✔
182
                    }
183
                }
184
            }
185
        }
186
    }
187

188
    /**
189
     * @param array<string, OAX\Middleware> $mergedMiddlewares
190
     *
191
     * @return array<string, OAX\Middleware>
192
     */
193
    protected function collectMiddlewares(mixed $attachables, array $mergedMiddlewares = []): array
194
    {
195
        if (Generator::isDefault($attachables) || !is_array($attachables)) {
27✔
196
            return $mergedMiddlewares;
26✔
197
        }
198

199
        // Last occurrence wins: child controllers override ancestors, operation overrides all.
200
        foreach ($attachables as $attachable) {
27✔
201
            if ($attachable instanceof OAX\Middleware && $attachable->names) {
27✔
202
                foreach ($attachable->names as $name) {
27✔
203
                    $mergedMiddlewares[$name] = $attachable;
27✔
204
                }
205
            }
206
        }
207

208
        return $mergedMiddlewares;
27✔
209
    }
210

211
    /**
212
     * @param array<string, OAX\Middleware> $mergedMiddlewares
213
     */
214
    protected function applyMiddlewares(OA\Operation $operation, array $mergedMiddlewares): void
215
    {
216
        if ($mergedMiddlewares === []) {
27✔
217
            return;
26✔
218
        }
219

220
        if (!Generator::isDefault($operation->attachables)) {
27✔
221
            $remaining = array_values(array_filter(
27✔
222
                $operation->attachables,
27✔
223
                fn ($a) => !($a instanceof OAX\Middleware)
27✔
224
            ));
27✔
225
            $operation->attachables = $remaining ?: Generator::UNDEFINED;
27✔
226
        }
227

228
        $operation->merge(array_values($mergedMiddlewares));
27✔
229
    }
230

231
    protected function clearMerged(Analysis $analysis, $annotations): void
232
    {
233
        if (Generator::isDefault($annotations) || !$annotations) {
27✔
234
            return;
27✔
235
        }
236

237
        $annotations = is_array($annotations) ? $annotations : [$annotations];
27✔
238

239
        foreach ($annotations as $annotation) {
27✔
240
            $analysis->annotations->offsetUnset($annotation);
27✔
241
        }
242
    }
243
}
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