1 | <?php |
||
24 | class ResourceGenerator |
||
25 | { |
||
26 | /** |
||
27 | * @var Context |
||
28 | */ |
||
29 | protected $context; |
||
30 | |||
31 | /** |
||
32 | * @var Stdio |
||
33 | */ |
||
34 | protected $stdio; |
||
35 | |||
36 | /** |
||
37 | * @var array |
||
38 | */ |
||
39 | protected $definitions = []; |
||
40 | |||
41 | /** |
||
42 | * @var string |
||
43 | */ |
||
44 | protected $path; |
||
45 | |||
46 | /** |
||
47 | * @var Fixer |
||
48 | */ |
||
49 | protected $fixer; |
||
50 | |||
51 | /** |
||
52 | * @var array |
||
53 | */ |
||
54 | protected $fixers; |
||
55 | |||
56 | 2 | public function __construct(Context $context, Stdio $stdio) |
|
57 | { |
||
58 | 2 | $this->context = $context; |
|
59 | 2 | $this->stdio = $stdio; |
|
60 | |||
61 | 2 | $this->setUpArguments(); |
|
62 | 2 | $this->setUpFixers(); |
|
63 | 2 | } |
|
64 | |||
65 | 2 | protected function setUpArguments() |
|
66 | { |
||
67 | 2 | $getOpt = $this->context->getopt([]); |
|
68 | 2 | $i = 0; |
|
69 | do { |
||
70 | 2 | $i++; |
|
71 | 2 | $opt = $getOpt->get($i); |
|
72 | 2 | if ($opt === null) { |
|
73 | 2 | break; |
|
74 | } |
||
75 | 2 | $this->definitions[] = $opt; |
|
76 | 2 | } while (true); |
|
77 | 2 | $this->path = array_pop($this->definitions); |
|
78 | 2 | } |
|
79 | |||
80 | 2 | protected function setUpFixers() |
|
81 | { |
||
82 | 2 | $this->fixer = new Fixer(); |
|
83 | 2 | $this->fixer->registerCustomFixers([ |
|
84 | 2 | new Fixer\Symfony\ExtraEmptyLinesFixer(), |
|
85 | 2 | new Fixer\Symfony\SingleBlankLineBeforeNamespaceFixer(), |
|
86 | 2 | new Fixer\PSR0\Psr0Fixer(), |
|
87 | 2 | new Fixer\PSR1\EncodingFixer(), |
|
88 | 2 | new Fixer\PSR1\ShortTagFixer(), |
|
89 | 2 | new Fixer\PSR2\BracesFixer(), |
|
90 | 2 | new Fixer\PSR2\ElseifFixer(), |
|
91 | 2 | new Fixer\PSR2\EofEndingFixer(), |
|
92 | 2 | new Fixer\PSR2\FunctionCallSpaceFixer(), |
|
93 | 2 | new Fixer\PSR2\FunctionDeclarationFixer(), |
|
94 | 2 | new Fixer\PSR2\IndentationFixer(), |
|
95 | 2 | new Fixer\PSR2\LineAfterNamespaceFixer(), |
|
96 | 2 | new Fixer\PSR2\LinefeedFixer(), |
|
97 | 2 | new Fixer\PSR2\LowercaseConstantsFixer(), |
|
98 | 2 | new Fixer\PSR2\LowercaseKeywordsFixer(), |
|
99 | 2 | new Fixer\PSR2\MethodArgumentSpaceFixer(), |
|
100 | 2 | new Fixer\PSR2\MultipleUseFixer(), |
|
101 | 2 | new Fixer\PSR2\ParenthesisFixer(), |
|
102 | 2 | new Fixer\PSR2\PhpClosingTagFixer(), |
|
103 | 2 | new Fixer\PSR2\SingleLineAfterImportsFixer(), |
|
104 | 2 | new Fixer\PSR2\TrailingSpacesFixer(), |
|
105 | 2 | new Fixer\PSR2\VisibilityFixer(), |
|
106 | 2 | new Fixer\Contrib\NewlineAfterOpenTagFixer(), |
|
107 | 2 | new EmptyLineAboveDocblocksFixer(), |
|
108 | ]); |
||
109 | 2 | $config = Config::create()-> |
|
110 | 2 | fixers($this->fixer->getFixers()) |
|
111 | ; |
||
112 | 2 | $this->fixer->addConfig($config); |
|
113 | 2 | $this->fixers = $this->prepareFixers($config); |
|
114 | 2 | } |
|
115 | |||
116 | 1 | public function run() |
|
117 | { |
||
118 | 1 | $this->checkValidity(); |
|
119 | |||
120 | 1 | foreach ($this->definitions as $definition) { |
|
121 | 1 | $this->stdio->outln('-----'); |
|
122 | 1 | $this->stdio->outln('- Definition: ' . $definition); |
|
123 | 1 | $this->stdio->outln('-----'); |
|
124 | 1 | $this->generateFromDefinition($definition); |
|
125 | 1 | $this->stdio->outln('-----'); |
|
126 | } |
||
127 | 1 | } |
|
128 | |||
129 | 1 | public function checkValidity() |
|
130 | { |
||
131 | 1 | if (count($this->definitions) < 1) { |
|
132 | throw new \InvalidArgumentException('Not enough arguments'); |
||
133 | } |
||
134 | |||
135 | 1 | if ($this->path === null) { |
|
136 | throw new \InvalidArgumentException('No path set'); |
||
137 | } |
||
138 | |||
139 | 1 | if (!file_exists($this->path)) { |
|
140 | throw new \InvalidArgumentException('Path "' . $this->path . '" doesn\'t exist'); |
||
141 | } |
||
142 | |||
143 | 1 | if (!is_dir($this->path)) { |
|
144 | throw new \InvalidArgumentException('Path "' . $this->path . '" isn\'t a directory'); |
||
145 | } |
||
146 | |||
147 | 1 | foreach ($this->definitions as $definition) { |
|
148 | 1 | if (!file_exists($definition)) { |
|
149 | 1 | throw new \InvalidArgumentException('Definition "' . $definition . '" doesn\'t exist'); |
|
150 | } |
||
151 | } |
||
152 | 1 | } |
|
153 | |||
154 | 1 | public function generateFromDefinition(string $definition) |
|
155 | { |
||
156 | 1 | $yaml = $this->readYaml($definition); |
|
157 | |||
158 | 1 | $namespacePadding = explode('\\', $yaml['class']); |
|
159 | 1 | $namespace = explode('\\', $yaml['namespace']); |
|
160 | |||
161 | 1 | $yaml['class'] = array_pop($namespacePadding); |
|
162 | 1 | $yaml['namespace'] = implode('\\', array_merge($namespace, $namespacePadding)); |
|
163 | |||
164 | 1 | $namespacePathPadding = implode(DIRECTORY_SEPARATOR, $namespacePadding); |
|
165 | 1 | $baseClass = implode( |
|
166 | 1 | '\\', |
|
167 | array_merge( |
||
168 | $namespace, |
||
169 | $namespacePadding, |
||
170 | [ |
||
171 | 1 | $yaml['class'] |
|
172 | ] |
||
173 | ) |
||
174 | ); |
||
175 | |||
176 | 1 | $this->stdio->out('Interface: generating'); |
|
177 | 1 | $this->save( |
|
178 | 1 | $this->path . |
|
179 | 1 | DIRECTORY_SEPARATOR . |
|
180 | 1 | $namespacePathPadding . |
|
181 | 1 | DIRECTORY_SEPARATOR, |
|
182 | 1 | $yaml['class'] . |
|
183 | 1 | 'Interface.php', |
|
184 | 1 | $this->createInterface($yaml) |
|
185 | ); |
||
186 | |||
187 | 1 | $this->stdio->out('Base class: generating'); |
|
188 | 1 | $this->save( |
|
189 | 1 | $this->path . |
|
190 | 1 | DIRECTORY_SEPARATOR . |
|
191 | 1 | $namespacePathPadding . |
|
192 | 1 | DIRECTORY_SEPARATOR, |
|
193 | 1 | $yaml['class'] . |
|
194 | 1 | '.php', |
|
195 | 1 | $this->createBaseClass($yaml) |
|
196 | ); |
||
197 | |||
198 | 1 | $this->stdio->out('Async class: generating'); |
|
199 | 1 | $this->save( |
|
200 | 1 | $this->path . |
|
201 | 1 | DIRECTORY_SEPARATOR . |
|
202 | 1 | 'Async' . |
|
203 | 1 | DIRECTORY_SEPARATOR . |
|
204 | 1 | $namespacePathPadding . |
|
205 | 1 | DIRECTORY_SEPARATOR, |
|
206 | 1 | $yaml['class'] . |
|
207 | 1 | '.php', |
|
208 | 1 | $this->createExtendingClass( |
|
209 | implode( |
||
210 | 1 | '\\', |
|
211 | array_merge( |
||
212 | $namespace, |
||
213 | [ |
||
214 | 1 | 'Async', |
|
215 | ], |
||
216 | $namespacePadding |
||
217 | ) |
||
218 | ), |
||
219 | 1 | $yaml['class'], |
|
220 | $baseClass |
||
221 | ) |
||
222 | ); |
||
223 | |||
224 | 1 | $this->stdio->out('Sync class: generating'); |
|
225 | 1 | $this->save( |
|
226 | 1 | $this->path . |
|
227 | 1 | DIRECTORY_SEPARATOR . |
|
228 | 1 | 'Sync' . |
|
229 | 1 | DIRECTORY_SEPARATOR . |
|
230 | 1 | $namespacePathPadding . |
|
231 | 1 | DIRECTORY_SEPARATOR, |
|
232 | 1 | $yaml['class'] . |
|
233 | 1 | '.php', |
|
234 | 1 | $this->createExtendingClass( |
|
235 | implode( |
||
236 | 1 | '\\', |
|
237 | array_merge( |
||
238 | $namespace, |
||
239 | [ |
||
240 | 1 | 'Sync', |
|
241 | ], |
||
242 | $namespacePadding |
||
243 | ) |
||
244 | ), |
||
245 | 1 | $yaml['class'], |
|
246 | $baseClass |
||
247 | ) |
||
248 | ); |
||
249 | 1 | } |
|
250 | |||
251 | 1 | protected function readYaml(string $filename): array |
|
252 | { |
||
253 | 1 | return Yaml::parse(file_get_contents($filename)); |
|
254 | } |
||
255 | |||
256 | 1 | protected function createBaseClass(array $yaml): string |
|
257 | { |
||
258 | 1 | $factory = new BuilderFactory; |
|
259 | |||
260 | 1 | $class = $factory->class($yaml['class']) |
|
261 | 1 | ->implement($yaml['class'] . 'Interface') |
|
262 | 1 | ->makeAbstract(); |
|
263 | 1 | $class->addStmt( |
|
264 | 1 | new Node\Stmt\TraitUse([ |
|
265 | 1 | new Node\Name('TransportAwareTrait') |
|
266 | ]) |
||
267 | ); |
||
268 | |||
269 | 1 | foreach ($yaml['properties'] as $name => $details) { |
|
270 | 1 | $type = $details; |
|
271 | 1 | if (is_array($details)) { |
|
272 | 1 | $type = $details['type']; |
|
273 | } |
||
274 | 1 | $class->addStmt($this->createProperty($factory, $type, $name, $details)); |
|
275 | 1 | $class->addStmt($this->createMethod($factory, $type, $name, $details)); |
|
276 | } |
||
277 | |||
278 | 1 | $node = $factory->namespace($yaml['namespace']) |
|
279 | 1 | ->addStmt($factory->use('WyriHaximus\ApiClient\Resource\TransportAwareTrait')) |
|
280 | 1 | ->addStmt($class) |
|
281 | |||
282 | 1 | ->getNode() |
|
283 | ; |
||
284 | |||
285 | 1 | $prettyPrinter = new PrettyPrinter\Standard(); |
|
286 | 1 | return $prettyPrinter->prettyPrintFile([ |
|
287 | 1 | $node |
|
288 | 1 | ]) . PHP_EOL; |
|
289 | } |
||
290 | |||
291 | 1 | protected function createInterface(array $yaml): string |
|
292 | { |
||
293 | 1 | $factory = new BuilderFactory; |
|
294 | |||
295 | 1 | $class = $factory->interface($yaml['class'] . 'Interface') |
|
296 | 1 | ->extend('ResourceInterface'); |
|
297 | |||
298 | 1 | foreach ($yaml['properties'] as $name => $details) { |
|
299 | 1 | $type = $details; |
|
300 | 1 | if (is_array($details)) { |
|
301 | 1 | $type = $details['type']; |
|
302 | } |
||
303 | 1 | $class->addStmt($this->createMethod($factory, $type, $name, $details)); |
|
304 | } |
||
305 | |||
306 | 1 | $node = $factory->namespace($yaml['namespace']) |
|
307 | 1 | ->addStmt($factory->use(ResourceInterface::class)) |
|
308 | 1 | ->addStmt($class) |
|
309 | 1 | ->getNode() |
|
310 | ; |
||
311 | |||
312 | 1 | $prettyPrinter = new PrettyPrinter\Standard(); |
|
313 | 1 | return $prettyPrinter->prettyPrintFile([ |
|
314 | 1 | $node |
|
315 | 1 | ]) . PHP_EOL; |
|
316 | } |
||
317 | |||
318 | 1 | protected function createProperty(BuilderFactory $factory, string $type, string $name, $details): Property |
|
319 | { |
||
320 | 1 | $property = $factory->property($name) |
|
321 | 1 | ->makeProtected() |
|
322 | 1 | ->setDocComment('/** |
|
323 | 1 | * @var ' . $type . ' |
|
324 | 1 | */'); |
|
325 | 1 | if (isset($details['default'])) { |
|
326 | 1 | $property->setDefault($details['default']); |
|
327 | } |
||
328 | |||
329 | 1 | return $property; |
|
330 | } |
||
331 | |||
332 | 1 | protected function createMethod(BuilderFactory $factory, string $type, string $name, $details): Method |
|
333 | { |
||
334 | 1 | return $factory->method(Inflector::camelize($name)) |
|
335 | 1 | ->makePublic() |
|
336 | 1 | ->setReturnType($type) |
|
337 | 1 | ->setDocComment('/** |
|
338 | 1 | * @return ' . $type . ' |
|
339 | 1 | */') |
|
340 | 1 | ->addStmt( |
|
341 | 1 | new Node\Stmt\Return_( |
|
342 | 1 | new Node\Expr\PropertyFetch( |
|
343 | 1 | new Node\Expr\Variable('this'), |
|
344 | $name |
||
345 | ) |
||
346 | ) |
||
347 | ); |
||
348 | } |
||
349 | |||
350 | 1 | protected function createExtendingClass(string $namespace, string $className, string $baseClass): string |
|
351 | { |
||
352 | 1 | $factory = new BuilderFactory; |
|
353 | |||
354 | 1 | $class = $factory->class($className) |
|
355 | 1 | ->extend('Base' . $className); |
|
356 | |||
357 | 1 | $class->addStmt($factory->method('refresh') |
|
358 | 1 | ->makePublic() |
|
359 | 1 | ->setReturnType($className) |
|
360 | 1 | ->addStmt( |
|
361 | 1 | new Node\Stmt\Return_( |
|
362 | 1 | new Node\Expr\MethodCall( |
|
363 | 1 | new Node\Expr\Variable('this'), |
|
364 | 1 | 'wait', |
|
365 | [ |
||
366 | 1 | new Node\Expr\MethodCall( |
|
367 | 1 | new Node\Expr\Variable('this'), |
|
368 | 1 | 'callAsync', |
|
369 | [ |
||
370 | 1 | new Node\Scalar\String_('refresh'), |
|
371 | ] |
||
372 | ), |
||
373 | ] |
||
374 | ) |
||
375 | ) |
||
376 | )); |
||
377 | |||
378 | 1 | $node = $factory->namespace($namespace) |
|
379 | 1 | ->addStmt($factory->use($baseClass)->as('Base' . $className)) |
|
380 | 1 | ->addStmt($class) |
|
381 | |||
382 | 1 | ->getNode() |
|
383 | ; |
||
384 | |||
385 | 1 | $prettyPrinter = new PrettyPrinter\Standard(); |
|
386 | 1 | return $prettyPrinter->prettyPrintFile([ |
|
387 | 1 | $node |
|
388 | 1 | ]) . PHP_EOL; |
|
389 | } |
||
390 | |||
391 | 1 | protected function save(string $directory, string $fileName, string $fileContents) |
|
392 | { |
||
393 | 1 | $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $fileName); |
|
394 | 1 | if (file_exists($directory . $fileName)) { |
|
395 | $this->stdio->outln(', exists!'); |
||
396 | return; |
||
397 | } |
||
398 | |||
399 | 1 | $path = $directory . $fileName; |
|
400 | 1 | $pathChunks = explode(DIRECTORY_SEPARATOR, $path); |
|
401 | 1 | array_pop($pathChunks); |
|
402 | 1 | $path = implode(DIRECTORY_SEPARATOR, $pathChunks); |
|
403 | 1 | if (!file_exists($path)) { |
|
404 | 1 | mkdir($path, 0777, true); |
|
405 | } |
||
406 | |||
407 | 1 | if (!file_exists($path)) { |
|
408 | throw new Exception('Unable to create: ' . $path); |
||
409 | } |
||
410 | |||
411 | 1 | $this->stdio->out(', writing'); |
|
412 | 1 | file_put_contents($directory . $fileName, $fileContents); |
|
413 | |||
414 | do { |
||
415 | 1 | usleep(500); |
|
416 | 1 | } while (!file_exists($directory . $fileName)); |
|
417 | |||
418 | 1 | $this->stdio->out(', applying PSR-2'); |
|
419 | 1 | $this->applyPsr2($directory . $fileName); |
|
420 | 1 | $this->stdio->outln(', done!'); |
|
421 | 1 | } |
|
422 | |||
423 | /** |
||
424 | * @param string $fileName |
||
425 | */ |
||
426 | 1 | protected function applyPsr2($fileName) |
|
427 | { |
||
428 | 1 | $file = new \SplFileInfo($fileName); |
|
429 | 1 | $this->fixer->fixFile( |
|
430 | $file, |
||
431 | 1 | $this->fixers, |
|
432 | 1 | false, |
|
433 | 1 | false, |
|
434 | 1 | new FileCacheManager( |
|
435 | 1 | false, |
|
436 | 1 | '', |
|
437 | 1 | $this->fixers |
|
438 | ) |
||
439 | ); |
||
440 | 1 | ||
441 | file_put_contents( |
||
442 | $fileName, |
||
443 | str_replace( |
||
444 | '<?php', |
||
445 | '<?php' . PHP_EOL . 'declare(strict_types=1);'', |
||
446 | file_get_contents( |
||
447 | $fileName |
||
448 | 2 | ) |
|
449 | ) |
||
450 | 2 | ); |
|
451 | } |
||
452 | 2 | ||
453 | 2 | ||
454 | 2 | /** |
|
455 | * @param ConfigInterface $config |
||
456 | * |
||
457 | * @return FixerInterface[] |
||
458 | 2 | */ |
|
459 | private function prepareFixers(ConfigInterface $config): array |
||
460 | { |
||
461 | $fixers = $config->getFixers(); |
||
472 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.