Completed
Push — variadic ( 64e2db )
by Akihito
01:41
created

CodeGenMethod::getMethodStatement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
cc 1
eloc 6
nc 1
nop 2
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 PhpParser\Builder\Method;
13
use PhpParser\Builder\Param;
14
use PhpParser\BuilderFactory;
15
use PhpParser\Comment\Doc;
16
use PhpParser\Node\NullableType;
17
use PhpParser\Node\Stmt\ClassMethod;
18
use PhpParser\NodeTraverser;
19
use PhpParser\Parser;
20
use PhpParser\PrettyPrinter\Standard;
21
use Ray\Aop\Annotation\AbstractAssisted;
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
    /**
48
     * @param \PhpParser\Parser                 $parser
49
     * @param \PhpParser\BuilderFactory         $factory
50
     * @param \PhpParser\PrettyPrinter\Standard $printer
51
     */
52 32
    public function __construct(
53
        Parser $parser,
54
        BuilderFactory $factory,
55
        Standard $printer
56
    ) {
57 32
        $this->parser = $parser;
58 32
        $this->factory = $factory;
59 32
        $this->printer = $printer;
60 32
        $this->reader = new AnnotationReader;
61 32
    }
62
63
    /**
64
     * @param \ReflectionClass $class
65
     * @param BindInterface    $bind
66
     *
67
     * @return array
68
     */
69 16
    public function getMethods(\ReflectionClass $class, BindInterface $bind) : array
70
    {
71 16
        $bindingMethods = array_keys($bind->getBindings());
72 16
        $stmts = [];
73 16
        $methods = $class->getMethods();
74 16
        foreach ($methods as $method) {
75 16
            $this->assisted = $this->reader->getMethodAnnotation($method, AbstractAssisted::class);
76 16
            $isBindingMethod = in_array($method->name, $bindingMethods, true);
77
            /* @var $method \ReflectionMethod */
78 16
            if ($isBindingMethod && $method->isPublic()) {
79 16
                $stmts[] = $this->getMethod($method);
80
            }
81
        }
82
83 16
        return $stmts;
84
    }
85
86
    /**
87
     * Return method statement
88
     *
89
     * @param \ReflectionMethod $method
90
     *
91
     * @return \PhpParser\Node\Stmt\ClassMethod
92
     */
93 15
    private function getMethod(\ReflectionMethod $method)
94
    {
95 15
        $methodStmt = $this->factory->method($method->name);
96 15
        $params = $method->getParameters();
97 15
        foreach ($params as $param) {
98 13
            $methodStmt = $this->getMethodStatement($param, $methodStmt);
99
        }
100 15
        $returnType = $method->getReturnType();
101 15
        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...
102 5
            $this->setReturnType($returnType, $methodStmt);
103
        }
104 15
        $methodInsideStatements = $this->getMethodInsideStatement($method);
105 15
        $methodStmt->addStmts($methodInsideStatements);
106
107 15
        return $this->addMethodDocComment($methodStmt, $method);
108
    }
109
110
    /**
111
     * Return parameter reflection
112
     */
113 13
    private function getMethodStatement(\ReflectionParameter $param, Method $methodStmt) : Method
114
    {
115
        /* @var $paramStmt Param */
116 13
        $paramStmt = $this->factory->param($param->name);
117
        /* @var $param \ReflectionParameter */
118 13
        $this->setParameterType($param, $paramStmt);
119 13
        $this->setDefault($param, $paramStmt);
120 13
        $methodStmt->addParam($paramStmt);
121
122 13
        return $methodStmt;
123
    }
124
125 15
    private function addMethodDocComment(Method $methodStmt, \ReflectionMethod $method) : ClassMethod
126
    {
127 15
        $node = $methodStmt->getNode();
128 15
        $docComment = $method->getDocComment();
129 15
        if ($docComment) {
130 6
            $node->setAttribute('comments', [new Doc($docComment)]);
131
        }
132
133 15
        return $node;
134
    }
135
136
    /**
137
     * @return \PhpParser\Node[]
138
     */
139 15
    private function getMethodInsideStatement(\ReflectionMethod $method) : array
140
    {
141 15
        $traverser = new NodeTraverser;
142 15
        $traverser->addVisitor(new AopTemplateConverter($method));
143
144 15
        $code = file_get_contents(dirname(__DIR__) . '/template/AopTemplate.php');
145 15
        $node = $this->parser->parse($code)[0];
146
        /* @var $node \PhpParser\Node\Stmt\Class_ */
147 15
        $node = $node->getMethods()[0];
148
        // traverse
149 15
        $stmts = $traverser->traverse($node->stmts);
150
151 15
        return $stmts;
152
    }
153
154
    /**
155
     * @codeCoverageIgnore
156
     */
157
    private function setTypeHint(\ReflectionParameter $param, Param $paramStmt, \ReflectionClass $typeHint = null)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
158
    {
159
        if ($typeHint) {
160
            $paramStmt->setTypeHint($typeHint->name);
161
        }
162
        if ($param->isArray()) {
163
            $paramStmt->setTypeHint('array');
164
        }
165
        if ($param->isCallable()) {
166
            $paramStmt->setTypeHint('callable');
167
        }
168
    }
169
170 13
    private function setDefault(\ReflectionParameter $param, Param $paramStmt)
171
    {
172 13
        if ($param->isDefaultValueAvailable()) {
173 3
            $paramStmt->setDefault($param->getDefaultValue());
174
175 3
            return;
176
        }
177 13
        if ($this->assisted && in_array($param->name, $this->assisted->values, true)) {
178 1
            $paramStmt->setDefault(null);
179
        }
180 13
    }
181
182 13
    private function setParameterType(\ReflectionParameter $param, Param $paramStmt)
183
    {
184 13
        $type = $param->getType();
185 13
        if (! $type) {
186 8
            return;
187
        }
188 7
        if ($param->isVariadic()) {
189 1
            $paramStmt->makeVariadic();
190
        }
191 7
        $paramString = (string) $param;
192 7
        $isNullableType = is_int(strpos($paramString, '<required>')) && strpos($paramString, 'or NULL');
193 7
        $destType = $isNullableType ? new NullableType((string) $type) : (string) $type;
194 7
        $paramStmt->setTypeHint($destType);
195 7
    }
196
197 5
    private function setReturnType(\ReflectionType $returnType, Method $methodStmt)
198
    {
199 5
        $type = $returnType->allowsNull() ? new NullableType((string) $returnType) : (string) $returnType;
200 5
        $methodStmt->setReturnType($type);
201 5
    }
202
}
203