Passed
Pull Request — master (#58)
by Alexander
05:15 queued 02:40
created

AbstractGenerator   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Test Coverage

Coverage 60.47%

Importance

Changes 8
Bugs 2 Features 0
Metric Value
eloc 120
c 8
b 2
f 0
dl 0
loc 235
ccs 26
cts 43
cp 0.6047
rs 10
wmc 16

9 Methods

Rating   Name   Duplication   Size   Complexity  
A defaultTemplate() 0 5 1
A validateClass() 0 8 2
A generate() 0 9 2
A getDirectory() 0 3 1
B isReservedKeyword() 0 86 1
A render() 0 28 4
A getTemplatePath() 0 13 3
A requiredTemplates() 0 3 1
A __construct() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Gii\Generator;
6
7
use ReflectionClass;
8
use ReflectionException;
9
use Throwable;
10
use Yiisoft\Aliases\Aliases;
11
use Yiisoft\Validator\Result;
12
use Yiisoft\Validator\ValidatorInterface;
13
use Yiisoft\Yii\Gii\CodeFile;
14
use Yiisoft\Yii\Gii\Exception\InvalidConfigException;
15
use Yiisoft\Yii\Gii\Exception\InvalidGeneratorCommandException;
16
use Yiisoft\Yii\Gii\GeneratorInterface;
17
use Yiisoft\Yii\Gii\GiiParametersProvider;
18
19
/**
20
 * This is the base class for all generator classes.
21
 *
22
 * A generator instance is responsible for taking user inputs, validating them,
23
 * and using them to generate the corresponding code based on a set of code template files.
24
 *
25
 * A generator class typically needs to implement the following methods:
26
 *
27
 * - {@see GeneratorInterface::getName()}: returns the name of the generator
28
 * - {@see GeneratorInterface::getDescription()}: returns the detailed description of the generator
29
 * - {@see GeneratorInterface::generate()}: generates the code based on the current user input and the specified code
30
 * template files. This is the place where main code generation code resides.
31
 */
32
abstract class AbstractGenerator implements GeneratorInterface
33
{
34
    private string $directory = 'src/Controller';
35
36 3
    public function __construct(
37
        protected Aliases $aliases,
38
        protected ValidatorInterface $validator,
39
        protected GiiParametersProvider $parametersProvider,
40
    ) {
41
    }
42
43
    /**
44
     * Returns a list of code template files that are required.
45
     * Derived classes usually should override this method if they require the existence of
46
     * certain template files.
47
     *
48
     * @return array list of code template files that are required. They should be file paths
49
     * relative to {@see getTemplatePath()}.
50
     */
51
    public function requiredTemplates(): array
52
    {
53
        return [];
54
    }
55
56
    /**
57
     * Returns the root path to the default code template files.
58
     * The default implementation will return the "templates" subdirectory of the
59
     * directory containing the generator class file.
60
     *
61
     * @throws ReflectionException
62
     *
63
     * @return string the root path to the default code template files.
64
     */
65 1
    private function defaultTemplate(): string
66
    {
67 1
        $class = new ReflectionClass($this);
68
69 1
        return dirname($class->getFileName()) . '/default';
70
    }
71
72
    /**
73
     * @throws InvalidConfigException
74
     * @throws ReflectionException
75
     *
76
     * @return string the root path of the template files that are currently being used.
77
     */
78 1
    public function getTemplatePath(AbstractGeneratorCommand $command): string
79
    {
80 1
        $template = $command->getTemplate();
81
82 1
        if ($template === 'default') {
83 1
            return $this->defaultTemplate();
84
        }
85
86
        if (isset($this->parametersProvider->getTemplates()[$template])) {
87
            return $this->parametersProvider->getTemplates()[$template];
88
        }
89
90
        throw new InvalidConfigException("Unknown template: {$template}");
91
    }
92
93
    /**
94
     * @param AbstractGeneratorCommand $command
95
     *
96
     * @throws InvalidGeneratorCommandException
97
     *
98
     * @return array|CodeFile
99
     */
100 2
    final public function generate(AbstractGeneratorCommand $command): array
101
    {
102 2
        $result = $this->validator->validate($command);
103
104 2
        if (!$result->isValid()) {
105 1
            throw new InvalidGeneratorCommandException($result);
106
        }
107
108 1
        return $this->doGenerate($command);
109
    }
110
111
    abstract protected function doGenerate(AbstractGeneratorCommand $command): array;
112
113
    /**
114
     * Generates code using the specified code template and parameters.
115
     * Note that the code template will be used as a PHP file.
116
     *
117
     * @param string $template the code template file. This must be specified as a file path
118
     * relative to {@see getTemplatePath()}.
119
     * @param array $params list of parameters to be passed to the template file.
120
     *
121
     * @throws Throwable
122
     *
123
     * @return string the generated code
124
     */
125 1
    protected function render(AbstractGeneratorCommand $command, string $template, array $params = []): string
126
    {
127 1
        $file = sprintf(
128
            '%s/%s.php',
129 1
            $this->aliases->get($this->getTemplatePath($command)),
130
            $template
131
        );
132
133 1
        $renderer = function (): void {
134 1
            extract(func_get_arg(1));
135
            /** @psalm-suppress UnresolvableInclude */
136 1
            require func_get_arg(0);
137
        };
138
139 1
        $obInitialLevel = ob_get_level();
140 1
        ob_start();
141 1
        ob_implicit_flush(false);
142
        try {
143
            /** @psalm-suppress PossiblyInvalidFunctionCall */
144 1
            $renderer->bindTo($this)($file, array_merge($params, ['command' => $command]));
145 1
            return ob_get_clean();
146
        } catch (Throwable $e) {
147
            while (ob_get_level() > $obInitialLevel) {
148
                if (!@ob_end_clean()) {
149
                    ob_clean();
150
                }
151
            }
152
            throw $e;
153
        }
154
    }
155
156
    /**
157
     * An inline validator that checks if the attribute value refers to an existing class name.
158
     *
159
     * @param string $value the attribute being validated
160
     */
161
    public function validateClass(string $value): Result
162
    {
163
        $result = new Result();
164
        if (!class_exists($value)) {
165
            $result->addError("Class '$value' does not exist or has syntax error.");
166
        }
167
168
        return $result;
169
    }
170
171
    /**
172
     * @param string $value the attribute to be validated
173
     *
174
     * @return bool whether the value is a reserved PHP keyword.
175
     */
176
    public function isReservedKeyword(string $value): bool
177
    {
178
        static $keywords = [
179
            '__class__',
180
            '__dir__',
181
            '__file__',
182
            '__function__',
183
            '__line__',
184
            '__method__',
185
            '__namespace__',
186
            '__trait__',
187
            'abstract',
188
            'and',
189
            'array',
190
            'as',
191
            'break',
192
            'case',
193
            'catch',
194
            'callable',
195
            'cfunction',
196
            'class',
197
            'clone',
198
            'const',
199
            'continue',
200
            'declare',
201
            'default',
202
            'die',
203
            'do',
204
            'echo',
205
            'else',
206
            'elseif',
207
            'empty',
208
            'enddeclare',
209
            'endfor',
210
            'endforeach',
211
            'endif',
212
            'endswitch',
213
            'endwhile',
214
            'eval',
215
            'exception',
216
            'exit',
217
            'extends',
218
            'final',
219
            'finally',
220
            'for',
221
            'foreach',
222
            'function',
223
            'global',
224
            'goto',
225
            'if',
226
            'implements',
227
            'include',
228
            'include_once',
229
            'instanceof',
230
            'insteadof',
231
            'interface',
232
            'isset',
233
            'list',
234
            'namespace',
235
            'new',
236
            'old_function',
237
            'or',
238
            'parent',
239
            'php_user_filter',
240
            'print',
241
            'private',
242
            'protected',
243
            'public',
244
            'require',
245
            'require_once',
246
            'return',
247
            'static',
248
            'switch',
249
            'this',
250
            'throw',
251
            'trait',
252
            'try',
253
            'unset',
254
            'use',
255
            'var',
256
            'while',
257
            'xor',
258
            'fn',
259
        ];
260
261
        return in_array(strtolower($value), $keywords, true);
262
    }
263
264 1
    public function getDirectory(): string
265
    {
266 1
        return $this->directory;
267
    }
268
}
269