BaseGenerateCommand::generateCode()   D
last analyzed

Complexity

Conditions 16
Paths 257

Size

Total Lines 89
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 272

Importance

Changes 0
Metric Value
eloc 63
c 0
b 0
f 0
dl 0
loc 89
ccs 0
cts 67
cp 0
rs 4.0208
cc 16
nc 257
nop 3
crap 272

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Gii\Command;
6
7
use Symfony\Component\Console\Command\Command;
8
use Symfony\Component\Console\Helper\QuestionHelper;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Symfony\Component\Console\Question\ChoiceQuestion;
13
use Symfony\Component\Console\Question\ConfirmationQuestion;
14
use Yiisoft\Validator\Result;
15
use Yiisoft\Yii\Console\ExitCode;
16
use Yiisoft\Yii\Gii\Component\CodeFile\CodeFile;
17
use Yiisoft\Yii\Gii\Component\CodeFile\CodeFileStateEnum;
18
use Yiisoft\Yii\Gii\Component\CodeFile\CodeFileWriteOperationEnum;
19
use Yiisoft\Yii\Gii\Component\CodeFile\CodeFileWriter;
20
use Yiisoft\Yii\Gii\Component\CodeFile\CodeFileWriteStatusEnum;
21
use Yiisoft\Yii\Gii\Exception\InvalidGeneratorCommandException;
22
use Yiisoft\Yii\Gii\GeneratorCommandInterface;
23
use Yiisoft\Yii\Gii\GeneratorInterface;
24
use Yiisoft\Yii\Gii\GiiInterface;
25
26
use function count;
27
28
abstract class BaseGenerateCommand extends Command
29
{
30
    public function __construct(
31
        protected GiiInterface $gii,
32
        protected CodeFileWriter $codeFileWriter,
33
    ) {
34
        parent::__construct();
35
    }
36
37
    protected function configure(): void
38
    {
39
        $this->addOption('overwrite', 'o', InputArgument::OPTIONAL, '')
40
            ->addOption('template', 't', InputArgument::OPTIONAL, '');
41
    }
42
43
    protected function execute(InputInterface $input, OutputInterface $output): int
44
    {
45
        $generator = $this->getGenerator();
46
        $generatorCommand = $this->createGeneratorCommand($input);
47
48
        $output->writeln("Running '{$generator->getName()}'...\n");
49
        try {
50
            $files = $generator->generate($generatorCommand);
51
        } catch (InvalidGeneratorCommandException $e) {
52
            $this->displayValidationErrors($e->getResult(), $output);
53
            return ExitCode::UNSPECIFIED_ERROR;
54
        }
55
        $this->generateCode($files, $input, $output);
56
        return ExitCode::OK;
57
    }
58
59
    abstract protected function getGenerator(): GeneratorInterface;
60
61
    protected function displayValidationErrors(Result $result, OutputInterface $output): void
62
    {
63
        $output->writeln("<fg=red>Code not generated. Please fix the following errors:</>\n");
64
        foreach ($result->getErrorMessages() as $attribute => $errorMessage) {
65
            $output->writeln(sprintf(' - <fg=cyan>%s</>: <fg=green>%s</>', $attribute, $errorMessage));
66
        }
67
        $output->writeln('');
68
    }
69
70
    /**
71
     * @param CodeFile[] $files
72
     */
73
    protected function generateCode(
74
        array $files,
75
        InputInterface $input,
76
        OutputInterface $output
77
    ): void {
78
        if (count($files) === 0) {
79
            $output->writeln('<fg=cyan>No code to be generated.</>');
80
            return;
81
        }
82
        $output->writeln("<fg=magenta>The following files will be generated</>:\n");
83
        $skipAll = $input->isInteractive() ? null : !$input->getArgument('overwrite');
84
        $answers = [];
85
        foreach ($files as $file) {
86
            $path = $file->getRelativePath();
87
            $color = match ($file->getState()) {
88
                CodeFileStateEnum::PRESENT_SAME => 'yellow',
89
                CodeFileStateEnum::PRESENT_DIFFERENT => 'blue',
90
                CodeFileStateEnum::NOT_EXIST => 'green',
91
                default => 'red',
92
            };
93
94
            if ($file->getState() === CodeFileStateEnum::NOT_EXIST) {
95
                $output->writeln("    <fg=$color>[new]</>       <fg=blue>$path</>");
96
                $answers[$file->getId()] = CodeFileWriteOperationEnum::SAVE->value;
97
            } elseif ($file->getState() === CodeFileStateEnum::PRESENT_SAME) {
98
                $output->writeln("    <fg=$color>[unchanged]</> <fg=blue>$path</>");
99
                $answers[$file->getId()] = CodeFileWriteOperationEnum::SKIP->value;
100
            } else {
101
                $output->writeln("    <fg=$color>[changed]</>   <fg=blue>$path</>");
102
                if ($skipAll !== null) {
103
                    $answers[$file->getId()] = CodeFileWriteOperationEnum::SAVE->value;
104
                } else {
105
                    $answer = $this->choice($input, $output);
106
                    $answers[$file->getId()] = ($answer === 'y' || $answer === 'ya')
107
                        ? CodeFileWriteOperationEnum::SAVE->value
108
                        : CodeFileWriteOperationEnum::SKIP->value;
109
                    if ($answer === 'ya') {
110
                        $skipAll = false;
111
                    } elseif ($answer === 'na') {
112
                        $skipAll = true;
113
                    }
114
                }
115
            }
116
        }
117
118
        if ($this->areAllFilesSkipped($answers)) {
119
            $output->writeln("\n<fg=cyan>No files were found to be generated.</>");
120
            return;
121
        }
122
123
        if (!$this->confirm($input, $output)) {
124
            $output->writeln("\n<fg=cyan>No file was generated.</>");
125
            return;
126
        }
127
128
        $result = $this->codeFileWriter->write($files, $answers);
129
130
        $hasError = false;
131
        foreach ($result->getResults() as $fileId => $result) {
132
            $file = $files[$fileId];
133
            $color = match ($result['status']) {
134
                CodeFileWriteStatusEnum::CREATED->value => 'green',
135
                CodeFileWriteStatusEnum::OVERWROTE->value => 'blue',
136
                CodeFileWriteStatusEnum::ERROR->value => 'red',
137
                default => 'yellow',
138
            };
139
            $output->writeln(
140
                sprintf(
141
                    '<fg=%s>%s</>: %s',
142
                    $color,
143
                    $result['status'],
144
                    $file->getRelativePath(),
145
                )
146
            );
147
            if (CodeFileWriteStatusEnum::ERROR->value === $result['status']) {
148
                $hasError = true;
149
                $output->writeln(
150
                    sprintf(
151
                        '<fg=red>%s</>',
152
                        $result['error']
153
                    )
154
                );
155
            }
156
        }
157
158
        if ($hasError) {
159
            $output->writeln("\n<fg=red>Some errors occurred while generating the files.</>");
160
        } else {
161
            $output->writeln("\n<fg=green>Files were generated successfully!</>");
162
        }
163
    }
164
165
    abstract protected function createGeneratorCommand(InputInterface $input): GeneratorCommandInterface;
166
167
    /**
168
     * @return bool|mixed|string|null
169
     */
170
    protected function confirm(InputInterface $input, OutputInterface $output)
171
    {
172
        $question = new ConfirmationQuestion("\nReady to generate the selected files? (yes|no) [yes]:", true);
173
        /**
174
         * @var QuestionHelper $helper
175
         */
176
        $helper = $this->getHelper('question');
177
        return $helper->ask($input, $output, $question);
178
    }
179
180
    /**
181
     * @return bool|mixed|string|null
182
     */
183
    protected function choice(InputInterface $input, OutputInterface $output)
184
    {
185
        $question = new ChoiceQuestion(
186
            "\nDo you want to overwrite this file?",
187
            [
188
                'y' => 'Overwrite this file.',
189
                'n' => 'Skip this file.',
190
                'ya' => 'Overwrite this and the rest of the changed files.',
191
                'na' => 'Skip this and the rest of the changed files.',
192
            ]
193
        );
194
        /**
195
         * @var QuestionHelper $helper
196
         */
197
        $helper = $this->getHelper('question');
198
        return $helper->ask($input, $output, $question);
199
    }
200
201
    private function areAllFilesSkipped(array $answers): bool
202
    {
203
        return [] === array_filter(
204
            $answers,
205
            fn ($answer) => CodeFileWriteOperationEnum::from($answer) !== CodeFileWriteOperationEnum::SKIP
206
        );
207
    }
208
}
209