Passed
Pull Request — master (#44)
by Evgeniy
02:06
created

ClosureExporter   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 86
Duplicated Lines 0 %

Test Coverage

Coverage 93.62%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 43
c 2
b 1
f 0
dl 0
loc 86
ccs 44
cts 47
cp 0.9362
rs 10
wmc 23

4 Methods

Rating   Name   Duplication   Size   Complexity  
A isCloseParenthesis() 0 3 1
A isOpenParenthesis() 0 3 1
A __construct() 0 3 1
D export() 0 58 20
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
 */
29
final class ClosureExporter
30
{
31
    private UseStatementParser $useStatementParser;
32
33 6
    public function __construct()
34
    {
35 6
        $this->useStatementParser = new UseStatementParser();
36 6
    }
37
38
    /**
39
     * Export closure as a string containing PHP code.
40
     *
41
     * @param Closure $closure Closure to export.
42
     *
43
     * @throws ReflectionException
44
     *
45
     * @return string String containing PHP code.
46
     */
47 34
    public function export(Closure $closure): string
48
    {
49 34
        $reflection = new ReflectionFunction($closure);
50
51 34
        $fileName = $reflection->getFileName();
52 34
        $start = $reflection->getStartLine();
53 34
        $end = $reflection->getEndLine();
54
55 34
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
56
            return 'function () {/* Error: unable to determine Closure source */}';
57
        }
58
59 34
        --$start;
60 34
        $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
64 34
        $buffer = '';
65 34
        $closureTokens = [];
66 34
        $pendingParenthesisCount = 0;
67
68 34
        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
            }
73 34
            if ($closureTokens !== []) {
74 34
                $readableToken = $token[1] ?? $token;
75 34
                if (TokenHelper::isPartOfNamespace($token)) {
76 20
                    $buffer .= $token[1];
77 20
                    if (PHP_VERSION_ID >= 80000 && $buffer !== '\\' && strpos($buffer, '\\') !== false) {
78
                        $usesKeys = array_filter(explode('\\', $buffer));
79
                        $buffer = array_pop($usesKeys);
80
                    }
81 20
                    if (isset($uses[$buffer])) {
82 9
                        $readableToken = $uses[$buffer];
83 9
                        $buffer = '';
84
                    }
85
                }
86 34
                if (is_string($token)) {
87 34
                    if ($this->isOpenParenthesis($token)) {
88 34
                        $pendingParenthesisCount++;
89 34
                    } elseif ($this->isCloseParenthesis($token)) {
90 34
                        if ($pendingParenthesisCount === 0) {
91 7
                            break;
92
                        }
93 34
                        $pendingParenthesisCount--;
94 31
                    } elseif ($token === ',' || $token === ';') {
95 29
                        if ($pendingParenthesisCount === 0) {
96 27
                            break;
97
                        }
98
                    }
99
                }
100 34
                $closureTokens[] = $readableToken;
101
            }
102
        }
103
104 34
        return implode('', $closureTokens);
105
    }
106
107 34
    private function isOpenParenthesis(string $value): bool
108
    {
109 34
        return in_array($value, ['{', '[', '(']);
110
    }
111
112 34
    private function isCloseParenthesis(string $value): bool
113
    {
114 34
        return in_array($value, ['}', ']', ')']);
115
    }
116
}
117