Passed
Push — master ( a56e1d...b3e52d )
by Alexander
02:09
created

VarDumper::exportInternal()   C

Complexity

Conditions 17
Paths 27

Size

Total Lines 56
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 17.167

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 17
eloc 37
c 5
b 0
f 0
nc 27
nop 3
dl 0
loc 56
ccs 33
cts 36
cp 0.9167
crap 17.167
rs 5.2166

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 Exception;
9
use IteratorAggregate;
10
use Yiisoft\Arrays\ArrayableInterface;
11
use function get_class;
12
13
/**
14
 * VarDumper provides enhanced versions of the PHP functions {@see var_dump()} and {@see var_export()}.
15
 * It can:
16
 *
17
 * - Correctly identify the recursively referenced objects in a complex object structure.
18
 * - Recursively control depth to avoid indefinite recursive display of some peculiar variables.
19
 * - Export closures and objects.
20
 * - Highlight output.
21
 * - Format output.
22
 */
23
final class VarDumper
24
{
25
    /**
26
     * @var mixed Variable to dump.
27
     */
28
    private $variable;
29
    private array $objects = [];
30
31
    private static ?ClosureExporter $closureExporter = null;
32
33
    /**
34
     * @param mixed $variable Variable to dump.
35
     */
36 68
    private function __construct($variable)
37
    {
38 68
        $this->variable = $variable;
39 68
    }
40
41
    /**
42
     * @param mixed $variable Variable to dump.
43
     *
44
     * @return static An instance containing variable to dump.
45
     */
46 68
    public static function create($variable): self
47
    {
48 68
        return new self($variable);
49
    }
50
51
    /**
52
     * Prints a variable.
53
     *
54
     * This method achieves the similar functionality as {@see var_dump()} and {@see print_r()}
55
     * but is more robust when handling complex objects.
56
     *
57
     * @param mixed $variable Variable to be dumped.
58
     * @param int $depth Maximum depth that the dumper should go into the variable. Defaults to 10.
59
     * @param bool $highlight Whether the result should be syntax-highlighted.
60
     */
61
    public static function dump($variable, int $depth = 10, bool $highlight = true): void
62
    {
63
        echo self::create($variable)->asString($depth, $highlight);
64
    }
65
66
    /**
67
     * Dumps a variable in terms of a string.
68
     *
69
     * This method achieves the similar functionality as {@see var_dump()} and {@see print_r()}
70
     * but is more robust when handling complex objects.
71
     *
72
     * @param int $depth Maximum depth that the dumper should go into the variable. Defaults to 10.
73
     * @param bool $highlight Whether the result should be syntax-highlighted.
74
     *
75
     * @return string The string representation of the variable.
76
     */
77 25
    public function asString(int $depth = 10, bool $highlight = false): string
78
    {
79 25
        $output = $this->dumpInternal($this->variable, true, $depth, 0);
80 25
        if ($highlight) {
81
            $result = highlight_string("<?php\n" . $output, true);
82
            $output = preg_replace('/&lt;\\?php<br \\/>/', '', $result, 1);
83
        }
84
85 25
        return $output;
86
    }
87
88
    /**
89
     * Exports a variable as a string containing PHP code.
90
     *
91
     * The string is a valid PHP expression that can be evaluated by PHP parser
92
     * and the evaluation result will give back the variable value.
93
     *
94
     * This method is similar to {@see var_export()}. The main difference is that
95
     * it generates more compact string representation using short array syntax.
96
     *
97
     * It also handles closures with {@see ClosureExporter} and objects
98
     * by using the PHP functions {@see serialize()} and {@see unserialize()}.
99
     *
100
     * @param bool $format Whatever to format code.
101
     *
102
     * @throws \ReflectionException
103
     *
104
     * @return string A PHP code representation of the variable.
105
     */
106 44
    public function export(bool $format = true): string
107
    {
108 44
        return $this->exportInternal($this->variable, $format, 0);
109
    }
110
111
    /**
112
     * @param mixed $var Variable to be dumped.
113
     * @param bool $format Whatever to format code.
114
     * @param int $depth Maximum depth.
115
     * @param int $level Current depth.
116
     *
117
     * @throws \ReflectionException
118
     *
119
     * @return string
120
     */
121 25
    private function dumpInternal($var, bool $format, int $depth, int $level): string
122
    {
123 25
        $type = gettype($var);
124 25
        switch ($type) {
125 25
            case 'resource':
126 24
            case 'resource (closed)':
127 1
                return '{resource}';
128 24
            case 'NULL':
129 1
                return 'null';
130 23
            case 'array':
131 4
                if ($depth <= $level) {
132
                    return '[...]';
133
                }
134
135 4
                if (empty($var)) {
136 1
                    return '[]';
137
                }
138
139 3
                $output = '';
140 3
                $keys = array_keys($var);
141 3
                $spaces = str_repeat(' ', $level * 4);
142 3
                $output .= '[';
143 3
                foreach ($keys as $name) {
144 3
                    if ($format) {
145 3
                        $output .= "\n" . $spaces . '    ';
146
                    }
147 3
                    $output .= $this->exportVariable($name);
148 3
                    $output .= ' => ';
149 3
                    $output .= $this->dumpInternal($var[$name], $format, $depth, $level + 1);
150
                }
151
152 3
                return $format
153 3
                    ? $output . "\n" . $spaces . ']'
154 3
                    : $output . ']';
155 22
            case 'object':
156 14
                if ($var instanceof Closure) {
157 11
                    return $this->exportClosure($var);
158
                }
159 5
                if ($depth <= $level) {
160
                    return $this->getObjectDescription($var) . ' (...)';
161
                }
162
163 5
                $this->objects[] = $var;
164 5
                $spaces = str_repeat(' ', $level * 4);
165 5
                $output = $this->getObjectDescription($var) . "\n" . $spaces . '(';
166 5
                $objectProperties = $this->getObjectProperties($var);
167 5
                foreach ($objectProperties as $name => $value) {
168 4
                    $propertyName = strtr(trim((string) $name), "\0", '::');
169 4
                    $output .= "\n" . $spaces . "    [$propertyName] => ";
170 4
                    $output .= $this->dumpInternal($value, $format, $depth, $level + 1);
171
                }
172 5
                return $output . "\n" . $spaces . ')';
173
            default:
174 10
                return $this->exportVariable($var);
175
        }
176
    }
177
178 5
    private function getObjectProperties($var): array
179
    {
180 5
        if ('__PHP_Incomplete_Class' !== get_class($var) && method_exists($var, '__debugInfo')) {
181 1
            $var = $var->__debugInfo();
182
        }
183
184 5
        return (array)$var;
185
    }
186
187
    /**
188
     * @param mixed $variable Variable to be exported.
189
     * @param bool $format Whatever to format code.
190
     * @param int $level Current depth.
191
     *
192
     * @throws \ReflectionException
193
     *
194
     * @return string
195
     */
196 44
    private function exportInternal($variable, bool $format, int $level): string
197
    {
198 44
        switch (gettype($variable)) {
199 44
            case 'NULL':
200 2
                return 'null';
201 42
            case 'array':
202 8
                if (empty($variable)) {
203 2
                    return '[]';
204
                }
205
206 6
                $keys = array_keys($variable);
207 6
                $outputKeys = ($keys !== range(0, count($variable) - 1));
208 6
                $spaces = str_repeat(' ', $level * 4);
209 6
                $output = '[';
210 6
                foreach ($keys as $key) {
211 6
                    if ($format) {
212 3
                        $output .= "\n" . $spaces . '    ';
213
                    }
214 6
                    if ($outputKeys) {
215 2
                        $output .= $this->exportVariable($key);
216 2
                        $output .= ' => ';
217
                    }
218 6
                    $output .= $this->exportInternal($variable[$key], $format, $level + 1);
219 6
                    if ($format || next($keys) !== false) {
220 5
                        $output .= ',';
221
                    }
222
                }
223 6
                return $format
224 3
                    ? $output . "\n" . $spaces . ']'
225 6
                    : $output . ']';
226 40
            case 'object':
227 23
                if ($variable instanceof Closure) {
228 18
                    return $this->exportClosure($variable);
229
                }
230
231
                try {
232 5
                    return 'unserialize(' . $this->exportVariable(serialize($variable)) . ')';
233 1
                } catch (Exception $e) {
234
                    // Serialize may fail, for example: if object contains a `\Closure` instance
235
                    // so we use a fallback.
236 1
                    if ($variable instanceof ArrayableInterface) {
237
                        return $this->exportInternal($variable->toArray(), $format, $level);
238
                    }
239
240 1
                    if ($variable instanceof IteratorAggregate) {
241
                        return $this->exportInternal(iterator_to_array($variable), $format, $level);
242
                    }
243
244 1
                    if ('__PHP_Incomplete_Class' !== get_class($variable) && method_exists($variable, '__toString')) {
245
                        return $this->exportVariable($variable->__toString());
246
                    }
247
248 1
                    return $this->exportVariable(self::create($variable)->asString());
249
                }
250
            default:
251 17
                return $this->exportVariable($variable);
252
        }
253
    }
254
255
    /**
256
     * Exports a {@see \Closure} instance.
257
     *
258
     * @param Closure $closure Closure instance.
259
     *
260
     * @throws \ReflectionException
261
     *
262
     * @return string
263
     */
264 29
    private function exportClosure(Closure $closure): string
265
    {
266 29
        if (self::$closureExporter === null) {
267 1
            self::$closureExporter = new ClosureExporter();
268
        }
269
270 29
        return self::$closureExporter->export($closure);
0 ignored issues
show
Bug introduced by
The method export() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

270
        return self::$closureExporter->/** @scrutinizer ignore-call */ export($closure);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
271
    }
272
273 5
    private function getObjectDescription(object $object): string
274
    {
275 5
        return get_class($object) . '#' . spl_object_id($object);
276
    }
277
278 33
    private function exportVariable($variable): string
279
    {
280 33
        return var_export($variable, true);
281
    }
282
}
283