Completed
Push — master ( 918b18...d84ead )
by Anton
07:12 queued 02:34
created

Dumper::dump()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 29
rs 8.439
cc 5
eloc 19
nc 8
nop 2
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
     * Options for dump() function to specify output.
27
     */
28
    const OUTPUT_ECHO     = 0;
29
    const OUTPUT_RETURN   = 1;
30
    const OUTPUT_LOG      = 2;
31
    const OUTPUT_LOG_NICE = 3;
32
33
    /**
34
     * Deepest level to be dumped.
35
     *
36
     * @var int
37
     */
38
    private $maxLevel = 10;
39
40
    /**
41
     * @invisible
42
     * @var Style
43
     */
44
    private $style = null;
45
46
    /**
47
     * @param int             $maxLevel
48
     * @param Style           $styler Light styler to be used by default.
49
     * @param LoggerInterface $logger
50
     */
51
    public function __construct(
52
        $maxLevel = 10,
53
        Style $styler = null,
54
        LoggerInterface $logger = null
55
    ) {
56
        $this->maxLevel = $maxLevel;
57
        $this->style = !empty($styler) ? $styler : new Style();
58
        $this->logger = $logger;
59
    }
60
61
    /**
62
     * Set dump styler.
63
     *
64
     * @param Style $style
65
     * @return $this
66
     */
67
    public function setStyle(Style $style)
68
    {
69
        $this->style = $style;
70
71
        return $this;
72
    }
73
74
    /**
75
     * Dump specified value.
76
     *
77
     * @param mixed $value
78
     * @param int   $output
79
     * @return null|string
80
     */
81
    public function dump($value, $output = self::OUTPUT_ECHO)
82
    {
83
        //Dumping is pretty slow operation, let's record it so we can exclude dump time from application
84
        //timeline
85
        $benchmark = $this->benchmark('dump');
86
        try {
87
            switch ($output) {
88
                case self::OUTPUT_ECHO:
89
                    echo $this->style->mountContainer($this->dumpValue($value, '', 0));
90
                    break;
91
92
                case self::OUTPUT_RETURN:
93
                    return $this->style->mountContainer($this->dumpValue($value, '', 0));
94
                    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...
95
96
                case self::OUTPUT_LOG:
97
                    $this->logger()->debug(print_r($value, true));
98
                    break;
99
100
                case self::OUTPUT_LOG_NICE:
101
                    $this->logger()->debug($this->dump($value, self::OUTPUT_RETURN));
102
                    break;
103
            }
104
105
            return null;
106
        } finally {
107
            $this->benchmark($benchmark);
108
        }
109
    }
110
111
    /**
112
     * Variable dumper. This is the oldest spiral function originally written in 2007. :)
113
     *
114
     * @param mixed  $value
115
     * @param string $name       Variable name, internal.
116
     * @param int    $level      Dumping level, internal.
117
     * @param bool   $hideHeader Hide array/object header, internal.
118
     * @return string
119
     */
120
    private function dumpValue($value, $name = '', $level = 0, $hideHeader = false)
121
    {
122
        //Any dump starts with initial indent (level based)
123
        $indent = $this->style->indent($level);
124
125
        if (!$hideHeader && !empty($name)) {
126
            //Showing element name (if any provided)
127
            $header = $indent . $this->style->style($name, "name");
128
129
            //Showing equal sing
130
            $header .= $this->style->style(" = ", "syntax", "=");
131
        } else {
132
            $header = $indent;
133
        }
134
135
        if ($level > $this->maxLevel) {
136
            //Dumper is not reference based, we can't dump too deep values
137
            return $indent . $this->style->style('-too deep-', 'maxLevel') . "\n";
138
        }
139
140
        $type = strtolower(gettype($value));
141
142
        if ($type == 'array') {
143
            return $header . $this->dumpArray($value, $level, $hideHeader);
144
        }
145
146
        if ($type == 'object') {
147
            return $header . $this->dumpObject($value, $level, $hideHeader);
148
        }
149
150
        if ($type == 'resource') {
151
            //No need to dump resource value
152
            $element = get_resource_type($value) . " resource ";
153
154
            return $header . $this->style->style($element, "type", "resource") . "\n";
155
        }
156
157
        //Value length
158
        $length = strlen($value);
159
160
        //Including type size
161
        $header .= $this->style->style("{$type}({$length})", "type", $type);
162
163
        $element = null;
164
        switch ($type) {
165
            case "string":
166
                $element = htmlspecialchars($value);
167
                break;
168
169
            case "boolean":
170
                $element = ($value ? "true" : "false");
171
                break;
172
173
            default:
174
                if ($value !== null) {
175
                    //Not showing null value, type is enough
176
                    $element = var_export($value, true);
177
                }
178
        }
179
180
        //Including value
181
        return $header . " " . $this->style->style($element, "value", $type) . "\n";
182
    }
183
184
    /**
185
     * @param array $array
186
     * @param int   $level
187
     * @param bool  $hideHeader
188
     * @return string
189
     */
190
    private function dumpArray(array $array, $level, $hideHeader = false)
191
    {
192
        $indent = $this->style->indent($level);
193
194 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...
195
            $count = count($array);
196
197
            //Array size and scope
198
            $output = $this->style->style("array({$count})", "type", "array") . "\n";
199
            $output .= $indent . $this->style->style("[", "syntax", "[") . "\n";
200
        } else {
201
            $output = '';
202
        }
203
204
        foreach ($array as $key => $value) {
205
            if (!is_numeric($key)) {
206
                if (is_string($key)) {
207
                    $key = htmlspecialchars($key);
208
                }
209
210
                $key = "'{$key}'";
211
            }
212
213
            $output .= $this->dumpValue($value, "[{$key}]", $level + 1);
214
        }
215
216
        if (!$hideHeader) {
217
            //Closing array scope
218
            $output .= $indent . $this->style->style("]", "syntax", "]") . "\n";
219
        }
220
221
        return $output;
222
    }
223
224
    /**
225
     * @param object $object
226
     * @param int    $level
227
     * @param bool   $hideHeader
228
     * @param string $class
229
     * @return string
230
     */
231
    private function dumpObject($object, $level, $hideHeader = false, $class = '')
232
    {
233
        $indent = $this->style->indent($level);
234
235 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...
236
            $type = ($class ?: get_class($object)) . " object ";
237
238
            $header = $this->style->style($type, "type", "object") . "\n";
239
            $header .= $indent . $this->style->style("(", "syntax", "(") . "\n";
240
        } else {
241
            $header = '';
242
        }
243
244
        //Let's use method specifically created for dumping
245
        if (method_exists($object, '__debugInfo')) {
246
            $debugInfo = $object->__debugInfo();
247
248
            if (is_array($debugInfo)) {
249
                //Pretty view
250
                $debugInfo = (object)$debugInfo;
251
            }
252
253
            if (is_object($debugInfo)) {
254
                //We are not including syntax elements here
255
                return $this->dumpObject($debugInfo, $level, false, get_class($object));
256
            }
257
258
            return $header
259
            . $this->dumpValue($debugInfo, '', $level + (is_scalar($object)), true)
260
            . $indent . $this->style->style(")", "syntax", ")") . "\n";
261
        }
262
263
        if ($object instanceof \Closure) {
264
            //todo: dump source code of closure
265
            $output = '';
266
        } else {
267
            $refection = new \ReflectionObject($object);
268
269
            $output = '';
270
            foreach ($refection->getProperties() as $property) {
271
                $output .= $this->dumpProperty($object, $property, $level);
272
            }
273
        }
274
275
        //Header, content, footer
276
        return $header . $output . $indent . $this->style->style(")", "syntax", ")") . "\n";
277
    }
278
279
    /**
280
     * @param object              $object
281
     * @param \ReflectionProperty $property
282
     * @param int                 $level
283
     * @return string
284
     */
285
    private function dumpProperty($object, \ReflectionProperty $property, $level)
286
    {
287
        if ($property->isStatic()) {
288
            return '';
289
        }
290
291
        if (
292
            !($object instanceof \stdClass)
293
            && strpos($property->getDocComment(), '@invisible') !== false
294
        ) {
295
            //Memory loop while reading doc comment for stdClass variables?
296
            //Report a PHP bug about treating comment INSIDE property declaration as doc comment.
297
            return '';
298
        }
299
300
        //Property access level
301
        $access = $this->getAccess($property);
302
303
        //To read private and protected properties
304
        $property->setAccessible(true);
305
306
        if ($object instanceof \stdClass) {
307
            $access = 'dynamic';
308
        }
309
310
        //Property name includes access level
311
        $name = $property->getName() . $this->style->style(":" . $access, "access", $access);
312
313
        return $this->dumpValue($property->getValue($object), $name, $level + 1);
314
    }
315
316
    /**
317
     * Property access level label.
318
     *
319
     * @param \ReflectionProperty $property
320
     * @return string
321
     */
322
    private function getAccess(\ReflectionProperty $property)
323
    {
324
        if ($property->isPrivate()) {
325
            return 'private';
326
        } elseif ($property->isProtected()) {
327
            return 'protected';
328
        }
329
330
        return 'public';
331
    }
332
}