Passed
Pull Request — master (#45)
by Evgeniy
02:07
created

ClosureExporter::export()   F

Complexity

Conditions 25
Paths 194

Size

Total Lines 80
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 50
CRAP Score 25.2542

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 25
eloc 53
c 2
b 1
f 0
nc 194
nop 1
dl 0
loc 80
ccs 50
cts 54
cp 0.9259
crap 25.2542
rs 3.3833

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_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 5
    public function __construct()
34
    {
35 5
        $this->useStatementParser = new UseStatementParser();
36 5
    }
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 36
    public function export(Closure $closure): string
48
    {
49 36
        $reflection = new ReflectionFunction($closure);
50
51 36
        $fileName = $reflection->getFileName();
52 36
        $start = $reflection->getStartLine();
53 36
        $end = $reflection->getEndLine();
54
55 36
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
56
            return 'function () {/* Error: unable to determine Closure source */}';
57
        }
58
59 36
        --$start;
60 36
        $uses = $this->useStatementParser->fromFile($fileName);
61 36
        $tokens = token_get_all('<?php ' . implode('', array_slice($fileContent, $start, $end - $start)));
62 36
        array_shift($tokens);
63
64 36
        $buffer = '';
65 36
        $closureTokens = [];
66 36
        $previousUsePart = '';
67 36
        $pendingParenthesisCount = 0;
68
69 36
        foreach ($tokens as $token) {
70 36
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
71 36
                $closureTokens[] = $token[1];
72 36
                continue;
73
            }
74
75 36
            if ($closureTokens === []) {
76 36
                continue;
77
            }
78
79 36
            $readableToken = is_array($token) ? $token[1] : $token;
80
81 36
            if ($this->useStatementParser->isTokenIsPartOfUse($token)) {
82 22
                $buffer .= $token[1];
83 22
                if ($this->isUseConsistingOfMultipleParts($buffer)) {
84 3
                    $buffer = $this->getUseLastPart($buffer);
85
                }
86 22
                if (!empty($previousUsePart) && $buffer === '\\') {
87 3
                    continue;
88
                }
89 22
                if (isset($uses[$buffer])) {
90 12
                    if ($this->isNotFullUseAlias($uses[$buffer], $uses)) {
91 3
                        $previousUsePart = $uses[$buffer];
92 3
                        $buffer = '';
93 3
                        continue;
94
                    }
95 12
                    $readableToken = (empty($previousUsePart) || strpos($uses[$buffer], $previousUsePart) === false)
96 12
                        ? $previousUsePart . $uses[$buffer]
97 12
                        : $uses[$buffer]
98
                    ;
99 12
                    $buffer = '';
100 12
                    $previousUsePart = '';
101 10
                } elseif (isset($uses[$token[1]])) {
102
                    $readableToken = $uses[$token[1]];
103
                    $previousUsePart = '';
104
                    $buffer = '';
105
                }
106
            }
107
108 36
            if (is_string($token)) {
109 36
                if ($this->isOpenParenthesis($token)) {
110 36
                    $pendingParenthesisCount++;
111 36
                } elseif ($this->isCloseParenthesis($token)) {
112 36
                    if ($pendingParenthesisCount === 0) {
113 7
                        break;
114
                    }
115 36
                    $pendingParenthesisCount--;
116 33
                } elseif ($token === ',' || $token === ';') {
117 31
                    if ($pendingParenthesisCount === 0) {
118 29
                        break;
119
                    }
120
                }
121
            }
122
123 36
            $closureTokens[] = $readableToken;
124
        }
125
126 36
        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 12
    private function getUseLastPart(string $use): string
137
    {
138 12
        $parts = array_filter(explode('\\', $use));
139 12
        return array_pop($parts);
140
    }
141
142
    /**
143
     * Checks whether the use statement data consists of multiple parts.
144
     *
145
     * @param string $use The use statement data.
146
     *
147
     * @return bool Whether the use statement data consists of multiple parts.
148
     */
149 22
    private function isUseConsistingOfMultipleParts(string $use): bool
150
    {
151 22
        return $use !== '\\' && strpos($use, '\\') !== false;
152
    }
153
154
    /**
155
     * Checks whether the use statement data is not a full use statement data alias.
156
     *
157
     * @param string $use The use statement data to check.
158
     * @param array<string, string> $uses The use statement data.
159
     *
160
     * @return bool Whether the use statement data is not a full use statement data alias.
161
     */
162 12
    private function isNotFullUseAlias(string $use, array $uses): bool
163
    {
164 12
        $lastPart = $this->getUseLastPart($use);
165 12
        return isset($uses[$lastPart]) && $uses[$lastPart] !== $use;
166
    }
167
168
    /**
169
     * Checks whether the value of the token is an opening parenthesis.
170
     *
171
     * @param string $value The token value.
172
     *
173
     * @return bool Whether the value of the token is an opening parenthesis.
174
     */
175 36
    private function isOpenParenthesis(string $value): bool
176
    {
177 36
        return in_array($value, ['{', '[', '(']);
178
    }
179
180
    /**
181
     * Checks whether the value of the token is a closing parenthesis.
182
     *
183
     * @param string $value The token value.
184
     *
185
     * @return bool Whether the value of the token is a closing parenthesis.
186
     */
187 36
    private function isCloseParenthesis(string $value): bool
188
    {
189 36
        return in_array($value, ['}', ']', ')']);
190
    }
191
}
192