Passed
Pull Request — master (#96)
by Dmitriy
03:14
created

Dumper::dumpNestedInternal()   C

Complexity

Conditions 14
Paths 10

Size

Total Lines 61
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 14.0826

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

228
        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...
229
    }
230
}
231