Passed
Pull Request — master (#11)
by
unknown
01:56
created

Methods::resetNodesCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Spiral Framework. Cycle ProxyFactory
5
 *
6
 * @license MIT
7
 * @author  Valentin V (Vvval)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\ORM\Promise\Declaration\Extractor;
13
14
use Cycle\ORM\Promise\Traverser;
15
use Cycle\ORM\Promise\Visitor\Declaration\FetchMethods;
16
use PhpParser\Builder\Param;
17
use PhpParser\Node;
18
use PhpParser\Parser;
19
use PhpParser\ParserFactory;
20
use ReflectionClass;
21
use ReflectionException;
22
use ReflectionMethod;
23
use ReflectionParameter;
24
use ReflectionType;
25
26
final class Methods
27
{
28
    private const MAGIC_METHODS = [
29
        '__construct',
30
        '__destruct',
31
        '__call',
32
        '__callStatic',
33
        '__get',
34
        '__set',
35
        '__isset',
36
        '__unset',
37
        '__sleep',
38
        '__wakeup',
39
        '__toString',
40
        '__invoke',
41
        '__set_state',
42
        '__debugInfo',
43
    ];
44
45
    private const RESERVED_UNQUALIFIED_RETURN_TYPES = ['self', 'static', 'object'];
46
47
    private static $nodesByFileNameCache = [];
48
49
    /** @varTraverser */
50
    private $traverser;
51
52
    /** @var Parser */
53
    private $parser;
54
55
    /**
56
     * @param Traverser   $traverser
57
     * @param Parser|null $parser
58
     */
59
    public function __construct(
60
        Traverser $traverser,
61
        Parser $parser = null
62
    ) {
63
        $this->traverser = $traverser;
64
        $this->parser = $parser ?? (new ParserFactory())->create(ParserFactory::ONLY_PHP7);
65
    }
66
67
    /**
68
     * @param ReflectionClass $reflection
69
     * @return array
70
     * @throws ReflectionException
71
     */
72
    public function getMethods(ReflectionClass $reflection): array
73
    {
74
        $parents = [$reflection->name => $reflection];
75
        foreach ($reflection->getMethods() as $method) {
76
            $class = $method->getDeclaringClass();
77
            $parents[$class->name] = $class;
78
        }
79
80
        $methodNodes = $this->getExtendedMethodNodes($parents);
81
        $methods = [];
82
83
        foreach ($reflection->getMethods() as $method) {
84
            if ($this->isIgnoredMethod($method)) {
85
                continue;
86
            }
87
88
            $methods[] = new Node\Stmt\ClassMethod($method->name, [
89
                'flags'      => $this->packFlags($method),
90
                'returnType' => $this->defineReturnType($method),
91
                'params'     => $this->packParams($method),
92
                'byRef'      => $method->returnsReference(),
93
                'stmts'      => !empty($methodNodes[$method->name]) ? $methodNodes[$method->name]->stmts : []
94
            ]);
95
        }
96
97
        return $methods;
98
    }
99
100
    /**
101
     * @param ReflectionMethod $method
102
     * @return bool
103
     */
104
    private function isIgnoredMethod(ReflectionMethod $method): bool
105
    {
106
        return $method->isPrivate()
107
            || $method->isStatic()
108
            || $method->isFinal()
109
            || $method->isAbstract()
110
            || $this->isMagicMethod($method->name);
111
    }
112
113
    /**
114
     * @param string $name
115
     * @return bool
116
     */
117
    private function isMagicMethod(string $name): bool
118
    {
119
        return in_array($name, self::MAGIC_METHODS, true);
120
    }
121
122
    /**
123
     * @param ReflectionMethod $method
124
     * @return int
125
     */
126
    private function packFlags(ReflectionMethod $method): int
127
    {
128
        $flags = [];
129
        if ($method->isPublic()) {
130
            $flags[] = Node\Stmt\Class_::MODIFIER_PUBLIC;
131
        } elseif ($method->isProtected()) {
132
            $flags[] = Node\Stmt\Class_::MODIFIER_PROTECTED;
133
        }
134
135
        return array_reduce($flags, static function ($a, $b) {
136
            return $a | $b;
137
        }, 0);
138
    }
139
140
    /**
141
     * @param ReflectionMethod $method
142
     * @return Node|null
143
     */
144
    private function defineReturnType(ReflectionMethod $method): ?Node
145
    {
146
        if (!$method->hasReturnType()) {
147
            return null;
148
        }
149
150
        $name = $this->defineType($method, $method->getReturnType());
151
152
        return $name !== null ? new Node\Identifier($name) : null;
153
    }
154
155
    /**
156
     * @param ReflectionMethod $method
157
     * @return array
158
     * @throws ReflectionException
159
     */
160
    private function packParams(ReflectionMethod $method): array
161
    {
162
        $params = [];
163
        foreach ($method->getParameters() as $parameter) {
164
            $param = new Param($parameter->name);
165
166
            if ($parameter->isDefaultValueAvailable()) {
167
                $param->setDefault($parameter->getDefaultValue());
168
            }
169
170
            if ($parameter->isVariadic()) {
171
                $param->makeVariadic();
172
            }
173
174
            if ($parameter->isPassedByReference()) {
175
                $param->makeByRef();
176
            }
177
178
            $type = $this->defineParamType($parameter, $method);
179
            if ($type !== null) {
180
                $param->setType($type);
181
            }
182
183
            $params[] = $param->getNode();
184
        }
185
186
        return $params;
187
    }
188
189
    /**
190
     * @param ReflectionParameter $parameter
191
     * @param ReflectionMethod    $method
192
     * @return string|null
193
     */
194
    private function defineParamType(ReflectionParameter $parameter, ReflectionMethod $method): ?string
195
    {
196
        if (!$parameter->hasType()) {
197
            return null;
198
        }
199
200
        return $this->defineType($method, $parameter->getType());
201
    }
202
203
    /**
204
     * @param ReflectionMethod    $method
205
     * @param ReflectionType|null $type
206
     * @return string|null
207
     */
208
    private function defineType(ReflectionMethod $method, ?ReflectionType $type): ?string
209
    {
210
        if ($type === null) {
211
            return null;
212
        }
213
214
        $name = $type->getName();
215
        $name = $this->replacedSelfTypeName($method, $name);
216
217
        if ($this->typeShouldBeQualified($type, $name)) {
218
            $name = '\\' . $name;
219
        }
220
221
        if ($type->allowsNull()) {
222
            $name = "?$name";
223
        }
224
225
        return $name;
226
    }
227
228
    /**
229
     * @param ReflectionMethod $method
230
     * @param string           $name
231
     * @return string
232
     */
233
    private function replacedSelfTypeName(ReflectionMethod $method, string $name): string
234
    {
235
        return $name === 'self' ? $method->class : $name;
236
    }
237
238
    /**
239
     * @param ReflectionType $returnType
240
     * @param string         $name
241
     * @return bool
242
     */
243
    private function typeShouldBeQualified(ReflectionType $returnType, string $name): bool
244
    {
245
        if (in_array($name, self::RESERVED_UNQUALIFIED_RETURN_TYPES, true)) {
246
            return false;
247
        }
248
249
        return !$returnType->isBuiltin();
250
    }
251
252
    /**
253
     * @param ReflectionClass[] $reflections
254
     * @return Node\Stmt\ClassMethod[]
255
     */
256
    private function getExtendedMethodNodes(array $reflections): array
257
    {
258
        $nodes = [];
259
        foreach ($reflections as $reflection) {
260
            if (!$fileName = $reflection->getFileName()) {
261
                continue;
262
            }
263
264
            foreach ($this->getMethodsByFile($fileName) as $name => $method) {
265
                if (!isset($nodes[$name])) {
266
                    $nodes[$name] = $method;
267
                }
268
            }
269
        }
270
271
        return $nodes;
272
    }
273
274
    /**
275
     * @param  string $fileName
276
     * @return array
277
     */
278
    private function getMethodsByFile(string $fileName): array
279
    {
280
        if (!array_key_exists($fileName, self::$nodesByFileNameCache)) {
281
            self::$nodesByFileNameCache[$fileName] = [];
282
            if (is_file($fileName)) {
283
                $methods = new FetchMethods();
284
                $this->traverser->traverse(
285
                    $this->parser->parse(
286
                        file_get_contents($fileName)
287
                    ),
288
                    $methods
289
                );
290
291
                self::$nodesByFileNameCache[$fileName] = $methods->getMethods();
292
            }
293
        }
294
        return self::$nodesByFileNameCache[$fileName];
295
    }
296
297
    public static function resetNodesCache(): void
298
    {
299
        self::$nodesByFileNameCache = [];
300
    }
301
}
302