Completed
Push — master ( 8a340c...157d96 )
by Lars
03:22
created

Kint::_stepIsInternal()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.5069

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 18
ccs 8
cts 11
cp 0.7272
rs 8.8571
cc 5
eloc 11
nc 4
nop 1
crap 5.5069
1
<?php
2
3
namespace kint;
4
5
use kint\decorators\Kint_Decorators;
6
use kint\inc\KintParser;
7
8
/**
9
 * Class Kint
10
 *
11
 * @package kint
12
 */
13
class Kint
14
{
15
  // these are all public and 1:1 config array keys so you can switch them easily
16
17
  const MODE_CLI = 'c'; # stores mode and active statuses
18
19
  const MODE_PLAIN = 'p';
20
21
  const MODE_JS = 'j';
22
23
  const MODE_RICH = 'r';
24
25
  const MODE_WHITESPACE = 'w';
26
27
  public static $delayedMode;
28
29
  public static $returnOutput;
30
31
  public static $fileLinkFormat;
32
33
  public static $displayCalledFrom;
34
35
  public static $maxStrLength;
36
37
  public static $appRootDirs;
38
39
  public static $maxLevels;
40
41
  public static $theme;
42
43
  public static $expandedByDefault;
44
45
  public static $cliDetection;
46
47
  public static $cliColors;
48
49
  public static $aliases = array(
50
      'methods'   => array(
51
          array('Kint', 'dump'),
52
          array('Kint', 'trace'),
53
      ),
54
      'functions' => array(
55
          'd',
56
          'dd',
57
          'ddd',
58
          'de',
59
          's',
60
          'sd',
61
          'se',
62
          'j',
63
          'jd',
64
          'je',
65
      ),
66
  );
67
68
  private static $_enabledMode;
69
70
  /**
71
   * returns parameter names that the function was passed, as well as any predefined symbols before function
72
   * call (modifiers)
73
   *
74
   * @param array $trace
75
   *
76
   * @return array( $parameters, $modifier, $callee, $previousCaller )
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
77
   */
78 2
  private static function _getCalleeInfo($trace)
79
  {
80 2
    $previousCaller = array();
81 2
    $miniTrace = array();
82 2
    $prevStep = array();
83
84
    # go from back of trace to find first occurrence of call to Kint or its wrappers
85
    /** @noinspection PhpAssignmentInConditionInspection */
86 2
    while ($step = array_pop($trace)) {
87
88 2
      if (self::_stepIsInternal($step)) {
89
        $previousCaller = $prevStep;
90
        break;
91 2
      } elseif (isset($step['file'], $step['line'])) {
92 2
        unset($step['object'], $step['args']);
93 2
        array_unshift($miniTrace, $step);
94 2
      }
95
96 2
      $prevStep = $step;
97 2
    }
98 2
    $callee = $step;
99
100
    if (
101 2
        !isset($callee['file'])
102 2
        ||
103
        !is_readable($callee['file'])
104 2
    ) {
105 2
      return array(null, null, $callee, $previousCaller, $miniTrace);
106
    }
107
108
    # open the file and read it up to the position where the function call expression ended
109
    $filePointer = fopen($callee['file'], 'r');
110
    $line = 0;
111
    $source = '';
112
    while (($row = fgets($filePointer)) !== false) {
113
      if (++$line > $callee['line']) {
114
        break;
115
      }
116
      $source .= $row;
117
    }
118
    fclose($filePointer);
119
    $source = self::_removeAllButCode($source);
120
121
    if (empty($callee['class'])) {
122
      $codePattern = $callee['function'];
123
    } else {
124
      if ($callee['type'] === '::') {
125
        $codePattern = $callee['class'] . "\x07*" . $callee['type'] . "\x07*" . $callee['function'];
126
      } else {
127
        $codePattern = ".*\x07*" . $callee['type'] . "\x07*" . $callee['function'];
128
      }
129
    }
130
131
    // TODO: if more than one call in one line - not possible to determine variable names
132
    // TODO: does not recognize string concat
133
    # get the position of the last call to the function
134
    preg_match_all(
135
        "
136
            [
137
            # beginning of statement
138
            [\x07{(]
139
140
            # search for modifiers (group 1)
141
            ([-+!@~]*)?
142
143
            # spaces
144
            \x07*
145
146
            # check if output is assigned to a variable (group 2) todo: does not detect concat
147
            (
148
                \\$[a-z0-9_]+ # variable
149
                \x07*\\.?=\x07*  # assignment
150
            )?
151
152
            # possibly a namespace symbol
153
            \\\\?
154
155
            # spaces again
156
            \x07*
157
158
            # main call to Kint
159
            ({$codePattern})
160
161
            # spaces everywhere
162
            \x07*
163
164
            # find the character where kint's opening bracket resides (group 3)
165
            (\\()
166
167
            ]ix",
168
        $source,
169
        $matches,
170
        PREG_OFFSET_CAPTURE
171
    );
172
173
    $modifiers = end($matches[1]);
174
    $assignment = end($matches[2]);
175
    $callToKint = end($matches[3]);
176
    $bracket = end($matches[4]);
177
178
    if (empty($callToKint)) {
179
      # if a wrapper is mis-configured, don't display the whole file as variable name
180
      return array(array(), $modifiers, $callee, $previousCaller, $miniTrace);
181
    }
182
183
    $modifiers = $modifiers[0];
184
    if ($assignment[1] !== -1) {
185
      $modifiers .= '@';
186
    }
187
188
    $paramsString = preg_replace("[\x07+]", ' ', substr($source, $bracket[1] + 1));
189
    # we now have a string like this:
190
    # <parameters passed>); <the rest of the last read line>
191
192
    # remove everything in brackets and quotes, we don't need nested statements nor literal strings which would
193
    # only complicate separating individual arguments
194
    $c = strlen($paramsString);
195
    $inString = $escaped = $openedBracket = $closingBracket = false;
196
    $i = 0;
197
    $inBrackets = 0;
198
    $openedBrackets = array();
199
200
    while ($i < $c) {
201
      $letter = $paramsString[$i];
202
203
      if (!$inString) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inString of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
204
        if ($letter === '\'' || $letter === '"') {
205
          $inString = $letter;
206
        } elseif ($letter === '(' || $letter === '[') {
207
          $inBrackets++;
208
          $openedBrackets[] = $openedBracket = $letter;
209
          $closingBracket = $openedBracket === '(' ? ')' : ']';
210
        } elseif ($inBrackets && $letter === $closingBracket) {
211
          $inBrackets--;
212
          array_pop($openedBrackets);
213
          $openedBracket = end($openedBrackets);
214
          $closingBracket = $openedBracket === '(' ? ')' : ']';
215
        } elseif (!$inBrackets && $letter === ')') {
216
          $paramsString = substr($paramsString, 0, $i);
217
          break;
218
        }
219
      } elseif ($letter === $inString && !$escaped) {
220
        $inString = false;
221
      }
222
223
      # replace whatever was inside quotes or brackets with untypeable characters, we don't
224
      # need that info. We'll later replace the whole string with '...'
225
      if (
226
          $inBrackets > 0
227
          &&
228
          (
229
              $inBrackets > 1
230
              ||
231
              $letter !== $openedBracket
232
          )
233
      ) {
234
        $paramsString[$i] = "\x07";
235
      }
236
237
      if (
238
          $inString
0 ignored issues
show
Bug Best Practice introduced by
The expression $inString of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
239
          &&
240
          (
241
              $letter !== $inString
242
              ||
243
              $escaped
244
          )
245
      ) {
246
        $paramsString[$i] = "\x07";
247
      }
248
249
      $escaped = !$escaped && ($letter === '\\');
250
      $i++;
251
    }
252
253
    # by now we have an un-nested arguments list, lets make it to an array for processing further
254
    $arguments = explode(',', preg_replace("[\x07+]", '...', $paramsString));
255
256
    # test each argument whether it was passed literary or was it an expression or a variable name
257
    $parameters = array();
258
    $blacklist = array('null', 'true', 'false', 'array(...)', 'array()', '"..."', '[...]', 'b"..."',);
259
    foreach ($arguments as $argument) {
260
      $argument = trim($argument);
261
262
      if (
263
          is_numeric($argument)
264
          ||
265
          in_array(str_replace("'", '"', strtolower($argument)), $blacklist, true)
266
      ) {
267
        $parameters[] = null;
268
      } else {
269
        $parameters[] = $argument;
270
      }
271
    }
272
273
    return array($parameters, $modifiers, $callee, $previousCaller, $miniTrace);
274
  }
275
276
  /**
277
   * @param array $data
278
   *
279
   * @return array|false
280
   */
281 2
  private static function _parseTrace(array $data)
282
  {
283 2
    $trace = array();
284 2
    $traceFields = array('file', 'line', 'args', 'class');
285 2
    $fileFound = false; # file element must exist in one of the steps
286
287
    # validate whether a trace was indeed passed
288
    /** @noinspection PhpAssignmentInConditionInspection */
289 2
    while ($step = array_pop($data)) {
290 2
      if (!is_array($step) || !isset($step['function'])) {
291 2
        return false;
292
      }
293
      if (!$fileFound && isset($step['file']) && file_exists($step['file'])) {
294
        $fileFound = true;
295
      }
296
297
      $valid = false;
298
      foreach ($traceFields as $element) {
299
        if (isset($step[$element])) {
300
          $valid = true;
301
          break;
302
        }
303
      }
304
      if (!$valid) {
305
        return false;
306
      }
307
308
      if (self::_stepIsInternal($step)) {
309
        $step = array(
310
            'file'     => $step['file'],
311
            'line'     => $step['line'],
312
            'function' => '',
313
        );
314
        array_unshift($trace, $step);
315
        break;
316
      }
317
318
      $step['index'] = count( $data ) - 1;
319
320
      if ($step['function'] !== 'spl_autoload_call') { # meaningless
321
        array_unshift($trace, $step);
322
      }
323
    }
324
    if (!$fileFound) {
325
      return false;
326
    }
327
328
    $output = array();
329
    foreach ($trace as $step) {
330
      if (isset($step['file'])) {
331
        $file = $step['file'];
332
333
        if (isset($step['line'])) {
334
          $line = $step['line'];
335
          # include the source of this step
336
          if (self::enabled() === self::MODE_RICH) {
337
            $source = self::_showSource($file, $line);
338
          }
339
        }
340
      }
341
342
      $function = $step['function'];
343
344
      if (in_array($function, array('include', 'include_once', 'require', 'require_once'), true)) {
345
        if (empty($step['args'])) {
346
          # no arguments
347
          $args = array();
348
        } else {
349
          # sanitize the included file path
350
          $args = array('file' => self::shortenPath($step['args'][0]));
351
        }
352
      } elseif (isset($step['args'])) {
353
        if (empty($step['class']) && !function_exists($function)) {
354
          # introspection on closures or language constructs in a stack trace is impossible before PHP 5.3
355
          $params = null;
356
        } else {
357
          try {
358
            if (isset($step['class'])) {
359
              if (method_exists($step['class'], $function)) {
360
                $reflection = new \ReflectionMethod($step['class'], $function);
361
              } elseif (isset($step['type']) && $step['type'] === '::') {
362
                $reflection = new \ReflectionMethod($step['class'], '__callStatic');
363
              } else {
364
                $reflection = new \ReflectionMethod($step['class'], '__call');
365
              }
366
            } else {
367
              $reflection = new \ReflectionFunction($function);
368
            }
369
370
            # get the function parameters
371
            $params = $reflection->getParameters();
372
          } catch (\Exception $e) { # avoid various PHP version incompatibilities
373
            $params = null;
374
          }
375
        }
376
377
        $args = array();
378
        foreach ($step['args'] as $i => $arg) {
379
          if (isset($params[$i])) {
380
            # assign the argument by the parameter name
381
            $args[$params[$i]->name] = $arg;
382
          } else {
383
            # assign the argument by number
384
            $args['#' . ($i + 1)] = $arg;
385
          }
386
        }
387
      }
388
389
      if (isset($step['class'])) {
390
        # Class->method() or Class::method()
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
391
        $function = $step['class'] . $step['type'] . $function;
392
      }
393
394
      // TODO: it's possible to parse the object name out from the source!
395
      $output[] = array(
396
          'function' => $function,
397
          'args'     => isset($args) ? $args : null,
398
          'file'     => isset($file) ? $file : null,
399
          'line'     => isset($line) ? $line : null,
400
          'source'   => isset($source) ? $source : null,
401
          'object'   => isset($step['object']) ? $step['object'] : null,
402
          'index'    => isset( $step['index'] ) ? $step['index'] - count( $data ) : null,
403
      );
404
405
      unset($function, $args, $file, $line, $source);
406
    }
407
408
    return $output;
409
  }
410
411
  /**
412
   * removes comments and zaps whitespace & <?php tags from php code, makes for easier further parsing
413
   *
414
   * @param string $source
415
   *
416
   * @return string
417
   */
418
  private static function _removeAllButCode($source)
419
  {
420
    $commentTokens = array(
421
        T_COMMENT     => true,
422
        T_INLINE_HTML => true,
423
        T_DOC_COMMENT => true,
424
    );
425
426
    $whiteSpaceTokens = array(
427
        T_WHITESPACE         => true,
428
        T_CLOSE_TAG          => true,
429
        T_OPEN_TAG           => true,
430
        T_OPEN_TAG_WITH_ECHO => true,
431
    );
432
433
    $cleanedSource = '';
434
    foreach (token_get_all($source) as $token) {
435
      if (is_array($token)) {
436
        if (isset($commentTokens[$token[0]])) {
437
          continue;
438
        }
439
440
        if (isset($whiteSpaceTokens[$token[0]])) {
441
          $token = "\x07";
442
        } else {
443
          $token = $token[1];
444
        }
445
      } elseif ($token === ';') {
446
        $token = "\x07";
447
      }
448
449
      $cleanedSource .= $token;
450
    }
451
452
    return $cleanedSource;
453
  }
454
455
  /**
456
   * trace helper, shows the place in code inline
457
   *
458
   * @param string $file       full path to file
459
   * @param int    $lineNumber the line to display
460
   * @param int    $padding    surrounding lines to show besides the main one
461
   *
462
   * @return bool|string
463
   */
464
  private static function _showSource($file, $lineNumber, $padding = 7)
465
  {
466
    if (
467
        !$file
468
        ||
469
        !is_readable($file)
470
    ) {
471
      # continuing will cause errors
472
      return false;
473
    }
474
475
    # open the file and set the line position
476
    $filePointer = fopen($file, 'r');
477
    $line = 0;
478
479
    # Set the reading range
480
    $range = array(
481
        'start' => $lineNumber - $padding,
482
        'end'   => $lineNumber + $padding,
483
    );
484
485
    # set the zero-padding amount for line numbers
486
    $format = '% ' . strlen($range['end']) . 'd';
487
488
    $source = '';
489
    while (($row = fgets($filePointer)) !== false) {
490
      # increment the line number
491
      if (++$line > $range['end']) {
492
        break;
493
      }
494
495
      if ($line >= $range['start']) {
496
        # make the row safe for output
497
        $row = htmlspecialchars($row, ENT_NOQUOTES, 'UTF-8');
498
499
        # trim whitespace and sanitize the row
500
        $row = '<span>' . sprintf($format, $line) . '</span> ' . $row;
501
502
        if ($line === $lineNumber) {
503
          # apply highlighting to this row
504
          $row = '<div class="kint-highlight">' . $row . '</div>';
505
        } else {
506
          $row = '<div>' . $row . '</div>';
507
        }
508
509
        # add to the captured source
510
        $source .= $row;
511
      }
512
    }
513
514
    # close the file-pointer
515
    fclose($filePointer);
516
517
    return $source;
518
  }
519
520
  /**
521
   * returns whether current trace step belongs to Kint or its wrappers
522
   *
523
   * @param $step
524
   *
525
   * @return array
526
   */
527 2
  private static function _stepIsInternal($step)
528
  {
529 2
    if (isset($step['class'])) {
530 2
      foreach (self::$aliases['methods'] as $alias) {
531
        if (
532 2
            $alias[0] === $step['class']
533 2
            &&
534
            $alias[1] === $step['function']
535 2
        ) {
536
          return true;
537
        }
538 2
      }
539
540 2
      return false;
541
    } else {
542
      return in_array($step['function'], self::$aliases['functions'], true);
543
    }
544
  }
545
546
  /**
547
   * Dump information about variables, accepts any number of parameters, supports modifiers:
548
   *
549
   *  clean up any output before kint and place the dump at the top of page:
550
   *   - Kint::dump()
551
   *  *****
552
   *  expand all nodes on display:
553
   *   ! Kint::dump()
554
   *  *****
555
   *  dump variables disregarding their depth:
556
   *   + Kint::dump()
557
   *  *****
558
   *  return output instead of displaying it:
559
   *   @ Kint::dump()
560
   *  *****
561
   *  force output as plain text
562
   *   ~ Kint::dump()
563
   *
564
   * Modifiers are supported by all dump wrapper functions, including Kint::trace(). Space is optional.
565
   *
566
   *
567
   * You can also use the following shorthand to display debug_backtrace():
568
   *   Kint::dump( 1 );
569
   *
570
   * Passing the result from debug_backtrace() to kint::dump() as a single parameter will display it as trace too:
571
   *   $trace = debug_backtrace( true );
572
   *   Kint::dump( $trace );
573
   *  Or simply:
574
   *   Kint::dump( debug_backtrace() );
575
   *
576
   *
577
   * @param mixed $data
578
   *
579
   * @return void|string
580
   */
581 2
  public static function dump($data = null)
582
  {
583 2
    if (!self::enabled()) {
584
      return '';
585
    }
586
587 2
    $stash = self::settings();
588
589 2
    list($names, $modifiers, $callee, $previousCaller, $miniTrace) = self::_getCalleeInfo(
590 2
        defined('DEBUG_BACKTRACE_IGNORE_ARGS')
591 2
            ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
592 2
            : debug_backtrace()
593 2
    );
594
595
    # set mode for current run
596 2
    $mode = self::enabled();
597 2
    if ($mode === true) {
598
      $mode = (PHP_SAPI === 'cli' && self::$cliDetection === true) ? self::MODE_CLI : self::MODE_RICH;
599
    }
600 2
    self::enabled($mode);
601
602 2
    if (strpos($modifiers, '~') !== false) {
603
      self::enabled(self::MODE_WHITESPACE);
604
    }
605
606 2
    switch (self::enabled()) {
607 2
      case self::MODE_RICH:
608 1
        $decorator = 'kint\decorators\Kint_Decorators_Rich';
609 1
        break;
610 1
      case self::MODE_JS:
611
        $decorator = 'kint\decorators\Kint_Decorators_JS';
612
        break;
613 1
      default:
614 1
      case self::MODE_PLAIN:
0 ignored issues
show
Unused Code introduced by
case self::MODE_PLAIN: ...tors_Plain'; break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
615 1
        $decorator = 'kint\decorators\Kint_Decorators_Plain';
616 1
        break;
617 2
    }
618
619
    /* @var Kint_Decorators $decorator */
620
621 2
    $firstRunOldValue = $decorator::$firstRun;
622
623
    # process modifiers: @, +, ! and -
624 2
    if (strpos($modifiers, '-') !== false) {
625
      $decorator::$firstRun = true;
626
      while (ob_get_level()) {
627
        ob_end_clean();
628
      }
629
    }
630
631 2
    if (strpos($modifiers, '!') !== false) {
632
      self::$expandedByDefault = true;
633
    }
634
635 2
    if (strpos($modifiers, '+') !== false) {
636
      self::$maxLevels = false;
637
    }
638
639 2
    if (strpos($modifiers, '@') !== false) {
640
      self::$returnOutput = true;
641
      $decorator::$firstRun = true;
642
    }
643
644 2
    $output = '';
645 2
    if ($decorator::$firstRun) {
646 1
      $output .= call_user_func(array($decorator, 'init'));
647 1
    }
648
649 2
    $trace = false;
650 2
    $tmpFuncNumArgs = func_num_args();
651
    if (
652
        $data === 1
653 2
        &&
654
        $tmpFuncNumArgs === 1
655 2
        &&
656
        $names === array(null)
657 2
    ) {
658
659
      # Kint::dump(1) shorthand
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
660
      $trace = debug_backtrace(true);
661
662
    } elseif (
663
        $tmpFuncNumArgs === 1
664 2
        &&
665 2
        is_array($data)
666 2
    ) {
667
668 2
      $trace = $data; # test if the single parameter is result of debug_backtrace()
669
670 2
    }
671 2
    $trace and $trace = self::_parseTrace($trace);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
672
673 2
    $output .= call_user_func(array($decorator, 'wrapStart'));
674 2
    if ($trace) {
675
      $output .= call_user_func(array($decorator, 'decorateTrace'), $trace);
676
    } else {
677 2
      $data = $tmpFuncNumArgs === 0 ? array('[[no arguments passed]]') : func_get_args();
678
679 2
      foreach ($data as $k => $argument) {
680 2
        KintParser::reset();
681
        # when the dump arguments take long to generate output, user might have changed the file and
682
        # Kint might not parse the arguments correctly, so check if names are set and while the
683
        # displayed names might be wrong, at least don't throw an error
684 2
        $output .= call_user_func(
685 2
            array($decorator, 'decorate'),
686 2
            KintParser::factory($argument, isset($names[$k]) ? $names[$k] : '')
687 2
        );
688 2
      }
689
    }
690
691 2
    $output .= call_user_func(array($decorator, 'wrapEnd'), $callee, $miniTrace, $previousCaller);
692
693 2
    $decorator::$firstRun = false;
694
695 2
    if (strpos($modifiers, '@') !== false) {
696
      $decorator::$firstRun = $firstRunOldValue;
697
    }
698
699 2
    if (self::$returnOutput) {
700
      self::settings($stash);
701
702
      return $output;
703
    }
704
705 2
    if (self::$delayedMode) {
706
      self::settings($stash);
707
      register_shutdown_function('printf', '%s', $output);
708
709
      return '';
710
    }
711
712 2
    self::settings($stash);
713 2
    echo $output;
714
715 2
    return '';
716
  }
717
718
  /**
719
   * Enables or disables Kint, can globally enforce the rendering mode. If called without parameters, returns the
720
   * current mode.
721
   *
722
   * @param mixed $forceMode
723
   *                     null or void - return current mode
724
   *                     false        - disable (no output)
725
   *                     true         - enable and detect cli automatically
726
   *                     Kint::MODE_* - enable and force selected mode disregarding detection and function
727
   *                     shorthand (s()/d()), note that you can still override this
728
   *                     with the "~" modifier
729
   *
730
   * @return mixed        previously set value if a new one is passed
731
   */
732 2
  public static function enabled($forceMode = null)
733
  {
734
    # act both as a setter...
735 2
    if (isset($forceMode)) {
736 2
      $before = self::$_enabledMode;
737 2
      self::$_enabledMode = $forceMode;
738
739 2
      return $before;
740
    }
741
742
    # ...and a getter
743 2
    return self::$_enabledMode;
744
  }
745
746
  /**
747
   * Stashes or sets all settings at once
748
   *
749
   * @param array|null $settings Array of all settings to be set or null to set none
750
   *
751
   * @return array Current settings
752
   */
753 2
  public static function settings(array $settings = null)
754
  {
755
    static $keys = array(
756
        'delayedMode',
757
        '_enabledMode',
758
        'aliases',
759
        'appRootDirs',
760
        'cliColors',
761
        'displayCalledFrom',
762
        'expandedByDefault',
763
        'fileLinkFormat',
764
        'maxLevels',
765
        'maxStrLength',
766
        'returnOutput',
767
        'theme',
768 2
    );
769
770 2
    $out = array();
771 2
    foreach ($keys as $key) {
772
      /** @noinspection PhpVariableVariableInspection */
773 2
      $out[$key] = self::$$key;
774 2
    }
775
776 2
    if ($settings !== null) {
777 2
      $in = array_intersect_key($settings, array_flip($keys));
778 2
      foreach ($in as $key => $val) {
779
        /** @noinspection PhpVariableVariableInspection */
780 2
        self::$$key = $val;
781 2
      }
782 2
    }
783
784 2
    return $out;
785
  }
786
787
  /**
788
   * @param string $file
789
   * @param int    $line
790
   *
791
   * @return mixed
792
   */
793
  public static function getIdeLink($file, $line)
794
  {
795
    return str_replace(array('%f', '%l'), array($file, $line), self::$fileLinkFormat);
796
  }
797
798
  /**
799
   * generic path display callback, can be configured in the settings; purpose is to show relevant path info and hide
800
   * as much of the path as possible.
801
   *
802
   * @param string $file
803
   *
804
   * @return string
805
   */
806
  public static function shortenPath($file)
807
  {
808
    $file = str_replace('\\', '/', $file);
809
    $shortenedName = $file;
810
    $replaced = false;
811
    if (is_array(self::$appRootDirs)) {
812
      foreach (self::$appRootDirs as $path => $replaceString) {
813
        if (empty($path)) {
814
          continue;
815
        }
816
817
        $path = str_replace('\\', '/', $path);
818
819
        if (strpos($file, $path) === 0) {
820
          $shortenedName = $replaceString . substr($file, strlen($path));
821
          $replaced = true;
822
          break;
823
        }
824
      }
825
    }
826
827
    # fallback to find common path with Kint dir
828
    if (!$replaced) {
829
      $pathParts = explode('/', str_replace('\\', '/', KINT_DIR));
830
      $fileParts = explode('/', $file);
831
      $i = 0;
832
      foreach ($fileParts as $i => $filePart) {
833
        if (!isset($pathParts[$i]) || $pathParts[$i] !== $filePart) {
834
          break;
835
        }
836
      }
837
838
      $shortenedName = ($i ? '.../' : '') . implode('/', array_slice($fileParts, $i));
839
    }
840
841
    return $shortenedName;
842
  }
843
844
  /**
845
   * Prints a debug backtrace, same as Kint::dump(1)
846
   *
847
   * @param array $trace [OPTIONAL] you can pass your own trace, otherwise, `debug_backtrace` will be called
848
   *
849
   * @return mixed
850
   */
851
  public static function trace($trace = null)
852
  {
853
    if (!self::enabled()) {
854
      return '';
855
    }
856
857
    return self::dump(isset($trace) ? $trace : debug_backtrace(true));
858
  }
859
}
860