Passed
Pull Request — master (#45)
by Evgeniy
01:58
created

VarDumper::dumpInternal()   C

Complexity

Conditions 14
Paths 16

Size

Total Lines 57
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 14.0245

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 14
eloc 40
c 6
b 0
f 0
nc 16
nop 4
dl 0
loc 57
ccs 38
cts 40
cp 0.95
crap 14.0245
rs 6.2666

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

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