Passed
Push — master ( 55f627...47fe5a )
by Dmitriy
02:36 queued 02:02
created

ClosureExporter   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 78
Duplicated Lines 0 %

Test Coverage

Coverage 95.92%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 47
c 1
b 0
f 0
dl 0
loc 78
ccs 47
cts 49
cp 0.9592
rs 10
wmc 26

3 Methods

Rating   Name   Duplication   Size   Complexity  
A isNextTokenIsPartOfNamespace() 0 7 3
A __construct() 0 3 1
D export() 0 60 22
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\VarDumper;
6
7
final class ClosureExporter
8
{
9
    private UseStatementParser $useStatementParser;
10
11 39
    public function __construct()
12
    {
13 39
        $this->useStatementParser = new UseStatementParser();
14 39
    }
15
16 39
    public function export(\Closure $closure)
17
    {
18 39
        $reflection = new \ReflectionFunction($closure);
19
20 39
        $fileName = $reflection->getFileName();
21 39
        $start = $reflection->getStartLine();
22 39
        $end = $reflection->getEndLine();
23
24 39
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
25
            return 'function() {/* Error: unable to determine Closure source */}';
26
        }
27
28 39
        --$start;
29 39
        $uses = $this->useStatementParser->fromFile($fileName);
30
31 39
        $source = implode('', array_slice($fileContent, $start, $end - $start));
32 39
        $tokens = token_get_all('<?php ' . $source);
33 39
        array_shift($tokens);
34
35 39
        $closureTokens = [];
36 39
        $pendingParenthesisCount = 0;
37 39
        $isShortClosure = false;
38 39
        $buffer = '';
39 39
        foreach ($tokens as $token) {
40 39
            if (!isset($token[0])) {
41
                continue;
42
            }
43 39
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
44 39
                $closureTokens[] = $token[1];
45 39
                if (!$isShortClosure && $token[0] === T_FN) {
46 31
                    $isShortClosure = true;
47
                }
48 39
                continue;
49
            }
50 39
            if ($closureTokens !== []) {
51 39
                $readableToken = $token[1] ?? $token;
52 39
                if ($this->isNextTokenIsPartOfNamespace($token)) {
53 20
                    $buffer .= $token[1];
54 20
                    if (!$this->isNextTokenIsPartOfNamespace(next($tokens)) && array_key_exists($buffer, $uses)) {
55 12
                        $readableToken = $uses[$buffer];
56 12
                        $buffer = '';
57
                    }
58
                }
59 39
                if ($token === '{' || $token === '[') {
60 12
                    $pendingParenthesisCount++;
61 39
                } elseif ($token === '}' || $token === ']') {
62 16
                    if ($pendingParenthesisCount === 0) {
63 4
                        break;
64
                    }
65 12
                    $pendingParenthesisCount--;
66 39
                } elseif ($token === ',' || $token === ';') {
67 35
                    if ($pendingParenthesisCount === 0) {
68 35
                        break;
69
                    }
70
                }
71 39
                $closureTokens[] = $readableToken;
72
            }
73
        }
74
75 39
        return implode('', $closureTokens);
76
    }
77
78 39
    private function isNextTokenIsPartOfNamespace($token): bool
79
    {
80 39
        if (!is_array($token)) {
81 39
            return false;
82
        }
83
84 39
        return $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
85
    }
86
}
87