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
Push — master ( bfa2d3...720179 )
by Cees-Jan
9s
created

ResourceGenerator::save()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 18
cts 18
cp 1
rs 8.439
c 0
b 0
f 0
cc 5
eloc 21
nc 5
nop 3
crap 5
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 2
58
    public function __construct(Context $context, Stdio $stdio)
59 2
    {
60 2
        $this->context = $context;
61
        $this->stdio = $stdio;
62 2
63 2
        $this->setUpArguments();
64 2
        $this->setUpFixers();
65
    }
66 2
67
    protected function setUpArguments()
68 2
    {
69 2
        $getOpt = $this->context->getopt([]);
70
        $i = 0;
71 2
        do {
72 2
            $i++;
73 2
            $opt = $getOpt->get($i);
74 2
            if ($opt === null) {
75
                break;
76 2
            }
77 2
            $this->definitions[] = $opt;
78 2
        } while (true);
79 2
        $this->path = array_pop($this->definitions);
80
    }
81 2
82
    protected function setUpFixers()
83 2
    {
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
            new EmptyLineAboveDocblocksFixer(),
110 2
        ]);
111 2
        $config = Config::create()->
112
        fixers($this->fixer->getFixers())
113 2
        ;
114 2
        $this->fixer->addConfig($config);
115 2
        $this->fixers = $this->prepareFixers($config);
116
    }
117 1
118
    public function run()
119 1
    {
120
        $this->checkValidity();
121 1
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
            $this->stdio->outln('-----');
128 1
        }
129
    }
130 1
131
    public function checkValidity()
132 1
    {
133
        if (count($this->definitions) < 1) {
134
            throw new \InvalidArgumentException('Not enough arguments');
135
        }
136 1
137
        if ($this->path === null) {
138
            throw new \InvalidArgumentException('No path set');
139
        }
140 1
141
        if (!file_exists($this->path)) {
142
            throw new \InvalidArgumentException('Path "' . $this->path . '" doesn\'t exist');
143
        }
144 1
145
        if (!is_dir($this->path)) {
146
            throw new \InvalidArgumentException('Path "' . $this->path . '" isn\'t a directory');
147
        }
148 1
149 1
        foreach ($this->definitions as $definition) {
150 1
            if (!file_exists($definition)) {
151
                throw new \InvalidArgumentException('Definition "' . $definition . '" doesn\'t exist');
152
            }
153 1
        }
154
    }
155 1
156
    public function generateFromDefinition(string $definition)
157 1
    {
158
        $yaml = $this->readYaml($definition);
159 1
160 1
        $namespacePadding = explode('\\', $yaml['class']);
161
        $namespace = explode('\\', $yaml['namespace']);
162 1
163 1
        $yaml['class'] = array_pop($namespacePadding);
164
        $yaml['namespace'] = implode('\\', array_merge($namespace, $namespacePadding));
165 1
166 1
        $namespacePathPadding = implode(DIRECTORY_SEPARATOR, $namespacePadding);
167 1
        $baseClass = implode(
168
            '\\',
169
            array_merge(
170
                $namespace,
171
                $namespacePadding,
172 1
                [
173
                    $yaml['class']
174
                ]
175
            )
176
        );
177 1
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
            $this->createInterface($yaml)
187
        );
188 1
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
            $this->createBaseClass($yaml)
198
        );
199 1
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
            $this->createExtendingClass(
211 1
                implode(
212
                    '\\',
213
                    array_merge(
214
                        $namespace,
215 1
                        [
216
                            'Async',
217
                        ],
218
                        $namespacePadding
219
                    )
220 1
                ),
221
                $yaml['class'],
222
                $baseClass
223
            )
224
        );
225 1
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
            $this->createExtendingClass(
237 1
                implode(
238
                    '\\',
239
                    array_merge(
240
                        $namespace,
241 1
                        [
242
                            'Sync',
243
                        ],
244
                        $namespacePadding
245
                    )
246 1
                ),
247
                $yaml['class'],
248
                $baseClass
249
            )
250 1
        );
251
    }
252 1
253
    protected function readYaml(string $filename): array
254 1
    {
255
        return Yaml::parse(file_get_contents($filename));
256
    }
257 1
258
    protected function createBaseClass(array $yaml): string
259 1
    {
260
        $factory = new BuilderFactory;
261 1
262 1
        $class = $factory->class($yaml['class'])
263 1
            ->implement($yaml['class'] . 'Interface')
264
            ->makeAbstract();
265 1
266 1
        $docBlock = [];
267 1
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
            $nestedResources = [];
270 1
            foreach ($yaml['collection'] as $key => $resource) {
271 1
                $nestedResources[] = $key . '="' . $resource . '"';
272
            }
273
            $docBlock[] = '@Collection(' . implode(', ', $nestedResources) . ')';
274 1
        }
275 1
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
            $nestedResources = [];
278
            foreach ($yaml['nested'] as $key => $resource) {
279
                $nestedResources[] = $key . '="' . $resource . '"';
280 1
            }
281 1
            $docBlock[] = '@Nested(' . implode(', ', $nestedResources) . ')';
282 1
        }
283 1
284
        if (count($docBlock) > 0) {
285 1
            $class->setDocComment("/**\r\n * " . implode("\r\n * ", $docBlock) . "\r\n */");
286 1
        }
287
288
        $class->addStmt(
289 1
            new Node\Stmt\TraitUse([
290 1
                new Node\Name('TransportAwareTrait')
291 1
            ])
292 1
        );
293
294
        foreach ($yaml['properties'] as $name => $details) {
295
            $type = $details;
296 1
            if (is_array($details)) {
297 1
                $type = $details['type'];
298
            }
299
            $class->addStmt($this->createProperty($factory, $type, $name, $details));
300 1
            $class->addStmt($this->createMethod($factory, $type, $name, $details));
301
        }
302 1
303 1
        $stmt = $factory->namespace($yaml['namespace']);
304 1
        if (isset($yaml['collection'])) {
305 1
            $stmt = $stmt->addStmt(
306
                $factory->use(Collection::class)
307
            );
308 1
        }
309
        if (isset($yaml['nested'])) {
310 1
            $stmt = $stmt->addStmt(
311
                $factory->use(Nested::class)
312 1
            );
313 1
        }
314
        $stmt
315 1
            ->addStmt($factory->use('WyriHaximus\ApiClient\Resource\TransportAwareTrait'))
316 1
            ->addStmt($class)
317 1
        ;
318 1
319
        $node = $stmt->getNode();
320 1
321
        $prettyPrinter = new PrettyPrinter\Standard();
322
        return $prettyPrinter->prettyPrintFile([
323 1
            $node
324 1
        ]) . PHP_EOL;
325 1
    }
326 1
327
    protected function createInterface(array $yaml): string
328
    {
329 1
        $factory = new BuilderFactory;
330 1
331 1
        $class = $factory->interface($yaml['class'] . 'Interface')
332 1
            ->extend('ResourceInterface');
333
334
        foreach ($yaml['properties'] as $name => $details) {
335 1
            $type = $details;
336
            if (is_array($details)) {
337 1
                $type = $details['type'];
338 1
            }
339 1
            $class->addStmt($this->createMethod($factory, $type, $name, $details));
340 1
        }
341 1
342 1
        $node = $factory->namespace($yaml['namespace'])
343 1
            ->addStmt($factory->use(ResourceInterface::class))
344
            ->addStmt($class)
345
            ->getNode()
346 1
        ;
347
348
        $prettyPrinter = new PrettyPrinter\Standard();
349 1
        return $prettyPrinter->prettyPrintFile([
350
            $node
351 1
        ]) . PHP_EOL;
352 1
    }
353 1
354 1
    protected function createProperty(BuilderFactory $factory, string $type, string $name, $details): Property
355 1
    {
356 1
        $property = $factory->property($name)
357 1
            ->makeProtected()
358 1
            ->setDocComment('/**
359 1
                              * @var ' . $type . '
360 1
                              */');
361
        if (isset($details['default'])) {
362
            $property->setDefault($details['default']);
363
        }
364
365
        return $property;
366
    }
367 1
368
    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 1
    {
370
        return $factory->method(Inflector::camelize($name))
371 1
            ->makePublic()
372 1
            ->setReturnType($type)
373
            ->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 1
                        $name
381 1
                    )
382
                )
383 1
            );
384 1
    }
385 1
386
    protected function createExtendingClass(string $namespace, string $className, string $baseClass): string
387 1
    {
388
        $factory = new BuilderFactory;
389
390
        $class = $factory->class($className)
391
            ->extend('Base' . $className);
392
393
        $class->addStmt($factory->method('refresh')
394
            ->makePublic()
395 1
            ->setReturnType($className)
396 1
            ->addStmt(
397 1
                new Node\Stmt\Return_(
398
                    new Node\Expr\MethodCall(
399 1
                        new Node\Expr\Variable('this'),
400
                        '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 1
                                [
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
                                    new Node\Scalar\String_('refresh'),
407
                                ]
408 1
                            ),
409
                        ]
410 1
                    )
411 1
                )
412
            ));
413
414
        $node = $factory->namespace($namespace)
415
            ->addStmt($factory->use($baseClass)->as('Base' . $className))
416 1
            ->addStmt($class)
417 1
418 1
            ->getNode()
419 1
        ;
420 1
421 1
        $prettyPrinter = new PrettyPrinter\Standard();
422
        return $prettyPrinter->prettyPrintFile([
423
            $node
424 1
        ]) . PHP_EOL;
425
    }
426
427
    protected function save(string $directory, string $fileName, string $fileContents)
428 1
    {
429 1
        $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $fileName);
430
        if (file_exists($directory . $fileName)) {
431
            $this->stdio->outln(', exists!');
432 1
            return;
433 1
        }
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
        if (!file_exists($path)) {
440
            mkdir($path, 0777, true);
441
        }
442
443 1
        if (!file_exists($path)) {
444
            throw new Exception('Unable to create: ' . $path);
445 1
        }
446 1
447
        $this->stdio->out(', writing');
448 1
        file_put_contents($directory . $fileName, $fileContents);
449 1
450 1
        do {
451 1
            usleep(500);
452 1
        } while (!file_exists($directory . $fileName));
453 1
454 1
        $this->stdio->out(', applying PSR-2');
455
        $this->applyPsr2($directory . $fileName);
456
        $this->stdio->outln(', done!');
457
    }
458 1
459
    /**
460
     * @param string $fileName
461 1
     */
462 1
    protected function applyPsr2($fileName)
463
    {
464
        $file = new \SplFileInfo($fileName);
465
        $this->fixer->fixFile(
466
            $file,
467
            $this->fixers,
468 1
            false,
469
            false,
470
            new FileCacheManager(
471
                false,
472
                '',
473
                $this->fixers
474
            )
475
        );
476 2
477
        file_put_contents(
478 2
            $fileName,
479
            str_replace(
480 2
                '<?php',
481 2
                '<?php declare(strict_types=1);',
482 2
                file_get_contents(
483
                    $fileName
484
                )
485
            )
486 2
        );
487
    }
488
489
490
    /**
491
     * @param ConfigInterface $config
492
     *
493
     * @return FixerInterface[]
494
     */
495
    private function prepareFixers(ConfigInterface $config): array
496
    {
497
        $fixers = $config->getFixers();
498
499
        foreach ($fixers as $fixer) {
500
            if ($fixer instanceof ConfigAwareInterface) {
501
                $fixer->setConfig($config);
502
            }
503
        }
504
505
        return $fixers;
506
    }
507
}
508