Passed
Push — master ( 52f0a7...cdda8a )
by Alexander
21:52 queued 20:01
created

VarDumper::exportInternal()   D

Complexity

Conditions 18
Paths 28

Size

Total Lines 61
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 18

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 18
eloc 39
c 5
b 0
f 0
nc 28
nop 3
dl 0
loc 61
ccs 38
cts 38
cp 1
crap 18
rs 4.8666

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

287
        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...
288
    }
289
290
    /**
291
     * @param mixed $variable
292
     *
293
     * @return string
294
     */
295 43
    private function exportVariable($variable): string
296
    {
297 43
        return var_export($variable, true);
298
    }
299
300 7
    private function getObjectDescription(object $object): string
301
    {
302 7
        return get_class($object) . '#' . spl_object_id($object);
303
    }
304
305 6
    private function getObjectProperties(object $var): array
306
    {
307 6
        if (!$var instanceof __PHP_Incomplete_Class && method_exists($var, '__debugInfo')) {
308
            /** @var array $var */
309 1
            $var = $var->__debugInfo();
310
        }
311
312 6
        return (array) $var;
313
    }
314
}
315