Completed
Push — global-namespace ( f70271...c0a0b5 )
by Akihito
01:18 queued 01:10
created

CodeGen::getNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use function array_merge;
8
use Doctrine\Common\Annotations\AnnotationReader;
9
use function implode;
10
use PhpParser\BuilderFactory;
11
use PhpParser\Node\Identifier;
12
use PhpParser\Node\Name;
13
use PhpParser\Node\Stmt\Property;
14
use PhpParser\NodeTraverser;
15
use PhpParser\Parser;
16
use PhpParser\PrettyPrinter\Standard;
17
use Ray\Aop\Exception\InvalidSourceClassException;
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 AnnotationReader
43
     */
44
    private $reader;
45
46
    /**
47
     * @throws \Doctrine\Common\Annotations\AnnotationException
48
     */
49
    public function __construct(
50
        Parser $parser,
51
        BuilderFactory $factory,
52
        Standard $printer
53
    ) {
54 33
        $this->parser = $parser;
55
        $this->factory = $factory;
56
        $this->printer = $printer;
57
        $this->codeGenMethod = new CodeGenMethod($parser, $factory, $printer);
58
        $this->reader = new AnnotationReader;
59 33
    }
60 33
61 33
    /**
62 33
     * {@inheritdoc}
63 33
     */
64 33
    public function generate(\ReflectionClass $sourceClass, BindInterface $bind) : Code
65
    {
66
        $source = $this->getVisitorCode($sourceClass);
67
        $methods = $this->codeGenMethod->getMethods($sourceClass, $bind, $source);
68
        $propStms = $this->getAopProps($sourceClass);
69 17
        $classStm = $source->class;
70
        $newClassName = sprintf('%s_%s', (string) $source->class->name, $bind->toString(''));
71 17
        $classStm->name = new Identifier($newClassName);
72 17
        $classStm->extends = new Name('\\' . $sourceClass->name);
73 17
        $classStm->implements[] = new Name('WeavedInterface');
74 17
        $classStm->stmts = array_merge($propStms, $methods);
0 ignored issues
show
Documentation Bug introduced by
It seems like \array_merge($propStms, $methods) of type array is incompatible with the declared type array<integer,object<PhpParser\Node\Stmt>> of property $stmts.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
75
        $ns = $this->getNamespace($source);
76 16
        $stmt = $this->factory->namespace($ns)
77
            ->addStmt($this->factory->use('Ray\Aop\WeavedInterface'))
78
            ->addStmt($this->factory->use('Ray\Aop\ReflectiveMethodInvocation')->as('Invocation'))
79
            ->addStmts($source->use)
80
            ->addStmt($classStm)
81
            ->getNode();
82
        $code = new Code;
83
        $code->code = $this->printer->prettyPrintFile(array_merge($source->declare, [$stmt]));
84 17
85
        return $code;
86 17
    }
87 17
88 17
    /**
89 17
     * Return "declare()" and "use" statement code
90 17
     */
91 1
    private function getVisitorCode(\ReflectionClass $class) : CodeVisitor
92
    {
93 16
        $traverser = new NodeTraverser();
94 16
        $visitor = new CodeVisitor();
95
        $traverser->addVisitor($visitor);
96
        $fileName = $class->getFileName();
97 16
        if (is_bool($fileName)) {
98 16
            throw new InvalidSourceClassException(get_class($class));
99 16
        }
100
        $file = file_get_contents($fileName);
101
        if ($file === false) {
102 16
            throw new \RuntimeException($fileName); // @codeCoverageIgnore
103
        }
104
        $stmts = $this->parser->parse($file);
105
        if (is_array($stmts)) {
106
            $traverser->traverse($stmts);
107
        }
108 17
109
        return $visitor;
110 17
    }
111
112 17
    private function getClassAnnotation(\ReflectionClass $class) : string
113 17
    {
114 17
        $classAnnotations = $this->reader->getClassAnnotations($class);
115 17
116 17
        return serialize($classAnnotations);
117
    }
118 17
119
    /**
120
     * @return Property[]
121
     */
122
    private function getAopProps(\ReflectionClass $class) : array
123
    {
124 17
        $pros = [];
125
        $pros[] = $this->factory
126 17
            ->property('bind')
127 17
            ->makePublic()
128 10
            ->getNode();
129
130
        $pros[] =
131 17
            $this->factory->property('bindings')
132
                ->makePublic()
133
                ->setDefault([])
134 17
                ->getNode();
135
136 17
        $pros[] = $this->factory
137
            ->property('methodAnnotations')
138 17
            ->setDefault($this->getMethodAnnotations($class))
139
            ->makePublic()
140
            ->getNode();
141 17
        $pros[] = $this->factory
142
            ->property('classAnnotations')
143 17
            ->setDefault($this->getClassAnnotation($class))
144 17
            ->makePublic()
145 17
            ->getNode();
146 17
        $pros[] = $this->factory
147 17
            ->property('isAspect')
148 17
            ->makePrivate()
149 17
            ->setDefault(true)
150 17
            ->getNode();
151
152
        return $pros;
153 17
    }
154
155
    private function getMethodAnnotations(\ReflectionClass $class) : string
156
    {
157
        $methodsAnnotation = [];
158
        $methods = $class->getMethods();
159 17
        foreach ($methods as $method) {
160
            $annotations = $this->reader->getMethodAnnotations($method);
161 17
            if ($annotations === []) {
162 17
                continue;
163 17
            }
164 17
            $methodsAnnotation[$method->name] = $annotations;
165 17
        }
166 17
167 17
        return serialize($methodsAnnotation);
168 17
    }
169 17
170 17
    /**
171
     * @return null|string
172
     */
173 17
    private function getNamespace(CodeVisitor $source)
174
    {
175
        $parts = $source->namespace->name->parts ?? [];
176 17
        $ns = implode('\\', $parts);
177
178 17
        return $ns ? $ns : null;
179 17
    }
180
}
181