Passed
Push — master ( fb5594...a56e1d )
by Alexander
03:25 queued 57s
created

ClosureExporter::export()   D

Complexity

Conditions 25
Paths 36

Size

Total Lines 71
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 25.3612

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 25
eloc 49
c 2
b 1
f 0
nc 36
nop 1
dl 0
loc 71
ccs 44
cts 48
cp 0.9167
crap 25.3612
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_key_exists;
12
use function array_filter;
13
use function array_pop;
14
use function array_shift;
15
use function array_slice;
16
use function defined;
17
use function explode;
18
use function implode;
19
use function in_array;
20
use function is_array;
21
use function strpos;
22
use function token_get_all;
23
24
/**
25
 * ClosureExporter exports PHP {@see \Closure} as a string containing PHP code.
26
 *
27
 * The string is a valid PHP expression that can be evaluated by PHP parser
28
 * and the evaluation result will give back the closure instance.
29
 */
30
final class ClosureExporter
31
{
32
    private UseStatementParser $useStatementParser;
33
34 1
    public function __construct()
35
    {
36 1
        $this->useStatementParser = new UseStatementParser();
37 1
    }
38
39
    /**
40
     * Export closure as a string containing PHP code.
41
     *
42
     * @param Closure $closure Closure to export.
43
     *
44
     * @throws ReflectionException
45
     *
46
     * @return string String containing PHP code.
47
     */
48 39
    public function export(Closure $closure): string
49
    {
50 39
        $reflection = new ReflectionFunction($closure);
51
52 39
        $fileName = $reflection->getFileName();
53 39
        $start = $reflection->getStartLine();
54 39
        $end = $reflection->getEndLine();
55
56 39
        if ($fileName === false || $start === false || $end === false || ($fileContent = file($fileName)) === false) {
57
            return 'function() {/* Error: unable to determine Closure source */}';
58
        }
59
60 39
        --$start;
61 39
        $uses = $this->useStatementParser->fromFile($fileName);
62
63 39
        $source = implode('', array_slice($fileContent, $start, $end - $start));
64 39
        $tokens = token_get_all('<?php ' . $source);
65 39
        array_shift($tokens);
66
67 39
        $closureTokens = [];
68 39
        $pendingParenthesisCount = 0;
69 39
        $isShortClosure = false;
70 39
        $buffer = '';
71 39
        foreach ($tokens as $token) {
72 39
            if (!isset($token[0])) {
73
                continue;
74
            }
75 39
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
76 39
                $closureTokens[] = $token[1];
77 39
                if (!$isShortClosure && $token[0] === T_FN) {
78 31
                    $isShortClosure = true;
79
                }
80 39
                continue;
81
            }
82 39
            if ($closureTokens !== []) {
83 39
                $readableToken = $token[1] ?? $token;
84 39
                if ($this->isNextTokenIsPartOfNamespace($token)) {
85 20
                    $buffer .= $token[1];
86
                    if (
87 20
                        PHP_VERSION_ID >= 80000
88 20
                        && $buffer !== '\\'
89 20
                        && strpos($buffer, '\\') !== false
90
                    ) {
91
                        $usesKeys = array_filter(explode('\\', $buffer));
92
                        $buffer = array_pop($usesKeys);
93
                    }
94
                    if (
95 20
                        array_key_exists($buffer, $uses)
96 20
                        && !$this->isNextTokenIsPartOfNamespace(next($tokens))
97
                    ) {
98 12
                        $readableToken = $uses[$buffer];
99 12
                        $buffer = '';
100
                    }
101
                }
102 39
                if ($token === '{' || $token === '[') {
103 12
                    $pendingParenthesisCount++;
104 39
                } elseif ($token === '}' || $token === ']') {
105 15
                    if ($pendingParenthesisCount === 0) {
106 3
                        break;
107
                    }
108 12
                    $pendingParenthesisCount--;
109 39
                } elseif ($token === ',' || $token === ';') {
110 36
                    if ($pendingParenthesisCount === 0) {
111 36
                        break;
112
                    }
113
                }
114 39
                $closureTokens[] = $readableToken;
115
            }
116
        }
117
118 39
        return implode('', $closureTokens);
119
    }
120
121 39
    private function isNextTokenIsPartOfNamespace($token): bool
122
    {
123 39
        if (!is_array($token)) {
124 39
            return false;
125
        }
126
127 39
        return $token[0] === T_STRING
128 39
            || $token[0] === T_NS_SEPARATOR
129 39
            || (defined('T_NAME_QUALIFIED') && $token[0] === T_NAME_QUALIFIED)
130 39
            || (defined('T_NAME_FULLY_QUALIFIED') && $token[0] === T_NAME_FULLY_QUALIFIED)
131 39
            || (defined('T_NAME_RELATIVE') && $token[0] === T_NAME_RELATIVE);
132
    }
133
}
134