Completed
Push — 2.x ( 4496a3...4e8f8c )
by Akihito
02:32
created

src/CodeGenMethod.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Parser;
19
use PhpParser\PrettyPrinter\Standard;
20
use Ray\Aop\Annotation\AbstractAssisted;
21
22
final class CodeGenMethod
23
{
24
    /**
25
     * @var \PhpParser\Parser
26
     */
27
    private $parser;
28
29
    /**
30
     * @var \PhpParser\BuilderFactory
31
     */
32
    private $factory;
33
34
    /**
35
     * @var \PhpParser\PrettyPrinter\Standard
36
     */
37
    private $printer;
38
39
    private $reader;
40
41
    /**
42
     * @var AbstractAssisted
43
     */
44
    private $assisted = [];
45
46
    /**
47
     * @param \PhpParser\Parser                 $parser
48
     * @param \PhpParser\BuilderFactory         $factory
49
     * @param \PhpParser\PrettyPrinter\Standard $printer
50
     */
51 29
    public function __construct(
52
        Parser $parser,
53
        BuilderFactory $factory,
54
        Standard $printer
55
    ) {
56 29
        $this->parser = $parser;
57 29
        $this->factory = $factory;
58 29
        $this->printer = $printer;
59 29
        $this->reader = new AnnotationReader;
60 29
    }
61
62
    /**
63
     * @param \ReflectionClass $class
64
     * @param BindInterface    $bind
65
     *
66
     * @return array
67
     */
68 13
    public function getMethods(\ReflectionClass $class, BindInterface $bind) : array
69
    {
70 13
        $bindingMethods = array_keys($bind->getBindings());
71 13
        $stmts = [];
72 13
        $methods = $class->getMethods();
73 13
        foreach ($methods as $method) {
74 13
            $this->assisted = $this->reader->getMethodAnnotation($method, AbstractAssisted::class);
75 13
            $isBindingMethod = in_array($method->name, $bindingMethods, true);
76
            /* @var $method \ReflectionMethod */
77 13
            if ($isBindingMethod && $method->isPublic()) {
78 13
                $stmts[] = $this->getMethod($method);
79
            }
80
        }
81
82 13
        return $stmts;
83
    }
84
85
    /**
86
     * Return method statement
87
     *
88
     * @param \ReflectionMethod $method
89
     *
90
     * @return \PhpParser\Node\Stmt\ClassMethod
91
     */
92 12
    private function getMethod(\ReflectionMethod $method)
93
    {
94 12
        $methodStmt = $this->factory->method($method->name);
95 12
        $params = $method->getParameters();
96 12
        foreach ($params as $param) {
97 10
            $methodStmt = $this->getMethodStatement($param, $methodStmt);
98
        }
99 12
        $returnType = $method->getReturnType();
100 12
        if ($returnType instanceof \ReflectionType) {
101 4
            $this->setReturnType($returnType, $methodStmt);
102
        }
103 12
        $methodInsideStatements = $this->getMethodInsideStatement();
104 12
        $methodStmt->addStmts($methodInsideStatements);
105
106 12
        return $this->addMethodDocComment($methodStmt, $method);
107
    }
108
109
    /**
110
     * Return parameter reflection
111
     */
112 10
    private function getMethodStatement(\ReflectionParameter $param, Method $methodStmt) : Method
113
    {
114
        /** @var $paramStmt Param */
115 10
        $paramStmt = $this->factory->param($param->name);
116
        /* @var $param \ReflectionParameter */
117 10
        $this->setParameterType($param, $paramStmt);
118 10
        $this->setDefault($param, $paramStmt);
119 10
        $methodStmt->addParam($paramStmt);
120
121 10
        return $methodStmt;
122
    }
123
124 12
    private function addMethodDocComment(Method $methodStmt, \ReflectionMethod $method) : ClassMethod
125
    {
126 12
        $node = $methodStmt->getNode();
127 12
        $docComment = $method->getDocComment();
128 12
        if ($docComment) {
129 5
            $node->setAttribute('comments', [new Doc($docComment)]);
130
        }
131
132 12
        return $node;
133
    }
134
135
    /**
136
     * @return \PhpParser\Node[]
137
     */
138 12
    private function getMethodInsideStatement() : array
139
    {
140 12
        $code = file_get_contents(dirname(__DIR__) . '/template/AopTemplate.php');
141 12
        $node = $this->parser->parse($code)[0];
142
        /** @var $node \PhpParser\Node\Stmt\Class_ */
143 12
        $node = $node->getMethods()[0];
144
145 12
        return $node->stmts;
146
    }
147
148
    /**
149
     * @codeCoverageIgnore
150
     */
151
    private function setTypeHint(\ReflectionParameter $param, Param $paramStmt, \ReflectionClass $typeHint = null) : void
0 ignored issues
show
This method is not used, and could be removed.
Loading history...
152
    {
153
        if ($typeHint) {
154
            $paramStmt->setTypeHint($typeHint->name);
155
        }
156
        if ($param->isArray()) {
157
            $paramStmt->setTypeHint('array');
158
        }
159
        if ($param->isCallable()) {
160
            $paramStmt->setTypeHint('callable');
161
        }
162
    }
163
164 10
    private function setDefault(\ReflectionParameter $param, Param $paramStmt) : void
165
    {
166 10
        if ($param->isDefaultValueAvailable()) {
167 2
            $paramStmt->setDefault($param->getDefaultValue());
168
169 2
            return;
170
        }
171 10
        if ($this->assisted && in_array($param->name, $this->assisted->values, true)) {
172 1
            $paramStmt->setDefault(null);
173
        }
174 10
    }
175
176 10
    private function setParameterType(\ReflectionParameter $param, Param $paramStmt) : void
177
    {
178 10
        $type = $param->getType();
179 10
        if ($type) {
180 5
            $paramStmt->setTypeHint((string) $type);
181
        }
182 10
    }
183
184 4
    private function setReturnType(\ReflectionType $returnType, Method $methodStmt) : void
185
    {
186 4
        $type = $returnType->allowsNull() ? new NullableType($returnType->getName()) : $returnType->getName();
187 4
        if ($returnType && method_exists($methodStmt, 'setReturnType')) {
188
            $methodStmt->setReturnType($type); // @codeCoverageIgnore
189
        }
190 4
    }
191
}
192