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

DoclerLabs / api-client-generator / 21433352645

28 Jan 2026 09:50AM UTC coverage: 86.69% (-0.3%) from 86.973%
21433352645

push

github

web-flow
Merge pull request #131 from DoclerLabs/dependabot/composer/phpunit/phpunit-9.6.33

Bump phpunit/phpunit from 9.5.9 to 9.6.33

3465 of 3997 relevant lines covered (86.69%)

7.11 hits per line

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

88.51
/src/Command/GenerateCommand.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace DoclerLabs\ApiClientGenerator\Command;
6

7
use DoclerLabs\ApiClientGenerator\CodeGeneratorFacade;
8
use DoclerLabs\ApiClientGenerator\Generator\Security\BasicAuthenticationSecurityStrategy;
9
use DoclerLabs\ApiClientGenerator\Input\Configuration;
10
use DoclerLabs\ApiClientGenerator\Input\FileReader;
11
use DoclerLabs\ApiClientGenerator\Input\Parser;
12
use DoclerLabs\ApiClientGenerator\Input\Specification;
13
use DoclerLabs\ApiClientGenerator\MetaTemplateFacade;
14
use DoclerLabs\ApiClientGenerator\Output\Copy\Request\AuthenticationCredentials;
15
use DoclerLabs\ApiClientGenerator\Output\Copy\Serializer\ContentType\FormUrlencodedContentTypeSerializer;
16
use DoclerLabs\ApiClientGenerator\Output\Copy\Serializer\ContentType\JsonContentTypeSerializer;
17
use DoclerLabs\ApiClientGenerator\Output\Copy\Serializer\ContentType\VdnApiJsonContentTypeSerializer;
18
use DoclerLabs\ApiClientGenerator\Output\Copy\Serializer\ContentType\XmlContentTypeSerializer;
19
use DoclerLabs\ApiClientGenerator\Output\DirectoryPrinter;
20
use DoclerLabs\ApiClientGenerator\Output\Meta\MetaFile;
21
use DoclerLabs\ApiClientGenerator\Output\Meta\MetaFileCollection;
22
use DoclerLabs\ApiClientGenerator\Output\MetaFilePrinter;
23
use DoclerLabs\ApiClientGenerator\Output\Php\PhpFile;
24
use DoclerLabs\ApiClientGenerator\Output\Php\PhpFileCollection;
25
use DoclerLabs\ApiClientGenerator\Output\PhpFilePrinter;
26
use DoclerLabs\ApiClientGenerator\Output\StaticPhpFileCopier;
27
use DoclerLabs\ApiClientGenerator\Output\WarningFormatter;
28
use ReflectionClass;
29
use Symfony\Component\Console\Command\Command;
30
use Symfony\Component\Console\Input\InputInterface;
31
use Symfony\Component\Console\Output\OutputInterface;
32
use Symfony\Component\Console\Style\StyleInterface;
33
use Symfony\Component\Console\Style\SymfonyStyle;
34
use Symfony\Component\Filesystem\Filesystem;
35
use Symfony\Component\Finder\Finder;
36
use Throwable;
37

38
class GenerateCommand extends Command
39
{
40
    public function __construct(
41
        private Configuration $configuration,
42
        private FileReader $fileReader,
43
        private Parser $parser,
44
        private CodeGeneratorFacade $codeGenerator,
45
        private PhpFilePrinter $phpPrinter,
46
        private DirectoryPrinter $directoryPrinter,
47
        private MetaTemplateFacade $metaTemplate,
48
        private MetaFilePrinter $templatePrinter,
49
        private Finder $fileFinder,
50
        private StaticPhpFileCopier $staticPhpPrinter,
51
        private Filesystem $filesystem,
52
        private WarningFormatter $warningFormatter
53
    ) {
54
        parent::__construct();
2✔
55
    }
56

57
    public function configure(): void
58
    {
59
        $this->setName('generate');
2✔
60
        $this->setDescription('Generate an api client based on a given OpenApi specification');
2✔
61
        $this->addUsage(
2✔
62
            'OPENAPI={path}/swagger.yaml NAMESPACE=Group\SomeApiClient PACKAGE=dh-group/some-api-client OUTPUT_DIR={path}/generated CODE_STYLE={path}/.php-cs-fixer.php.dist ./bin/api-client-generator generate'
2✔
63
        );
2✔
64
    }
65

66
    public function execute(InputInterface $input, OutputInterface $output): int
67
    {
68
        $this->initWarningPrinting($input);
2✔
69
        $specificationFilePath = $this->configuration->specificationFilePath;
2✔
70

71
        $specification = $this->parser->parse(
2✔
72
            $this->fileReader->read($specificationFilePath),
2✔
73
            $specificationFilePath
2✔
74
        );
2✔
75

76
        $ss = new SymfonyStyle($input, $output);
2✔
77

78
        $this->backup($ss);
2✔
79

80
        try {
81
            $this->generatePhpFiles($ss, $specification);
2✔
82
            $this->copyStaticPhpFiles($ss, $specification);
2✔
83
            $this->copySpecification($ss);
2✔
84
            $this->generateMetaFiles($ss, $specification);
2✔
85
        } catch (Throwable $throwable) {
×
86
            $this->restoreBackup($ss);
×
87
            trigger_error($throwable->getMessage(), E_USER_WARNING);
×
88

89
            return Command::FAILURE;
×
90
        }
91

92
        $this->removeBackup($ss);
2✔
93

94
        return Command::SUCCESS;
2✔
95
    }
96

97
    private function generatePhpFiles(StyleInterface $ss, Specification $specification): void
98
    {
99
        $phpFiles = new PhpFileCollection();
2✔
100
        $this->codeGenerator->generate($specification, $phpFiles);
2✔
101

102
        $ss->text(sprintf('<info>AST generated for %d PHP files.</info>', $phpFiles->count()));
2✔
103
        $ss->text(sprintf('Write PHP files to %s:', $this->configuration->outputDirectory));
2✔
104

105
        $ss->progressStart($phpFiles->count());
2✔
106
        foreach ($phpFiles as $phpFile) {
2✔
107
            /** @var PhpFile $phpFile */
108
            $this->phpPrinter->print(
2✔
109
                sprintf(
2✔
110
                    '%s/%s/%s',
2✔
111
                    $this->configuration->outputDirectory,
2✔
112
                    $this->configuration->sourceDirectory,
2✔
113
                    $phpFile->fileName
2✔
114
                ),
2✔
115
                $phpFile
2✔
116
            );
2✔
117
            $ss->progressAdvance();
2✔
118
        }
119
        $ss->progressFinish();
2✔
120
    }
121

122
    private function generateMetaFiles(StyleInterface $ss, Specification $specification): void
123
    {
124
        $metaFiles = new MetaFileCollection();
2✔
125
        $this->metaTemplate->render($specification, $metaFiles);
2✔
126

127
        $ss->text(sprintf('<info>Templates rendered for %d meta files.</info>', $metaFiles->count()));
2✔
128
        $ss->text(sprintf('Write meta files to %s:', $this->configuration->outputDirectory));
2✔
129

130
        $ss->progressStart($metaFiles->count());
2✔
131
        foreach ($metaFiles as $metaFile) {
2✔
132
            /** @var MetaFile $metaFile */
133
            $this->templatePrinter->print(
2✔
134
                sprintf('%s/%s', $this->configuration->outputDirectory, $metaFile->filePath),
2✔
135
                $metaFile
2✔
136
            );
2✔
137
            $ss->progressAdvance();
2✔
138
        }
139
        $ss->progressFinish();
2✔
140
    }
141

142
    private function copyStaticPhpFiles(StyleInterface $ss, Specification $specification): void
143
    {
144
        $blacklistedFiles = $this->getBlacklistedFiles($specification);
2✔
145
        $originalFiles    = $this->fileFinder
2✔
146
            ->files()
2✔
147
            ->name('*.php')
2✔
148
            ->in(Configuration::STATIC_PHP_FILE_DIRECTORY);
2✔
149

150
        $ss->text(sprintf('<info>Collected %d static PHP files.</info>', $originalFiles->count()));
2✔
151
        $ss->text(sprintf('Copy static PHP files to %s:', $this->configuration->outputDirectory));
2✔
152

153
        $ss->progressStart($originalFiles->count());
2✔
154
        foreach ($originalFiles as $originalFile) {
2✔
155
            if (!in_array($originalFile->getBasename(), $blacklistedFiles, true)) {
2✔
156
                $destinationPath = sprintf(
2✔
157
                    '%s/%s/%s',
2✔
158
                    $this->configuration->outputDirectory,
2✔
159
                    $this->configuration->sourceDirectory,
2✔
160
                    $originalFile->getRelativePathname()
2✔
161
                );
2✔
162

163
                $this->staticPhpPrinter->copy(
2✔
164
                    $destinationPath,
2✔
165
                    $originalFile
2✔
166
                );
2✔
167
            }
168

169
            $ss->progressAdvance();
2✔
170
        }
171
        $ss->progressFinish();
2✔
172
    }
173

174
    private function copySpecification(StyleInterface $ss): void
175
    {
176
        $destinationPath = sprintf(
2✔
177
            '%s/doc/%s',
2✔
178
            $this->configuration->outputDirectory,
2✔
179
            basename($this->configuration->specificationFilePath)
2✔
180
        );
2✔
181

182
        $ss->text(sprintf('Copy specification file to %s.', $destinationPath));
2✔
183

184
        $this->filesystem->copy(
2✔
185
            $this->configuration->specificationFilePath,
2✔
186
            $destinationPath,
2✔
187
            true
2✔
188
        );
2✔
189
    }
190

191
    private function backup(StyleInterface $ss): void
192
    {
193
        $ss->text('<info>Backup original source.</info>');
2✔
194

195
        $originalPath = sprintf(
2✔
196
            '%s/%s',
2✔
197
            $this->configuration->outputDirectory,
2✔
198
            $this->configuration->sourceDirectory
2✔
199
        );
2✔
200

201
        $backupPath = $originalPath . '_old';
2✔
202

203
        $this->directoryPrinter->move(
2✔
204
            $backupPath,
2✔
205
            $originalPath
2✔
206
        );
2✔
207
    }
208

209
    private function restoreBackup(StyleInterface $ss): void
210
    {
211
        $ss->text('<error>Restore original source from backup.</error>');
×
212

213
        $originalPath = sprintf(
×
214
            '%s/%s',
×
215
            $this->configuration->outputDirectory,
×
216
            $this->configuration->sourceDirectory
×
217
        );
×
218

219
        $backupPath = $originalPath . '_old';
×
220

221
        $this->directoryPrinter->move(
×
222
            $originalPath,
×
223
            $backupPath
×
224
        );
×
225
    }
226

227
    private function removeBackup(StyleInterface $ss): void
228
    {
229
        $ss->text('<info>Delete backup.</info>');
2✔
230

231
        $backupPath = sprintf(
2✔
232
            '%s/%s_old',
2✔
233
            $this->configuration->outputDirectory,
2✔
234
            $this->configuration->sourceDirectory
2✔
235
        );
2✔
236

237
        $this->directoryPrinter->delete($backupPath);
2✔
238
    }
239

240
    private function initWarningPrinting(InputInterface $input): void
241
    {
242
        if ($input->getOption('quiet')) {
2✔
243
            set_error_handler(
2✔
244
                static function (): bool {
2✔
245
                    return true;
2✔
246
                },
2✔
247
                E_USER_WARNING
2✔
248
            );
2✔
249
        } else {
250
            set_error_handler($this->warningFormatter, E_USER_WARNING);
×
251
        }
252
    }
253

254
    private function getUnusedSerializers(Specification $specification): array
255
    {
256
        $contentTypeMapping = [
2✔
257
            XmlContentTypeSerializer::MIME_TYPE            => XmlContentTypeSerializer::class,
2✔
258
            FormUrlencodedContentTypeSerializer::MIME_TYPE => FormUrlencodedContentTypeSerializer::class,
2✔
259
            JsonContentTypeSerializer::MIME_TYPE           => JsonContentTypeSerializer::class,
2✔
260
            VdnApiJsonContentTypeSerializer::MIME_TYPE     => VdnApiJsonContentTypeSerializer::class,
2✔
261
        ];
2✔
262

263
        $allContentTypes = $specification->getAllContentTypes();
2✔
264

265
        return array_values(
2✔
266
            array_filter(
2✔
267
                $contentTypeMapping,
2✔
268
                static fn (string $key) => !in_array($key, $allContentTypes, true),
2✔
269
                ARRAY_FILTER_USE_KEY
2✔
270
            )
2✔
271
        );
2✔
272
    }
273

274
    /**
275
     * @return string[]
276
     */
277
    private function getUnusedValueObjects(Specification $specification): array
278
    {
279
        $unusedClasses = [];
2✔
280

281
        if (!$specification->isSecuritySchemeEnabled(BasicAuthenticationSecurityStrategy::SCHEME)) {
2✔
282
            $unusedClasses[] = AuthenticationCredentials::class;
×
283
        }
284

285
        return $unusedClasses;
2✔
286
    }
287

288
    /**
289
     * @return string[]
290
     */
291
    private function getBlacklistedFiles(Specification $specification): array
292
    {
293
        return array_map(
2✔
294
            static fn ($class) => basename((string)(new ReflectionClass($class))->getFileName()),
2✔
295
            array_merge(
2✔
296
                $this->getUnusedSerializers($specification),
2✔
297
                $this->getUnusedValueObjects($specification)
2✔
298
            )
2✔
299
        );
2✔
300
    }
301
}
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