Completed
Push — 2.x ( 41234f...52dad4 )
by Akihito
13s queued 10s
created

src/CodeGen.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use Doctrine\Common\Annotations\AnnotationException;
8
use Doctrine\Common\Annotations\AnnotationReader;
9
use PhpParser\BuilderFactory;
10
use PhpParser\Node\Identifier;
11
use PhpParser\Node\Name;
12
use PhpParser\Node\Stmt;
13
use PhpParser\Node\Stmt\Class_;
14
use PhpParser\Node\Stmt\Property;
15
use PhpParser\NodeTraverser;
16
use PhpParser\Parser;
17
use Ray\Aop\Exception\InvalidSourceClassException;
18
use ReflectionClass;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Ray\Aop\ReflectionClass.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
use RuntimeException;
20
21
use function array_merge;
22
use function assert;
23
use function file_get_contents;
24
use function get_class;
25
use function implode;
26
use function is_array;
27
use function is_bool;
28
use function serialize;
29
30
final class CodeGen implements CodeGenInterface
31
{
32
    /** @var Parser */
33
    private $parser;
34
35
    /** @var BuilderFactory */
36
    private $factory;
37
38
    /** @var CodeGenMethod */
39
    private $codeGenMethod;
40
41
    /** @var AnnotationReader */
42
    private $reader;
43
44
    /** @var AopClassName */
45
    private $aopClassName;
46
47
    /**
48
     * @throws AnnotationException
49
     */
50
    public function __construct(
51
        Parser $parser,
52
        BuilderFactory $factory,
53
        AopClassName $aopClassName
54 33
    ) {
55
        $this->parser = $parser;
56
        $this->factory = $factory;
57
        $this->codeGenMethod = new CodeGenMethod($parser);
58
        $this->reader = new AnnotationReader();
59 33
        $this->aopClassName = $aopClassName;
60 33
    }
61 33
62 33
    /**
63 33
     * {@inheritdoc}
64 33
     *
65
     * @param ReflectionClass<object> $sourceClass
0 ignored issues
show
The doc-type ReflectionClass<object> could not be parsed: Expected "|" or "end of type", but got "<" at position 15. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
66
     */
67
    public function generate(ReflectionClass $sourceClass, BindInterface $bind): Code
68
    {
69 17
        $source = $this->getVisitorCode($sourceClass);
70
        assert($source->class instanceof Class_);
71 17
        $methods = $this->codeGenMethod->getMethods($bind, $source);
72 17
        $propStms = $this->getAopProps($sourceClass);
73 17
        $classStm = $source->class;
74 17
        $newClassName = ($this->aopClassName)((string) $source->class->name, $bind->toString(''));
75
        $classStm->name = new Identifier($newClassName);
76 16
        $classStm->extends = new Name('\\' . $sourceClass->name);
77
        $classStm->implements[] = new Name('WeavedInterface');
78
        /** @var array<int, Stmt> $stmts */
79
        $stmts = array_merge($propStms, $methods);
80
        $classStm->stmts = $stmts;
81
        $ns = $this->getNamespace($source);
82
        $stmt = $this->factory->namespace($ns)
83
            ->addStmt($this->factory->use(WeavedInterface::class))
84 17
            ->addStmt($this->factory->use(ReflectiveMethodInvocation::class)->as('Invocation'))
85
            ->addStmts($source->use)
86 17
            ->addStmt($classStm)
87 17
            ->getNode();
88 17
89 17
        return new Code(array_merge($source->declare, [$stmt]));
90 17
    }
91 1
92
    /**
93 16
     * Return "declare()" and "use" statement code
94 16
     *
95
     * @param ReflectionClass<object> $class
0 ignored issues
show
The doc-type ReflectionClass<object> could not be parsed: Expected "|" or "end of type", but got "<" at position 15. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
96
     */
97 16
    private function getVisitorCode(ReflectionClass $class): CodeVisitor
98 16
    {
99 16
        $traverser = new NodeTraverser();
100
        $visitor = new CodeVisitor();
101
        $traverser->addVisitor($visitor);
102 16
        $fileName = $class->getFileName();
103
        if (is_bool($fileName)) {
104
            throw new InvalidSourceClassException(get_class($class));
105
        }
106
107
        $file = file_get_contents($fileName);
108 17
        if ($file === false) {
109
            throw new RuntimeException($fileName); // @codeCoverageIgnore
110 17
        }
111
112 17
        $stmts = $this->parser->parse($file);
113 17
        if (is_array($stmts)) {
114 17
            $traverser->traverse($stmts);
115 17
        }
116 17
117
        return $visitor;
118 17
    }
119
120
    /**
121
     * @param ReflectionClass<object> $class
0 ignored issues
show
The doc-type ReflectionClass<object> could not be parsed: Expected "|" or "end of type", but got "<" at position 15. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
122
     */
123
    private function getClassAnnotation(ReflectionClass $class): string
124 17
    {
125
        $classAnnotations = $this->reader->getClassAnnotations($class);
126 17
127 17
        return serialize($classAnnotations);
128 10
    }
129
130
    /**
131 17
     * @param ReflectionClass<object> $class
0 ignored issues
show
The doc-type ReflectionClass<object> could not be parsed: Expected "|" or "end of type", but got "<" at position 15. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
132
     *
133
     * @return Property[]
134 17
     */
135
    private function getAopProps(ReflectionClass $class): array
136 17
    {
137
        $pros = [];
138 17
        $pros[] = $this->factory
139
            ->property('bind')
140
            ->makePublic()
141 17
            ->getNode();
142
143 17
        $pros[] =
144 17
            $this->factory->property('bindings')
145 17
                ->makePublic()
146 17
                ->setDefault([])
147 17
                ->getNode();
148 17
149 17
        $pros[] = $this->factory
150 17
            ->property('methodAnnotations')
151
            ->setDefault($this->getMethodAnnotations($class))
152
            ->makePublic()
153 17
            ->getNode();
154
        $pros[] = $this->factory
155
            ->property('classAnnotations')
156
            ->setDefault($this->getClassAnnotation($class))
157
            ->makePublic()
158
            ->getNode();
159 17
        $pros[] = $this->factory
160
            ->property('isAspect')
161 17
            ->makePrivate()
162 17
            ->setDefault(true)
163 17
            ->getNode();
164 17
165 17
        return $pros;
166 17
    }
167 17
168 17
    /**
169 17
     * @param ReflectionClass<object> $class
0 ignored issues
show
The doc-type ReflectionClass<object> could not be parsed: Expected "|" or "end of type", but got "<" at position 15. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
170 17
     */
171
    private function getMethodAnnotations(ReflectionClass $class): string
172
    {
173 17
        $methodsAnnotation = [];
174
        $methods = $class->getMethods();
175
        foreach ($methods as $method) {
176 17
            $annotations = $this->reader->getMethodAnnotations($method);
177
            if ($annotations === []) {
178 17
                continue;
179 17
            }
180 17
181 16
            $methodsAnnotation[$method->name] = $annotations;
182 16
        }
183 13
184
        return serialize($methodsAnnotation);
185 4
    }
186
187
    /**
188 17
     * @return string|null
189
     */
190
    private function getNamespace(CodeVisitor $source)
191 17
    {
192
        $parts = $source->namespace->name->parts ?? [];
193
        $ns = implode('\\', $parts);
194 17
195 17
        return $ns ? $ns : null;
196 17
    }
197
}
198