Completed
Pull Request — 2.x (#91)
by Akihito
02:24
created

CodeGenMethod::getMethodInsideStatement()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.3906

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 12
cts 16
cp 0.75
rs 9.2088
c 0
b 0
f 0
cc 5
nc 5
nop 1
crap 5.3906
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
        if (is_bool($code)) {
146
            throw new \LogicException;
147
        }
148 15
        $node = $this->parser->parse($code)[0];
149 15
        if (! $node instanceof \PhpParser\Node\Stmt\Class_) {
150
            throw new \LogicException;
151
        }
152 15
        $methodNode = $node->getMethods()[0];
153 15
        if (! $methodNode instanceof ClassMethod) {
154
            throw new \LogicException;
155
        }
156 15
        if ($methodNode->stmts === null) {
157
            throw new \LogicException;
158
        }
159
        // traverse
160 15
        $stmts = $traverser->traverse($methodNode->stmts);
161
162 15
        return $stmts;
163
    }
164
165 13
    private function setDefault(\ReflectionParameter $param, Param $paramStmt)
166
    {
167 13
        if ($param->isDefaultValueAvailable()) {
168 3
            $paramStmt->setDefault($param->getDefaultValue());
169
170 3
            return;
171
        }
172 13
        if ($this->assisted instanceof AbstractAssisted && in_array($param->name, $this->assisted->values, true)) {
173 1
            $paramStmt->setDefault(null);
174
        }
175 13
    }
176
177 13
    private function setParameterType(\ReflectionParameter $param, Param $paramStmt)
178
    {
179 13
        $type = $param->getType();
180 13
        if ($type == null) {
181 8
            return;
182
        }
183 7
        if ($param->isVariadic()) {
184 1
            $paramStmt->makeVariadic();
185
        }
186 7
        $paramString = (string) $param;
187 7
        $isNullableType = is_int(strpos($paramString, '<required>')) && is_int(strpos($paramString, 'or NULL'));
188 7
        $destType = $isNullableType ? new NullableType((string) $type) : (string) $type;
189 7
        $paramStmt->setTypeHint($destType);
190 7
    }
191
192 5
    private function setReturnType(\ReflectionType $returnType, Method $methodStmt)
193
    {
194 5
        $type = $returnType->allowsNull() ? new NullableType((string) $returnType) : (string) $returnType;
195 5
        $methodStmt->setReturnType($type);
196 5
    }
197
}
198