Completed
Pull Request — master (#5)
by Cees-Jan
02:01
created

AsyncTableGenerator::extractNamespace()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 1
crap 3.072
1
<?php
2
3
namespace WyriHaximus\React\Cake\Orm;
4
5
use Cake\Datasource\EntityInterface;
6
use Composer\Autoload\ClassLoader;
7
use Generator;
8
use PhpParser\BuilderFactory;
9
use PhpParser\Node;
10
use PhpParser\Parser;
11
use PhpParser\ParserFactory;
12
use PhpParser\PrettyPrinter\Standard;
13
use RuntimeException;
14
15
final class AsyncTableGenerator
16
{
17
    /**
18
     * @var string
19
     */
20
    private $storageLocation;
21
22
    /**
23
     * @var BuilderFactory
24
     */
25
    private $factory;
26
27
    /**
28
     * @var Parser
29
     */
30
    private $parser;
31
32
    /**
33
     * @var ClassLoader
34
     */
35
    private $classLoader;
36
37
    /**
38
     * @param string $storageLocation
39
     */
40 1
    public function __construct($storageLocation)
41
    {
42 1
        $this->storageLocation = $storageLocation;
43 1
        $this->factory = new BuilderFactory();
44 1
        $this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
45 1
        $this->classLoader = $this->locateClassloader();
46 1
    }
47
48 1
    private function locateClassloader()
49
    {
50
        foreach ([
51 1
            dirname(__DIR__) . DS . 'vendor' . DS . 'autoload.php',
52 1
            dirname(dirname(dirname(__DIR__))) . DS . 'autoload.php',
53
        ] as $path) {
54 1
            if (file_exists($path)) {
55 1
                return require $path;
56
            }
57
        }
58
59
        throw new RuntimeException('Unable to locate class loader');
60
    }
61
62
    /**
63
     * @param string $tableClass
64
     * @return string
65
     */
66 1
    public function generate($tableClass)
67
    {
68 1
        $fileName = $this->classLoader->findFile($tableClass);
69 1
        $ast = $this->parser->parse(file_get_contents($fileName));
70
71 1
        $hashedClass = 'C' . md5($tableClass);
72
73 1
        $class = $this->factory->class($hashedClass)
74 1
            ->extend('BaseTable')
75 1
            ->implement('AsyncTableInterface')
76
        ;
77
78 1
        $class->addStmt(
79 1
            new Node\Stmt\TraitUse([
80 1
                new Node\Name('AsyncTable')
81
            ])
82
        );
83
84 1
        $class->addStmt(
85 1
            self::createMethod(
86 1
                'save',
87
                [
88 1
                    new Node\Param('entity', null, 'EntityInterface'),
89 1
                    new Node\Param('options', new Node\Expr\Array_()),
90
                ]
91
            )
92
        );
93
94 1
        foreach ($this->extractMethods($tableClass, $ast) as $method) {
0 ignored issues
show
Bug introduced by
It seems like $ast defined by $this->parser->parse(fil...et_contents($fileName)) on line 69 can also be of type null; however, WyriHaximus\React\Cake\O...rator::extractMethods() does only seem to accept array<integer,object<PhpParser\Node>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
95 1
            $class->addStmt(
96 1
                self::createMethod(
97 1
                    $method->name,
98 1
                    $method->params
99
                )
100
            );
101
        }
102
103 1
        $node = $this->factory->namespace('WyriHaximus\GeneratedAsyncCakeTable\\' . $this->extractNamespace($ast))
0 ignored issues
show
Bug introduced by
It seems like $ast defined by $this->parser->parse(fil...et_contents($fileName)) on line 69 can also be of type null; however, WyriHaximus\React\Cake\O...tor::extractNamespace() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
104 1
            ->addStmt($this->factory->use(EntityInterface::class))
105 1
            ->addStmt($this->factory->use($tableClass)->as('BaseTable'))
106 1
            ->addStmt($this->factory->use(AsyncTable::class))
107 1
            ->addStmt($this->factory->use(AsyncTableInterface::class))
108 1
            ->addStmt($class)
109 1
            ->getNode()
110
        ;
111
112 1
        $prettyPrinter = new Standard();
113 1
        file_put_contents(
114 1
            $this->storageLocation . DIRECTORY_SEPARATOR . $hashedClass . '.php',
115 1
            $prettyPrinter->prettyPrintFile([
116 1
                $node
117 1
            ]) . PHP_EOL
118
        );
119
120 1
        return $hashedClass;
121
    }
122
123 1
    protected function createMethod($method, array $params)
124
    {
125 1
        return $this->factory->method($method)
126 1
            ->makePublic()
127 1
            ->addParams($params)
128 1
            ->addStmt(
129 1
                new Node\Stmt\Return_(
130 1
                    new Node\Expr\MethodCall(
131 1
                        new Node\Expr\Variable('this'),
132 1
                        'callAsyncOrSync',
133
                        [
0 ignored issues
show
Documentation introduced by
array(new \PhpParser\Nod...hodArguments($params))) is of type array<integer,object<Php...\Node\\Expr\\Array_>"}>, 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...
134 1
                            new Node\Scalar\String_($method),
135 1
                            new Node\Expr\Array_(
136 1
                                $this->createMethodArguments($params)
137
                            ),
138
                        ]
139
                    )
140
                )
141
            )
142
        ;
143
    }
144
145
    /**
146
     * @param array $params
147
     * @return array
148
     */
149 1
    protected function createMethodArguments(array $params)
150
    {
151 1
        $arguments = [];
152 1
        foreach ($params as $param) {
153 1
            if (!($param instanceof Node\Param)) {
154
                continue;
155
            }
156 1
            $arguments[] = new Node\Expr\Variable($param->name);
157
        }
158 1
        return $arguments;
159
    }
160
161
    /**
162
     * @param string $tableClass
163
     * @param Node[] $ast
164
     * @return Generator
165
     */
166 1
    protected function extractMethods($tableClass, array $ast)
0 ignored issues
show
Unused Code introduced by
The parameter $tableClass 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...
167
    {
168 1
        foreach ($ast as $node) {
169 1
            if (!isset($node->stmts)) {
0 ignored issues
show
Bug introduced by
Accessing stmts on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
170
                continue;
171
            }
172
173 1
            yield from $this->iterageStmts($node->stmts);
0 ignored issues
show
Bug introduced by
Accessing stmts on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
174
        }
175 1
    }
176
177 1
    protected function iterageStmts(array $stmts)
178
    {
179 1
        foreach ($stmts as $stmt) {
180 1
            if ($stmt instanceof Node\Stmt\ClassMethod) {
181 1
                yield $stmt;
182
            }
183
184 1
            if (!isset($stmt->stmts)) {
185 1
                continue;
186
            }
187
188 1
            yield from $this->iterageStmts($stmt->stmts);
189
        }
190 1
    }
191
192 1
    protected function extractNamespace(array $ast)
193
    {
194 1
        foreach ($ast as $node) {
195 1
            if ($node instanceof Node\Stmt\Namespace_) {
196 1
                return (string)$node->name;
197
            }
198
        }
199
200
        return 'N' . uniqid('', true);
201
    }
202
}
203