Test Failed
Pull Request — master (#58)
by Dmitriy
02:36
created

AbstractGenerator   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 233
Duplicated Lines 0 %

Test Coverage

Coverage 26.67%

Importance

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