Passed
Push — master ( 896efe...707faa )
by Alexander
02:16
created

ClosureExporter::export()   D

Complexity

Conditions 26
Paths 99

Size

Total Lines 76
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 51
CRAP Score 26.1161

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 26
eloc 53
c 2
b 1
f 0
nc 99
nop 1
dl 0
loc 76
ccs 51
cts 54
cp 0.9444
crap 26.1161
rs 4.1666

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 6
    public function __construct()
34
    {
35 6
        $this->useStatementParser = new UseStatementParser();
36 6
    }
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 34
    public function export(Closure $closure): string
48
    {
49 34
        $reflection = new ReflectionFunction($closure);
50
51 34
        $fileName = $reflection->getFileName();
52 34
        $start = $reflection->getStartLine();
53 34
        $end = $reflection->getEndLine();
54
55 34
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
56
            return 'function () {/* Error: unable to determine Closure source */}';
57
        }
58
59 34
        --$start;
60 34
        $uses = $this->useStatementParser->fromFile($fileName);
61 34
        $tokens = token_get_all('<?php ' . implode('', array_slice($fileContent, $start, $end - $start)));
62 34
        array_shift($tokens);
63
64 34
        $buffer = '';
65 34
        $closureTokens = [];
66 34
        $previousUsePart = '';
67 34
        $pendingParenthesisCount = 0;
68
69 34
        foreach ($tokens as $token) {
70 34
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
71 34
                $closureTokens[] = $token[1];
72 34
                continue;
73
            }
74 34
            if ($closureTokens !== []) {
75 34
                $readableToken = $token[1] ?? $token;
76 34
                if (TokenHelper::isPartOfNamespace($token)) {
77 20
                    $buffer .= $token[1];
78 20
                    if (PHP_VERSION_ID >= 80000 && $buffer !== '\\' && strpos($buffer, '\\') !== false) {
79
                        $usesKeys = array_filter(explode('\\', $buffer));
80
                        $buffer = array_pop($usesKeys);
81
                    }
82 20
                    if (!empty($previousUsePart) && $buffer === '\\') {
83 3
                        continue;
84
                    }
85 20
                    if (isset($uses[$buffer])) {
86 9
                        if ($this->isUseNamespaceAlias($buffer, $uses)) {
87 3
                            $previousUsePart = $uses[$buffer];
88 3
                            $buffer = '';
89 3
                            continue;
90
                        }
91 6
                        $readableToken = (empty($previousUsePart) || strpos($uses[$buffer], $previousUsePart) === false)
92 6
                            ? $previousUsePart . $uses[$buffer]
93 6
                            : $uses[$buffer]
94
                        ;
95 6
                        $buffer = '';
96 6
                        $previousUsePart = '';
97 20
                    } elseif (isset($uses[$token[1]])) {
98 3
                        $readableToken = $uses[$token[1]];
99 3
                        $previousUsePart = '';
100 3
                        $buffer = '';
101
                    }
102
                }
103 34
                if (is_string($token)) {
104 34
                    if ($this->isOpenParenthesis($token)) {
105 34
                        $pendingParenthesisCount++;
106 34
                    } elseif ($this->isCloseParenthesis($token)) {
107 34
                        if ($pendingParenthesisCount === 0) {
108 7
                            break;
109
                        }
110 34
                        $pendingParenthesisCount--;
111 31
                    } elseif ($token === ',' || $token === ';') {
112 29
                        if ($pendingParenthesisCount === 0) {
113 27
                            break;
114
                        }
115
                    }
116
                }
117
118 34
                $closureTokens[] = $readableToken;
119
            }
120
        }
121
122 34
        return implode('', $closureTokens);
123
    }
124
125 34
    private function isOpenParenthesis(string $value): bool
126
    {
127 34
        return in_array($value, ['{', '[', '(']);
128
    }
129
130 34
    private function isCloseParenthesis(string $value): bool
131
    {
132 34
        return in_array($value, ['}', ']', ')']);
133
    }
134
135 9
    private function isUseNamespaceAlias(string $useKey, array $uses): bool
136
    {
137 9
        if (!isset($uses[$useKey])) {
138
            return false;
139
        }
140
141 9
        $usesKeys = array_filter(explode('\\', (string) $uses[$useKey]));
142 9
        $lastPartUse = array_pop($usesKeys);
143
144 9
        return isset($uses[$lastPartUse]) && $uses[$lastPartUse] !== $uses[$useKey];
145
    }
146
}
147