Passed
Pull Request — master (#50)
by Evgeniy
02:14
created

ClosureExporter::export()   F

Complexity

Conditions 29
Paths 150

Size

Total Lines 76
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 47
CRAP Score 29.4052

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 29
eloc 50
c 3
b 1
f 0
nc 150
nop 1
dl 0
loc 76
ccs 47
cts 51
cp 0.9216
crap 29.4052
rs 3.75

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_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_array;
20
use function is_string;
21
use function mb_strlen;
22
use function mb_substr;
23
use function strpos;
24
use function token_get_all;
25
use function trim;
26
27
/**
28
 * ClosureExporter exports PHP {@see \Closure} as a string containing PHP code.
29
 *
30
 * The string is a valid PHP expression that can be evaluated by PHP parser
31
 * and the evaluation result will give back the closure instance.
32
 */
33
final class ClosureExporter
34
{
35
    private UseStatementParser $useStatementParser;
36
37 8
    public function __construct()
38
    {
39 8
        $this->useStatementParser = new UseStatementParser();
40 8
    }
41
42
    /**
43
     * Export closure as a string containing PHP code.
44
     *
45
     * @param Closure $closure Closure to export.
46
     *
47
     * @throws ReflectionException
48
     *
49
     * @return string String containing PHP code.
50
     */
51 42
    public function export(Closure $closure): string
52
    {
53 42
        $reflection = new ReflectionFunction($closure);
54
55 42
        $fileName = $reflection->getFileName();
56 42
        $start = $reflection->getStartLine();
57 42
        $end = $reflection->getEndLine();
58
59 42
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
60
            return 'function () {/* Error: unable to determine Closure source */}';
61
        }
62
63 42
        --$start;
64 42
        $uses = $this->useStatementParser->fromFile($fileName);
65 42
        $tokens = token_get_all('<?php ' . implode('', array_slice($fileContent, $start, $end - $start)));
66 42
        array_shift($tokens);
67
68 42
        $bufferUse = '';
69 42
        $closureTokens = [];
70 42
        $pendingParenthesisCount = 0;
71
72 42
        foreach ($tokens as $i => $token) {
73 42
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
74 42
                $closureTokens[] = $token[1];
75 42
                continue;
76
            }
77
78 42
            if ($closureTokens === []) {
79 42
                continue;
80
            }
81
82 42
            $readableToken = is_array($token) ? $token[1] : $token;
83
84 42
            if ($this->useStatementParser->isTokenIsPartOfUse($token)) {
85 28
                if ($this->isUseConsistingOfMultipleParts($readableToken)) {
86
                    $readableToken = $this->processFullUse($readableToken, $uses);
87
                    $bufferUse = '';
88 28
                } elseif (isset($uses[$readableToken])) {
89 16
                    if (isset($tokens[$i + 2]) && $this->useStatementParser->isTokenIsPartOfUse($tokens[$i + 2])) {
90 5
                        $bufferUse .= $uses[$readableToken];
91 5
                        continue;
92
                    }
93 16
                    $readableToken = $uses[$readableToken];
94 19
                } elseif ($readableToken === '\\' && isset($tokens[$i - 1][1]) && $tokens[$i - 1][1] === '\\') {
95
                    continue;
96 19
                } elseif (isset($tokens[$i + 1]) && $this->useStatementParser->isTokenIsPartOfUse($tokens[$i + 1])) {
97 6
                    $bufferUse .= $readableToken;
98 6
                    continue;
99
                }
100 28
                if (!empty($bufferUse)) {
101 6
                    if ($bufferUse !== $readableToken && strpos($readableToken, $bufferUse) === false) {
102 2
                        $readableToken = $bufferUse . $readableToken;
103
                    }
104 6
                    $bufferUse = '';
105
                }
106
            }
107
108 42
            if (is_string($token)) {
109 42
                if ($this->isOpenParenthesis($token)) {
110 42
                    $pendingParenthesisCount++;
111 42
                } elseif ($this->isCloseParenthesis($token)) {
112 42
                    if ($pendingParenthesisCount === 0) {
113 11
                        break;
114
                    }
115 42
                    $pendingParenthesisCount--;
116 36
                } elseif ($token === ',' || $token === ';') {
117 33
                    if ($pendingParenthesisCount === 0) {
118 31
                        break;
119
                    }
120
                }
121
            }
122
123 42
            $closureTokens[] = $readableToken;
124
        }
125
126 42
        return implode('', $closureTokens);
127
    }
128
129
    /**
130
     * Returns the last part from the use statement data.
131
     *
132
     * @param string $use The full use statement data.
133
     *
134
     * @return string The last part from the use statement data.
135
     */
136
    private function getUseLastPart(string $use): string
137
    {
138
        $parts = array_filter(explode('\\', $use));
139
        return (string) array_pop($parts);
140
    }
141
142
    /**
143
     * Processes and returns the full use statement data.
144
     *
145
     * @param string $use The use statement data to process.
146
     * @param array<string, string> $uses The use statement data.
147
     *
148
         * @return string The processed full use statement.
149
     */
150
    private function processFullUse(string $use, array $uses): string
151
    {
152
        $lastPart = $this->getUseLastPart($use);
153
154
        if (isset($uses[$lastPart])) {
155
            return $uses[$lastPart];
156
        }
157
158
        $result = '';
159
160
        do {
161
            $lastPart = $this->getUseLastPart($use);
162
            $use = mb_substr($use, 0, -mb_strlen("\\{$lastPart}"));
163
            $result = ($uses[$lastPart] ?? $lastPart) . '\\' . $result;
164
        } while (!empty($lastPart) && !isset($uses[$lastPart]));
165
166
        return '\\' . trim($result, '\\');
167
    }
168
169
    /**
170
     * Checks whether the use statement data consists of multiple parts.
171
     *
172
     * @param string $use The use statement data.
173
     *
174
     * @return bool Whether the use statement data consists of multiple parts.
175
     */
176 28
    private function isUseConsistingOfMultipleParts(string $use): bool
177
    {
178 28
        return $use !== '\\' && strpos($use, '\\') !== false;
179
    }
180
181
    /**
182
     * Checks whether the value of the token is an opening parenthesis.
183
     *
184
     * @param string $value The token value.
185
     *
186
     * @return bool Whether the value of the token is an opening parenthesis.
187
     */
188 42
    private function isOpenParenthesis(string $value): bool
189
    {
190 42
        return in_array($value, ['{', '[', '(']);
191
    }
192
193
    /**
194
     * Checks whether the value of the token is a closing parenthesis.
195
     *
196
     * @param string $value The token value.
197
     *
198
     * @return bool Whether the value of the token is a closing parenthesis.
199
     */
200 42
    private function isCloseParenthesis(string $value): bool
201
    {
202 42
        return in_array($value, ['}', ']', ')']);
203
    }
204
}
205