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

DarkaOnLine / L5-Swagger / 20711689680

05 Jan 2026 09:57AM UTC coverage: 99.381%. Remained the same
20711689680

push

github

DarkaOnLine
make phpStan happy

321 of 323 relevant lines covered (99.38%)

5.23 hits per line

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

98.85
/src/Generator.php
1
<?php
2

3
namespace L5Swagger;
4

5
use Exception;
6
use Illuminate\Contracts\Filesystem\FileNotFoundException;
7
use Illuminate\Filesystem\Filesystem;
8
use Illuminate\Support\Arr;
9
use L5Swagger\Exceptions\L5SwaggerException;
10
use OpenApi\Annotations\OpenApi;
11
use OpenApi\Annotations\Server;
12
use OpenApi\Generator as OpenApiGenerator;
13
use OpenApi\OpenApiException;
14
use OpenApi\Pipeline;
15
use OpenApi\Util;
16
use Symfony\Component\Finder\Finder;
17
use Symfony\Component\Yaml\Dumper as YamlDumper;
18
use Symfony\Component\Yaml\Yaml;
19

20
class Generator
21
{
22
    public const OPEN_API_DEFAULT_SPEC_VERSION = '3.0.0';
23

24
    protected const SCAN_OPTION_PROCESSORS = 'processors';
25
    protected const SCAN_OPTION_PATTERN = 'pattern';
26
    protected const SCAN_OPTION_ANALYSER = 'analyser';
27
    protected const SCAN_OPTION_ANALYSIS = 'analysis';
28
    protected const SCAN_OPTION_EXCLUDE = 'exclude';
29

30
    protected const AVAILABLE_SCAN_OPTIONS = [
31
        self::SCAN_OPTION_PATTERN,
32
        self::SCAN_OPTION_ANALYSER,
33
        self::SCAN_OPTION_ANALYSIS,
34
        self::SCAN_OPTION_EXCLUDE,
35
    ];
36

37
    /**
38
     * @var string|array<string>
39
     */
40
    protected string|array $annotationsDir;
41

42
    protected string $docDir;
43

44
    protected string $docsFile;
45

46
    protected string $yamlDocsFile;
47

48
    /**
49
     * @var array<string>
50
     */
51
    protected array $excludedDirs;
52

53
    /**
54
     * @var array<string>
55
     */
56
    protected array $constants;
57

58
    protected ?OpenApi $openApi;
59

60
    protected bool $yamlCopyRequired;
61

62
    protected ?string $basePath;
63

64
    protected SecurityDefinitions $security;
65

66
    /**
67
     * @var array<string,mixed>
68
     */
69
    protected array $scanOptions;
70

71
    protected Filesystem $fileSystem;
72

73
    /**
74
     * Constructor to initialize documentation generation settings and dependencies.
75
     *
76
     * @param  array<string,mixed>  $paths  Array of paths including annotations, docs, excluded directories, and base path.
77
     * @param  array<string>  $constants  Array of constants to be used during documentation generation.
78
     * @param  bool  $yamlCopyRequired  Determines if a YAML copy of the documentation is required.
79
     * @param  SecurityDefinitions  $security  Security definitions for the documentation.
80
     * @param  array<string>  $scanOptions  Additional options for scanning files or directories.
81
     * @param  Filesystem|null  $filesystem  Filesystem instance, optional, defaults to a new Filesystem.
82
     * @return void
83
     */
84
    public function __construct(
85
        array $paths,
86
        array $constants,
87
        bool $yamlCopyRequired,
88
        SecurityDefinitions $security,
89
        array $scanOptions,
90
        ?Filesystem $filesystem = null
91
    ) {
92
        $this->annotationsDir = $paths['annotations'];
10✔
93
        $this->docDir = $paths['docs'];
10✔
94
        $this->docsFile = $this->docDir.DIRECTORY_SEPARATOR.($paths['docs_json'] ?? 'api-docs.json');
10✔
95
        $this->yamlDocsFile = $this->docDir.DIRECTORY_SEPARATOR.($paths['docs_yaml'] ?? 'api-docs.yaml');
10✔
96
        $this->excludedDirs = $paths['excludes'];
10✔
97
        $this->basePath = $paths['base'];
10✔
98
        $this->constants = $constants;
10✔
99
        $this->yamlCopyRequired = $yamlCopyRequired;
10✔
100
        $this->security = $security;
10✔
101
        $this->scanOptions = $scanOptions;
10✔
102

103
        $this->fileSystem = $filesystem ?? new Filesystem();
10✔
104
    }
105

106
    /**
107
     * Generate necessary documentation files by scanning and processing the required data.
108
     *
109
     * @return void
110
     *
111
     * @throws L5SwaggerException
112
     * @throws Exception
113
     */
114
    public function generateDocs(): void
115
    {
116
        $this->prepareDirectory()
10✔
117
            ->defineConstants()
10✔
118
            ->scanFilesForDocumentation()
10✔
119
            ->populateServers()
10✔
120
            ->saveJson()
10✔
121
            ->makeYamlCopy();
10✔
122
    }
123

124
    /**
125
     * Prepares the directory for storing documentation by ensuring it exists and is writable.
126
     *
127
     * @return self
128
     *
129
     * @throws L5SwaggerException If the directory is not writable or cannot be created.
130
     */
131
    protected function prepareDirectory(): self
132
    {
133
        if ($this->fileSystem->exists($this->docDir) && ! $this->fileSystem->isWritable($this->docDir)) {
10✔
134
            throw new L5SwaggerException('Documentation storage directory is not writable');
1✔
135
        }
136

137
        if (! $this->fileSystem->exists($this->docDir)) {
9✔
138
            $this->fileSystem->makeDirectory($this->docDir);
9✔
139
        }
140

141
        if (! $this->fileSystem->exists($this->docDir)) {
9✔
142
            throw new L5SwaggerException('Documentation storage directory could not be created');
1✔
143
        }
144

145
        return $this;
8✔
146
    }
147

148
    /**
149
     * Define and set constants if not already defined.
150
     *
151
     * @return self
152
     */
153
    protected function defineConstants(): self
154
    {
155
        if (! empty($this->constants)) {
8✔
156
            foreach ($this->constants as $key => $value) {
8✔
157
                defined($key) || define($key, $value);
8✔
158
            }
159
        }
160

161
        return $this;
8✔
162
    }
163

164
    /**
165
     * Scans files to generate documentation.
166
     *
167
     * @return self
168
     */
169
    protected function scanFilesForDocumentation(): self
170
    {
171
        $generator = $this->createOpenApiGenerator();
8✔
172
        $finder = $this->createScanFinder();
8✔
173

174
        // Analysis.
175
        $analysis = Arr::get($this->scanOptions, self::SCAN_OPTION_ANALYSIS);
8✔
176

177
        $this->openApi = $generator->generate($finder, $analysis);
8✔
178

179
        return $this;
8✔
180
    }
181

182
    /**
183
     * Create and configure an instance of OpenApiGenerator.
184
     *
185
     * @return OpenApiGenerator
186
     */
187
    protected function createOpenApiGenerator(): OpenApiGenerator
188
    {
189
        $generator = new OpenApiGenerator();
8✔
190

191
        if (! empty($this->scanOptions['default_processors_configuration'])
8✔
192
            && is_array($this->scanOptions['default_processors_configuration'])
8✔
193
        ) {
194
            $generator->setConfig($this->scanOptions['default_processors_configuration']);
1✔
195
        }
196

197
        $generator->setVersion(
8✔
198
            $this->scanOptions['open_api_spec_version'] ?? self::OPEN_API_DEFAULT_SPEC_VERSION
8✔
199
        );
8✔
200

201
        // Processors.
202
        $this->setProcessors($generator);
8✔
203

204
        // Analyser.
205
        $this->setAnalyser($generator);
8✔
206

207
        return $generator;
8✔
208
    }
209

210
    /**
211
     * Set the processors for the OpenAPI generator.
212
     *
213
     * @param  OpenApiGenerator  $generator  The OpenAPI generator instance to configure.
214
     * @return void
215
     */
216
    protected function setProcessors(OpenApiGenerator $generator): void
217
    {
218
        $processorClasses = Arr::get($this->scanOptions, self::SCAN_OPTION_PROCESSORS, []);
8✔
219
        $newPipeLine = [];
8✔
220

221
        $generator->getProcessorPipeline()->walk(
8✔
222
            function (callable $pipe) use ($processorClasses, &$newPipeLine) {
8✔
223
                $newPipeLine[] = $pipe;
8✔
224
                if ($pipe instanceof \OpenApi\Processors\BuildPaths) {
8✔
225
                    foreach ($processorClasses as $customProcessor) {
8✔
226
                        $newPipeLine[] = new $customProcessor();
1✔
227
                    }
228
                }
229
            }
8✔
230
        );
8✔
231

232
        if (! empty($newPipeLine)) {
8✔
233
            $generator->setProcessorPipeline(new Pipeline($newPipeLine));
8✔
234
        }
235
    }
236

237
    /**
238
     * Set the analyser for the OpenAPI generator based on scan options.
239
     *
240
     * @param  OpenApiGenerator  $generator  The OpenAPI generator instance.
241
     * @return void
242
     */
243
    protected function setAnalyser(OpenApiGenerator $generator): void
244
    {
245
        $analyser = Arr::get($this->scanOptions, self::SCAN_OPTION_ANALYSER);
8✔
246

247
        if (! empty($analyser)) {
8✔
248
            $generator->setAnalyser($analyser);
1✔
249

250
            return;
1✔
251
        }
252

253
        // Use AttributeAnnotationFactory for PHP 8.1+ native attributes
254
        $generator->setAnalyser(new \OpenApi\Analysers\ReflectionAnalyser([
7✔
255
            new \OpenApi\Analysers\AttributeAnnotationFactory(),
7✔
256
        ]));
7✔
257
    }
258

259
    /**
260
     * Create and return a Finder instance configured for scanning directories.
261
     *
262
     * @return Finder
263
     */
264
    protected function createScanFinder(): Finder
265
    {
266
        $pattern = Arr::get($this->scanOptions, self::SCAN_OPTION_PATTERN);
8✔
267
        $exclude = Arr::get($this->scanOptions, self::SCAN_OPTION_EXCLUDE);
8✔
268

269
        $exclude = ! empty($exclude) ? $exclude : $this->excludedDirs;
8✔
270

271
        return Util::finder($this->annotationsDir, $exclude, $pattern);
8✔
272
    }
273

274
    /**
275
     * Populate the servers list in the OpenAPI configuration using the base path.
276
     *
277
     * @return self
278
     */
279
    protected function populateServers(): self
280
    {
281
        if ($this->basePath !== null && $this->openApi !== null) {
8✔
282
            if (
283
                $this->openApi->servers === OpenApiGenerator::UNDEFINED // @phpstan-ignore-line
1✔
284
                || is_array($this->openApi->servers) === false // @phpstan-ignore-line
1✔
285
            ) {
286
                $this->openApi->servers = [];
×
287
            }
288

289
            $this->openApi->servers[] = new Server(['url' => $this->basePath]);
1✔
290
        }
291

292
        return $this;
8✔
293
    }
294

295
    /**
296
     * Saves the JSON data and applies security measures to the file.
297
     *
298
     * @return self
299
     *
300
     * @throws FileNotFoundException
301
     * @throws OpenApiException
302
     */
303
    protected function saveJson(): self
304
    {
305
        if ($this->openApi !== null) {
8✔
306
            $this->openApi->saveAs($this->docsFile);
8✔
307
        }
308

309
        $this->security->generate($this->docsFile);
8✔
310

311
        return $this;
8✔
312
    }
313

314
    /**
315
     * Creates a YAML copy of the OpenAPI documentation if required.
316
     *
317
     * This method converts the JSON documentation file to YAML format and saves it
318
     * to the specified file path when the YAML copy requirement is enabled.
319
     *
320
     * @return void
321
     *
322
     * @throws FileNotFoundException
323
     */
324
    protected function makeYamlCopy(): void
325
    {
326
        if ($this->yamlCopyRequired) {
8✔
327
            $yamlDocs = (new YamlDumper(2))->dump(
8✔
328
                json_decode($this->fileSystem->get($this->docsFile), true),
8✔
329
                20,
8✔
330
                0,
8✔
331
                Yaml::DUMP_OBJECT_AS_MAP ^ Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE
8✔
332
            );
8✔
333

334
            $this->fileSystem->put(
8✔
335
                $this->yamlDocsFile,
8✔
336
                $yamlDocs
8✔
337
            );
8✔
338
        }
339
    }
340
}
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