Passed
Push — master ( c69a4e...3c6756 )
by Michael
21:44 queued 13:20
created

Kint   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 620
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 281
dl 0
loc 620
rs 2
c 0
b 0
f 0
wmc 92

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setStatesFromCallInfo() 0 9 4
A setRenderer() 0 3 1
C getCallInfo() 0 50 13
B createFromStatics() 0 34 11
A dumpVar() 0 4 1
B dump() 0 44 9
A getParser() 0 3 1
A getBasesFromParamInfo() 0 31 6
A __construct() 0 4 1
A setParser() 0 3 1
B setStatesFromStatics() 0 30 8
A getRenderer() 0 3 1
C getSingleCall() 0 86 17
B trace() 0 58 11
A dumpNothing() 0 7 1
A getStatics() 0 14 1
A dumpAll() 0 22 5

How to fix   Complexity   

Complex Class

Complex classes like Kint 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.

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

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2013 Jonathan Vollebregt ([email protected]), Rokas Šleinius ([email protected])
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
11
 * this software and associated documentation files (the "Software"), to deal in
12
 * the Software without restriction, including without limitation the rights to
13
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
 * the Software, and to permit persons to whom the Software is furnished to do so,
15
 * subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
22
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
23
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
 */
27
28
namespace Kint;
29
30
use InvalidArgumentException;
31
use Kint\Parser\ConstructablePluginInterface;
32
use Kint\Parser\Parser;
33
use Kint\Parser\PluginInterface;
34
use Kint\Renderer\ConstructableRendererInterface;
35
use Kint\Renderer\RendererInterface;
36
use Kint\Renderer\TextRenderer;
37
use Kint\Value\Context\BaseContext;
38
use Kint\Value\Context\ContextInterface;
39
use Kint\Value\UninitializedValue;
40
41
/**
42
 * @psalm-consistent-constructor
43
 * Psalm bug #8523
44
 *
45
 * @psalm-import-type CallParameter from CallFinder
46
 *
47
 * @psalm-type KintMode = Kint::MODE_*|bool
48
 *
49
 * @psalm-api
50
 */
51
class Kint implements FacadeInterface
52
{
53
    public const MODE_RICH = 'r';
54
    public const MODE_TEXT = 't';
55
    public const MODE_CLI = 'c';
56
    public const MODE_PLAIN = 'p';
57
58
    /**
59
     * @var mixed Kint mode
60
     *
61
     * false: Disabled
62
     * true: Enabled, default mode selection
63
     * other: Manual mode selection
64
     *
65
     * @psalm-var KintMode
66
     */
67
    public static $enabled_mode = true;
68
69
    /**
70
     * Default mode.
71
     *
72
     * @psalm-var KintMode
73
     */
74
    public static $mode_default = self::MODE_RICH;
75
76
    /**
77
     * Default mode in CLI with cli_detection on.
78
     *
79
     * @psalm-var KintMode
80
     */
81
    public static $mode_default_cli = self::MODE_CLI;
82
83
    /**
84
     * @var bool enable detection when Kint is command line.
85
     *
86
     * Formats output with whitespace only; does not HTML-escape it
87
     */
88
    public static bool $cli_detection = true;
89
90
    /**
91
     * @var bool Return output instead of echoing
92
     */
93
    public static bool $return = false;
94
95
    /**
96
     * @var int depth limit for array/object traversal. 0 for no limit
97
     */
98
    public static int $depth_limit = 7;
99
100
    /**
101
     * @var bool expand all trees by default for rich view
102
     */
103
    public static bool $expanded = false;
104
105
    /**
106
     * @var bool whether to display where kint was called from
107
     */
108
    public static bool $display_called_from = true;
109
110
    /**
111
     * @var array Kint aliases. Add debug functions in Kint wrappers here to fix modifiers and backtraces
112
     */
113
    public static array $aliases = [
114
        [self::class, 'dump'],
115
        [self::class, 'trace'],
116
        [self::class, 'dumpAll'],
117
    ];
118
119
    /**
120
     * @psalm-var array<RendererInterface|class-string<ConstructableRendererInterface>>
121
     *
122
     * Array of modes to renderer class names
123
     */
124
    public static array $renderers = [
125
        self::MODE_RICH => Renderer\RichRenderer::class,
126
        self::MODE_PLAIN => Renderer\PlainRenderer::class,
127
        self::MODE_TEXT => TextRenderer::class,
128
        self::MODE_CLI => Renderer\CliRenderer::class,
129
    ];
130
131
    /**
132
     * @psalm-var array<PluginInterface|class-string<ConstructablePluginInterface>>
133
     */
134
    public static array $plugins = [
135
        \Kint\Parser\ArrayLimitPlugin::class,
136
        \Kint\Parser\ArrayObjectPlugin::class,
137
        \Kint\Parser\Base64Plugin::class,
138
        \Kint\Parser\BinaryPlugin::class,
139
        \Kint\Parser\BlacklistPlugin::class,
140
        \Kint\Parser\ClassHooksPlugin::class,
141
        \Kint\Parser\ClassMethodsPlugin::class,
142
        \Kint\Parser\ClassStaticsPlugin::class,
143
        \Kint\Parser\ClassStringsPlugin::class,
144
        \Kint\Parser\ClosurePlugin::class,
145
        \Kint\Parser\ColorPlugin::class,
146
        \Kint\Parser\DateTimePlugin::class,
147
        \Kint\Parser\DomPlugin::class,
148
        \Kint\Parser\EnumPlugin::class,
149
        \Kint\Parser\FsPathPlugin::class,
150
        \Kint\Parser\HtmlPlugin::class,
151
        \Kint\Parser\IteratorPlugin::class,
152
        \Kint\Parser\JsonPlugin::class,
153
        \Kint\Parser\MicrotimePlugin::class,
154
        \Kint\Parser\MysqliPlugin::class,
155
        // \Kint\Parser\SerializePlugin::class,
156
        \Kint\Parser\SimpleXMLElementPlugin::class,
157
        \Kint\Parser\SplFileInfoPlugin::class,
158
        \Kint\Parser\StreamPlugin::class,
159
        \Kint\Parser\TablePlugin::class,
160
        \Kint\Parser\ThrowablePlugin::class,
161
        \Kint\Parser\TimestampPlugin::class,
162
        \Kint\Parser\ToStringPlugin::class,
163
        \Kint\Parser\TracePlugin::class,
164
        \Kint\Parser\XmlPlugin::class,
165
    ];
166
167
    protected Parser $parser;
168
    protected RendererInterface $renderer;
169
170
    public function __construct(Parser $p, RendererInterface $r)
171
    {
172
        $this->parser = $p;
173
        $this->renderer = $r;
174
    }
175
176
    public function setParser(Parser $p): void
177
    {
178
        $this->parser = $p;
179
    }
180
181
    public function getParser(): Parser
182
    {
183
        return $this->parser;
184
    }
185
186
    public function setRenderer(RendererInterface $r): void
187
    {
188
        $this->renderer = $r;
189
    }
190
191
    public function getRenderer(): RendererInterface
192
    {
193
        return $this->renderer;
194
    }
195
196
    public function setStatesFromStatics(array $statics): void
197
    {
198
        $this->renderer->setStatics($statics);
199
200
        $this->parser->setDepthLimit($statics['depth_limit'] ?? 0);
201
        $this->parser->clearPlugins();
202
203
        if (!isset($statics['plugins'])) {
204
            return;
205
        }
206
207
        $plugins = [];
208
209
        foreach ($statics['plugins'] as $plugin) {
210
            if ($plugin instanceof PluginInterface) {
211
                $plugins[] = $plugin;
212
            } elseif (\is_string($plugin) && \is_a($plugin, ConstructablePluginInterface::class, true)) {
213
                $plugins[] = new $plugin($this->parser);
214
            }
215
        }
216
217
        $plugins = $this->renderer->filterParserPlugins($plugins);
218
219
        foreach ($plugins as $plugin) {
220
            try {
221
                $this->parser->addPlugin($plugin);
222
            } catch (InvalidArgumentException $e) {
223
                \trigger_error(
224
                    'Plugin '.Utils::errorSanitizeString(\get_class($plugin)).' could not be added to a Kint parser: '.Utils::errorSanitizeString($e->getMessage()),
225
                    E_USER_WARNING
226
                );
227
            }
228
        }
229
    }
230
231
    public function setStatesFromCallInfo(array $info): void
232
    {
233
        $this->renderer->setCallInfo($info);
234
235
        if (isset($info['modifiers']) && \is_array($info['modifiers']) && \in_array('+', $info['modifiers'], true)) {
236
            $this->parser->setDepthLimit(0);
237
        }
238
239
        $this->parser->setCallerClass($info['caller']['class'] ?? null);
240
    }
241
242
    public function dumpAll(array $vars, array $base): string
243
    {
244
        if (\array_keys($vars) !== \array_keys($base)) {
245
            throw new InvalidArgumentException('Kint::dumpAll requires arrays of identical size and keys as arguments');
246
        }
247
248
        if ([] === $vars) {
249
            return $this->dumpNothing();
250
        }
251
252
        $output = $this->renderer->preRender();
253
254
        foreach ($vars as $key => $_) {
255
            if (!$base[$key] instanceof ContextInterface) {
256
                throw new InvalidArgumentException('Kint::dumpAll requires all elements of the second argument to be ContextInterface instances');
257
            }
258
            $output .= $this->dumpVar($vars[$key], $base[$key]);
259
        }
260
261
        $output .= $this->renderer->postRender();
262
263
        return $output;
264
    }
265
266
    protected function dumpNothing(): string
267
    {
268
        $output = $this->renderer->preRender();
269
        $output .= $this->renderer->render(new UninitializedValue(new BaseContext('No argument')));
270
        $output .= $this->renderer->postRender();
271
272
        return $output;
273
    }
274
275
    /**
276
     * Dumps and renders a var.
277
     *
278
     * @param mixed &$var Data to dump
279
     */
280
    protected function dumpVar(&$var, ContextInterface $c): string
281
    {
282
        return $this->renderer->render(
283
            $this->parser->parse($var, $c)
284
        );
285
    }
286
287
    /**
288
     * Gets all static settings at once.
289
     *
290
     * @return array Current static settings
291
     */
292
    public static function getStatics(): array
293
    {
294
        return [
295
            'aliases' => static::$aliases,
296
            'cli_detection' => static::$cli_detection,
297
            'depth_limit' => static::$depth_limit,
298
            'display_called_from' => static::$display_called_from,
299
            'enabled_mode' => static::$enabled_mode,
300
            'expanded' => static::$expanded,
301
            'mode_default' => static::$mode_default,
302
            'mode_default_cli' => static::$mode_default_cli,
303
            'plugins' => static::$plugins,
304
            'renderers' => static::$renderers,
305
            'return' => static::$return,
306
        ];
307
    }
308
309
    /**
310
     * Creates a Kint instance based on static settings.
311
     *
312
     * @param array $statics array of statics as returned by getStatics
313
     */
314
    public static function createFromStatics(array $statics): ?FacadeInterface
315
    {
316
        $mode = false;
317
318
        if (isset($statics['enabled_mode'])) {
319
            $mode = $statics['enabled_mode'];
320
321
            if (true === $mode && isset($statics['mode_default'])) {
322
                $mode = $statics['mode_default'];
323
324
                if (PHP_SAPI === 'cli' && !empty($statics['cli_detection']) && isset($statics['mode_default_cli'])) {
325
                    $mode = $statics['mode_default_cli'];
326
                }
327
            }
328
        }
329
330
        if (false === $mode) {
331
            return null;
332
        }
333
334
        $renderer = null;
335
        if (isset($statics['renderers'][$mode])) {
336
            if ($statics['renderers'][$mode] instanceof RendererInterface) {
337
                $renderer = $statics['renderers'][$mode];
338
            }
339
340
            if (\is_a($statics['renderers'][$mode], ConstructableRendererInterface::class, true)) {
341
                $renderer = new $statics['renderers'][$mode]();
342
            }
343
        }
344
345
        $renderer ??= new TextRenderer();
346
347
        return new static(new Parser(), $renderer);
348
    }
349
350
    /**
351
     * Creates base contexts given parameter info.
352
     *
353
     * @psalm-param list<CallParameter> $params
354
     *
355
     * @return BaseContext[] Base contexts for the arguments
356
     */
357
    public static function getBasesFromParamInfo(array $params, int $argc): array
358
    {
359
        $bases = [];
360
361
        for ($i = 0; $i < $argc; ++$i) {
362
            $param = $params[$i] ?? null;
363
364
            if (!empty($param['literal'])) {
365
                $name = 'literal';
366
            } else {
367
                $name = $param['name'] ?? '$'.$i;
368
            }
369
370
            if (isset($param['path'])) {
371
                $access_path = $param['path'];
372
373
                if ($param['expression']) {
374
                    $access_path = '('.$access_path.')';
375
                } elseif ($param['new_without_parens']) {
376
                    $access_path .= '()';
377
                }
378
            } else {
379
                $access_path = '$'.$i;
380
            }
381
382
            $base = new BaseContext($name);
383
            $base->access_path = $access_path;
384
            $bases[] = $base;
385
        }
386
387
        return $bases;
388
    }
389
390
    /**
391
     * Gets call info from the backtrace, alias, and argument count.
392
     *
393
     * Aliases must be normalized beforehand (Utils::normalizeAliases)
394
     *
395
     * @param array   $aliases Call aliases as found in Kint::$aliases
396
     * @param array[] $trace   Backtrace
397
     * @param array   $args    Arguments
398
     *
399
     * @return array Call info
400
     *
401
     * @psalm-param list<non-empty-array> $trace
402
     */
403
    public static function getCallInfo(array $aliases, array $trace, array $args): array
404
    {
405
        $found = false;
406
        $callee = null;
407
        $caller = null;
408
        $miniTrace = [];
409
410
        foreach ($trace as $frame) {
411
            if (Utils::traceFrameIsListed($frame, $aliases)) {
412
                $found = true;
413
                $miniTrace = [];
414
            }
415
416
            if (!Utils::traceFrameIsListed($frame, ['spl_autoload_call'])) {
417
                $miniTrace[] = $frame;
418
            }
419
        }
420
421
        if ($found) {
422
            $callee = \reset($miniTrace) ?: null;
423
            $caller = \next($miniTrace) ?: null;
424
        }
425
426
        foreach ($miniTrace as $index => $frame) {
427
            if ((0 === $index && $callee === $frame) || isset($frame['file'], $frame['line'])) {
428
                unset($frame['object'], $frame['args']);
429
                $miniTrace[$index] = $frame;
430
            } else {
431
                unset($miniTrace[$index]);
432
            }
433
        }
434
435
        $miniTrace = \array_values($miniTrace);
436
437
        $call = static::getSingleCall($callee ?: [], $args);
438
439
        $ret = [
440
            'params' => null,
441
            'modifiers' => [],
442
            'callee' => $callee,
443
            'caller' => $caller,
444
            'trace' => $miniTrace,
445
        ];
446
447
        if (null !== $call) {
448
            $ret['params'] = $call['parameters'];
449
            $ret['modifiers'] = $call['modifiers'];
450
        }
451
452
        return $ret;
453
    }
454
455
    /**
456
     * Dumps a backtrace.
457
     *
458
     * Functionally equivalent to Kint::dump(1) or Kint::dump(debug_backtrace(true))
459
     *
460
     * @return int|string
461
     */
462
    public static function trace()
463
    {
464
        if (false === static::$enabled_mode) {
465
            return 0;
466
        }
467
468
        static::$aliases = Utils::normalizeAliases(static::$aliases);
469
470
        $call_info = static::getCallInfo(static::$aliases, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), []);
471
472
        $statics = static::getStatics();
473
474
        if (\in_array('~', $call_info['modifiers'], true)) {
475
            $statics['enabled_mode'] = static::MODE_TEXT;
476
        }
477
478
        $kintstance = static::createFromStatics($statics);
479
        if (!$kintstance) {
0 ignored issues
show
introduced by
$kintstance is of type Kint\Kint, thus it always evaluated to true.
Loading history...
480
            return 0;
481
        }
482
483
        if (\in_array('-', $call_info['modifiers'], true)) {
484
            while (\ob_get_level()) {
485
                \ob_end_clean();
486
            }
487
        }
488
489
        $kintstance->setStatesFromStatics($statics);
490
        $kintstance->setStatesFromCallInfo($call_info);
491
492
        $trimmed_trace = [];
493
        $trace = \debug_backtrace();
494
495
        foreach ($trace as $frame) {
496
            if (Utils::traceFrameIsListed($frame, static::$aliases)) {
497
                $trimmed_trace = [];
498
            }
499
500
            $trimmed_trace[] = $frame;
501
        }
502
503
        \array_shift($trimmed_trace);
504
505
        $base = new BaseContext('Kint\\Kint::trace()');
506
        $base->access_path = 'debug_backtrace()';
507
        $output = $kintstance->dumpAll([$trimmed_trace], [$base]);
508
509
        if (static::$return || \in_array('@', $call_info['modifiers'], true)) {
510
            return $output;
511
        }
512
513
        echo $output;
514
515
        if (\in_array('-', $call_info['modifiers'], true)) {
516
            \flush(); // @codeCoverageIgnore
517
        }
518
519
        return 0;
520
    }
521
522
    /**
523
     * Dumps some data.
524
     *
525
     * Functionally equivalent to Kint::dump(1) or Kint::dump(debug_backtrace())
526
     *
527
     * @psalm-param mixed ...$args
528
     *
529
     * @return int|string
530
     */
531
    public static function dump(...$args)
532
    {
533
        if (false === static::$enabled_mode) {
534
            return 0;
535
        }
536
537
        static::$aliases = Utils::normalizeAliases(static::$aliases);
538
539
        $call_info = static::getCallInfo(static::$aliases, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), $args);
540
541
        $statics = static::getStatics();
542
543
        if (\in_array('~', $call_info['modifiers'], true)) {
544
            $statics['enabled_mode'] = static::MODE_TEXT;
545
        }
546
547
        $kintstance = static::createFromStatics($statics);
548
        if (!$kintstance) {
0 ignored issues
show
introduced by
$kintstance is of type Kint\Kint, thus it always evaluated to true.
Loading history...
549
            return 0;
550
        }
551
552
        if (\in_array('-', $call_info['modifiers'], true)) {
553
            while (\ob_get_level()) {
554
                \ob_end_clean();
555
            }
556
        }
557
558
        $kintstance->setStatesFromStatics($statics);
559
        $kintstance->setStatesFromCallInfo($call_info);
560
561
        $bases = static::getBasesFromParamInfo($call_info['params'] ?? [], \count($args));
562
        $output = $kintstance->dumpAll(\array_values($args), $bases);
563
564
        if (static::$return || \in_array('@', $call_info['modifiers'], true)) {
565
            return $output;
566
        }
567
568
        echo $output;
569
570
        if (\in_array('-', $call_info['modifiers'], true)) {
571
            \flush(); // @codeCoverageIgnore
572
        }
573
574
        return 0;
575
    }
576
577
    /**
578
     * Returns specific function call info from a stack trace frame, or null if no match could be found.
579
     *
580
     * @param array $frame The stack trace frame in question
581
     * @param array $args  The arguments
582
     *
583
     * @return ?array params and modifiers, or null if a specific call could not be determined
584
     */
585
    protected static function getSingleCall(array $frame, array $args): ?array
586
    {
587
        if (
588
            !isset($frame['file'], $frame['line'], $frame['function']) ||
589
            !\is_readable($frame['file']) ||
590
            false === ($source = \file_get_contents($frame['file']))
591
        ) {
592
            return null;
593
        }
594
595
        if (empty($frame['class'])) {
596
            $callfunc = $frame['function'];
597
        } else {
598
            $callfunc = [$frame['class'], $frame['function']];
599
        }
600
601
        $calls = CallFinder::getFunctionCalls($source, $frame['line'], $callfunc);
602
603
        $argc = \count($args);
604
605
        $return = null;
606
607
        foreach ($calls as $call) {
608
            $is_unpack = false;
609
610
            // Handle argument unpacking as a last resort
611
            foreach ($call['parameters'] as $i => &$param) {
612
                if (0 === \strpos($param['name'], '...')) {
613
                    $is_unpack = true;
614
615
                    // If we're on the last param
616
                    if ($i < $argc && $i === \count($call['parameters']) - 1) {
617
                        unset($call['parameters'][$i]);
618
619
                        if (Utils::isAssoc($args)) {
620
                            // Associated unpacked arrays can be accessed by key
621
                            $keys = \array_slice(\array_keys($args), $i);
622
623
                            foreach ($keys as $key) {
624
                                $call['parameters'][] = [
625
                                    'name' => \substr($param['name'], 3).'['.\var_export($key, true).']',
626
                                    'path' => \substr($param['path'], 3).'['.\var_export($key, true).']',
627
                                    'expression' => false,
628
                                    'literal' => false,
629
                                    'new_without_parens' => false,
630
                                ];
631
                            }
632
                        } else {
633
                            // Numeric unpacked arrays have their order blown away like a pass
634
                            // through array_values so we can't access them directly at all
635
                            for ($j = 0; $j + $i < $argc; ++$j) {
636
                                $call['parameters'][] = [
637
                                    'name' => 'array_values('.\substr($param['name'], 3).')['.$j.']',
638
                                    'path' => 'array_values('.\substr($param['path'], 3).')['.$j.']',
639
                                    'expression' => false,
640
                                    'literal' => false,
641
                                    'new_without_parens' => false,
642
                                ];
643
                            }
644
                        }
645
646
                        $call['parameters'] = \array_values($call['parameters']);
647
                    } else {
648
                        $call['parameters'] = \array_slice($call['parameters'], 0, $i);
649
                    }
650
651
                    break;
652
                }
653
654
                if ($i >= $argc) {
655
                    continue 2;
656
                }
657
            }
658
659
            if ($is_unpack || \count($call['parameters']) === $argc) {
660
                if (null === $return) {
661
                    $return = $call;
662
                } else {
663
                    // If we have multiple calls on the same line with the same amount of arguments,
664
                    // we can't be sure which it is so just return null and let them figure it out
665
                    return null;
666
                }
667
            }
668
        }
669
670
        return $return;
671
    }
672
}
673