Completed
Push — 2.x ( a9c363...4c102d )
by Akihito
03:19
created

CodeGen::getVisitorCode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 11
cts 11
cp 1
rs 9.6
c 0
b 0
f 0
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use function array_merge;
8
use Doctrine\Common\Annotations\AnnotationReader;
9
use function implode;
10
use PhpParser\BuilderFactory;
11
use PhpParser\Node\Identifier;
12
use PhpParser\Node\Name;
13
use PhpParser\Node\Stmt\Class_;
14
use PhpParser\Node\Stmt\Property;
15
use PhpParser\NodeTraverser;
16
use PhpParser\Parser;
17
use PhpParser\PrettyPrinter\Standard;
18
use Ray\Aop\Exception\InvalidSourceClassException;
19
20
final class CodeGen implements CodeGenInterface
21
{
22
    /**
23
     * @var \PhpParser\Parser
24
     */
25
    private $parser;
26
27
    /**
28
     * @var \PhpParser\BuilderFactory
29
     */
30
    private $factory;
31
32
    /**
33
     * @var \PhpParser\PrettyPrinter\Standard
34
     */
35
    private $printer;
36
37
    /**
38
     * @var CodeGenMethod
39
     */
40
    private $codeGenMethod;
41
42
    /**
43
     * @var AnnotationReader
44
     */
45
    private $reader;
46
47
    /**
48
     * @throws \Doctrine\Common\Annotations\AnnotationException
49
     */
50
    public function __construct(
51
        Parser $parser,
52
        BuilderFactory $factory,
53
        Standard $printer
54 33
    ) {
55
        $this->parser = $parser;
56
        $this->factory = $factory;
57
        $this->printer = $printer;
58
        $this->codeGenMethod = new CodeGenMethod($parser, $factory, $printer);
59 33
        $this->reader = new AnnotationReader;
60 33
    }
61 33
62 33
    /**
63 33
     * {@inheritdoc}
64 33
     */
65
    public function generate(\ReflectionClass $sourceClass, BindInterface $bind) : Code
66
    {
67
        $source = $this->getVisitorCode($sourceClass);
68
        assert($source->class instanceof Class_);
69 17
        $methods = $this->codeGenMethod->getMethods($sourceClass, $bind, $source);
70
        $propStms = $this->getAopProps($sourceClass);
71 17
        $classStm = $source->class;
72 17
        $newClassName = sprintf('%s_%s', (string) $source->class->name, $bind->toString(''));
73 17
        $classStm->name = new Identifier($newClassName);
74 17
        $classStm->extends = new Name('\\' . $sourceClass->name);
75
        $classStm->implements[] = new Name('WeavedInterface');
76 16
        $classStm->stmts = array_merge($propStms, $methods);
0 ignored issues
show
Documentation Bug introduced by
It seems like \array_merge($propStms, $methods) of type array is incompatible with the declared type array<integer,object<PhpParser\Node\Stmt>> of property $stmts.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
77
        $ns = $this->getNamespace($source);
78
        $stmt = $this->factory->namespace($ns)
79
            ->addStmt($this->factory->use('Ray\Aop\WeavedInterface'))
80
            ->addStmt($this->factory->use('Ray\Aop\ReflectiveMethodInvocation')->as('Invocation'))
81
            ->addStmts($source->use)
82
            ->addStmt($classStm)
83
            ->getNode();
84 17
        $code = new Code;
85
        $code->code = $this->printer->prettyPrintFile(array_merge($source->declare, [$stmt]));
86 17
87 17
        return $code;
88 17
    }
89 17
90 17
    /**
91 1
     * Return "declare()" and "use" statement code
92
     */
93 16
    private function getVisitorCode(\ReflectionClass $class) : CodeVisitor
94 16
    {
95 16
        $traverser = new NodeTraverser();
96 16
        $visitor = new CodeVisitor();
97
        $traverser->addVisitor($visitor);
98
        $fileName = $class->getFileName();
99 16
        if (is_bool($fileName)) {
100
            throw new InvalidSourceClassException(get_class($class));
101
        }
102
        $file = file_get_contents($fileName);
103
        if ($file === false) {
104
            throw new \RuntimeException($fileName); // @codeCoverageIgnore
105 17
        }
106
        $stmts = $this->parser->parse($file);
107 17
        if (is_array($stmts)) {
108
            $traverser->traverse($stmts);
109 17
        }
110 17
111 17
        return $visitor;
112 17
    }
113 17
114
    private function getClassAnnotation(\ReflectionClass $class) : string
115 17
    {
116
        $classAnnotations = $this->reader->getClassAnnotations($class);
117
118
        return serialize($classAnnotations);
119
    }
120
121 17
    /**
122
     * @return Property[]
123 17
     */
124 17
    private function getAopProps(\ReflectionClass $class) : array
125 10
    {
126
        $pros = [];
127
        $pros[] = $this->factory
128 17
            ->property('bind')
129
            ->makePublic()
130
            ->getNode();
131 17
132
        $pros[] =
133 17
            $this->factory->property('bindings')
134
                ->makePublic()
135 17
                ->setDefault([])
136
                ->getNode();
137
138 17
        $pros[] = $this->factory
139
            ->property('methodAnnotations')
140 17
            ->setDefault($this->getMethodAnnotations($class))
141 17
            ->makePublic()
142 17
            ->getNode();
143 17
        $pros[] = $this->factory
144 17
            ->property('classAnnotations')
145 17
            ->setDefault($this->getClassAnnotation($class))
146 17
            ->makePublic()
147 17
            ->getNode();
148
        $pros[] = $this->factory
149
            ->property('isAspect')
150 17
            ->makePrivate()
151
            ->setDefault(true)
152
            ->getNode();
153
154
        return $pros;
155
    }
156 17
157
    private function getMethodAnnotations(\ReflectionClass $class) : string
158 17
    {
159 17
        $methodsAnnotation = [];
160 17
        $methods = $class->getMethods();
161 17
        foreach ($methods as $method) {
162 17
            $annotations = $this->reader->getMethodAnnotations($method);
163 17
            if ($annotations === []) {
164 17
                continue;
165 17
            }
166 17
            $methodsAnnotation[$method->name] = $annotations;
167 17
        }
168
169
        return serialize($methodsAnnotation);
170 17
    }
171
172
    /**
173 17
     * @return null|string
174
     */
175 17
    private function getNamespace(CodeVisitor $source)
176 17
    {
177 17
        $parts = $source->namespace->name->parts ?? [];
178 16
        $ns = implode('\\', $parts);
179 16
180 13
        return $ns ? $ns : null;
181
    }
182
}
183