Completed
Push — 2.x ( ee1bb7...bfb9a9 )
by Akihito
05:07
created

CodeGen::addSerialisedAnnotationProp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 13
cts 13
cp 1
rs 9.4285
cc 1
eloc 12
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * This file is part of the Ray.Aop package
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
namespace Ray\Aop;
8
9
use Doctrine\Common\Annotations\AnnotationReader;
10
use Doctrine\Common\Annotations\IndexedReader;
11
use PhpParser\BuilderFactory;
12
use PhpParser\Comment\Doc;
13
use PhpParser\Node\Stmt;
14
use PhpParser\Node\Stmt\Class_;
15
use PhpParser\Builder\Class_ as Builder;
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 IndexedReader
44
     */
45
    private $reader;
46
47
    /**
48
     * @param \PHPParser\Parser                 $parser
49
     * @param \PHPParser\BuilderFactory         $factory
50
     * @param \PHPParser\PrettyPrinter\Standard $printer
51
     */
52 24
    public function __construct(
53
        Parser $parser,
54
        BuilderFactory $factory,
55
        Standard $printer
56
    ) {
57 24
        $this->parser = $parser;
58 24
        $this->factory = $factory;
59 24
        $this->printer = $printer;
60 24
        $this->codeGenMethod = new CodeGenMethod($parser, $factory, $printer);
61 24
        $this->reader = new IndexedReader(new AnnotationReader);
62 24
    }
63
64
    /**
65
     * @param string           $class
66
     * @param \ReflectionClass $sourceClass
67
     *
68
     * @return string
69
     */
70 9
    public function generate($class, \ReflectionClass $sourceClass, BindInterface $bind)
71
    {
72 9
        $methods = $this->codeGenMethod->getMethods($sourceClass, $bind);
73 9
        $stmt = $this
74 9
            ->getClass($class, $sourceClass)
75 9
            ->addStmts($methods)
76 9
            ->getNode();
77 9
        $stmt = $this->addClassDocComment($stmt, $sourceClass);
78 9
        $code = $this->printer->prettyPrint([$stmt]);
79 9
        $statements = $this->getUseStatements($sourceClass);
80
81 9
        return $statements . $code;
82
    }
83
84
    /**
85
     * @param \ReflectionClass $class
86
     *
87
     * @return string
88
     */
89 9
    private function getUseStatements(\ReflectionClass $class)
90
    {
91 9
        $traverser = new NodeTraverser();
92 9
        $useStmtsVisitor = new CodeGenVisitor();
93 9
        $traverser->addVisitor($useStmtsVisitor);
94
        // parse
95 9
        $stmts = $this->parser->parse(file_get_contents($class->getFileName()));
96
        // traverse
97 9
        $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 9
        $code = $this->printer->prettyPrint($useStmtsVisitor());
100
101 9
        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 9
    private function getClass($newClassName, \ReflectionClass $class)
113
    {
114 9
        $parentClass = $class->name;
115 9
        $builder = $this->factory
116 9
            ->class($newClassName)
117 9
            ->extend($parentClass)
118 9
            ->implement('Ray\Aop\WeavedInterface');
119 9
        $builder = $this->addInterceptorProp($builder);
120 9
        $builder = $this->addSerialisedAnnotationProp($builder, $class);
121
122 9
        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 9
    private function addClassDocComment(Class_ $node, \ReflectionClass $class)
134
    {
135 9
        $docComment = $class->getDocComment();
136 9
        if ($docComment) {
137 7
            $node->setAttribute('comments', [new Doc($docComment)]);
138 7
        }
139
140 9
        return $node;
141
    }
142
143
    /**
144
     * @param \ReflectionClass $class
145
     *
146
     * @return string
147
     */
148 9
    private function getClassAnnotation(\ReflectionClass $class)
149
    {
150 9
        $classAnnotations = $this->reader->getClassAnnotations($class);
151
152 9
        return serialize($classAnnotations);
153
    }
154
155
    /**
156
     * @param Builder $builder
157
     *
158
     * @return Builder
159
     */
160 9
    private function addInterceptorProp(Builder $builder)
161
    {
162 9
        $builder->addStmt(
163 9
            $this->factory
164 9
                ->property('isIntercepting')
165 9
                ->makePrivate()
166 9
                ->setDefault(true)
167 9
        )->addStmt(
168 9
            $this->factory->property('bind')
169 9
            ->makePublic()
170 9
        );
171
172 9
        return $builder;
173
    }
174
175
176
    /**
177
     * Add serialised
178
     *
179
     * @param Builder          $builder
180
     * @param \ReflectionClass $class
181
     *
182
     * @return Builder
183
     */
184 9
    private function addSerialisedAnnotationProp(Builder $builder, \ReflectionClass $class)
185
    {
186 9
        $builder->addStmt(
187 9
            $this->factory
188 9
                ->property('methodAnnotations')
189 9
                ->setDefault($this->getMethodAnnotations($class))
190 9
                ->makePublic()
191 9
        )->addStmt(
192 9
            $this->factory
193 9
                ->property('classAnnotations')
194 9
                ->setDefault($this->getClassAnnotation($class))
195 9
                ->makePublic()
196 9
        );
197
198 9
        return $builder;
199
    }
200
201
    /**
202
     * @param \ReflectionClass $class
203
     *
204
     * @return string
205
     */
206 9
    private function getMethodAnnotations(\ReflectionClass $class)
207
    {
208 9
        $methodsAnnotation = [];
209 9
        $methods = $class->getMethods();
210 9
        foreach ($methods as $method) {
211 9
            $annotations = $this->reader->getMethodAnnotations($method);
212 9
            if ($annotations === []) {
213 6
                continue;
214
            }
215 4
            $methodsAnnotation[$method->getName()] = $annotations;
216 9
        }
217
218 9
        return serialize($methodsAnnotation);
219
    }
220
}
221