Passed
Pull Request — master (#58)
by Dmitriy
14:22 queued 02:13
created

AbstractGenerator::isReservedKeyword()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 86
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 82
nc 1
nop 1
dl 0
loc 86
ccs 0
cts 2
cp 0
c 0
b 0
f 0
cc 1
crap 2
rs 8.3927

How to fix   Long Method   

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\Generator;
6
7
use Exception;
8
use InvalidArgumentException;
9
use ReflectionClass;
10
use ReflectionException;
11
use RuntimeException;
12
use Throwable;
13
use Yiisoft\Aliases\Aliases;
14
use Yiisoft\Json\Json;
15
use Yiisoft\Validator\DataSetInterface;
16
use Yiisoft\Validator\Result;
17
use Yiisoft\Validator\Rule\Required;
18
use Yiisoft\Validator\ValidatorInterface;
19
use Yiisoft\Yii\Gii\CodeFile;
20
use Yiisoft\Yii\Gii\Exception\InvalidConfigException;
21
use Yiisoft\Yii\Gii\GeneratorInterface;
22
use Yiisoft\Yii\Gii\GiiParametersProvider;
23
24
/**
25
 * This is the base class for all generator classes.
26
 *
27
 * A generator instance is responsible for taking user inputs, validating them,
28
 * and using them to generate the corresponding code based on a set of code template files.
29
 *
30
 * A generator class typically needs to implement the following methods:
31
 *
32
 * - {@see GeneratorInterface::getName()}: returns the name of the generator
33
 * - {@see GeneratorInterface::getDescription()}: returns the detailed description of the generator
34
 * - {@see GeneratorInterface::validate()}: returns generator validation result
35
 * - {@see GeneratorInterface::generate()}: generates the code based on the current user input and the specified code
36
 * template files. This is the place where main code generation code resides.
37
 */
38
abstract class AbstractGenerator implements GeneratorInterface, DataSetInterface
39
{
40
    private array $errors = [];
41
42
    /**
43
     * @var string the name of the code template that the user has selected.
44
     * The value of this property is internally managed by this class.
45
     */
46
    private string $template = 'default';
47
    private string $directory = 'src/Controller';
48
49 5
    public function __construct(
50
        protected Aliases $aliases,
51
        protected ValidatorInterface $validator,
52
        protected         GiiParametersProvider $parametersProvider,
53
    ) {
54
    }
55
56
    public function attributeLabels(): array
57
    {
58
        return [
59
            'enableI18N' => 'Enable I18N',
60
            'messageCategory' => 'Message Category',
61
        ];
62
    }
63
64
    /**
65
     * Returns a list of code template files that are required.
66
     * Derived classes usually should override this method if they require the existence of
67
     * certain template files.
68
     *
69
     * @return array list of code template files that are required. They should be file paths
70
     * relative to {@see getTemplatePath()}.
71
     */
72
    public function requiredTemplates(): array
73
    {
74
        return [];
75
    }
76
77
    /**
78
     * Returns the list of sticky attributes.
79
     * A sticky attribute will remember its value and will initialize the attribute with this value
80
     * when the generator is restarted.
81
     *
82
     * @return array list of sticky attributes
83
     */
84
    public function stickyAttributes(): array
85
    {
86
        return ['template', 'enableI18N', 'messageCategory'];
87
    }
88
89
    /**
90
     * Returns the list of hint messages.
91
     * The array keys are the attribute names, and the array values are the corresponding hint messages.
92
     * Hint messages will be displayed to end users when they are filling the form for the generator.
93
     *
94
     * @return array the list of hint messages
95
     */
96
    public function hints(): array
97
    {
98
        return [
99
            'enableI18N' => 'This indicates whether the generator should generate strings using <code>Yii::t()</code> method.
100
                Set this to <code>true</code> if you are planning to make your application translatable.',
101
            'messageCategory' => 'This is the category used by <code>Yii::t()</code> in case you enable I18N.',
102
        ];
103
    }
104
105
    /**
106
     * Returns the list of auto complete values.
107
     * The array keys are the attribute names, and the array values are the corresponding auto complete values.
108
     * Auto complete values can also be callable typed in order one want to make postponed data generation.
109
     *
110
     * @return array the list of auto complete values
111
     */
112
    public function autoCompleteData(): array
113
    {
114
        return [];
115
    }
116
117
    /**
118
     * Returns the root path to the default code template files.
119
     * The default implementation will return the "templates" subdirectory of the
120
     * directory containing the generator class file.
121
     *
122
     * @throws ReflectionException
123
     *
124
     * @return string the root path to the default code template files.
125
     */
126
    private function defaultTemplate(): string
127
    {
128
        $class = new ReflectionClass($this);
129
130
        return dirname($class->getFileName()) . '/default';
131
    }
132
133
    public function getDescription(): string
134
    {
135
        return '';
136
    }
137
138 1
    public function validate(AbstractGeneratorCommand $command): Result
139
    {
140 1
        return $this->validator->validate($command);
141
    }
142
143
    /**
144
     * Loads sticky attributes from an internal file and populates them into the generator.
145
     *
146
     * @internal
147
     */
148
    public function loadStickyAttributes(): void
149
    {
150
        $stickyAttributes = $this->stickyAttributes();
151
        $path = $this->getStickyDataFile();
152
        if (is_file($path)) {
153
            $result = Json::decode(file_get_contents($path));
154
            if (is_array($result)) {
155
                foreach ($stickyAttributes as $name) {
156
                    $method = 'set' . $name;
157
                    if (array_key_exists($name, $result) && method_exists($this, $method)) {
158
                        $this->$method($result[$name]);
159
                    }
160
                }
161
            }
162
        }
163
    }
164
165
    /**
166
     * Loads sticky attributes from an internal file and populates them into the generator.
167
     */
168
    public function load(array $data): void
169
    {
170
        foreach ($data as $name => $value) {
171
            $method = 'set' . $name;
172
            if (method_exists($this, $method)) {
173
                $this->$method($value);
174
            }
175
        }
176
    }
177
178
    /**
179
     * Saves sticky attributes into an internal file.
180
     */
181
    public function saveStickyAttributes(): void
182
    {
183
        $stickyAttributes = $this->stickyAttributes();
184
        $stickyAttributes[] = 'template';
185
        $values = [];
186
        foreach ($stickyAttributes as $name) {
187
            $method = 'get' . $name;
188
            if (method_exists($this, $method)) {
189
                $values[$name] = $this->$method();
190
            }
191
        }
192
        $path = $this->getStickyDataFile();
193
        if (!mkdir($concurrentDirectory = dirname($path), 0755, true) && !is_dir($concurrentDirectory)) {
194
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
195
        }
196
        file_put_contents($path, Json::encode($values));
197
    }
198
199
    protected function getStickyDataFile(): string
200
    {
201
        return sprintf('%s/gii/%s.json', $this->aliases->get('@runtime'), str_replace('\\', '-', static::class));
202
    }
203
204
    /**
205
     * Saves the generated code into files.
206
     *
207
     * @param CodeFile[] $files the code files to be saved
208
     * @param string[] $results this parameter receives a value from this method indicating the log messages
209
     * generated while saving the code files.
210
     *
211
     * @throws ReflectionException
212
     * @throws InvalidConfigException
213
     *
214
     * @return bool whether files are successfully saved without any error.
215
     */
216
    public function save(array $files, array $answers, array &$results): bool
217
    {
218
//        $results = ['Generating code using template "' . $this->getTemplatePath() . '"...'];
219
        $hasError = false;
220
        foreach ($files as $file) {
221
            $relativePath = $file->getRelativePath();
222
            if (!empty($answers[$file->getId()]) && $file->getOperation() !== CodeFile::OP_SKIP) {
223
                try {
224
                    $file->save();
225
                    $results[] = $file->getOperation() === CodeFile::OP_CREATE
226
                        ? " generated  $relativePath"
227
                        : " overwrote  $relativePath";
228
                } catch (Exception $e) {
229
                    $hasError = true;
230
                    $results[] = sprintf(
231
                        "   generating %s\n    - <span class=\"error\">%s</span>",
232
                        $relativePath,
233
                        $e->getMessage()
234
                    );
235
                }
236
            } else {
237
                $results[] = "   skipped    $relativePath";
238
            }
239
        }
240
        $results[] = 'done!';
241
242
        return !$hasError;
243
    }
244
245
    /**
246
     * @throws ReflectionException
247
     * @throws InvalidConfigException
248
     *
249
     * @return string the root path of the template files that are currently being used.
250
     */
251
    public function getTemplatePath(AbstractGeneratorCommand $command): string
252
    {
253
        $template = $command->getTemplate();
254
255
        if ($template === 'default') {
256
            return $this->defaultTemplate();
257
        }
258
259
        if (isset($this->parametersProvider->getTemplates()[$template])) {
260
            return $this->parametersProvider->getTemplates()[$template];
261
        }
262
263
        throw new InvalidConfigException("Unknown template: {$template}");
264
    }
265
266
    /**
267
     * Generates code using the specified code template and parameters.
268
     * Note that the code template will be used as a PHP file.
269
     *
270
     * @param string $template the code template file. This must be specified as a file path
271
     * relative to {@see getTemplatePath()}.
272
     * @param array $params list of parameters to be passed to the template file.
273
     *
274
     * @throws Throwable
275
     *
276
     * @return string the generated code
277
     */
278
    protected function render(AbstractGeneratorCommand $command, string $template, array $params = []): string
279
    {
280
        $file = sprintf(
281
            '%s/%s.php',
282
            $this->aliases->get($this->getTemplatePath($command)),
283
            $template
284
        );
285
286
        $renderer = function (): void {
287
            extract(func_get_arg(1));
288
            /** @psalm-suppress UnresolvableInclude */
289
            require func_get_arg(0);
290
        };
291
292
        $obInitialLevel = ob_get_level();
293
        ob_start();
294
        ob_implicit_flush(false);
295
        try {
296
            /** @psalm-suppress PossiblyInvalidFunctionCall */
297
            $renderer->bindTo($this)($file, array_merge($params, ['command' => $command]));
298
            return ob_get_clean();
299
        } catch (Throwable $e) {
300
            while (ob_get_level() > $obInitialLevel) {
301
                if (!@ob_end_clean()) {
302
                    ob_clean();
303
                }
304
            }
305
            throw $e;
306
        }
307
    }
308
309
    /**
310
     * An inline validator that checks if the attribute value refers to an existing class name.
311
     *
312
     * @param string $value the attribute being validated
313
     */
314
    public function validateClass(string $value): Result
315
    {
316
        $result = new Result();
317
        if (!class_exists($value)) {
318
            $result->addError("Class '$value' does not exist or has syntax error.");
319
        }
320
321
        return $result;
322
    }
323
324
    /**
325
     * @param string $value the attribute to be validated
326
     *
327
     * @return bool whether the value is a reserved PHP keyword.
328
     */
329
    public function isReservedKeyword(string $value): bool
330
    {
331
        static $keywords = [
332
            '__class__',
333
            '__dir__',
334
            '__file__',
335
            '__function__',
336
            '__line__',
337
            '__method__',
338
            '__namespace__',
339
            '__trait__',
340
            'abstract',
341
            'and',
342
            'array',
343
            'as',
344
            'break',
345
            'case',
346
            'catch',
347
            'callable',
348
            'cfunction',
349
            'class',
350
            'clone',
351
            'const',
352
            'continue',
353
            'declare',
354
            'default',
355
            'die',
356
            'do',
357
            'echo',
358
            'else',
359
            'elseif',
360
            'empty',
361
            'enddeclare',
362
            'endfor',
363
            'endforeach',
364
            'endif',
365
            'endswitch',
366
            'endwhile',
367
            'eval',
368
            'exception',
369
            'exit',
370
            'extends',
371
            'final',
372
            'finally',
373
            'for',
374
            'foreach',
375
            'function',
376
            'global',
377
            'goto',
378
            'if',
379
            'implements',
380
            'include',
381
            'include_once',
382
            'instanceof',
383
            'insteadof',
384
            'interface',
385
            'isset',
386
            'list',
387
            'namespace',
388
            'new',
389
            'old_function',
390
            'or',
391
            'parent',
392
            'php_user_filter',
393
            'print',
394
            'private',
395
            'protected',
396
            'public',
397
            'require',
398
            'require_once',
399
            'return',
400
            'static',
401
            'switch',
402
            'this',
403
            'throw',
404
            'trait',
405
            'try',
406
            'unset',
407
            'use',
408
            'var',
409
            'while',
410
            'xor',
411
            'fn',
412
        ];
413
414
        return in_array(strtolower($value), $keywords, true);
415
    }
416
417
    /**
418
     * Generates a string depending on enableI18N property
419
     *
420
     * @param string $string the text be generated
421
     * @param array $placeholders the placeholders to use by `Yii::t()`
422
     */
423
    public function generateString(string $string = '', array $placeholders = []): string
424
    {
425
        $string = addslashes($string);
426
        if (!empty($placeholders)) {
427
            $phKeys = array_map(
428
                static fn ($word) => '{' . $word . '}',
429
                array_keys($placeholders)
430
            );
431
            $phValues = array_values($placeholders);
432
            $str = "'" . str_replace($phKeys, $phValues, $string) . "'";
433
        } else {
434
            // No placeholders, just the given string
435
            $str = "'" . $string . "'";
436
        }
437
        return $str;
438
    }
439
440
    public function getAttributeValue(string $attribute): mixed
441
    {
442
        if (!$this->hasAttribute($attribute)) {
443
            throw new InvalidArgumentException(sprintf('There is no "%s" in %s.', $attribute, $this->getName()));
444
        }
445
        $method = 'get' . $attribute;
446
        return $this->$method();
447
    }
448
449
    public function hasAttribute(string $attribute): bool
450
    {
451
        $method = 'get' . $attribute;
452
        return method_exists($this, $method);
453
    }
454
455
    public function getErrors(): array
456
    {
457
        return $this->errors;
458
    }
459
460
    public function hasErrors(): bool
461
    {
462
        return $this->errors !== [];
463
    }
464
465
    public function getDirectory(): string
466
    {
467
        return $this->directory;
468
    }
469
470
    public function getData(): array
471
    {
472
        return [
473
            //            'templates' => $this->templates,
474
            'template' => $this->template,
475
        ];
476
    }
477
}
478