Passed
Pull Request — master (#49)
by Evgeniy
02:16
created

ClosureExporter::export()   F

Complexity

Conditions 27
Paths 158

Size

Total Lines 79
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 49
CRAP Score 27.14

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 27
eloc 51
c 3
b 1
f 0
nc 158
nop 1
dl 0
loc 79
ccs 49
cts 52
cp 0.9423
crap 27.14
rs 3.6833

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