Completed
Pull Request — 2.x (#91)
by Akihito
02:24
created

CodeGen::getPhpFileStatementCode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0378

Importance

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