Passed
Pull Request — master (#34)
by Evgeniy
02:22
created

ClosureExporter::export()   D

Complexity

Conditions 22
Paths 24

Size

Total Lines 61
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 22.0521

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 22
eloc 41
c 2
b 1
f 0
nc 24
nop 1
dl 0
loc 61
ccs 40
cts 42
cp 0.9524
crap 22.0521
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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_key_exists;
12
use function array_slice;
13
use function in_array;
14
use function is_array;
15
16
/**
17
 * ClosureExporter exports PHP {@see \Closure} as a string containing PHP code.
18
 *
19
 * The string is a valid PHP expression that can be evaluated by PHP parser
20
 * and the evaluation result will give back the closure instance.
21
 */
22
final class ClosureExporter
23
{
24
    private UseStatementParser $useStatementParser;
25
26 1
    public function __construct()
27
    {
28 1
        $this->useStatementParser = new UseStatementParser();
29 1
    }
30
31
    /**
32
     * Export closure as a string containing PHP code.
33
     *
34
     * @param Closure $closure Closure to export.
35
     *
36
     * @throws ReflectionException
37
     *
38
     * @return string String containing PHP code.
39
     */
40 39
    public function export(Closure $closure): string
41
    {
42 39
        $reflection = new ReflectionFunction($closure);
43
44 39
        $fileName = $reflection->getFileName();
45 39
        $start = $reflection->getStartLine();
46 39
        $end = $reflection->getEndLine();
47
48 39
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
49
            return 'function() {/* Error: unable to determine Closure source */}';
50
        }
51
52 39
        --$start;
53 39
        $uses = $this->useStatementParser->fromFile($fileName);
54
55 39
        $source = implode('', array_slice($fileContent, $start, $end - $start));
56 39
        $tokens = token_get_all('<?php ' . $source);
57 39
        array_shift($tokens);
58
59 39
        $closureTokens = [];
60 39
        $pendingParenthesisCount = 0;
61 39
        $isShortClosure = false;
62 39
        $buffer = '';
63 39
        foreach ($tokens as $token) {
64 39
            if (!isset($token[0])) {
65
                continue;
66
            }
67 39
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
68 39
                $closureTokens[] = $token[1];
69 39
                if (!$isShortClosure && $token[0] === T_FN) {
70 31
                    $isShortClosure = true;
71
                }
72 39
                continue;
73
            }
74 39
            if ($closureTokens !== []) {
75 39
                $readableToken = $token[1] ?? $token;
76 39
                if ($this->isNextTokenIsPartOfNamespace($token)) {
77 20
                    $buffer .= $token[1];
78
                    // HERE we need to match partially because now NS can be a single token in PHP 8
79 20
                    if (array_key_exists($buffer, $uses) && !$this->isNextTokenIsPartOfNamespace(next($tokens))) {
80 12
                        $readableToken = $uses[$buffer];
81 12
                        $buffer = '';
82
                    }
83
                }
84 39
                if ($token === '{' || $token === '[') {
85 12
                    $pendingParenthesisCount++;
86 39
                } elseif ($token === '}' || $token === ']') {
87 15
                    if ($pendingParenthesisCount === 0) {
88 3
                        break;
89
                    }
90 12
                    $pendingParenthesisCount--;
91 39
                } elseif ($token === ',' || $token === ';') {
92 36
                    if ($pendingParenthesisCount === 0) {
93 36
                        break;
94
                    }
95
                }
96 39
                $closureTokens[] = $readableToken;
97
            }
98
        }
99
100 39
        return implode('', $closureTokens);
101
    }
102
103 39
    private function isNextTokenIsPartOfNamespace($token): bool
104
    {
105 39
        if (!is_array($token)) {
106 39
            return false;
107
        }
108
109 39
        if ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || $token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) {
110 20
            return true;
111
        }
112
113 39
        if (version_compare(PHP_VERSION, '8.0.0', '>=')) {
114
            return $token[0] === T_NAME_RELATIVE;
115
        }
116
117 39
        return false;
118
    }
119
}
120