Completed
Push — cs ( 9c9fc7 )
by Akihito
01:43
created

CodeGenMethod::getMethodInsideStatement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 5
cp 0
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use _HumbugBox90c4dcb919ed\Symfony\Component\Console\Exception\LogicException;
8
use Doctrine\Common\Annotations\AnnotationReader;
9
use PhpParser\Builder\Method;
10
use PhpParser\Builder\Param;
11
use PhpParser\BuilderFactory;
12
use PhpParser\Comment\Doc;
13
use PhpParser\Node;
14
use PhpParser\Node\NullableType;
15
use PhpParser\Node\Stmt\Class_;
16
use PhpParser\Node\Stmt\ClassMethod;
17
use PhpParser\NodeTraverser;
18
use PhpParser\Parser;
19
use PhpParser\PrettyPrinter\Standard;
20
use Ray\Aop\Annotation\AbstractAssisted;
21
use function is_string;
22
23
final class CodeGenMethod
24
{
25
    /**
26
     * @var \PhpParser\Parser
27
     */
28
    private $parser;
29
30
    /**
31
     * @var \PhpParser\BuilderFactory
32
     */
33
    private $factory;
34
35
    /**
36
     * @var \PhpParser\PrettyPrinter\Standard
37
     */
38
    private $printer;
39
40
    private $reader;
41
42
    /**
43
     * @var AbstractAssisted
44
     */
45
    private $assisted;
46
47 4
    public function __construct(
48
        Parser $parser,
49
        BuilderFactory $factory,
50
        Standard $printer
51
    ) {
52 4
        $this->parser = $parser;
53 4
        $this->factory = $factory;
54 4
        $this->printer = $printer;
55 4
        $this->reader = new AnnotationReader;
56 4
    }
57
58 2
    public function getMethods(\ReflectionClass $class, BindInterface $bind) : array
59
    {
60 2
        $bindingMethods = array_keys($bind->getBindings());
61 2
        $stmts = [];
62 2
        $methods = $class->getMethods();
63 2
        foreach ($methods as $method) {
64 1
            $this->assisted = $this->reader->getMethodAnnotation($method, AbstractAssisted::class);
65 1
            $isBindingMethod = in_array($method->name, $bindingMethods, true);
66
            /* @var $method \ReflectionMethod */
67 1
            if ($isBindingMethod && $method->isPublic()) {
68 1
                $stmts[] = $this->getMethod($method);
69
            }
70
        }
71
72 2
        return $stmts;
73
    }
74
75
    /**
76
     * Return method statement
77
     *
78
     * @return \PhpParser\Node\Stmt\ClassMethod
79
     */
80
    private function getMethod(\ReflectionMethod $method)
81
    {
82
        $methodStmt = $this->factory->method($method->name);
83
        $params = $method->getParameters();
84
        foreach ($params as $param) {
85
            $methodStmt = $this->getMethodStatement($param, $methodStmt);
86
        }
87
        $returnType = $method->getReturnType();
88
        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...
89
            $this->setReturnType($returnType, $methodStmt);
90
        }
91
        $methodInsideStatements = $this->getMethodInsideStatement($method);
92
        $methodStmt->addStmts($methodInsideStatements);
93
94
        return $this->addMethodDocComment($methodStmt, $method);
95
    }
96
97
    /**
98
     * Return parameter reflection
99
     */
100
    private function getMethodStatement(\ReflectionParameter $param, Method $methodStmt) : Method
101
    {
102
        /* @var $paramStmt Param */
103
        $paramStmt = $this->factory->param($param->name);
104
        /* @var $param \ReflectionParameter */
105
        $this->setParameterType($param, $paramStmt);
106
        $this->setDefault($param, $paramStmt);
107
        $methodStmt->addParam($paramStmt);
108
109
        return $methodStmt;
110
    }
111
112
    private function addMethodDocComment(Method $methodStmt, \ReflectionMethod $method) : ClassMethod
113
    {
114
        $node = $methodStmt->getNode();
115
        $docComment = $method->getDocComment();
116
        if ($docComment) {
117
            $node->setAttribute('comments', [new Doc($docComment)]);
118
        }
119
120
        return $node;
121
    }
122
123
    /**
124
     * @return \PhpParser\Node[]
125
     */
126
    private function getMethodInsideStatement(\ReflectionMethod $method) : array
127
    {
128
        $traverser = new NodeTraverser;
129
        $traverser->addVisitor(new AopTemplateConverter($method));
130
        $stmts = $this->getTemplateMethodNodeStmts();
131
132
        // traverse
133
        return $traverser->traverse($stmts);
134
    }
135
136
    private function setDefault(\ReflectionParameter $param, Param $paramStmt)
137
    {
138
        if ($param->isDefaultValueAvailable()) {
139
            $paramStmt->setDefault($param->getDefaultValue());
140
141
            return;
142
        }
143
        if ($this->assisted instanceof AbstractAssisted && in_array($param->name, $this->assisted->values, true)) {
144
            $paramStmt->setDefault(null);
145
        }
146
    }
147
148
    private function setParameterType(\ReflectionParameter $param, Param $paramStmt)
149
    {
150
        $type = $param->getType();
151
        if ($type == null) {
152
            return;
153
        }
154
        if ($param->isVariadic()) {
155
            $paramStmt->makeVariadic();
156
        }
157
        $paramString = (string) $param;
158
        $isNullableType = is_int(strpos($paramString, '<required>')) && is_int(strpos($paramString, 'or NULL'));
159
        $destType = $isNullableType ? new NullableType((string) $type) : (string) $type;
160
        $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...
161
    }
162
163
    private function setReturnType(\ReflectionType $returnType, Method $methodStmt)
164
    {
165
        $type = $returnType->allowsNull() ? new NullableType((string) $returnType) : (string) $returnType;
166
        $methodStmt->setReturnType($type);
167
    }
168
169
    /**
170
     * @return Node[]
171
     */
172
    private function getTemplateMethodNodeStmts() : array
173
    {
174
        $templateFile = dirname(__DIR__) . '/template/AopTemplate.php';
175
        $code = file_get_contents($templateFile);
176
        if (! is_string($code)) {
177
            throw new LogicException; // @codeCoverageIgnore
178
        }
179
        /** @var string $code */
180
        $node = $this->parser->parse($code)[0];
181
        if (! $node instanceof Class_) {
182
            throw new \LogicException; // @codeCoverageIgnore
183
        }
184
        $methodNode = $node->getMethods()[0];
185
        if ($methodNode->stmts === null) {
186
            throw new \LogicException; // @codeCoverageIgnore
187
        }
188
189
        return $methodNode->stmts;
190
    }
191
}
192