Completed
Push — 2.x ( 314088...41df79 )
by Akihito
12s
created

CodeGen::getPhpFileStatementCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 9.4285
cc 1
eloc 8
nc 1
nop 1
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 Doctrine\Common\Annotations\IndexedReader;
13
use PhpParser\Builder\Class_ as Builder;
14
use PhpParser\BuilderFactory;
15
use PhpParser\Comment\Doc;
16
use PhpParser\Node\Stmt\Class_;
17
use PhpParser\NodeTraverser;
18
use PhpParser\Parser;
19
use PhpParser\PrettyPrinter\Standard;
20
21
final class CodeGen implements CodeGenInterface
22
{
23
    /**
24
     * @var \PhpParser\Parser
25
     */
26
    private $parser;
27
28
    /**
29
     * @var \PhpParser\BuilderFactory
30
     */
31
    private $factory;
32
33
    /**
34
     * @var \PhpParser\PrettyPrinter\Standard
35
     */
36
    private $printer;
37
38
    /**
39
     * @var CodeGenMethod
40
     */
41
    private $codeGenMethod;
42
43
    /**
44
     * @var IndexedReader
45
     */
46
    private $reader;
47
48
    /**
49
     * @param \PhpParser\Parser                 $parser
50
     * @param \PhpParser\BuilderFactory         $factory
51
     * @param \PhpParser\PrettyPrinter\Standard $printer
52
     */
53 29
    public function __construct(
54
        Parser $parser,
55
        BuilderFactory $factory,
56
        Standard $printer
57
    ) {
58 29
        $this->parser = $parser;
59 29
        $this->factory = $factory;
60 29
        $this->printer = $printer;
61 29
        $this->codeGenMethod = new CodeGenMethod($parser, $factory, $printer);
62 29
        $this->reader = new IndexedReader(new AnnotationReader);
63 29
    }
64
65 13
    public function generate($class, \ReflectionClass $sourceClass, BindInterface $bind) : string
66
    {
67 13
        $methods = $this->codeGenMethod->getMethods($sourceClass, $bind);
68
        $stmt = $this
69 13
            ->getClass($class, $sourceClass)
70 13
            ->addStmts($methods)
71 13
            ->getNode();
72 13
        $stmt = $this->addClassDocComment($stmt, $sourceClass);
73 13
        $code = $this->printer->prettyPrint([$stmt]);
74 13
        $statements = $this->getPhpFileStatementCode($sourceClass);
75
76 13
        return $statements . $code;
77
    }
78
79
    /**
80
     * Return "declare()" and "use" statement code
81
     */
82 13
    private function getPhpFileStatementCode(\ReflectionClass $class) : string
83
    {
84 13
        $traverser = new NodeTraverser();
85 13
        $visitor = new CodeGenVisitor();
86 13
        $traverser->addVisitor($visitor);
87 13
        $stmts = $this->parser->parse(file_get_contents($class->getFileName()));
88 13
        $traverser->traverse($stmts);
0 ignored issues
show
Bug introduced by
It seems like $stmts defined by $this->parser->parse(fil...$class->getFileName())) on line 87 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...
89 13
        $code = $this->printer->prettyPrint($visitor());
90
91 13
        return (string) $code . PHP_EOL;
92
    }
93
94
    /**
95
     * Return class statement
96
     */
97 13
    private function getClass(string $newClassName, \ReflectionClass $class) : Builder
98
    {
99 13
        $parentClass = $class->name;
100 13
        $builder = $this->factory
101 13
            ->class($newClassName)
102 13
            ->extend($parentClass)
103 13
            ->implement('Ray\Aop\WeavedInterface');
104 13
        $builder = $this->addInterceptorProp($builder);
105 13
        $builder = $this->addSerialisedAnnotationProp($builder, $class);
106
107 13
        return $builder;
108
    }
109
110
    /**
111
     * Add class doc comment
112
     */
113 13
    private function addClassDocComment(Class_ $node, \ReflectionClass $class) : Class_
114
    {
115 13
        $docComment = $class->getDocComment();
116 13
        if ($docComment) {
117 9
            $node->setAttribute('comments', [new Doc($docComment)]);
118
        }
119
120 13
        return $node;
121
    }
122
123 13
    private function getClassAnnotation(\ReflectionClass $class) : string
124
    {
125 13
        $classAnnotations = $this->reader->getClassAnnotations($class);
126
127 13
        return serialize($classAnnotations);
128
    }
129
130 13
    private function addInterceptorProp(Builder $builder) : Builder
131
    {
132 13
        $builder->addStmt(
133 13
            $this->factory
134 13
                ->property('isIntercepting')
135 13
                ->makePrivate()
136 13
                ->setDefault(true)
137 13
        )->addStmt(
138 13
            $this->factory->property('bind')
139 13
            ->makePublic()
140
        );
141
142 13
        return $builder;
143
    }
144
145
    /**
146
     * Add serialised
147
     */
148 13
    private function addSerialisedAnnotationProp(Builder $builder, \ReflectionClass $class) : Builder
149
    {
150 13
        $builder->addStmt(
151 13
            $this->factory
152 13
                ->property('methodAnnotations')
153 13
                ->setDefault($this->getMethodAnnotations($class))
154 13
                ->makePublic()
155 13
        )->addStmt(
156 13
            $this->factory
157 13
                ->property('classAnnotations')
158 13
                ->setDefault($this->getClassAnnotation($class))
159 13
                ->makePublic()
160
        );
161
162 13
        return $builder;
163
    }
164
165 13
    private function getMethodAnnotations(\ReflectionClass $class) : string
166
    {
167 13
        $methodsAnnotation = [];
168 13
        $methods = $class->getMethods();
169 13
        foreach ($methods as $method) {
170 13
            $annotations = $this->reader->getMethodAnnotations($method);
171 13
            if ($annotations === []) {
172 10
                continue;
173
            }
174 4
            $methodsAnnotation[$method->name] = $annotations;
175
        }
176
177 13
        return serialize($methodsAnnotation);
178
    }
179
}
180