Completed
Pull Request — 2.x (#102)
by Akihito
02:58 queued 01:44
created

CodeGen::getClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 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\Class_;
12
use PhpParser\NodeTraverser;
13
use PhpParser\Parser;
14
use PhpParser\PrettyPrinter\Standard;
15
use Ray\Aop\Exception\InvalidSourceClassException;
16
17
final class CodeGen implements CodeGenInterface
18
{
19
    /**
20
     * @var \PhpParser\Parser
21
     */
22
    private $parser;
23
24
    /**
25
     * @var \PhpParser\BuilderFactory
26
     */
27
    private $factory;
28
29
    /**
30
     * @var \PhpParser\PrettyPrinter\Standard
31
     */
32
    private $printer;
33
34
    /**
35
     * @var CodeGenMethod
36
     */
37
    private $codeGenMethod;
38
39
    /**
40
     * @var AnnotationReader
41
     */
42
    private $reader;
43
44
    /**
45
     * @throws \Doctrine\Common\Annotations\AnnotationException
46
     */
47
    public function __construct(
48
        Parser $parser,
49
        BuilderFactory $factory,
50
        Standard $printer
51
    ) {
52
        $this->parser = $parser;
53
        $this->factory = $factory;
54 33
        $this->printer = $printer;
55
        $this->codeGenMethod = new CodeGenMethod($parser, $factory, $printer);
56
        $this->reader = new AnnotationReader;
57
    }
58
59 33
    /**
60 33
     * {@inheritdoc}
61 33
     */
62 33
    public function generate(string $class, \ReflectionClass $sourceClass, BindInterface $bind) : string
63 33
    {
64 33
        $methods = $this->codeGenMethod->getMethods($sourceClass, $bind);
65
        $classStmt = $this->buildClass($class, $sourceClass, $methods);
66
        $classDocStmt = $this->addClassDocComment($classStmt, $sourceClass);
67
        $code = $this->getVisitorCode($sourceClass);
68
        $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...
69 17
        $stmt = $this->factory->namespace('RayAop')
70
            ->addStmt($this->factory->use('Ray\Aop\WeavedInterface'))
71 17
            ->addStmt($this->factory->use('Ray\Aop\ReflectiveMethodInvocation')->as('Invocation'))
72 17
            ->addStmts($code->use)
73 17
            ->addStmt($classDocStmt)->getNode();
74 17
75
        return $this->printer->prettyPrintFile(array_merge($code->declare, [$stmt]));
76 16
    }
77
78
    /**
79
     * Return "declare()" and "use" statement code
80
     */
81
    private function getVisitorCode(\ReflectionClass $class) : CodeVisitor
82
    {
83
        $traverser = new NodeTraverser();
84 17
        $visitor = new CodeVisitor();
85
        $traverser->addVisitor($visitor);
86 17
        $fileName = $class->getFileName();
87 17
        if (is_bool($fileName)) {
88 17
            throw new InvalidSourceClassException(get_class($class));
89 17
        }
90 17
        $file = file_get_contents($fileName);
91 1
        if ($file === false) {
92
            throw new \RuntimeException($fileName); // @codeCoverageIgnore
93 16
        }
94 16
        $stmts = $this->parser->parse($file);
95
        if (is_array($stmts)) {
96
            $traverser->traverse($stmts);
97 16
        }
98 16
99 16
        return $visitor;
100
    }
101
102 16
    /**
103
     * Return class statement
104
     */
105
    private function getClass(BuilderFactory $factory, string $newClassName, \ReflectionClass $class) : Builder
106
    {
107
        $parentClass = '\\' . $class->name;
108 17
        $builder = $factory
109
            ->class($newClassName)
110 17
            ->extend($parentClass)
111
            ->implement('WeavedInterface');
112 17
        $builder = $this->addInterceptorProp($builder);
113 17
114 17
        return $this->addSerialisedAnnotationProp($builder, $class);
115 17
    }
116 17
117
    /**
118 17
     * Add class doc comment
119
     */
120
    private function addClassDocComment(Class_ $node, \ReflectionClass $class) : Class_
121
    {
122
        $docComment = $class->getDocComment();
123
        if ($docComment) {
124 17
            $node->setDocComment(new Doc($docComment));
125
        }
126 17
127 17
        return $node;
128 10
    }
129
130
    private function getClassAnnotation(\ReflectionClass $class) : string
131 17
    {
132
        $classAnnotations = $this->reader->getClassAnnotations($class);
133
134 17
        return serialize($classAnnotations);
135
    }
136 17
137
    private function addInterceptorProp(Builder $builder) : Builder
138 17
    {
139
        $builder->addStmt(
140
            $this->factory
141 17
                ->property('isAspect')
142
                ->makePrivate()
143 17
                ->setDefault(true)
144 17
        )->addStmt(
145 17
            $this->factory->property('bind')
146 17
                ->makePublic()
147 17
        )->addStmt(
148 17
            $this->factory->property('bindings')
149 17
                ->makePublic()
150 17
                ->setDefault([])
151
        );
152
153 17
        return $builder;
154
    }
155
156
    /**
157
     * Add serialised
158
     */
159 17
    private function addSerialisedAnnotationProp(Builder $builder, \ReflectionClass $class) : Builder
160
    {
161 17
        $builder->addStmt(
162 17
            $this->factory
163 17
                ->property('methodAnnotations')
164 17
                ->setDefault($this->getMethodAnnotations($class))
165 17
                ->makePublic()
166 17
        )->addStmt(
167 17
            $this->factory
168 17
                ->property('classAnnotations')
169 17
                ->setDefault($this->getClassAnnotation($class))
170 17
                ->makePublic()
171
        );
172
173 17
        return $builder;
174
    }
175
176 17
    private function getMethodAnnotations(\ReflectionClass $class) : string
177
    {
178 17
        $methodsAnnotation = [];
179 17
        $methods = $class->getMethods();
180 17
        foreach ($methods as $method) {
181 16
            $annotations = $this->reader->getMethodAnnotations($method);
182 16
            if ($annotations === []) {
183 13
                continue;
184
            }
185 4
            $methodsAnnotation[$method->name] = $annotations;
186
        }
187
188 17
        return serialize($methodsAnnotation);
189
    }
190
191 17
    private function buildClass(string $class, \ReflectionClass $sourceClass, array $methods) : Class_
192
    {
193
        return $this
194 17
            ->getClass($this->factory, $class, $sourceClass)
195 17
            ->addStmts($methods)
196 17
            ->getNode();
197
    }
198
}
199