Completed
Pull Request — master (#69)
by Anton
06:13
created

Dumper::dumpObject()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 42
Code Lines 23

Duplication

Lines 8
Ratio 19.05 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 8
loc 42
rs 6.7273
cc 7
eloc 23
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
    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
        if ($object instanceof \Closure) {
273
            //todo: dump source code of closure
274
            $output = '';
275
        } else {
276
            $refection = new \ReflectionObject($object);
277
278
            $output = '';
279
            foreach ($refection->getProperties() as $property) {
280
                $output .= $this->dumpProperty($object, $property, $level);
281
            }
282
        }
283
284
        //Header, content, footer
285
        return $header . $output . $indent . $this->style->style(")", "syntax", ")") . "\n";
286
    }
287
288
    /**
289
     * @param object              $object
290
     * @param \ReflectionProperty $property
291
     * @param int                 $level
292
     * @return string
293
     */
294
    private function dumpProperty($object, \ReflectionProperty $property, $level)
295
    {
296
        if ($property->isStatic()) {
297
            return '';
298
        }
299
300
        if (
301
            !($object instanceof \stdClass)
302
            && strpos($property->getDocComment(), '@invisible') !== false
303
        ) {
304
            //Memory loop while reading doc comment for stdClass variables?
305
            //Report a PHP bug about treating comment INSIDE property declaration as doc comment.
306
            return '';
307
        }
308
309
        //Property access level
310
        $access = $this->getAccess($property);
311
312
        //To read private and protected properties
313
        $property->setAccessible(true);
314
315
        if ($object instanceof \stdClass) {
316
            $access = 'dynamic';
317
        }
318
319
        //Property name includes access level
320
        $name = $property->getName() . $this->style->style(":" . $access, "access", $access);
321
322
        return $this->dumpValue($property->getValue($object), $name, $level + 1);
323
    }
324
325
    /**
326
     * Property access level label.
327
     *
328
     * @param \ReflectionProperty $property
329
     * @return string
330
     */
331
    private function getAccess(\ReflectionProperty $property)
332
    {
333
        if ($property->isPrivate()) {
334
            return 'private';
335
        } elseif ($property->isProtected()) {
336
            return 'protected';
337
        }
338
339
        return 'public';
340
    }
341
}