Passed
Push — master ( a4ffc4...615af5 )
by Alexander
03:23
created

Dumper::dumpNestedInternal()   C

Complexity

Conditions 14
Paths 10

Size

Total Lines 61
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 14.1045

Importance

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

231
        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...
232
    }
233
}
234