GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#19)
by Cees-Jan
02:12
created

ResourceGenerator::createExtendingClass()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 40
ccs 24
cts 24
cp 1
rs 8.8571
cc 1
eloc 24
nc 1
nop 3
crap 1
1
<?php
2
declare(strict_types=1);
3
4
namespace WyriHaximus\ApiClient\Tools;
5
6
use Aura\Cli\Context;
7
use Aura\Cli\Stdio;
8
use Doctrine\Common\Inflector\Inflector;
9
use Exception;
10
use PhpParser\Builder\Method;
11
use PhpParser\Builder\Property;
12
use PhpParser\BuilderFactory;
13
use PhpParser\PrettyPrinter;
14
use PhpParser\Node;
15
use Symfony\Component\Yaml\Yaml;
16
use Symfony\CS\Config\Config;
17
use Symfony\CS\ConfigAwareInterface;
18
use Symfony\CS\ConfigInterface;
19
use Symfony\CS\FileCacheManager;
20
use Symfony\CS\Fixer;
21
use Symfony\CS\FixerInterface;
22
use WyriHaximus\ApiClient\Annotations\Collection;
23
use WyriHaximus\ApiClient\Annotations\Nested;
24
use WyriHaximus\ApiClient\Resource\ResourceInterface;
25
26
class ResourceGenerator
27
{
28
    /**
29
     * @var Context
30
     */
31
    protected $context;
32
33
    /**
34
     * @var Stdio
35
     */
36
    protected $stdio;
37
38
    /**
39
     * @var array
40
     */
41
    protected $definitions = [];
42
43
    /**
44
     * @var string
45
     */
46
    protected $path;
47
48
    /**
49
     * @var Fixer
50
     */
51
    protected $fixer;
52
53
    /**
54
     * @var array
55
     */
56
    protected $fixers;
57
58 2
    public function __construct(Context $context, Stdio $stdio)
59
    {
60 2
        $this->context = $context;
61 2
        $this->stdio = $stdio;
62
63 2
        $this->setUpArguments();
64 2
        $this->setUpFixers();
65 2
    }
66
67 2
    protected function setUpArguments()
68
    {
69 2
        $getOpt = $this->context->getopt([]);
70 2
        $i = 0;
71
        do {
72 2
            $i++;
73 2
            $opt = $getOpt->get($i);
74 2
            if ($opt === null) {
75 2
                break;
76
            }
77 2
            $this->definitions[] = $opt;
78 2
        } while (true);
79 2
        $this->path = array_pop($this->definitions);
80 2
    }
81
82 2
    protected function setUpFixers()
83
    {
84 2
        $this->fixer = new Fixer();
85 2
        $this->fixer->registerCustomFixers([
86 2
            new Fixer\Symfony\ExtraEmptyLinesFixer(),
87 2
            new Fixer\Symfony\SingleBlankLineBeforeNamespaceFixer(),
88 2
            new Fixer\PSR0\Psr0Fixer(),
89 2
            new Fixer\PSR1\EncodingFixer(),
90 2
            new Fixer\PSR1\ShortTagFixer(),
91 2
            new Fixer\PSR2\BracesFixer(),
92 2
            new Fixer\PSR2\ElseifFixer(),
93 2
            new Fixer\PSR2\EofEndingFixer(),
94 2
            new Fixer\PSR2\FunctionCallSpaceFixer(),
95 2
            new Fixer\PSR2\FunctionDeclarationFixer(),
96 2
            new Fixer\PSR2\IndentationFixer(),
97 2
            new Fixer\PSR2\LineAfterNamespaceFixer(),
98 2
            new Fixer\PSR2\LinefeedFixer(),
99 2
            new Fixer\PSR2\LowercaseConstantsFixer(),
100 2
            new Fixer\PSR2\LowercaseKeywordsFixer(),
101 2
            new Fixer\PSR2\MethodArgumentSpaceFixer(),
102 2
            new Fixer\PSR2\MultipleUseFixer(),
103 2
            new Fixer\PSR2\ParenthesisFixer(),
104 2
            new Fixer\PSR2\PhpClosingTagFixer(),
105 2
            new Fixer\PSR2\SingleLineAfterImportsFixer(),
106 2
            new Fixer\PSR2\TrailingSpacesFixer(),
107 2
            new Fixer\PSR2\VisibilityFixer(),
108 2
            new Fixer\Contrib\NewlineAfterOpenTagFixer(),
109 2
            new EmptyLineAboveDocblocksFixer(),
110
        ]);
111 2
        $config = Config::create()->
112 2
        fixers($this->fixer->getFixers())
113
        ;
114 2
        $this->fixer->addConfig($config);
115 2
        $this->fixers = $this->prepareFixers($config);
116 2
    }
117
118 1
    public function run()
119
    {
120 1
        $this->checkValidity();
121
122 1
        foreach ($this->definitions as $definition) {
123 1
            $this->stdio->outln('-----');
124 1
            $this->stdio->outln('- Definition: ' . $definition);
125 1
            $this->stdio->outln('-----');
126 1
            $this->generateFromDefinition($definition);
127 1
            $this->stdio->outln('-----');
128
        }
129 1
    }
130
131 1
    public function checkValidity()
132
    {
133 1
        if (count($this->definitions) < 1) {
134
            throw new \InvalidArgumentException('Not enough arguments');
135
        }
136
137 1
        if ($this->path === null) {
138
            throw new \InvalidArgumentException('No path set');
139
        }
140
141 1
        if (!file_exists($this->path)) {
142
            throw new \InvalidArgumentException('Path "' . $this->path . '" doesn\'t exist');
143
        }
144
145 1
        if (!is_dir($this->path)) {
146
            throw new \InvalidArgumentException('Path "' . $this->path . '" isn\'t a directory');
147
        }
148
149 1
        foreach ($this->definitions as $definition) {
150 1
            if (!file_exists($definition)) {
151 1
                throw new \InvalidArgumentException('Definition "' . $definition . '" doesn\'t exist');
152
            }
153
        }
154 1
    }
155
156 1
    public function generateFromDefinition(string $definition)
157
    {
158 1
        $yaml = $this->readYaml($definition);
159
160 1
        $namespacePadding = explode('\\', $yaml['class']);
161 1
        $namespace = explode('\\', $yaml['namespace']);
162
163 1
        $yaml['class'] = array_pop($namespacePadding);
164 1
        $yaml['namespace'] = implode('\\', array_merge($namespace, $namespacePadding));
165
166 1
        $namespacePathPadding = implode(DIRECTORY_SEPARATOR, $namespacePadding);
167 1
        $baseClass = implode(
168 1
            '\\',
169
            array_merge(
170
                $namespace,
171
                $namespacePadding,
172
                [
173 1
                    $yaml['class']
174
                ]
175
            )
176
        );
177
178 1
        $this->stdio->out('Interface: generating');
179 1
        $this->save(
180 1
            $this->path .
181 1
                DIRECTORY_SEPARATOR .
182 1
                $namespacePathPadding .
183 1
                DIRECTORY_SEPARATOR,
184 1
            $yaml['class'] .
185 1
                'Interface.php',
186 1
            $this->createInterface($yaml)
187
        );
188
189 1
        $this->stdio->out('Base class: generating');
190 1
        $this->save(
191 1
            $this->path .
192 1
                DIRECTORY_SEPARATOR .
193 1
                $namespacePathPadding .
194 1
                DIRECTORY_SEPARATOR,
195 1
            $yaml['class'] .
196 1
                '.php',
197 1
            $this->createBaseClass($yaml)
198
        );
199
200 1
        $this->stdio->out('Async class: generating');
201 1
        $this->save(
202 1
            $this->path .
203 1
                DIRECTORY_SEPARATOR .
204 1
                'Async' .
205 1
                DIRECTORY_SEPARATOR .
206 1
                $namespacePathPadding .
207 1
                DIRECTORY_SEPARATOR,
208 1
            $yaml['class'] .
209 1
                '.php',
210 1
            $this->createExtendingClass(
211
                implode(
212 1
                    '\\',
213
                    array_merge(
214
                        $namespace,
215
                        [
216 1
                            'Async',
217
                        ],
218
                        $namespacePadding
219
                    )
220
                ),
221 1
                $yaml['class'],
222
                $baseClass
223
            )
224
        );
225
226 1
        $this->stdio->out('Sync class: generating');
227 1
        $this->save(
228 1
            $this->path .
229 1
                DIRECTORY_SEPARATOR .
230 1
                'Sync' .
231 1
                DIRECTORY_SEPARATOR .
232 1
                $namespacePathPadding .
233 1
                DIRECTORY_SEPARATOR,
234 1
            $yaml['class'] .
235 1
                '.php',
236 1
            $this->createExtendingClass(
237
                implode(
238 1
                    '\\',
239
                    array_merge(
240
                        $namespace,
241
                        [
242 1
                            'Sync',
243
                        ],
244
                        $namespacePadding
245
                    )
246
                ),
247 1
                $yaml['class'],
248
                $baseClass
249
            )
250
        );
251 1
    }
252
253 1
    protected function readYaml(string $filename): array
254
    {
255 1
        return Yaml::parse(file_get_contents($filename));
256
    }
257
258 1
    protected function createBaseClass(array $yaml): string
259
    {
260 1
        $factory = new BuilderFactory;
261
262 1
        $class = $factory->class($yaml['class'])
263 1
            ->implement($yaml['class'] . 'Interface')
264 1
            ->makeAbstract();
265
266 1
        $docBlock = [];
267
268 1 View Code Duplication
        if (isset($yaml['collection'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269 1
            $nestedResources = [];
270 1
            foreach ($yaml['collection'] as $key => $resource) {
271 1
                $nestedResources[] = $key . '="' . $resource . '"';
272
            }
273 1
            $docBlock[] = '@Collection(' . implode(', ', $nestedResources) . ')';
274
        }
275
276 1 View Code Duplication
        if (isset($yaml['nested'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
277 1
            $nestedResources = [];
278 1
            foreach ($yaml['nested'] as $key => $resource) {
279 1
                $nestedResources[] = $key . '="' . $resource . '"';
280
            }
281 1
            $docBlock[] = '@Nested(' . implode(', ', $nestedResources) . ')';
282
        }
283
284 1
        if (count($docBlock) > 0) {
285 1
            $class->setDocComment("/**\r\n * " . implode("\r\n * ", $docBlock) . "\r\n */");
286
        }
287
288 1
        $class->addStmt(
289 1
            new Node\Stmt\TraitUse([
290 1
                new Node\Name('TransportAwareTrait')
291
            ])
292
        );
293
294 1
        foreach ($yaml['properties'] as $name => $details) {
295 1
            $type = $details;
296 1
            if (is_array($details)) {
297 1
                $type = $details['type'];
298
            }
299 1
            $class->addStmt($this->createProperty($factory, $type, $name, $details));
300 1
            $class->addStmt($this->createMethod($factory, $type, $name, $details));
301
        }
302
303 1
        $stmt = $factory->namespace($yaml['namespace']);
304 1
        if (isset($yaml['collection'])) {
305 1
            $stmt = $stmt->addStmt(
306 1
                $factory->use(Collection::class)
307
            );
308
        }
309 1
        if (isset($yaml['nested'])) {
310 1
            $stmt = $stmt->addStmt(
311 1
                $factory->use(Nested::class)
312
            );
313
        }
314
        $stmt
315 1
            ->addStmt($factory->use('WyriHaximus\ApiClient\Resource\TransportAwareTrait'))
316 1
            ->addStmt($class)
317
        ;
318
319 1
        $node = $stmt->getNode();
320
321 1
        $prettyPrinter = new PrettyPrinter\Standard();
322 1
        return $prettyPrinter->prettyPrintFile([
323 1
            $node
324 1
        ]) . PHP_EOL;
325
    }
326
327 1
    protected function createInterface(array $yaml): string
328
    {
329 1
        $factory = new BuilderFactory;
330
331 1
        $class = $factory->interface($yaml['class'] . 'Interface')
332 1
            ->extend('ResourceInterface');
333
334 1
        foreach ($yaml['properties'] as $name => $details) {
335 1
            $type = $details;
336 1
            if (is_array($details)) {
337 1
                $type = $details['type'];
338
            }
339 1
            $class->addStmt($this->createMethod($factory, $type, $name, $details));
340
        }
341
342 1
        $node = $factory->namespace($yaml['namespace'])
343 1
            ->addStmt($factory->use(ResourceInterface::class))
344 1
            ->addStmt($class)
345 1
            ->getNode()
346
        ;
347
348 1
        $prettyPrinter = new PrettyPrinter\Standard();
349 1
        return $prettyPrinter->prettyPrintFile([
350 1
            $node
351 1
        ]) . PHP_EOL;
352
    }
353
354 1
    protected function createProperty(BuilderFactory $factory, string $type, string $name, $details): Property
355
    {
356 1
        $property = $factory->property($name)
357 1
            ->makeProtected()
358 1
            ->setDocComment('/**
359 1
                              * @var ' . $type . '
360 1
                              */');
361 1
        if (isset($details['default'])) {
362 1
            $property->setDefault($details['default']);
363
        }
364
365 1
        return $property;
366
    }
367
368 1
    protected function createMethod(BuilderFactory $factory, string $type, string $name, $details): Method
0 ignored issues
show
Unused Code introduced by
The parameter $details is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
369
    {
370 1
        return $factory->method(Inflector::camelize($name))
371 1
            ->makePublic()
372 1
            ->setReturnType($type)
373 1
            ->setDocComment('/**
374 1
                              * @return ' . $type . '
375 1
                              */')
376 1
            ->addStmt(
377 1
                new Node\Stmt\Return_(
378 1
                    new Node\Expr\PropertyFetch(
379 1
                        new Node\Expr\Variable('this'),
380
                        $name
381
                    )
382
                )
383
            );
384
    }
385
386 1
    protected function createExtendingClass(string $namespace, string $className, string $baseClass): string
387
    {
388 1
        $factory = new BuilderFactory;
389
390 1
        $class = $factory->class($className)
391 1
            ->extend('Base' . $className);
392
393 1
        $class->addStmt($factory->method('refresh')
394 1
            ->makePublic()
395 1
            ->setReturnType($className)
396 1
            ->addStmt(
397 1
                new Node\Stmt\Return_(
398 1
                    new Node\Expr\MethodCall(
399 1
                        new Node\Expr\Variable('this'),
400 1
                        'wait',
401
                        [
0 ignored issues
show
Documentation introduced by
array(new \PhpParser\Nod...r\String_('refresh')))) is of type array<integer,object<Php...e\\Expr\\MethodCall>"}>, but the function expects a array<integer,object<PhpParser\Node\Arg>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
402 1
                            new Node\Expr\MethodCall(
403 1
                                new Node\Expr\Variable('this'),
404 1
                                'callAsync',
405
                                [
0 ignored issues
show
Documentation introduced by
array(new \PhpParser\Nod...lar\String_('refresh')) is of type array<integer,object<Php...de\\Scalar\\String_>"}>, but the function expects a array<integer,object<PhpParser\Node\Arg>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
406 1
                                    new Node\Scalar\String_('refresh'),
407
                                ]
408
                            ),
409
                        ]
410
                    )
411
                )
412
            ));
413
414 1
        $node = $factory->namespace($namespace)
415 1
            ->addStmt($factory->use($baseClass)->as('Base' . $className))
416 1
            ->addStmt($class)
417
418 1
            ->getNode()
419
        ;
420
421 1
        $prettyPrinter = new PrettyPrinter\Standard();
422 1
        return $prettyPrinter->prettyPrintFile([
423 1
            $node
424 1
        ]) . PHP_EOL;
425
    }
426
427 1
    protected function save(string $directory, string $fileName, string $fileContents)
428
    {
429 1
        $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $fileName);
430 1
        if (file_exists($directory . $fileName)) {
431
            $this->stdio->outln(', exists!');
432
            return;
433
        }
434
435 1
        $path = $directory . $fileName;
436 1
        $pathChunks = explode(DIRECTORY_SEPARATOR, $path);
437 1
        array_pop($pathChunks);
438 1
        $path = implode(DIRECTORY_SEPARATOR, $pathChunks);
439 1
        if (!file_exists($path)) {
440 1
            mkdir($path, 0777, true);
441
        }
442
443 1
        if (!file_exists($path)) {
444
            throw new Exception('Unable to create: ' . $path);
445
        }
446
447 1
        $this->stdio->out(', writing');
448 1
        file_put_contents($directory . $fileName, $fileContents);
449
450
        do {
451 1
            usleep(500);
452 1
        } while (!file_exists($directory . $fileName));
453
454 1
        $this->stdio->out(', applying PSR-2');
455 1
        $this->applyPsr2($directory . $fileName);
456 1
        $this->stdio->outln(', done!');
457 1
    }
458
459
    /**
460
     * @param string $fileName
461
     */
462 1
    protected function applyPsr2($fileName)
463
    {
464 1
        $file = new \SplFileInfo($fileName);
465 1
        $this->fixer->fixFile(
466
            $file,
467 1
            $this->fixers,
468 1
            false,
469 1
            false,
470 1
            new FileCacheManager(
471 1
                false,
472 1
                '',
473 1
                $this->fixers
474
            )
475
        );
476
477 1
        file_put_contents(
478
            $fileName,
479
            str_replace(
480 1
                '<?php',
481 1
                '<?php declare(strict_types=1);',
482
                file_get_contents(
483
                    $fileName
484
                )
485
            )
486
        );
487 1
    }
488
489
490
    /**
491
     * @param ConfigInterface $config
492
     *
493
     * @return FixerInterface[]
494
     */
495 2
    private function prepareFixers(ConfigInterface $config): array
496
    {
497 2
        $fixers = $config->getFixers();
498
499 2
        foreach ($fixers as $fixer) {
500 2
            if ($fixer instanceof ConfigAwareInterface) {
501 2
                $fixer->setConfig($config);
502
            }
503
        }
504
505 2
        return $fixers;
506
    }
507
}
508