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

DoclerLabs / api-client-generator / 7259428899

19 Dec 2023 08:47AM UTC coverage: 89.733% (-0.3%) from 90.043%
7259428899

push

github

web-flow
Merge pull request #102 from DoclerLabs/generator-php-8

generator php 8

355 of 381 new or added lines in 40 files covered. (93.18%)

6 existing lines in 4 files now uncovered.

2657 of 2961 relevant lines covered (89.73%)

3.35 hits per line

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

88.62
/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();
1✔
55
    }
1✔
56

57
    public function configure(): void
58
    {
59
        $this->setName('generate');
1✔
60
        $this->setDescription('Generate an api client based on a given OpenApi specification');
1✔
61
        $this->addUsage(
1✔
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'
1✔
63
        );
64
    }
1✔
65

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

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

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

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

80
        try {
81
            $this->generatePhpFiles($ss, $specification);
1✔
82
            $this->copyStaticPhpFiles($ss, $specification);
1✔
83
            $this->copySpecification($ss);
1✔
84
            $this->generateMetaFiles($ss, $specification);
1✔
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);
1✔
93

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

203
        $this->directoryPrinter->move(
1✔
204
            $backupPath,
205
            $originalPath
206
        );
207
    }
1✔
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',
×
NEW
215
            $this->configuration->outputDirectory,
×
NEW
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>');
1✔
230

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

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

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

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

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

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

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

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

285
        return $unusedClasses;
1✔
286
    }
287

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

© 2025 Coveralls, Inc