Passed
Pull Request — master (#40)
by Sergei
02:00
created

ClosureExporter::export()   D

Complexity

Conditions 21
Paths 39

Size

Total Lines 66
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 21.1398

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 21
eloc 45
nc 39
nop 1
dl 0
loc 66
ccs 41
cts 44
cp 0.9318
crap 21.1398
rs 4.1666
c 2
b 1
f 0

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_slice;
12
use function defined;
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 6
    public function __construct()
27
    {
28 6
        $this->useStatementParser = new UseStatementParser();
29 6
    }
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 34
    public function export(Closure $closure): string
41
    {
42 34
        $reflection = new ReflectionFunction($closure);
43
44 34
        $fileName = $reflection->getFileName();
45 34
        $start = $reflection->getStartLine();
46 34
        $end = $reflection->getEndLine();
47
48 34
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
49
            return 'function() {/* Error: unable to determine Closure source */}';
50
        }
51
52 34
        --$start;
53 34
        $uses = $this->useStatementParser->fromFile($fileName);
54
55 34
        $source = implode('', array_slice($fileContent, $start, $end - $start));
56 34
        $tokens = token_get_all('<?php ' . $source);
57 34
        array_shift($tokens);
58
59 34
        $closureTokens = [];
60 34
        $pendingParenthesisCount = 0;
61 34
        $buffer = '';
62 34
        foreach ($tokens as $token) {
63 34
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
64 34
                $closureTokens[] = $token[1];
65 34
                continue;
66
            }
67 34
            if ($closureTokens !== []) {
68 34
                $readableToken = $token[1] ?? $token;
69 34
                if ($this->isNextTokenIsPartOfNamespace($token)) {
70 20
                    $buffer .= $token[1];
71
                    if (
72 20
                        PHP_VERSION_ID >= 80000
73 20
                        && $buffer !== '\\'
74 20
                        && strpos($buffer, '\\') !== false
75
                    ) {
76
                        $usesKeys = array_filter(explode('\\', $buffer));
77
                        $buffer = array_pop($usesKeys);
78
                    }
79
                    if (
80 20
                        array_key_exists($buffer, $uses)
81 20
                        && !$this->isNextTokenIsPartOfNamespace(next($tokens))
82
                    ) {
83 9
                        $readableToken = $uses[$buffer];
84 9
                        $buffer = '';
85
                    }
86
                }
87 34
                if (is_string($token)) {
88 34
                    if ($this->isOpenParenthesis($token)) {
89 34
                        $pendingParenthesisCount++;
90 34
                    } elseif ($this->isCloseParenthesis($token)) {
91 34
                        if ($pendingParenthesisCount === 0) {
92 7
                            break;
93
                        }
94 34
                        $pendingParenthesisCount--;
95 31
                    } elseif ($token === ',' || $token === ';') {
96 29
                        if ($pendingParenthesisCount === 0) {
97 27
                            break;
98
                        }
99
                    }
100
                }
101 34
                $closureTokens[] = $readableToken;
102
            }
103
        }
104
105 34
        return implode('', $closureTokens);
106
    }
107
108 34
    private function isOpenParenthesis(string $value): bool
109
    {
110 34
        return in_array($value, ['{', '[', '(']);
111
    }
112
113 34
    private function isCloseParenthesis(string $value): bool
114
    {
115 34
        return in_array($value, ['}', ']', ')']);
116
    }
117
118
    /**
119
     * @param array|string $token
120
     *
121
     * @return bool
122
     */
123 34
    private function isNextTokenIsPartOfNamespace($token): bool
124
    {
125 34
        if (!is_array($token)) {
126 34
            return false;
127
        }
128
129 34
        return $token[0] === T_STRING
130 34
            || $token[0] === T_NS_SEPARATOR
131 34
            || (defined('T_NAME_QUALIFIED') && $token[0] === T_NAME_QUALIFIED)
132 34
            || (defined('T_NAME_FULLY_QUALIFIED') && $token[0] === T_NAME_FULLY_QUALIFIED)
133 34
            || (defined('T_NAME_RELATIVE') && $token[0] === T_NAME_RELATIVE);
134
    }
135
}
136