Completed
Push — cs ( 9c9fc7...96045f )
by Akihito
02:05
created

CodeGen   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 171
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 12
dl 0
loc 171
ccs 76
cts 76
cp 1
rs 10
c 0
b 0
f 0

10 Methods

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