1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* Copyright 2011 Johannes M. Schmitt <[email protected]> |
4
|
|
|
* |
5
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
6
|
|
|
* you may not use this file except in compliance with the License. |
7
|
|
|
* You may obtain a copy of the License at |
8
|
|
|
* |
9
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
10
|
|
|
* |
11
|
|
|
* Unless required by applicable law or agreed to in writing, software |
12
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
13
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14
|
|
|
* See the License for the specific language governing permissions and |
15
|
|
|
* limitations under the License. |
16
|
|
|
*/ |
17
|
|
|
namespace gossi\codegen\visitor; |
18
|
|
|
|
19
|
|
|
use gossi\codegen\config\CodeGeneratorConfig; |
20
|
|
|
use gossi\codegen\model\AbstractPhpStruct; |
21
|
|
|
use gossi\codegen\model\NamespaceInterface; |
22
|
|
|
use gossi\codegen\model\PhpClass; |
23
|
|
|
use gossi\codegen\model\PhpConstant; |
24
|
|
|
use gossi\codegen\model\PhpFunction; |
25
|
|
|
use gossi\codegen\model\PhpInterface; |
26
|
|
|
use gossi\codegen\model\PhpMethod; |
27
|
|
|
use gossi\codegen\model\PhpProperty; |
28
|
|
|
use gossi\codegen\model\PhpTrait; |
29
|
|
|
use gossi\codegen\model\TraitsInterface; |
30
|
|
|
use gossi\codegen\utils\Writer; |
31
|
|
|
use gossi\docblock\Docblock; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* The default code generation visitor. |
35
|
|
|
* |
36
|
|
|
* @author Johannes M. Schmitt <[email protected]> |
37
|
|
|
*/ |
38
|
|
|
class GeneratorVisitor implements GeneratorVisitorInterface { |
39
|
|
|
|
40
|
|
|
protected $writer; |
41
|
|
|
|
42
|
|
|
protected $scalarTypeHints; |
43
|
|
|
protected $returnTypeHints; |
44
|
|
|
|
45
|
|
|
protected $config; |
46
|
|
|
|
47
|
|
|
protected static $noTypeHints = [ |
48
|
|
|
'string', |
49
|
|
|
'int', |
50
|
|
|
'integer', |
51
|
|
|
'bool', |
52
|
|
|
'boolean', |
53
|
|
|
'float', |
54
|
|
|
'double', |
55
|
|
|
'object', |
56
|
|
|
'mixed', |
57
|
|
|
'resource' |
58
|
|
|
]; |
59
|
|
|
|
60
|
22 |
|
public function __construct(CodeGeneratorConfig $config = null) { |
61
|
|
|
// Make sure we retain the old default behavior for this class |
62
|
22 |
|
$this->config = $config ?: new CodeGeneratorConfig(['generateEmptyDocblock' => false]); |
63
|
22 |
|
$this->writer = new Writer(); |
64
|
22 |
|
} |
65
|
|
|
|
66
|
18 |
|
public function reset() { |
67
|
18 |
|
$this->writer->reset(); |
68
|
18 |
|
} |
69
|
|
|
|
70
|
5 |
|
private function ensureBlankLine() { |
71
|
5 |
|
if (!$this->writer->endsWith("\n\n") && strlen($this->writer->rtrim()->getContent()) > 0) { |
72
|
5 |
|
$this->writer->writeln(); |
73
|
5 |
|
} |
74
|
5 |
|
} |
75
|
|
|
|
76
|
13 |
|
protected function visitNamespace(NamespaceInterface $model) { |
77
|
13 |
|
if ($namespace = $model->getNamespace()) { |
78
|
3 |
|
$this->writer->writeln('namespace ' . $namespace . ';'); |
79
|
3 |
|
} |
80
|
13 |
|
} |
81
|
|
|
|
82
|
13 |
|
protected function visitRequiredFiles(AbstractPhpStruct $struct) { |
83
|
13 |
|
if ($files = $struct->getRequiredFiles()) { |
84
|
|
|
$this->ensureBlankLine(); |
85
|
|
|
foreach ($files as $file) { |
86
|
|
|
$this->writer->writeln('require_once ' . var_export($file, true) . ';'); |
87
|
|
|
} |
88
|
|
|
} |
89
|
13 |
|
} |
90
|
|
|
|
91
|
13 |
|
protected function visitUseStatements(AbstractPhpStruct $struct) { |
92
|
13 |
|
if ($useStatements = $struct->getUseStatements()) { |
93
|
3 |
|
$this->ensureBlankLine(); |
94
|
3 |
|
foreach ($useStatements as $alias => $namespace) { |
95
|
3 |
|
if (false === strpos($namespace, '\\')) { |
96
|
|
|
$commonName = $namespace; |
97
|
|
|
} else { |
98
|
3 |
|
$commonName = substr($namespace, strrpos($namespace, '\\') + 1); |
99
|
|
|
} |
100
|
|
|
|
101
|
3 |
|
if (false === strpos($namespace, '\\') && !$struct->getNamespace()) { |
102
|
|
|
//avoid fatal 'The use statement with non-compound name '$commonName' has no effect' |
|
|
|
|
103
|
|
|
continue; |
104
|
|
|
} |
105
|
|
|
|
106
|
3 |
|
$this->writer->write('use ' . $namespace); |
107
|
|
|
|
108
|
3 |
|
if ($commonName !== $alias) { |
109
|
1 |
|
$this->writer->write(' as ' . $alias); |
110
|
1 |
|
} |
111
|
|
|
|
112
|
3 |
|
$this->writer->write(";\n"); |
113
|
3 |
|
} |
114
|
3 |
|
$this->ensureBlankLine(); |
115
|
3 |
|
} |
116
|
13 |
|
} |
117
|
|
|
|
118
|
22 |
|
protected function visitDocblock(Docblock $docblock) { |
119
|
22 |
|
if (!$docblock->isEmpty() || $this->config->getGenerateEmptyDocblock()) { |
120
|
3 |
|
$this->writeDocblock($docblock); |
121
|
3 |
|
} |
122
|
22 |
|
} |
123
|
|
|
|
124
|
3 |
|
protected function writeDocblock(Docblock $docblock) { |
125
|
3 |
|
$docblock = $docblock->toString(); |
126
|
3 |
|
if (!empty($docblock)) { |
127
|
3 |
|
$this->ensureBlankLine(); |
128
|
3 |
|
$this->writer->writeln($docblock); |
129
|
3 |
|
} |
130
|
3 |
|
} |
131
|
|
|
|
132
|
12 |
|
protected function visitTraits(TraitsInterface $struct) { |
133
|
12 |
|
foreach ($struct->getTraits() as $trait) { |
134
|
|
|
$this->writer->write('use '); |
135
|
|
|
$this->writer->writeln($trait . ';'); |
136
|
12 |
|
} |
137
|
12 |
|
} |
138
|
|
|
|
139
|
11 |
|
public function startVisitingClass(PhpClass $class) { |
140
|
11 |
|
$this->visitNamespace($class); |
141
|
11 |
|
$this->visitRequiredFiles($class); |
142
|
11 |
|
$this->visitUseStatements($class); |
143
|
11 |
|
$this->visitDocblock($class->getDocblock()); |
144
|
|
|
|
145
|
|
|
// signature |
146
|
11 |
|
if ($class->isAbstract()) { |
147
|
|
|
$this->writer->write('abstract '); |
148
|
|
|
} |
149
|
|
|
|
150
|
11 |
|
if ($class->isFinal()) { |
151
|
|
|
$this->writer->write('final '); |
152
|
|
|
} |
153
|
|
|
|
154
|
11 |
|
$this->writer->write('class '); |
155
|
11 |
|
$this->writer->write($class->getName()); |
156
|
|
|
|
157
|
11 |
|
if ($parentClassName = $class->getParentClassName()) { |
158
|
|
|
$this->writer->write(' extends ' . $parentClassName); |
159
|
|
|
} |
160
|
|
|
|
161
|
11 |
|
if ($class->hasInterfaces()) { |
162
|
|
|
$this->writer->write(' implements '); |
163
|
|
|
$this->writer->write(implode(', ', $class->getInterfaces())); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// body |
167
|
11 |
|
$this->writer->writeln(" {\n")->indent(); |
168
|
|
|
|
169
|
11 |
|
$this->visitTraits($class); |
170
|
11 |
|
} |
171
|
|
|
|
172
|
1 |
|
public function startVisitingInterface(PhpInterface $interface) { |
173
|
1 |
|
$this->visitNamespace($interface); |
174
|
1 |
|
$this->visitRequiredFiles($interface); |
175
|
1 |
|
$this->visitUseStatements($interface); |
176
|
1 |
|
$this->visitDocblock($interface->getDocblock()); |
177
|
|
|
|
178
|
|
|
// signature |
179
|
1 |
|
$this->writer->write('interface '); |
180
|
1 |
|
$this->writer->write($interface->getName()); |
181
|
|
|
|
182
|
1 |
|
if ($interface->hasInterfaces()) { |
183
|
|
|
$this->writer->write(' extends '); |
184
|
|
|
$this->writer->write(implode(', ', $interface->getInterfaces())); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
// body |
188
|
1 |
|
$this->writer->writeln(" {\n")->indent(); |
189
|
1 |
|
} |
190
|
|
|
|
191
|
1 |
|
public function startVisitingTrait(PhpTrait $trait) { |
192
|
1 |
|
$this->visitNamespace($trait); |
193
|
1 |
|
$this->visitRequiredFiles($trait); |
194
|
1 |
|
$this->visitUseStatements($trait); |
195
|
1 |
|
$this->visitDocblock($trait->getDocblock()); |
196
|
|
|
|
197
|
|
|
// signature |
198
|
1 |
|
$this->writer->write('trait '); |
199
|
1 |
|
$this->writer->write($trait->getName()); |
200
|
|
|
|
201
|
|
|
// body |
202
|
1 |
|
$this->writer->writeln(" {\n")->indent(); |
203
|
|
|
|
204
|
1 |
|
$this->visitTraits($trait); |
205
|
1 |
|
} |
206
|
|
|
|
207
|
6 |
|
public function startVisitingStructConstants() { |
208
|
6 |
|
} |
209
|
|
|
|
210
|
6 |
|
public function visitStructConstant(PhpConstant $constant) { |
211
|
6 |
|
$this->visitDocblock($constant->getDocblock()); |
212
|
6 |
|
$this->writer->writeln('const ' . $constant->getName() . ' = ' . $this->getPhpExport($constant->getValue()) . ';'); |
213
|
6 |
|
} |
214
|
|
|
|
215
|
6 |
|
public function endVisitingStructConstants() { |
216
|
6 |
|
$this->writer->write("\n"); |
217
|
6 |
|
} |
218
|
|
|
|
219
|
6 |
|
public function startVisitingProperties() { |
220
|
6 |
|
} |
221
|
|
|
|
222
|
7 |
|
public function visitProperty(PhpProperty $property) { |
223
|
7 |
|
$this->visitDocblock($property->getDocblock()); |
224
|
|
|
|
225
|
7 |
|
$this->writer->write($property->getVisibility() . ' ' . ($property->isStatic() ? 'static ' : '') . '$' . $property->getName()); |
226
|
|
|
|
227
|
7 |
|
if ($property->hasValue()) { |
228
|
3 |
|
if ($property->isExpression()) { |
229
|
1 |
|
$this->writer->write(' = ' . $property->getExpression()); |
230
|
1 |
|
} else { |
231
|
2 |
|
$this->writer->write(' = ' . $this->getPhpExport($property->getValue())); |
232
|
|
|
} |
233
|
3 |
|
} |
234
|
|
|
|
235
|
7 |
|
$this->writer->writeln(';'); |
236
|
7 |
|
} |
237
|
|
|
|
238
|
7 |
|
protected function getPhpExport($value) { |
239
|
|
|
// Simply to be sure a null value is displayed in lowercase. |
240
|
7 |
|
if (null === $value) { |
241
|
|
|
return 'null'; |
242
|
|
|
} |
243
|
|
|
|
244
|
7 |
|
return var_export($value, true); |
245
|
|
|
} |
246
|
|
|
|
247
|
6 |
|
public function endVisitingProperties() { |
248
|
6 |
|
$this->writer->writeln(); |
249
|
6 |
|
} |
250
|
|
|
|
251
|
8 |
|
public function startVisitingMethods() { |
252
|
8 |
|
} |
253
|
|
|
|
254
|
10 |
|
public function visitMethod(PhpMethod $method) { |
255
|
10 |
|
$this->visitDocblock($method->getDocblock()); |
256
|
|
|
|
257
|
10 |
|
if ($method->isAbstract()) { |
258
|
|
|
$this->writer->write('abstract '); |
259
|
|
|
} |
260
|
|
|
|
261
|
10 |
|
$this->writer->write($method->getVisibility() . ' '); |
262
|
|
|
|
263
|
10 |
|
if ($method->isStatic()) { |
264
|
|
|
$this->writer->write('static '); |
265
|
|
|
} |
266
|
|
|
|
267
|
10 |
|
$this->writer->write('function '); |
268
|
|
|
|
269
|
10 |
|
if ($method->isReferenceReturned()) { |
270
|
1 |
|
$this->writer->write('& '); |
271
|
1 |
|
} |
272
|
|
|
|
273
|
10 |
|
$this->writer->write($method->getName() . '('); |
274
|
|
|
|
275
|
10 |
|
$this->writeParameters($method->getParameters()); |
276
|
10 |
|
$this->writer->write(")"); |
277
|
10 |
|
$this->writeFunctionReturnType($method->getType()); |
278
|
|
|
|
279
|
10 |
|
if ($method->isAbstract() || $method->getParent() instanceof PhpInterface) { |
280
|
|
|
$this->writer->write(";\n\n"); |
281
|
|
|
|
282
|
|
|
return; |
283
|
|
|
} |
284
|
|
|
|
285
|
10 |
|
$this->writer->writeln(' {')->indent()->writeln(trim($method->getBody()))->outdent()->rtrim()->write("}\n\n"); |
286
|
10 |
|
} |
287
|
|
|
|
288
|
8 |
|
public function endVisitingMethods() { |
289
|
8 |
|
} |
290
|
|
|
|
291
|
13 |
|
protected function endVisitingStruct(AbstractPhpStruct $struct) { |
292
|
13 |
|
$this->writer->outdent()->rtrim()->write('}'); |
293
|
13 |
|
} |
294
|
|
|
|
295
|
11 |
|
public function endVisitingClass(PhpClass $class) { |
296
|
11 |
|
$this->endVisitingStruct($class); |
297
|
11 |
|
} |
298
|
|
|
|
299
|
1 |
|
public function endVisitingInterface(PhpInterface $interface) { |
300
|
1 |
|
$this->endVisitingStruct($interface); |
301
|
1 |
|
} |
302
|
|
|
|
303
|
1 |
|
public function endVisitingTrait(PhpTrait $trait) { |
304
|
1 |
|
$this->endVisitingStruct($trait); |
305
|
1 |
|
} |
306
|
|
|
|
307
|
6 |
|
public function visitFunction(PhpFunction $function) { |
308
|
6 |
|
if ($namespace = $function->getNamespace()) { |
309
|
|
|
$this->writer->write("namespace $namespace;\n\n"); |
310
|
|
|
} |
311
|
|
|
|
312
|
6 |
|
$this->visitDocblock($function->getDocblock()); |
313
|
|
|
|
314
|
6 |
|
$this->writer->write("function {$function->getName()}("); |
315
|
6 |
|
$this->writeParameters($function->getParameters()); |
316
|
6 |
|
$this->writer->write(')'); |
317
|
6 |
|
$this->writeFunctionReturnType($function->getType()); |
318
|
6 |
|
$this->writer->write(" {\n")->indent()->writeln(trim($function->getBody()))->outdent()->rtrim()->write('}'); |
319
|
6 |
|
} |
320
|
|
|
|
321
|
22 |
|
public function getContent() { |
322
|
22 |
|
return $this->writer->getContent(); |
323
|
|
|
} |
324
|
|
|
|
325
|
16 |
|
protected function writeParameters(array $parameters) { |
326
|
16 |
|
$first = true; |
327
|
16 |
|
foreach ($parameters as $parameter) { |
328
|
11 |
|
if (!$first) { |
329
|
2 |
|
$this->writer->write(', '); |
330
|
2 |
|
} |
331
|
11 |
|
$first = false; |
332
|
|
|
|
333
|
11 |
|
if (false === strpos($parameter->getType(), '|') && |
334
|
11 |
|
($type = $parameter->getType()) && |
335
|
11 |
|
(!in_array($type, self::$noTypeHints) || $this->config->getGenerateScalarTypeHints())) { |
336
|
4 |
|
$this->writer->write($type . ' '); |
337
|
4 |
|
} |
338
|
|
|
|
339
|
11 |
|
if ($parameter->isPassedByReference()) { |
340
|
|
|
$this->writer->write('&'); |
341
|
|
|
} |
342
|
|
|
|
343
|
11 |
|
$this->writer->write('$' . $parameter->getName()); |
344
|
|
|
|
345
|
11 |
|
if ($parameter->hasValue()) { |
346
|
2 |
|
$this->writer->write(' = '); |
347
|
|
|
|
348
|
2 |
|
if ($parameter->isExpression()) { |
349
|
1 |
|
$this->writer->write($parameter->getExpression()); |
350
|
1 |
|
} else { |
351
|
1 |
|
$value = $parameter->getValue(); |
352
|
|
|
|
353
|
1 |
|
if (is_array($value) && empty($value)) { |
354
|
|
|
$this->writer->write('[]'); |
355
|
1 |
|
} else if ($value instanceof PhpConstant) { |
356
|
|
|
$this->writer->write($value->getName()); |
357
|
|
|
} else { |
358
|
1 |
|
$this->writer->write($this->getPhpExport($value)); |
359
|
|
|
} |
360
|
|
|
} |
361
|
2 |
|
} |
362
|
16 |
|
} |
363
|
16 |
|
} |
364
|
|
|
|
365
|
16 |
|
protected function writeFunctionReturnType($type) { |
366
|
16 |
|
if ($this->config->getGenerateReturnTypeHints() && $type != NULL && false === strpos($type, '|')) { |
367
|
2 |
|
$this->writer->write(': ')->write($type); |
368
|
2 |
|
} |
369
|
16 |
|
} |
370
|
|
|
} |
371
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.