Completed
Branch develop (c2aa4c)
by Anton
05:17
created

Dumper::dumpObject()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 37
Code Lines 20

Duplication

Lines 8
Ratio 21.62 %

Importance

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