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

VarDumper   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Test Coverage

Coverage 90.48%

Importance

Changes 11
Bugs 2 Features 0
Metric Value
wmc 44
eloc 98
c 11
b 2
f 0
dl 0
loc 258
ccs 95
cts 105
cp 0.9048
rs 8.8798

11 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 3 1
A __construct() 0 3 1
C exportInternal() 0 56 17
A getObjectProperties() 0 7 3
A exportVariable() 0 3 1
A dump() 0 3 1
A asString() 0 9 2
A export() 0 3 1
A exportClosure() 0 7 2
C dumpInternal() 0 54 14
A getObjectDescription() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like VarDumper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use VarDumper, and based on these observations, apply Extract Interface, too.

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