Completed
Push — hotfix-use ( fcb514 )
by Akihito
01:38
created

CodeGen::getVisitorCode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use PhpParser\Builder\Class_ as Builder;
9
use PhpParser\BuilderFactory;
10
use PhpParser\Comment\Doc;
11
use PhpParser\Node\Stmt;
12
use PhpParser\Node\Stmt\Class_;
13
use PhpParser\NodeTraverser;
14
use PhpParser\Parser;
15
use PhpParser\PrettyPrinter\Standard;
16
use Ray\Aop\Exception\InvalidSourceClassException;
17
18
final class CodeGen implements CodeGenInterface
19
{
20
    /**
21
     * @var \PhpParser\Parser
22
     */
23
    private $parser;
24
25
    /**
26
     * @var \PhpParser\BuilderFactory
27
     */
28
    private $factory;
29
30
    /**
31
     * @var \PhpParser\PrettyPrinter\Standard
32
     */
33
    private $printer;
34
35
    /**
36
     * @var CodeGenMethod
37
     */
38
    private $codeGenMethod;
39
40
    /**
41
     * @var AnnotationReader
42
     */
43
    private $reader;
44
45
    /**
46
     * @throws \Doctrine\Common\Annotations\AnnotationException
47
     */
48
    public function __construct(
49
        Parser $parser,
50
        BuilderFactory $factory,
51
        Standard $printer
52
    ) {
53
        $this->parser = $parser;
54
        $this->factory = $factory;
55
        $this->printer = $printer;
56
        $this->codeGenMethod = new CodeGenMethod($parser, $factory, $printer);
57
        $this->reader = new AnnotationReader;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function generate(string $class, \ReflectionClass $sourceClass, BindInterface $bind) : string
64
    {
65
        $methods = $this->codeGenMethod->getMethods($sourceClass, $bind);
66
        $classStmt = $this->buildClass($class, $sourceClass, $methods);
67
        $classDocStmt = $this->addClassDocComment($classStmt, $sourceClass);
68
        $code = $this->getPhpFileStmt($sourceClass);
0 ignored issues
show
Bug introduced by
The method getPhpFileStmt() does not seem to exist on object<Ray\Aop\CodeGen>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
69
        $a = $this->factory->use('Ray\Aop\ReflectiveMethodInvocation')->as('Invocation');
0 ignored issues
show
Unused Code introduced by
$a is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
70
        $stmt = $this->factory->namespace('RayAop')
71
            ->addStmt($this->factory->use('Ray\Aop\WeavedInterface'))
72
            ->addStmt($this->factory->use('Ray\Aop\ReflectiveMethodInvocation')->as('Invocation'))
73
            ->addStmts($code->use)
74
            ->addStmt($classDocStmt)->getNode();
75
76
        return $this->printer->prettyPrintFile(array_merge($code->declare, [$stmt]));
77
    }
78
79
    /**
80
     * Return "declare()" and "use" statement code
81
     *
82
     * @return Stmt[]
83
     */
84
    private function getVisitorCode(\ReflectionClass $class) : CodeGenVisitor
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
85
    {
86
        $traverser = new NodeTraverser();
87
        $visitor = new CodeGenVisitor();
88
        $traverser->addVisitor($visitor);
89
        $fileName = $class->getFileName();
90
        if (is_bool($fileName)) {
91
            throw new InvalidSourceClassException(get_class($class));
92
        }
93
        $file = file_get_contents($fileName);
94
        if ($file === false) {
95
            throw new \RuntimeException($fileName); // @codeCoverageIgnore
96
        }
97
        $stmts = $this->parser->parse($file);
98
        if (is_array($stmts)) {
99
            $traverser->traverse($stmts);
100
        }
101
102
        return $visitor;
103
    }
104
105
    /**
106
     * Return class statement
107
     */
108
    private function getClass(BuilderFactory $factory, string $newClassName, \ReflectionClass $class) : Builder
109
    {
110
        $parentClass = '\\' . $class->name;
111
        $builder = $factory
112
            ->class($newClassName)
113
            ->extend($parentClass)
114
            ->implement('WeavedInterface');
115
        $builder = $this->addInterceptorProp($builder);
116
117
        return $this->addSerialisedAnnotationProp($builder, $class);
118
    }
119
120
    /**
121
     * Add class doc comment
122
     */
123
    private function addClassDocComment(Class_ $node, \ReflectionClass $class) : Class_
124
    {
125
        $docComment = $class->getDocComment();
126
        if ($docComment) {
127
            $node->setDocComment(new Doc($docComment));
128
        }
129
130
        return $node;
131
    }
132
133
    private function getClassAnnotation(\ReflectionClass $class) : string
134
    {
135
        $classAnnotations = $this->reader->getClassAnnotations($class);
136
137
        return serialize($classAnnotations);
138
    }
139
140
    private function addInterceptorProp(Builder $builder) : Builder
141
    {
142
        $builder->addStmt(
143
            $this->factory
144
                ->property('isAspect')
145
                ->makePrivate()
146
                ->setDefault(true)
147
        )->addStmt(
148
            $this->factory->property('bind')
149
                ->makePublic()
150
        )->addStmt(
151
            $this->factory->property('bindings')
152
                ->makePublic()
153
                ->setDefault([])
154
        );
155
156
        return $builder;
157
    }
158
159
    /**
160
     * Add serialised
161
     */
162
    private function addSerialisedAnnotationProp(Builder $builder, \ReflectionClass $class) : Builder
163
    {
164
        $builder->addStmt(
165
            $this->factory
166
                ->property('methodAnnotations')
167
                ->setDefault($this->getMethodAnnotations($class))
168
                ->makePublic()
169
        )->addStmt(
170
            $this->factory
171
                ->property('classAnnotations')
172
                ->setDefault($this->getClassAnnotation($class))
173
                ->makePublic()
174
        );
175
176
        return $builder;
177
    }
178
179
    private function getMethodAnnotations(\ReflectionClass $class) : string
180
    {
181
        $methodsAnnotation = [];
182
        $methods = $class->getMethods();
183
        foreach ($methods as $method) {
184
            $annotations = $this->reader->getMethodAnnotations($method);
185
            if ($annotations === []) {
186
                continue;
187
            }
188
            $methodsAnnotation[$method->name] = $annotations;
189
        }
190
191
        return serialize($methodsAnnotation);
192
    }
193
194
    private function buildClass(string $class, \ReflectionClass $sourceClass, array $methods) : Class_
195
    {
196
        return $this
197
            ->getClass($this->factory, $class, $sourceClass)
198
            ->addStmts($methods)
199
            ->getNode();
200
    }
201
}
202