Completed
Push — 2.x ( 266f66...8e3b41 )
by Akihito
01:57
created

CodeGen::generate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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