Completed
Push — cs ( 9c9fc7 )
by Akihito
01:43
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.0123

Importance

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