Passed
Pull Request — master (#44)
by Evgeniy
02:06
created

ClosureExporter::export()   D

Complexity

Conditions 20
Paths 39

Size

Total Lines 58
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 20.1687

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 20
eloc 39
c 2
b 1
f 0
nc 39
nop 1
dl 0
loc 58
ccs 37
cts 40
cp 0.925
crap 20.1687
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
        $pendingParenthesisCount = 0;
67
68 34
        foreach ($tokens as $token) {
69 34
            if (in_array($token[0], [T_FUNCTION, T_FN, T_STATIC], true)) {
70 34
                $closureTokens[] = $token[1];
71 34
                continue;
72
            }
73 34
            if ($closureTokens !== []) {
74 34
                $readableToken = $token[1] ?? $token;
75 34
                if (TokenHelper::isPartOfNamespace($token)) {
76 20
                    $buffer .= $token[1];
77 20
                    if (PHP_VERSION_ID >= 80000 && $buffer !== '\\' && strpos($buffer, '\\') !== false) {
78
                        $usesKeys = array_filter(explode('\\', $buffer));
79
                        $buffer = array_pop($usesKeys);
80
                    }
81 20
                    if (isset($uses[$buffer])) {
82 9
                        $readableToken = $uses[$buffer];
83 9
                        $buffer = '';
84
                    }
85
                }
86 34
                if (is_string($token)) {
87 34
                    if ($this->isOpenParenthesis($token)) {
88 34
                        $pendingParenthesisCount++;
89 34
                    } elseif ($this->isCloseParenthesis($token)) {
90 34
                        if ($pendingParenthesisCount === 0) {
91 7
                            break;
92
                        }
93 34
                        $pendingParenthesisCount--;
94 31
                    } elseif ($token === ',' || $token === ';') {
95 29
                        if ($pendingParenthesisCount === 0) {
96 27
                            break;
97
                        }
98
                    }
99
                }
100 34
                $closureTokens[] = $readableToken;
101
            }
102
        }
103
104 34
        return implode('', $closureTokens);
105
    }
106
107 34
    private function isOpenParenthesis(string $value): bool
108
    {
109 34
        return in_array($value, ['{', '[', '(']);
110
    }
111
112 34
    private function isCloseParenthesis(string $value): bool
113
    {
114 34
        return in_array($value, ['}', ']', ')']);
115
    }
116
}
117