Completed
Push — 2.x ( 867c01...cbcabc )
by Akihito
07:06
created

CodeGenMethod   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 100%

Importance

Changes 14
Bugs 4 Features 1
Metric Value
wmc 26
lcom 1
cbo 10
dl 0
loc 205
c 14
b 4
f 1
ccs 66
cts 66
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A getMethods() 0 16 4
A getMethod() 0 13 2
A getMethodStatement() 0 16 2
A addMethodDocComment() 0 10 2
A getMethodInsideStatement() 0 9 1
A setTypeHint() 0 12 4
A setDefault() 0 11 4
A setParameterType() 0 12 3
A setReturnType() 0 7 3
1
<?php
2
/**
3
 * This file is part of the Ray.Aop package
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
namespace Ray\Aop;
8
9
use Doctrine\Common\Annotations\AnnotationReader;
10
use PHPParser\Builder\Method;
11
use PhpParser\Builder\Param;
12
use PhpParser\BuilderFactory;
13
use PhpParser\Comment\Doc;
14
use PhpParser\Parser;
15
use PhpParser\PrettyPrinter\Standard;
16
use Ray\Aop\Annotation\AbstractAssisted;
17
18
final class CodeGenMethod
19
{
20
    /**
21
     * @var \PHPParser\Parser
22
     */
23
    private $parser;
24
25
    /**
26
     * @var \PHPParser\BuilderFactory
27
     */
28
    private $factory;
29
30
    /**
31
     * @var \PHPParser\PrettyPrinter\Standard
32
     */
33
    private $printer;
34
35
    private $reader;
36
37
    /**
38
     * @var AbstractAssisted
39
     */
40
    private $assisted = [];
41
42
    /**
43
     * @param \PHPParser\Parser                 $parser
44
     * @param \PHPParser\BuilderFactory         $factory
45
     * @param \PHPParser\PrettyPrinter\Standard $printer
46
     */
47 24
    public function __construct(
48
        Parser $parser,
49
        BuilderFactory $factory,
50
        Standard $printer
51
    ) {
52 24
        $this->parser = $parser;
53 24
        $this->factory = $factory;
54 24
        $this->printer = $printer;
55 24
        $this->reader = new AnnotationReader;
56 24
    }
57
58
    /**
59
     * @param \ReflectionClass $class
60
     *
61
     * @return array
62
     */
63 9
    public function getMethods(\ReflectionClass $class, BindInterface $bind)
64
    {
65 9
        $bindingMethods = array_keys($bind->getBindings());
66 9
        $stmts = [];
67 9
        $methods = $class->getMethods();
68 9
        foreach ($methods as $method) {
69 9
            $this->assisted = $this->reader->getMethodAnnotation($method, AbstractAssisted::class);
70 9
            $isBindingMethod = in_array($method->getName(), $bindingMethods);
71
            /* @var $method \ReflectionMethod */
72 9
            if ($isBindingMethod && $method->isPublic()) {
73 8
                $stmts[] = $this->getMethod($method);
74 8
            }
75 9
        }
76
77 9
        return $stmts;
78
    }
79
80
    /**
81
     * Return method statement
82
     *
83
     * @param \ReflectionMethod $method
84
     *
85
     * @return \PhpParser\Node\Stmt\ClassMethod
86
     */
87 8
    private function getMethod(\ReflectionMethod $method)
88
    {
89 8
        $methodStmt = $this->factory->method($method->name);
90 8
        $params = $method->getParameters();
91 8
        foreach ($params as $param) {
92 8
            $methodStmt = $this->getMethodStatement($param, $methodStmt);
93 8
        }
94 8
        $methodInsideStatements = $this->getMethodInsideStatement();
95 8
        $methodStmt->addStmts($methodInsideStatements);
96 8
        $node = $this->addMethodDocComment($methodStmt, $method);
97
98 8
        return $node;
99
    }
100
101
    /**
102
     * Return parameter reflection
103
     *
104
     * @param \ReflectionParameter      $param
105
     * @param \PHPParser\Builder\Method $methodStmt
106
     *
107
     * @return \PHPParser\Builder\Method
108
     */
109 8
    private function getMethodStatement(\ReflectionParameter $param, Method $methodStmt)
110
    {
111
        $isOverPhp7 = version_compare(PHP_VERSION, '7.0.0') >= 0;
112 8
        /** @var $paramStmt Param */
113
        $paramStmt = $this->factory->param($param->name);
114 8
        /* @var $param \ReflectionParameter */
115 8
        $typeHint = $param->getClass();
116 8
        $this->setParameterType($param, $paramStmt, $isOverPhp7, $typeHint);
117 8
        $this->setDefault($param, $paramStmt);
118
        if ($isOverPhp7) {
119 8
            $this->setReturnType($param, $methodStmt, $isOverPhp7);
0 ignored issues
show
Unused Code introduced by
The call to CodeGenMethod::setReturnType() has too many arguments starting with $isOverPhp7.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
120
        }
121
        $methodStmt->addParam($paramStmt);
122
123
        return $methodStmt;
124
    }
125
126
    /**
127
     * @param Method            $methodStmt
128 8
     * @param \ReflectionMethod $method
129
     *
130 8
     * @return \PhpParser\Node\Stmt\ClassMethod
131 8
     */
132 8
    private function addMethodDocComment(Method $methodStmt, \ReflectionMethod $method)
133 5
    {
134 5
        $node = $methodStmt->getNode();
135
        $docComment = $method->getDocComment();
136 8
        if ($docComment) {
137
            $node->setAttribute('comments', [new Doc($docComment)]);
138
        }
139
140
        return $node;
141
    }
142 8
143
    /**
144 8
     * @return \PHPParser\Node[]
145 8
     */
146
    private function getMethodInsideStatement()
147 8
    {
148
        $code = file_get_contents(dirname(__DIR__) . '/src-data/CodeGenTemplate.php');
149 8
        $node = $this->parser->parse($code)[0];
150
        /** @var $node \PHPParser\Node\Stmt\Class_ */
151
        $node = $node->getMethods()[0];
152
153
        return $node->stmts;
154
    }
155
156
    /**
157 8
     * @param \ReflectionParameter $param
158
     * @param Param                $paramStmt
159 8
     * @param \ReflectionClass     $typeHint
160 2
     *
161 2
     * @codeCoverageIgnore
162 8
     */
163 1
    private function setTypeHint(\ReflectionParameter $param, Param $paramStmt, \ReflectionClass $typeHint = null)
164 1
    {
165 8
        if ($typeHint) {
166 1
            $paramStmt->setTypeHint($typeHint->name);
167 1
        }
168 8
        if ($param->isArray()) {
169
            $paramStmt->setTypeHint('array');
170
        }
171
        if ($param->isCallable()) {
172
            $paramStmt->setTypeHint('callable');
173
        }
174 8
    }
175
176 8
    /**
177 2
     * @param \ReflectionParameter $param
178
     * @param Param                $paramStmt
179 2
     */
180
    private function setDefault(\ReflectionParameter $param, $paramStmt)
181 8
    {
182 1
        if ($param->isDefaultValueAvailable()) {
183 1
            $paramStmt->setDefault($param->getDefaultValue());
184 8
185
            return;
186
        }
187
        if ($this->assisted && in_array($param->getName(), $this->assisted->values)) {
188
            $paramStmt->setDefault(null);
189
        }
190
    }
191
192
    /**
193
     * @param \ReflectionParameter $param
194
     * @param Param                $paramStmt
195
     * @param bool                 $isOverPhp7
196
     * @param \ReflectionClass     $typeHint
197
     */
198
    private function setParameterType(\ReflectionParameter $param, Param $paramStmt, $isOverPhp7, \ReflectionClass $typeHint = null)
199
    {
200
        if (! $isOverPhp7) {
201
            $this->setTypeHint($param, $paramStmt, $typeHint); // @codeCoverageIgnore
202
203
            return; // @codeCoverageIgnore
204
        }
205
        $type = $param->getType();
206
        if ($type) {
207
            $paramStmt->setTypeHint((string) $type);
208
        }
209
    }
210
211
    /**
212
     * @param \ReflectionParameter $param
213
     * @param Method $methodStmt
0 ignored issues
show
Coding Style introduced by
Expected 15 spaces after parameter type; 1 found
Loading history...
214
     */
215
    private function setReturnType(\ReflectionParameter $param, Method $methodStmt)
216
    {
217
        $returnType = $param->getDeclaringFunction()->getReturnType();
218
        if ($returnType && method_exists($methodStmt, 'setReturnType')) {
219
            $methodStmt->setReturnType((string)$returnType); // @codeCoverageIgnore
220
        }
221
    }
222
}
223