Completed
Push — 2.x ( eb2bc2...ce9b15 )
by Akihito
03:04
created

CodeGen::getClassAnnotation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * This file is part of the Ray.Aop package
4
 *
5
 * @license http://opensource.org/licenses/MIT MIT
6
 */
7
namespace Ray\Aop;
8
9
use Doctrine\Common\Annotations\AnnotationReader;
10
use Doctrine\Common\Annotations\IndexedReader;
11
use PhpParser\Builder\Class_ as Builder;
12
use PhpParser\BuilderFactory;
13
use PhpParser\Comment\Doc;
14
use PhpParser\Node\Stmt\Class_;
15
use PhpParser\NodeTraverser;
16
use PhpParser\Parser;
17
use PhpParser\PrettyPrinter\Standard;
18
19
final class CodeGen implements CodeGenInterface
20
{
21
    /**
22
     * @var \PhpParser\Parser
23
     */
24
    private $parser;
25
26
    /**
27
     * @var \PhpParser\BuilderFactory
28
     */
29
    private $factory;
30
31
    /**
32
     * @var \PhpParser\PrettyPrinter\Standard
33
     */
34
    private $printer;
35
36
    /**
37
     * @var CodeGenMethod
38
     */
39
    private $codeGenMethod;
40
41
    /**
42
     * @var IndexedReader
43
     */
44
    private $reader;
45
46
    /**
47
     * @param \PhpParser\Parser                 $parser
48
     * @param \PhpParser\BuilderFactory         $factory
49
     * @param \PhpParser\PrettyPrinter\Standard $printer
50
     */
51 26
    public function __construct(
52
        Parser $parser,
53
        BuilderFactory $factory,
54
        Standard $printer
55
    ) {
56 26
        $this->parser = $parser;
57 26
        $this->factory = $factory;
58 26
        $this->printer = $printer;
59 26
        $this->codeGenMethod = new CodeGenMethod($parser, $factory, $printer);
60 26
        $this->reader = new IndexedReader(new AnnotationReader);
61 26
    }
62
63
    /**
64
     * @param string           $class
65
     * @param \ReflectionClass $sourceClass
66
     * @param BindInterface    $bind
67
     *
68
     * @return string
69
     */
70 11
    public function generate($class, \ReflectionClass $sourceClass, BindInterface $bind)
71
    {
72 11
        $methods = $this->codeGenMethod->getMethods($sourceClass, $bind);
73
        $stmt = $this
74 11
            ->getClass($class, $sourceClass)
75 11
            ->addStmts($methods)
76 11
            ->getNode();
77 11
        $stmt = $this->addClassDocComment($stmt, $sourceClass);
78 11
        $code = $this->printer->prettyPrint([$stmt]);
79 11
        $statements = $this->getUseStatements($sourceClass);
80
81 11
        return $statements . $code;
82
    }
83
84
    /**
85
     * @param \ReflectionClass $class
86
     *
87
     * @return string
88
     */
89 11
    private function getUseStatements(\ReflectionClass $class)
90
    {
91 11
        $traverser = new NodeTraverser();
92 11
        $useStmtsVisitor = new CodeGenVisitor();
93 11
        $traverser->addVisitor($useStmtsVisitor);
94
        // parse
95 11
        $stmts = $this->parser->parse(file_get_contents($class->getFileName()));
96
        // traverse
97 11
        $traverser->traverse($stmts);
0 ignored issues
show
Bug introduced by
It seems like $stmts defined by $this->parser->parse(fil...$class->getFileName())) on line 95 can also be of type null; however, PhpParser\NodeTraverser::traverse() does only seem to accept array<integer,object<PhpParser\Node>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
98
        // pretty print
99 11
        $code = $this->printer->prettyPrint($useStmtsVisitor());
100
101 11
        return (string) $code;
102
    }
103
104
    /**
105
     * Return class statement
106
     *
107
     * @param string           $newClassName
108
     * @param \ReflectionClass $class
109
     *
110
     * @return \PhpParser\Builder\Class_
111
     */
112 11
    private function getClass($newClassName, \ReflectionClass $class)
113
    {
114 11
        $parentClass = $class->name;
115 11
        $builder = $this->factory
116 11
            ->class($newClassName)
117 11
            ->extend($parentClass)
118 11
            ->implement('Ray\Aop\WeavedInterface');
119 11
        $builder = $this->addInterceptorProp($builder);
120 11
        $builder = $this->addSerialisedAnnotationProp($builder, $class);
121
122 11
        return $builder;
123
    }
124
125
    /**
126
     * Add class doc comment
127
     *
128
     * @param Class_           $node
129
     * @param \ReflectionClass $class
130
     *
131
     * @return \PhpParser\Node\Stmt\Class_
132
     */
133 11
    private function addClassDocComment(Class_ $node, \ReflectionClass $class)
134
    {
135 11
        $docComment = $class->getDocComment();
136 11
        if ($docComment) {
137 9
            $node->setAttribute('comments', [new Doc($docComment)]);
138
        }
139
140 11
        return $node;
141
    }
142
143
    /**
144
     * @param \ReflectionClass $class
145
     *
146
     * @return string
147
     */
148 11
    private function getClassAnnotation(\ReflectionClass $class)
149
    {
150 11
        $classAnnotations = $this->reader->getClassAnnotations($class);
151
152 11
        return serialize($classAnnotations);
153
    }
154
155
    /**
156
     * @param Builder $builder
157
     *
158
     * @return Builder
159
     */
160 11
    private function addInterceptorProp(Builder $builder)
161
    {
162 11
        $builder->addStmt(
163 11
            $this->factory
164 11
                ->property('isIntercepting')
165 11
                ->makePrivate()
166 11
                ->setDefault(true)
167 11
        )->addStmt(
168 11
            $this->factory->property('bind')
169 11
            ->makePublic()
170
        );
171
172 11
        return $builder;
173
    }
174
175
    /**
176
     * Add serialised
177
     *
178
     * @param Builder          $builder
179
     * @param \ReflectionClass $class
180
     *
181
     * @return Builder
182
     */
183 11
    private function addSerialisedAnnotationProp(Builder $builder, \ReflectionClass $class)
184
    {
185 11
        $builder->addStmt(
186 11
            $this->factory
187 11
                ->property('methodAnnotations')
188 11
                ->setDefault($this->getMethodAnnotations($class))
189 11
                ->makePublic()
190 11
        )->addStmt(
191 11
            $this->factory
192 11
                ->property('classAnnotations')
193 11
                ->setDefault($this->getClassAnnotation($class))
194 11
                ->makePublic()
195
        );
196
197 11
        return $builder;
198
    }
199
200
    /**
201
     * @param \ReflectionClass $class
202
     *
203
     * @return string
204
     */
205 11
    private function getMethodAnnotations(\ReflectionClass $class)
206
    {
207 11
        $methodsAnnotation = [];
208 11
        $methods = $class->getMethods();
209 11
        foreach ($methods as $method) {
210 11
            $annotations = $this->reader->getMethodAnnotations($method);
211 11
            if ($annotations === []) {
212 8
                continue;
213
            }
214 4
            $methodsAnnotation[$method->getName()] = $annotations;
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
215
        }
216
217 11
        return serialize($methodsAnnotation);
218
    }
219
}
220