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

DarkaOnLine / L5-Swagger / 13582472448

28 Feb 2025 06:14AM UTC coverage: 99.381% (-0.3%) from 99.688%
13582472448

Pull #639

github

StyleCIBot
Apply fixes from StyleCI
Pull Request #639: Fix possible operator not supported for strings error

2 of 3 new or added lines in 1 file covered. (66.67%)

321 of 323 relevant lines covered (99.38%)

5.22 hits per line

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

98.8
/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
    }
251

252
    /**
253
     * Create and return a Finder instance configured for scanning directories.
254
     *
255
     * @return Finder
256
     */
257
    protected function createScanFinder(): Finder
258
    {
259
        $pattern = Arr::get($this->scanOptions, self::SCAN_OPTION_PATTERN);
8✔
260
        $exclude = Arr::get($this->scanOptions, self::SCAN_OPTION_EXCLUDE);
8✔
261

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

264
        return Util::finder($this->annotationsDir, $exclude, $pattern);
8✔
265
    }
266

267
    /**
268
     * Populate the servers list in the OpenAPI configuration using the base path.
269
     *
270
     * @return self
271
     */
272
    protected function populateServers(): self
273
    {
274
        if ($this->basePath !== null && $this->openApi !== null) {
8✔
275
            if (
276
                $this->openApi->servers === OpenApiGenerator::UNDEFINED // @phpstan-ignore-line
1✔
277
                || is_array($this->openApi->servers) === false // @phpstan-ignore-line
1✔
278
            ) {
NEW
279
                $this->openApi->servers = [];
×
280
            }
281

282
            $this->openApi->servers[] = new Server(['url' => $this->basePath]);
1✔
283
        }
284

285
        return $this;
8✔
286
    }
287

288
    /**
289
     * Saves the JSON data and applies security measures to the file.
290
     *
291
     * @return self
292
     *
293
     * @throws FileNotFoundException
294
     * @throws OpenApiException
295
     */
296
    protected function saveJson(): self
297
    {
298
        if ($this->openApi !== null) {
8✔
299
            $this->openApi->saveAs($this->docsFile);
8✔
300
        }
301

302
        $this->security->generate($this->docsFile);
8✔
303

304
        return $this;
8✔
305
    }
306

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

327
            $this->fileSystem->put(
8✔
328
                $this->yamlDocsFile,
8✔
329
                $yamlDocs
8✔
330
            );
8✔
331
        }
332
    }
333
}
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