Passed
Push — master ( 707faa...52f0a7 )
by Alexander
02:15
created

ClosureExporter::export()   F

Complexity

Conditions 25
Paths 194

Size

Total Lines 80
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 25.0039

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 53
cts 54
cp 0.9815
crap 25.0039
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 38
    public function export(Closure $closure): string
48
    {
49 38
        $reflection = new ReflectionFunction($closure);
50
51 38
        $fileName = $reflection->getFileName();
52 38
        $start = $reflection->getStartLine();
53 38
        $end = $reflection->getEndLine();
54
55 38
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
56
            return 'function () {/* Error: unable to determine Closure source */}';
57
        }
58
59 38
        --$start;
60 38
        $uses = $this->useStatementParser->fromFile($fileName);
61 38
        $tokens = token_get_all('<?php ' . implode('', array_slice($fileContent, $start, $end - $start)));
62 38
        array_shift($tokens);
63
64 38
        $buffer = '';
65 38
        $closureTokens = [];
66 38
        $previousUsePart = '';
67 38
        $pendingParenthesisCount = 0;
68
69 38
        foreach ($tokens as $token) {
70 38
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
71 38
                $closureTokens[] = $token[1];
72 38
                continue;
73
            }
74
75 38
            if ($closureTokens === []) {
76 38
                continue;
77
            }
78
79 38
            $readableToken = is_array($token) ? $token[1] : $token;
80
81 38
            if ($this->useStatementParser->isTokenIsPartOfUse($token)) {
82 24
                $buffer .= $token[1];
83 24
                if ($this->isUseConsistingOfMultipleParts($buffer)) {
84 3
                    $buffer = $this->getUseLastPart($buffer);
85
                }
86 24
                if (!empty($previousUsePart) && $buffer === '\\') {
87 3
                    continue;
88
                }
89 24
                if (isset($uses[$buffer])) {
90 13
                    if ($this->isNotFullUseAlias($uses[$buffer], $uses)) {
91 3
                        $previousUsePart = $uses[$buffer];
92 3
                        $buffer = '';
93 3
                        continue;
94
                    }
95 13
                    $readableToken = (empty($previousUsePart) || strpos($uses[$buffer], $previousUsePart) === false)
96 13
                        ? $previousUsePart . $uses[$buffer]
97 13
                        : $uses[$buffer]
98
                    ;
99 13
                    $buffer = '';
100 13
                    $previousUsePart = '';
101 12
                } elseif (isset($uses[$token[1]])) {
102 1
                    $readableToken = $uses[$token[1]];
103 1
                    $previousUsePart = '';
104 1
                    $buffer = '';
105
                }
106
            }
107
108 38
            if (is_string($token)) {
109 38
                if ($this->isOpenParenthesis($token)) {
110 38
                    $pendingParenthesisCount++;
111 38
                } elseif ($this->isCloseParenthesis($token)) {
112 38
                    if ($pendingParenthesisCount === 0) {
113 8
                        break;
114
                    }
115 38
                    $pendingParenthesisCount--;
116 35
                } elseif ($token === ',' || $token === ';') {
117 32
                    if ($pendingParenthesisCount === 0) {
118 30
                        break;
119
                    }
120
                }
121
            }
122
123 38
            $closureTokens[] = $readableToken;
124
        }
125
126 38
        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 13
    private function getUseLastPart(string $use): string
137
    {
138 13
        $parts = array_filter(explode('\\', $use));
139 13
        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 24
    private function isUseConsistingOfMultipleParts(string $use): bool
150
    {
151 24
        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 13
    private function isNotFullUseAlias(string $use, array $uses): bool
163
    {
164 13
        $lastPart = $this->getUseLastPart($use);
165 13
        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 38
    private function isOpenParenthesis(string $value): bool
176
    {
177 38
        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 38
    private function isCloseParenthesis(string $value): bool
188
    {
189 38
        return in_array($value, ['}', ']', ')']);
190
    }
191
}
192