Passed
Push — master ( 678bb1...5ccb66 )
by Alexander
02:43
created

Dumper::dumpNestedInternal()   C

Complexity

Conditions 13
Paths 10

Size

Total Lines 61
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 13.0711

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 39
c 1
b 0
f 0
nc 10
nop 4
dl 0
loc 61
ccs 37
cts 40
cp 0.925
crap 13.0711
rs 6.6166

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\Yii\Debug;
6
7
use Closure;
8
use Yiisoft\VarDumper\ClosureExporter;
9
10
final class Dumper
11
{
12
    /**
13
     * @var mixed Variable to dump.
14
     */
15
    private $variable;
16
17
    private array $objects = [];
18
19
    private static ?ClosureExporter $closureExporter = null;
20
21
    /**
22
     * @param mixed $variable Variable to dump.
23
     */
24 49
    private function __construct($variable)
25
    {
26 49
        $this->variable = $variable;
27 49
    }
28
29
    /**
30
     * @param mixed $variable Variable to dump.
31
     *
32
     * @return static An instance containing variable to dump.
33
     */
34 49
    public static function create($variable): self
35
    {
36 49
        return new self($variable);
37
    }
38
39
    /**
40
     * Export variable as JSON.
41
     *
42
     * @param int $depth Maximum depth that the dumper should go into the variable.
43
     * @param bool $format Whatever to format exported code.
44
     *
45
     * @return string JSON string.
46
     */
47 47
    public function asJson(int $depth = 50, bool $format = false): string
48
    {
49 47
        return $this->asJsonInternal($this->variable, $format, $depth, 0);
50
    }
51
52
    /**
53
     * Export variable as JSON summary of topmost items.
54
     *
55
     * @param int $depth Maximum depth that the dumper should go into the variable.
56
     * @param bool $prettyPrint Whatever to format exported code.
57
     *
58
     * @return string JSON string containing summary.
59
     */
60 26
    public function asJsonObjectsMap(int $depth = 50, bool $prettyPrint = false): string
61
    {
62 26
        $this->buildObjectsCache($this->variable, $depth);
63
64 26
        return $this->asJsonInternal($this->objects, $prettyPrint, $depth, 1);
65
    }
66
67 49
    private function buildObjectsCache($variable, int $depth, int $level = 0): void
68
    {
69 49
        if ($depth <= $level) {
70
            return;
71
        }
72 49
        if (is_object($variable)) {
73 16
            if (in_array($variable, $this->objects, true)) {
74 15
                return;
75
            }
76 16
            $this->objects[] = $variable;
77 16
            $variable = $this->getObjectProperties($variable);
78
        }
79 49
        if (is_array($variable)) {
80 40
            foreach ($variable as $value) {
81 38
                $this->buildObjectsCache($value, $depth, $level + 1);
82
            }
83
        }
84 49
    }
85
86 49
    private function asJsonInternal($variable, bool $format, int $depth, int $objectCollapseLevel)
87
    {
88 49
        $options = JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE;
89
90 49
        if ($format) {
91
            $options |= JSON_PRETTY_PRINT;
92
        }
93
94 49
        return json_encode($this->dumpNested($variable, $depth, $objectCollapseLevel), $options);
95
    }
96
97 49
    private function dumpNested($variable, int $depth, int $objectCollapseLevel)
98
    {
99 49
        $this->buildObjectsCache($variable, $depth);
100 49
        return $this->dumpNestedInternal($variable, $depth, 0, $objectCollapseLevel);
101
    }
102
103 16
    private function getObjectProperties($var): array
104
    {
105 16
        if ('__PHP_Incomplete_Class' !== get_class($var) && method_exists($var, '__debugInfo')) {
106
            $var = $var->__debugInfo();
107
        }
108
109 16
        return (array)$var;
110
    }
111
112 49
    private function dumpNestedInternal($var, int $depth, int $level, int $objectCollapseLevel = 0)
113
    {
114 49
        $output = $var;
115
116 49
        switch (gettype($var)) {
117 49
            case 'array':
118 30
                if ($depth <= $level) {
119
                    return 'array [...]';
120
                }
121
122 30
                $output = [];
123 30
                foreach ($var as $key => $value) {
124 29
                    $keyDisplay = str_replace("\0", '::', trim((string)$key));
125 29
                    $output[$keyDisplay] = $this->dumpNestedInternal($value, $depth, $level + 1, $objectCollapseLevel);
126
                }
127
128 30
                break;
129 48
            case 'object':
130 16
                $objectDescription = $this->getObjectDescription($var);
131 16
                if ($depth <= $level) {
132
                    $output = $objectDescription . ' (...)';
133
                    break;
134
                }
135
136 16
                if ($var instanceof Closure) {
137 10
                    $output = [$objectDescription => $this->exportClosure($var)];
138 10
                    break;
139
                }
140
141 7
                if ($objectCollapseLevel < $level && in_array($var, $this->objects, true)) {
142 4
                    $output = 'object@' . $objectDescription;
143 4
                    break;
144
                }
145
146 7
                $output = [];
147 7
                $properties = $this->getObjectProperties($var);
148 7
                if (empty($properties)) {
149 4
                    $output[$objectDescription] = '{stateless object}';
150 4
                    break;
151
                }
152 3
                foreach ($properties as $key => $value) {
153 3
                    $keyDisplay = $this->normalizeProperty((string)$key);
154
                    /**
155
                     * @psalm-suppress InvalidArrayOffset
156
                     */
157 3
                    $output[$objectDescription][$keyDisplay] = $this->dumpNestedInternal(
158 3
                        $value,
159 3
                        $depth,
160 3
                        $level + 1,
161 3
                        $objectCollapseLevel
162
                    );
163
                }
164
165 3
                break;
166 37
            case 'resource':
167 36
            case 'resource (closed)':
168 1
                $output = $this->getResourceDescription($var);
169 1
                break;
170
        }
171
172 49
        return $output;
173
    }
174
175 16
    private function getObjectDescription(object $object): string
176
    {
177 16
        return get_class($object) . '#' . spl_object_id($object);
178
    }
179
180 3
    private function normalizeProperty(string $property): string
181
    {
182 3
        $property = str_replace("\0", '::', trim($property));
183
184 3
        if (strpos($property, '*::') === 0) {
185
            return 'protected $' . substr($property, 3);
186
        }
187
188 3
        if (($pos = strpos($property, '::')) !== false) {
189
            return 'private $' . substr($property, $pos + 2);
190
        }
191
192 3
        return 'public $' . $property;
193
    }
194
195 1
    private function getResourceDescription($resource)
196
    {
197 1
        $type = get_resource_type($resource);
198 1
        if ($type === 'stream') {
199 1
            $desc = stream_get_meta_data($resource);
200
        } else {
201
            $desc = '{resource}';
202
        }
203
204 1
        return $desc;
205
    }
206
207
    /**
208
     * Exports a {@see \Closure} instance.
209
     *
210
     * @param Closure $closure Closure instance.
211
     *
212
     * @throws \ReflectionException
213
     *
214
     * @return string
215
     */
216 10
    private function exportClosure(Closure $closure): string
217
    {
218 10
        if (self::$closureExporter === null) {
219 1
            self::$closureExporter = new ClosureExporter();
220
        }
221
222 10
        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

222
        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...
223
    }
224
}
225