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