Test Failed
Pull Request — master (#44)
by Alexander
04:16 queued 01:58
created

ClosureExporter::isNextTokenIsPartOfNamespace()   B

Complexity

Conditions 9
Paths 24

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 7
c 1
b 0
f 0
nc 24
nop 1
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 9
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\VarDumper;
6
7
use Closure;
8
use ReflectionException;
9
use ReflectionFunction;
10
11
use function array_filter;
12
use function array_pop;
13
use function array_shift;
14
use function array_slice;
15
use function explode;
16
use function file;
17
use function implode;
18
use function in_array;
19
use function is_string;
20
use function strpos;
21
use function token_get_all;
22
23
/**
24
 * ClosureExporter exports PHP {@see \Closure} as a string containing PHP code.
25
 *
26
 * The string is a valid PHP expression that can be evaluated by PHP parser
27
 * and the evaluation result will give back the closure instance.
28 6
 */
29
final class ClosureExporter
30 6
{
31 6
    private UseStatementParser $useStatementParser;
32
33
    public function __construct()
34
    {
35
        $this->useStatementParser = new UseStatementParser();
36
    }
37
38
    /**
39
     * Export closure as a string containing PHP code.
40
     *
41
     * @param Closure $closure Closure to export.
42 34
     *
43
     * @throws ReflectionException
44 34
     *
45
     * @return string String containing PHP code.
46 34
     */
47 34
    public function export(Closure $closure): string
48 34
    {
49
        $reflection = new ReflectionFunction($closure);
50 34
51
        $fileName = $reflection->getFileName();
52
        $start = $reflection->getStartLine();
53
        $end = $reflection->getEndLine();
54 34
55 34
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
56
            return 'function () {/* Error: unable to determine Closure source */}';
57 34
        }
58 34
59 34
        --$start;
60
        $uses = $this->useStatementParser->fromFile($fileName);
61 34
        $tokens = token_get_all('<?php ' . implode('', array_slice($fileContent, $start, $end - $start)));
62 34
        array_shift($tokens);
63 34
64 34
        $buffer = '';
65 34
        $closureTokens = [];
66 34
        $pendingParenthesisCount = 0;
67 34
68
        foreach ($tokens as $token) {
69 34
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
70 34
                $closureTokens[] = $token[1];
71 34
                continue;
72 20
            }
73
            if ($closureTokens !== []) {
74 20
                $readableToken = $token[1] ?? $token;
75 20
                if (TokenHelper::isPartOfNamespace($token)) {
76 20
                    $buffer .= $token[1];
77
                    if (PHP_VERSION_ID >= 80000 && $buffer !== '\\' && strpos($buffer, '\\') !== false) {
78
                        $usesKeys = array_filter(explode('\\', $buffer));
79
                        $buffer = array_pop($usesKeys);
80
                    }
81
                    if (isset($uses[$buffer])) {
82 20
                        $readableToken = $uses[$buffer];
83 20
                        $buffer = '';
84
                    }
85 9
                }
86 9
                if (is_string($token)) {
87
                    if ($this->isOpenParenthesis($token)) {
88
                        $pendingParenthesisCount++;
89 34
                    } elseif ($this->isCloseParenthesis($token)) {
90 34
                        if ($pendingParenthesisCount === 0) {
91 34
                            break;
92 34
                        }
93 34
                        $pendingParenthesisCount--;
94 7
                    } elseif ($token === ',' || $token === ';') {
95
                        if ($pendingParenthesisCount === 0) {
96 34
                            break;
97 31
                        }
98 29
                    }
99 27
                }
100
                $closureTokens[] = $readableToken;
101
            }
102
        }
103 34
104
        return implode('', $closureTokens);
105
    }
106
107 34
    private function isOpenParenthesis(string $value): bool
108
    {
109
        return in_array($value, ['{', '[', '(']);
110 34
    }
111
112 34
    private function isCloseParenthesis(string $value): bool
113
    {
114
        return in_array($value, ['}', ']', ')']);
115 34
    }
116
}
117