Completed
Push — 2.x ( 47c14a...69bf09 )
by Akihito
11s queued 10s
created

CodeGen::getMethodAnnotations()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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