Completed
Push — master ( c4e3cd...c5cae6 )
by Anton
05:38
created

Dumper   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 316
Duplicated Lines 5.38 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 42
c 5
b 0
f 0
lcom 1
cbo 5
dl 17
loc 316
rs 8.2951

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A setStyle() 0 6 1
C dump() 0 38 8
C dumpValue() 0 63 11
B dumpArray() 9 33 6
B dumpObject() 8 37 6
B dumpProperty() 0 30 5
A getAccess() 0 10 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Dumper 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Dumper, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\Debug;
9
10
use Psr\Log\LoggerAwareInterface;
11
use Psr\Log\LoggerInterface;
12
use Spiral\Core\Component;
13
use Spiral\Core\Container\SingletonInterface;
14
use Spiral\Debug\Dumper\Style;
15
use Spiral\Debug\Traits\BenchmarkTrait;
16
use Spiral\Debug\Traits\LoggerTrait;
17
18
/**
19
 * One of the oldest spiral parts, used to dump variables content in user friendly way.
20
 */
21
class Dumper extends Component implements SingletonInterface, LoggerAwareInterface
22
{
23
    use LoggerTrait, BenchmarkTrait;
24
25
    /**
26
     * Declaring to IoC that class is singleton.
27
     */
28
    const SINGLETON = self::class;
29
30
    /**
31
     * Options for dump() function to specify output.
32
     */
33
    const OUTPUT_ECHO     = 0;
34
    const OUTPUT_RETURN   = 1;
35
    const OUTPUT_LOG      = 2;
36
    const OUTPUT_LOG_NICE = 3;
37
38
    /**
39
     * Deepest level to be dumped.
40
     *
41
     * @var int
42
     */
43
    private $maxLevel = 10;
44
45
    /**
46
     * @invisible
47
     * @var Style
48
     */
49
    private $style = null;
50
51
    /**
52
     * @param int             $maxLevel
53
     * @param Style           $styler Light styler to be used by default.
54
     * @param LoggerInterface $logger
55
     */
56
    public function __construct(
57
        $maxLevel = 10,
58
        Style $styler = null,
59
        LoggerInterface $logger = null
60
    ) {
61
        $this->maxLevel = $maxLevel;
62
        $this->style = !empty($styler) ? $styler : new Style();
63
        $this->logger = $logger;
64
    }
65
66
    /**
67
     * Set dump styler.
68
     *
69
     * @param Style $style
70
     * @return $this
71
     */
72
    public function setStyle(Style $style)
73
    {
74
        $this->style = $style;
75
76
        return $this;
77
    }
78
79
    /**
80
     * Dump specified value.
81
     *
82
     * @param mixed $value
83
     * @param int   $output
84
     * @return null|string
85
     */
86
    public function dump($value, $output = self::OUTPUT_ECHO)
87
    {
88
        if (php_sapi_name() === 'cli' && $output == self::OUTPUT_ECHO) {
89
            print_r($value);
90
            if (is_scalar($value)) {
91
                echo "\n";
92
            }
93
94
            return null;
95
        }
96
97
        //Dumping is pretty slow operation, let's record it so we can exclude dump time from application
98
        //timeline
99
        $benchmark = $this->benchmark('dump');
100
        try {
101
            switch ($output) {
102
                case self::OUTPUT_ECHO:
103
                    echo $this->style->mountContainer($this->dumpValue($value, '', 0));
104
                    break;
105
106
                case self::OUTPUT_RETURN:
107
                    return $this->style->mountContainer($this->dumpValue($value, '', 0));
108
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
109
110
                case self::OUTPUT_LOG:
111
                    $this->logger()->debug(print_r($value, true));
112
                    break;
113
114
                case self::OUTPUT_LOG_NICE:
115
                    $this->logger()->debug($this->dump($value, self::OUTPUT_RETURN));
116
                    break;
117
            }
118
119
            return null;
120
        } finally {
121
            $this->benchmark($benchmark);
122
        }
123
    }
124
125
    /**
126
     * Variable dumper. This is the oldest spiral function originally written in 2007. :)
127
     *
128
     * @param mixed  $value
129
     * @param string $name       Variable name, internal.
130
     * @param int    $level      Dumping level, internal.
131
     * @param bool   $hideHeader Hide array/object header, internal.
132
     * @return string
133
     */
134
    private function dumpValue($value, $name = '', $level = 0, $hideHeader = false)
135
    {
136
        //Any dump starts with initial indent (level based)
137
        $indent = $this->style->indent($level);
138
139
        if (!$hideHeader && !empty($name)) {
140
            //Showing element name (if any provided)
141
            $header = $indent . $this->style->style($name, "name");
142
143
            //Showing equal sing
144
            $header .= $this->style->style(" = ", "syntax", "=");
145
        } else {
146
            $header = $indent;
147
        }
148
149
        if ($level > $this->maxLevel) {
150
            //Dumper is not reference based, we can't dump too deep values
151
            return $indent . $this->style->style('-too deep-', 'maxLevel') . "\n";
152
        }
153
154
        $type = strtolower(gettype($value));
155
156
        if ($type == 'array') {
157
            return $header . $this->dumpArray($value, $level, $hideHeader);
158
        }
159
160
        if ($type == 'object') {
161
            return $header . $this->dumpObject($value, $level, $hideHeader);
162
        }
163
164
        if ($type == 'resource') {
165
            //No need to dump resource value
166
            $element = get_resource_type($value) . " resource ";
167
168
            return $header . $this->style->style($element, "type", "resource") . "\n";
169
        }
170
171
        //Value length
172
        $length = strlen($value);
173
174
        //Including type size
175
        $header .= $this->style->style("{$type}({$length})", "type", $type);
176
177
        $element = null;
178
        switch ($type) {
179
            case "string":
180
                $element = htmlspecialchars($value);
181
                break;
182
183
            case "boolean":
184
                $element = ($value ? "true" : "false");
185
                break;
186
187
            default:
188
                if ($value !== null) {
189
                    //Not showing null value, type is enough
190
                    $element = var_export($value, true);
191
                }
192
        }
193
194
        //Including value
195
        return $header . " " . $this->style->style($element, "value", $type) . "\n";
196
    }
197
198
    /**
199
     * @param array $array
200
     * @param int   $level
201
     * @param bool  $hideHeader
202
     * @return string
203
     */
204
    private function dumpArray(array $array, $level, $hideHeader = false)
205
    {
206
        $indent = $this->style->indent($level);
207
208 View Code Duplication
        if (!$hideHeader) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
209
            $count = count($array);
210
211
            //Array size and scope
212
            $output = $this->style->style("array({$count})", "type", "array") . "\n";
213
            $output .= $indent . $this->style->style("[", "syntax", "[") . "\n";
214
        } else {
215
            $output = '';
216
        }
217
218
        foreach ($array as $key => $value) {
219
            if (!is_numeric($key)) {
220
                if (is_string($key)) {
221
                    $key = htmlspecialchars($key);
222
                }
223
224
                $key = "'{$key}'";
225
            }
226
227
            $output .= $this->dumpValue($value, "[{$key}]", $level + 1);
228
        }
229
230
        if (!$hideHeader) {
231
            //Closing array scope
232
            $output .= $indent . $this->style->style("]", "syntax", "]") . "\n";
233
        }
234
235
        return $output;
236
    }
237
238
    /**
239
     * @param object $object
240
     * @param int    $level
241
     * @param bool   $hideHeader
242
     * @param string $class
243
     * @return string
244
     */
245
    private function dumpObject($object, $level, $hideHeader = false, $class = '')
246
    {
247
        $indent = $this->style->indent($level);
248
249 View Code Duplication
        if (!$hideHeader) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
            $type = ($class ?: get_class($object)) . " object ";
251
252
            $header = $this->style->style($type, "type", "object") . "\n";
253
            $header .= $indent . $this->style->style("(", "syntax", "(") . "\n";
254
        } else {
255
            $header = '';
256
        }
257
258
        //Let's use method specifically created for dumping
259
        if (method_exists($object, '__debugInfo')) {
260
            $debugInfo = $object->__debugInfo();
261
262
            if (is_object($debugInfo)) {
263
                //We are not including syntax elements here
264
                return $this->dumpObject($debugInfo, $level, false, get_class($object));
265
            }
266
267
            return $header
268
            . $this->dumpValue($debugInfo, '', $level + (is_scalar($object)), true)
269
            . $indent . $this->style->style(")", "syntax", ")") . "\n";
270
        }
271
272
        $refection = new \ReflectionObject($object);
273
274
        $output = '';
275
        foreach ($refection->getProperties() as $property) {
276
            $output .= $this->dumpProperty($object, $property, $level);
277
        }
278
279
        //Header, content, footer
280
        return $header . $output . $indent . $this->style->style(")", "syntax", ")") . "\n";
281
    }
282
283
    /**
284
     * @param object              $object
285
     * @param \ReflectionProperty $property
286
     * @param int                 $level
287
     * @return string
288
     */
289
    private function dumpProperty($object, \ReflectionProperty $property, $level)
290
    {
291
        if ($property->isStatic()) {
292
            return '';
293
        }
294
295
        if (
296
            !($object instanceof \stdClass)
297
            && strpos($property->getDocComment(), '@invisible') !== false
298
        ) {
299
            //Memory loop while reading doc comment for stdClass variables?
300
            //Report a PHP bug about treating comment INSIDE property declaration as doc comment.
301
            return '';
302
        }
303
304
        //Property access level
305
        $access = $this->getAccess($property);
306
307
        //To read private and protected properties
308
        $property->setAccessible(true);
309
310
        if ($object instanceof \stdClass) {
311
            $access = 'dynamic';
312
        }
313
314
        //Property name includes access level
315
        $name = $property->getName() . $this->style->style(":" . $access, "access", $access);
316
317
        return $this->dumpValue($property->getValue($object), $name, $level + 1);
318
    }
319
320
    /**
321
     * Property access level label.
322
     *
323
     * @param \ReflectionProperty $property
324
     * @return string
325
     */
326
    private function getAccess(\ReflectionProperty $property)
327
    {
328
        if ($property->isPrivate()) {
329
            return 'private';
330
        } elseif ($property->isProtected()) {
331
            return 'protected';
332
        }
333
334
        return 'public';
335
    }
336
}