Completed
Pull Request — 2.x (#97)
by Akihito
03:57 queued 02:44
created

CodeGenMethod::getTemplateCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 6
cts 6
cp 1
rs 9.264
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use PhpParser\Builder\Method;
9
use PhpParser\Builder\Param;
10
use PhpParser\BuilderFactory;
11
use PhpParser\Comment\Doc;
12
use PhpParser\Node;
13
use PhpParser\Node\NullableType;
14
use PhpParser\Node\Stmt\Class_;
15
use PhpParser\Node\Stmt\ClassMethod;
16
use PhpParser\NodeTraverser;
17
use PhpParser\Parser;
18
use PhpParser\PrettyPrinter\Standard;
19
use Ray\Aop\Annotation\AbstractAssisted;
20
21
final class CodeGenMethod
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
    private $reader;
39
40
    /**
41
     * @var AbstractAssisted
42
     */
43
    private $assisted;
44
45
    public function __construct(
46
        Parser $parser,
47
        BuilderFactory $factory,
48
        Standard $printer
49
    ) {
50
        $this->parser = $parser;
51
        $this->factory = $factory;
52
        $this->printer = $printer;
53
        $this->reader = new AnnotationReader;
54
    }
55
56 33
    public function getMethods(\ReflectionClass $class, BindInterface $bind) : array
57
    {
58
        $bindingMethods = array_keys($bind->getBindings());
59
        $stmts = [];
60
        $methods = $class->getMethods();
61 33
        foreach ($methods as $method) {
62 33
            $this->assisted = $this->reader->getMethodAnnotation($method, AbstractAssisted::class);
63 33
            $isBindingMethod = in_array($method->name, $bindingMethods, true);
64 33
            /* @var $method \ReflectionMethod */
65 33
            if ($isBindingMethod && $method->isPublic()) {
66
                $stmts[] = $this->getMethod($method);
67
            }
68
        }
69
70
        return $stmts;
71
    }
72
73 17
    /**
74
     * Return method statement
75 17
     *
76 17
     * @return \PhpParser\Node\Stmt\ClassMethod
77 17
     */
78 17
    private function getMethod(\ReflectionMethod $method)
79 16
    {
80 16
        $methodStmt = $this->factory->method($method->name);
81
        $params = $method->getParameters();
82 16
        foreach ($params as $param) {
83 16
            $methodStmt = $this->getMethodStatement($param, $methodStmt);
84
        }
85
        $returnType = $method->getReturnType();
86
        if ($returnType instanceof \ReflectionType) {
0 ignored issues
show
Bug introduced by
The class ReflectionType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
87 17
            $this->setReturnType($returnType, $methodStmt);
88
        }
89
        $methodInsideStatements = $this->getMethodInsideStatement($method);
90
        $methodStmt->addStmts($methodInsideStatements);
91
92
        return $this->addMethodDocComment($methodStmt, $method);
93
    }
94
95
    /**
96
     * Return parameter reflection
97 15
     */
98
    private function getMethodStatement(\ReflectionParameter $param, Method $methodStmt) : Method
99 15
    {
100 15
        /* @var $paramStmt Param */
101 15
        $paramStmt = $this->factory->param($param->name);
102 13
        /* @var $param \ReflectionParameter */
103
        $this->setParameterType($param, $paramStmt);
104 15
        $this->setDefault($param, $paramStmt);
105 15
        $methodStmt->addParam($paramStmt);
106 5
107
        return $methodStmt;
108 15
    }
109 15
110
    private function addMethodDocComment(Method $methodStmt, \ReflectionMethod $method) : ClassMethod
111 15
    {
112
        $node = $methodStmt->getNode();
113
        $docComment = $method->getDocComment();
114
        if ($docComment) {
115
            $node->setAttribute('comments', [new Doc($docComment)]);
116
        }
117 13
118
        return $node;
119
    }
120 13
121
    /**
122 13
     * @return \PhpParser\Node[]
123 13
     */
124 13
    private function getMethodInsideStatement(\ReflectionMethod $method) : array
125
    {
126 13
        $traverser = new NodeTraverser;
127
        $traverser->addVisitor(new AopTemplateConverter($method));
128
        $stmts = $this->getTemplateMethodNodeStmts();
129 15
130
        // traverse
131 15
        return $traverser->traverse($stmts);
132 15
    }
133 15
134 6
    private function setDefault(\ReflectionParameter $param, Param $paramStmt)
135
    {
136
        if ($param->isDefaultValueAvailable()) {
137 15
            $paramStmt->setDefault($param->getDefaultValue());
138
139
            return;
140
        }
141
        if ($this->assisted instanceof AbstractAssisted && in_array($param->name, $this->assisted->values, true)) {
142
            $paramStmt->setDefault(null);
143 15
        }
144
    }
145 15
146 15
    private function setParameterType(\ReflectionParameter $param, Param $paramStmt)
147 15
    {
148
        $type = $param->getType();
149
        if ($type == null) {
150 15
            return;
151
        }
152 15
        if ($param->isVariadic()) {
153
            $paramStmt->makeVariadic();
154
        }
155 13
        $paramString = (string) $param;
156
        $isNullableType = is_int(strpos($paramString, '<required>')) && is_int(strpos($paramString, 'or NULL'));
157 13
        $destType = $isNullableType ? new NullableType((string) $type) : (string) $type;
158 3
        $paramStmt->setTypeHint($destType);
0 ignored issues
show
Deprecated Code introduced by
The method PhpParser\Builder\Param::setTypeHint() has been deprecated with message: Use setType() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
159
    }
160 3
161
    private function setReturnType(\ReflectionType $returnType, Method $methodStmt)
162 13
    {
163 1
        $type = $returnType->allowsNull() ? new NullableType((string) $returnType) : (string) $returnType;
164
        $methodStmt->setReturnType($type);
165 13
    }
166
167 13
    /**
168
     * @return Node[]
169 13
     */
170 13
    private function getTemplateMethodNodeStmts() : array
171 8
    {
172
        $code = $this->getTemplateCode();
173 7
        $node = $this->parser->parse($code)[0];
174 1
        if (! $node instanceof Class_) {
175
            throw new \LogicException; // @codeCoverageIgnore
176 7
        }
177 7
        $methodNode = $node->getMethods()[0];
178 7
        if ($methodNode->stmts === null) {
179 7
            throw new \LogicException; // @codeCoverageIgnore
180 7
        }
181
182 5
        return $methodNode->stmts;
183
    }
184 5
185 5
    /**
186 5
     * Return CodeGenTemplate string
187
     *
188
     * Compiler takes only the statements in the method. Then create new inherit code with interceptors.
189
     *
190
     * @see http://paul-m-jones.com/archives/182
191 15
     * @see http://stackoverflow.com/questions/8343399/calling-a-function-with-explicit-parameters-vs-call-user-func-array
192
     * @see http://stackoverflow.com/questions/1796100/what-is-faster-many-ifs-or-else-if
193 15
     * @see http://stackoverflow.com/questions/2401478/why-is-faster-than-in-php
194 15
     */
195 15
    private function getTemplateCode() : string
196
    {
197
        return <<<'EOT'
198
<?php
199 15
class AopTemplate extends \Ray\Aop\FakeMock implements Ray\Aop\WeavedInterface
200 15
{
201
    /**
202
     * @var array
203 15
     *
204 15
     * [$methodName => [$interceptorA[]][]
205
     */
206
    public $bindings;
207
208 15
    /**
209
     * @var bool
210
     */
211
    private $isIntercepting = true;
212
213
    /**
214
     * Method Template
215
     *
216
     * @param mixed $a
217
     */
218
    public function templateMethod($a, $b)
219
    {
220
        if ($this->isIntercepting === false) {
221
            $this->isIntercepting = true;
222
223
            return parent::templateMethod($a, $b);
224
        }
225
226
        $this->isIntercepting = false;
227
        // invoke interceptor
228
        $result = (new \Ray\Aop\ReflectiveMethodInvocation($this, __FUNCTION__, [$a, $b], $this->bindings[__FUNCTION__]))->proceed();
229
        $this->isIntercepting = true;
230
231
        return $result;
232
    }
233
}
234
EOT;
235
    }
236
}
237