Kint::_parseTrace()   F
last analyzed

Complexity

Conditions 36
Paths > 20000

Size

Total Lines 129
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 980.784

Importance

Changes 0
Metric Value
cc 36
dl 0
loc 129
ccs 7
cts 70
cp 0.1
crap 980.784
rs 2
c 0
b 0
f 0
eloc 77
nc 165421
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Kint', 'dump'),
52
          array('kint\Kint', 'trace'),
53
      ),
54
      'functions' => array(
55
          'kint\d',
56
          'kint\dd',
57
          'kint\ddd',
58
          'kint\de',
59
          'kint\s',
60
          'kint\sd',
61
          'kint\se',
62
          'kint\j',
63
          'kint\jd',
64
          'kint\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 2
        $previousCaller = $prevStep;
90 2
        break;
91 2
      } elseif (isset($step['file'], $step['line'])) {
92 2
        unset($step['object'], $step['args']);
93 2
        array_unshift($miniTrace, $step);
94
      }
95
96 2
      $prevStep = $step;
97
    }
98 2
    $callee = $step;
99
100
    if (
101 2
        !isset($callee['file'])
102
        ||
103 2
        !is_readable($callee['file'])
104
    ) {
105
      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 2
    $filePointer = fopen($callee['file'], 'r');
110 2
    $line = 0;
111 2
    $source = '';
112 2
    while (($row = fgets($filePointer)) !== false) {
113 2
      if (++$line > $callee['line']) {
114 2
        break;
115
      }
116 2
      $source .= $row;
117
    }
118 2
    fclose($filePointer);
119 2
    $source = self::_removeAllButCode($source);
120
121 2
    if (empty($callee['class'])) {
122
      $codePattern = $callee['function'];
123
    } else {
124 2
      if ($callee['type'] === '::') {
125 2
        $codePattern = $callee['class'] . "\x07*" . $callee['type'] . "\x07*" . $callee['function'];
126
      } else {
127
        $codePattern = ".*\x07*" . $callee['type'] . "\x07*" . $callee['function'];
128
      }
129
    }
130
131 2
    if ($codePattern) {
132
      // fix namespace for the regex
133 2
      $codePattern = str_replace('\\', '\\\\', $codePattern);
134
    }
135
136
    // TODO: if more than one call in one line - not possible to determine variable names
137
    // TODO: does not recognize string concat
138
    # get the position of the last call to the function
139 2
    preg_match_all(
140
        "
141
            [
142
            # beginning of statement
143
            [\x07{(]
144
145
            # search for modifiers (group 1)
146
            ([-+!@~]*)?
147
148
            # spaces
149
            \x07*
150
151
            # check if output is assigned to a variable (group 2) todo: does not detect concat
152
            (
153
                \\$[a-z0-9_]+ # variable
154
                \x07*\\.?=\x07*  # assignment
155
            )?
156
157
            # possibly a namespace symbol
158
            \\\\?
159
160
            # spaces again
161
            \x07*
162
163
            # main call to Kint
164 2
            ({$codePattern})
165
166
            # spaces everywhere
167
            \x07*
168
169
            # find the character where kint's opening bracket resides (group 3)
170
            (\\()
171
172
            ]ix",
173 2
        $source,
174 2
        $matches,
175 2
        PREG_OFFSET_CAPTURE
176
    );
177
178 2
    $modifiers = end($matches[1]);
179 2
    $assignment = end($matches[2]);
180 2
    $callToKint = end($matches[3]);
181 2
    $bracket = end($matches[4]);
182
183 2
    if (empty($callToKint)) {
184
      # if a wrapper is mis-configured, don't display the whole file as variable name
185 2
      return array(array(), $modifiers, $callee, $previousCaller, $miniTrace);
186
    }
187
188
    $modifiers = $modifiers[0];
189
    if ($assignment[1] !== -1) {
190
      $modifiers .= '@';
191
    }
192
193
    $paramsString = preg_replace("[\x07+]", ' ', substr($source, $bracket[1] + 1));
194
    # we now have a string like this:
195
    # <parameters passed>); <the rest of the last read line>
196
197
    # remove everything in brackets and quotes, we don't need nested statements nor literal strings which would
198
    # only complicate separating individual arguments
199
    $c = strlen($paramsString);
200
    $inString = $escaped = $openedBracket = $closingBracket = false;
201
    $i = 0;
202
    $inBrackets = 0;
203
    $openedBrackets = array();
204
205
    while ($i < $c) {
206
      $letter = $paramsString[$i];
207
208
      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...
209
        if ($letter === '\'' || $letter === '"') {
210
          $inString = $letter;
211
        } elseif ($letter === '(' || $letter === '[') {
212
          $inBrackets++;
213
          $openedBrackets[] = $openedBracket = $letter;
214
          $closingBracket = $openedBracket === '(' ? ')' : ']';
215
        } elseif ($inBrackets && $letter === $closingBracket) {
216
          $inBrackets--;
217
          array_pop($openedBrackets);
218
          $openedBracket = end($openedBrackets);
219
          $closingBracket = $openedBracket === '(' ? ')' : ']';
220
        } elseif (!$inBrackets && $letter === ')') {
221
          $paramsString = substr($paramsString, 0, $i);
222
          break;
223
        }
224
      } elseif ($letter === $inString && !$escaped) {
225
        $inString = false;
226
      }
227
228
      # replace whatever was inside quotes or brackets with untypeable characters, we don't
229
      # need that info. We'll later replace the whole string with '...'
230
      if (
231
          $inBrackets > 0
232
          &&
233
          (
234
              $inBrackets > 1
235
              ||
236
              $letter !== $openedBracket
237
          )
238
      ) {
239
        $paramsString[$i] = "\x07";
240
      }
241
242
      if (
243
          $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...
244
          &&
245
          (
246
              $letter !== $inString
247
              ||
248
              $escaped
249
          )
250
      ) {
251
        $paramsString[$i] = "\x07";
252
      }
253
254
      $escaped = !$escaped && ($letter === '\\');
255
      $i++;
256
    }
257
258
    # by now we have an un-nested arguments list, lets make it to an array for processing further
259
    $arguments = explode(',', preg_replace("[\x07+]", '...', $paramsString));
260
261
    # test each argument whether it was passed literary or was it an expression or a variable name
262
    $parameters = array();
263
    $blacklist = array('null', 'true', 'false', 'array(...)', 'array()', '"..."', '[...]', 'b"..."',);
264
    foreach ($arguments as $argument) {
265
      $argument = trim($argument);
266
267
      if (
268
          is_numeric($argument)
269
          ||
270
          in_array(str_replace("'", '"', strtolower($argument)), $blacklist, true)
271
      ) {
272
        $parameters[] = null;
273
      } else {
274
        $parameters[] = $argument;
275
      }
276
    }
277
278
    return array($parameters, $modifiers, $callee, $previousCaller, $miniTrace);
279
  }
280
281
  /**
282
   * @param array $data
283
   *
284
   * @return array|false
285
   */
286 2
  private static function _parseTrace(array $data)
287
  {
288 2
    $trace = array();
289 2
    $traceFields = array('file', 'line', 'args', 'class');
290 2
    $fileFound = false; # file element must exist in one of the steps
291
292
    # validate whether a trace was indeed passed
293
    /** @noinspection PhpAssignmentInConditionInspection */
294 2
    while ($step = array_pop($data)) {
295 2
      if (!is_array($step) || !isset($step['function'])) {
296 2
        return false;
297
      }
298
      if (!$fileFound && isset($step['file']) && file_exists($step['file'])) {
299
        $fileFound = true;
300
      }
301
302
      $valid = false;
303
      foreach ($traceFields as $element) {
304
        if (isset($step[$element])) {
305
          $valid = true;
306
          break;
307
        }
308
      }
309
      if (!$valid) {
310
        return false;
311
      }
312
313
      if (self::_stepIsInternal($step)) {
314
        $step = array(
315
            'file'     => $step['file'],
316
            'line'     => $step['line'],
317
            'function' => '',
318
        );
319
        array_unshift($trace, $step);
320
        break;
321
      }
322
323
      $step['index'] = count($data) - 1;
324
325
      if ($step['function'] !== 'spl_autoload_call') { # meaningless
326
        array_unshift($trace, $step);
327
      }
328
    }
329
    if (!$fileFound) {
330
      return false;
331
    }
332
333
    $output = array();
334
    foreach ($trace as $step) {
335
      if (isset($step['file'])) {
336
        $file = $step['file'];
337
338
        if (isset($step['line'])) {
339
          $line = $step['line'];
340
          # include the source of this step
341
          if (self::enabled() === self::MODE_RICH) {
342
            $source = self::_showSource($file, $line);
343
          }
344
        }
345
      }
346
347
      $function = $step['function'];
348
349
      if (in_array($function, array('include', 'include_once', 'require', 'require_once'), true)) {
350
        if (empty($step['args'])) {
351
          # no arguments
352
          $args = array();
353
        } else {
354
          # sanitize the included file path
355
          $args = array('file' => self::shortenPath($step['args'][0]));
356
        }
357
      } elseif (isset($step['args'])) {
358
        if (empty($step['class']) && !function_exists($function)) {
359
          # introspection on closures or language constructs in a stack trace is impossible before PHP 5.3
360
          $params = null;
361
        } else {
362
          try {
363
            if (isset($step['class'])) {
364
              if (method_exists($step['class'], $function)) {
365
                $reflection = new \ReflectionMethod($step['class'], $function);
366
              } elseif (isset($step['type']) && $step['type'] === '::') {
367
                $reflection = new \ReflectionMethod($step['class'], '__callStatic');
368
              } else {
369
                $reflection = new \ReflectionMethod($step['class'], '__call');
370
              }
371
            } else {
372
              $reflection = new \ReflectionFunction($function);
373
            }
374
375
            # get the function parameters
376
            $params = $reflection->getParameters();
377
          } catch (\Exception $e) { # avoid various PHP version incompatibilities
378
            $params = null;
379
          }
380
        }
381
382
        $args = array();
383
        foreach ($step['args'] as $i => $arg) {
384
          if (isset($params[$i])) {
385
            # assign the argument by the parameter name
386
            $args[$params[$i]->name] = $arg;
387
          } else {
388
            # assign the argument by number
389
            $args['#' . ($i + 1)] = $arg;
390
          }
391
        }
392
      }
393
394
      if (isset($step['class'])) {
395
        # 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...
396
        $function = $step['class'] . $step['type'] . $function;
397
      }
398
399
      // TODO: it's possible to parse the object name out from the source!
400
      $output[] = array(
401
          'function' => $function,
402
          'args'     => isset($args) ? $args : null,
403
          'file'     => isset($file) ? $file : null,
404
          'line'     => isset($line) ? $line : null,
405
          'source'   => isset($source) ? $source : null,
406
          'object'   => isset($step['object']) ? $step['object'] : null,
407
          'index'    => isset($step['index']) ? $step['index'] - count($data) : null,
408
      );
409
410
      unset($function, $args, $file, $line, $source);
411
    }
412
413
    return $output;
414
  }
415
416
  /**
417
   * removes comments and zaps whitespace & <?php tags from php code, makes for easier further parsing
418
   *
419
   * @param string $source
420
   *
421
   * @return string
422
   */
423 2
  private static function _removeAllButCode($source)
424
  {
425
    $commentTokens = array(
426 2
        T_COMMENT     => true,
427 2
        T_INLINE_HTML => true,
428 2
        T_DOC_COMMENT => true,
429
    );
430
431
    $whiteSpaceTokens = array(
432 2
        T_WHITESPACE         => true,
433 2
        T_CLOSE_TAG          => true,
434 2
        T_OPEN_TAG           => true,
435 2
        T_OPEN_TAG_WITH_ECHO => true,
436
    );
437
438 2
    $cleanedSource = '';
439 2
    foreach (token_get_all($source) as $token) {
440 2
      if (is_array($token)) {
441 2
        if (isset($commentTokens[$token[0]])) {
442 2
          continue;
443
        }
444
445 2
        if (isset($whiteSpaceTokens[$token[0]])) {
446 2
          $token = "\x07";
447
        } else {
448 2
          $token = $token[1];
449
        }
450 2
      } elseif ($token === ';') {
451 2
        $token = "\x07";
452
      }
453
454 2
      $cleanedSource .= $token;
455
    }
456
457 2
    return $cleanedSource;
458
  }
459
460
  /**
461
   * trace helper, shows the place in code inline
462
   *
463
   * @param string $file       full path to file
464
   * @param int    $lineNumber the line to display
465
   * @param int    $padding    surrounding lines to show besides the main one
466
   *
467
   * @return bool|string
468
   */
469
  private static function _showSource($file, $lineNumber, $padding = 7)
470
  {
471
    if (
472
        !$file
473
        ||
474
        !is_readable($file)
475
    ) {
476
      # continuing will cause errors
477
      return false;
478
    }
479
480
    # open the file and set the line position
481
    $filePointer = fopen($file, 'r');
482
    $line = 0;
483
484
    # Set the reading range
485
    $range = array(
486
        'start' => $lineNumber - $padding,
487
        'end'   => $lineNumber + $padding,
488
    );
489
490
    # set the zero-padding amount for line numbers
491
    $format = '% ' . strlen($range['end']) . 'd';
492
493
    $source = '';
494
    while (($row = fgets($filePointer)) !== false) {
495
      # increment the line number
496
      if (++$line > $range['end']) {
497
        break;
498
      }
499
500
      if ($line >= $range['start']) {
501
        # make the row safe for output
502
        $row = htmlspecialchars($row, ENT_NOQUOTES, 'UTF-8');
503
504
        # trim whitespace and sanitize the row
505
        $row = '<span>' . sprintf($format, $line) . '</span> ' . $row;
506
507
        if ($line === $lineNumber) {
508
          # apply highlighting to this row
509
          $row = '<div class="kint-highlight">' . $row . '</div>';
510
        } else {
511
          $row = '<div>' . $row . '</div>';
512
        }
513
514
        # add to the captured source
515
        $source .= $row;
516
      }
517
    }
518
519
    # close the file-pointer
520
    fclose($filePointer);
521
522
    return $source;
523
  }
524
525
  /**
526
   * returns whether current trace step belongs to Kint or its wrappers
527
   *
528
   * @param $step
529
   *
530
   * @return array
531
   */
532 2
  private static function _stepIsInternal($step)
533
  {
534 2
    if (isset($step['class'])) {
535 2
      foreach (self::$aliases['methods'] as $alias) {
536
        if (
537 2
            $alias[0] === $step['class']
538
            &&
539 2
            $alias[1] === $step['function']
540
        ) {
541 2
          return true;
542
        }
543
      }
544
545 2
      return false;
546
    } else {
547
      return in_array($step['function'], self::$aliases['functions'], true);
548
    }
549
  }
550
551
  /**
552
   * Dump information about variables, accepts any number of parameters, supports modifiers:
553
   *
554
   *  clean up any output before kint and place the dump at the top of page:
555
   *   - Kint::dump()
556
   *  *****
557
   *  expand all nodes on display:
558
   *   ! Kint::dump()
559
   *  *****
560
   *  dump variables disregarding their depth:
561
   *   + Kint::dump()
562
   *  *****
563
   *  return output instead of displaying it:
564
   *   @ Kint::dump()
565
   *  *****
566
   *  force output as plain text
567
   *   ~ Kint::dump()
568
   *
569
   * Modifiers are supported by all dump wrapper functions, including Kint::trace(). Space is optional.
570
   *
571
   *
572
   * You can also use the following shorthand to display debug_backtrace():
573
   *   Kint::dump( 1 );
574
   *
575
   * Passing the result from debug_backtrace() to kint::dump() as a single parameter will display it as trace too:
576
   *   $trace = debug_backtrace( true );
577
   *   Kint::dump( $trace );
578
   *  Or simply:
579
   *   Kint::dump( debug_backtrace() );
580
   *
581
   *
582
   * @param mixed $data
583
   *
584
   * @return void|string
585
   */
586 2
  public static function dump($data = null)
587
  {
588 2
    if (!self::enabled()) {
589
      return '';
590
    }
591
592 2
    $stash = self::settings();
593
594 2
    list($names, $modifiers, $callee, $previousCaller, $miniTrace) = self::_getCalleeInfo(
595
        // DEBUG_BACKTRACE_IGNORE_ARGS was introduced in PHP 5.3.6
596 2
        defined('DEBUG_BACKTRACE_IGNORE_ARGS')
597 2
            ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
598 2
            : debug_backtrace()
599
    );
600
601
    # set mode for current run
602 2
    $mode = self::enabled();
603 2
    if ($mode === true) {
604
      $mode = (PHP_SAPI === 'cli' && self::$cliDetection === true) ? self::MODE_CLI : self::MODE_RICH;
605
    }
606 2
    self::enabled($mode);
607
608 2
    if (strpos($modifiers, '~') !== false) {
609
      self::enabled(self::MODE_WHITESPACE);
610
    }
611
612 2
    switch (self::enabled()) {
613 2
      case self::MODE_RICH:
614 1
        $decorator = 'kint\decorators\Kint_Decorators_Rich';
615 1
        break;
616 1
      case self::MODE_JS:
617
        $decorator = 'kint\decorators\Kint_Decorators_JS';
618
        break;
619
      default:
620 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...
621 1
        $decorator = 'kint\decorators\Kint_Decorators_Plain';
622 1
        break;
623
    }
624
625
    /* @var Kint_Decorators $decorator */
626
627 2
    $firstRunOldValue = $decorator::$firstRun;
628
629
    # process modifiers: @, +, ! and -
630 2
    if (strpos($modifiers, '-') !== false) {
631
      $decorator::$firstRun = true;
632
      while (ob_get_level()) {
633
        ob_end_clean();
634
      }
635
    }
636
637 2
    if (strpos($modifiers, '!') !== false) {
638
      self::$expandedByDefault = true;
639
    }
640
641 2
    if (strpos($modifiers, '+') !== false) {
642
      self::$maxLevels = false;
643
    }
644
645 2
    if (strpos($modifiers, '@') !== false) {
646
      self::$returnOutput = true;
647
      $decorator::$firstRun = true;
648
    }
649
650 2
    $output = '';
651 2
    if ($decorator::$firstRun) {
652 1
      $output .= call_user_func(array($decorator, 'init'));
653
    }
654
655 2
    $trace = false;
656 2
    $tmpFuncNumArgs = func_num_args();
657
    if (
658 2
        $data === 1
659
        &&
660 2
        $tmpFuncNumArgs === 1
661
        &&
662 2
        $names === array(null)
663
    ) {
664
665
      # 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...
666
      $trace = debug_backtrace(true);
667
668
    } elseif (
669 2
        $tmpFuncNumArgs === 1
670
        &&
671 2
        is_array($data)
672
    ) {
673
674 2
      $trace = $data; # test if the single parameter is result of debug_backtrace()
675
676
    }
677 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...
678
679 2
    $output .= call_user_func(array($decorator, 'wrapStart'));
680 2
    if ($trace) {
681
      $output .= call_user_func(array($decorator, 'decorateTrace'), $trace);
682
    } else {
683 2
      $data = $tmpFuncNumArgs === 0 ? array('[[no arguments passed]]') : func_get_args();
684
685 2
      foreach ($data as $k => $argument) {
686 2
        KintParser::reset();
687
        # when the dump arguments take long to generate output, user might have changed the file and
688
        # Kint might not parse the arguments correctly, so check if names are set and while the
689
        # displayed names might be wrong, at least don't throw an error
690 2
        $output .= call_user_func(
691 2
            array($decorator, 'decorate'),
692 2
            KintParser::factory($argument, isset($names[$k]) ? $names[$k] : '')
693
        );
694
      }
695
    }
696
697 2
    $output .= call_user_func(array($decorator, 'wrapEnd'), $callee, $miniTrace, $previousCaller);
698
699 2
    $decorator::$firstRun = false;
700
701 2
    if (strpos($modifiers, '@') !== false) {
702
      $decorator::$firstRun = $firstRunOldValue;
703
    }
704
705 2
    if (self::$returnOutput) {
706
      self::settings($stash);
707
708
      return $output;
709
    }
710
711 2
    if (self::$delayedMode) {
712
      self::settings($stash);
713
      register_shutdown_function('printf', '%s', $output);
714
715
      return '';
716
    }
717
718 2
    self::settings($stash);
719 2
    echo $output;
720
721 2
    return '';
722
  }
723
724
  /**
725
   * Enables or disables Kint, can globally enforce the rendering mode. If called without parameters, returns the
726
   * current mode.
727
   *
728
   * @param mixed $forceMode
729
   *                     null or void - return current mode
730
   *                     false        - disable (no output)
731
   *                     true         - enable and detect cli automatically
732
   *                     Kint::MODE_* - enable and force selected mode disregarding detection and function
733
   *                     shorthand (s()/d()), note that you can still override this
734
   *                     with the "~" modifier
735
   *
736
   * @return mixed        previously set value if a new one is passed
737
   */
738 2
  public static function enabled($forceMode = null)
739
  {
740
    # act both as a setter...
741 2
    if (isset($forceMode)) {
742 2
      $before = self::$_enabledMode;
743 2
      self::$_enabledMode = $forceMode;
744
745 2
      return $before;
746
    }
747
748
    # ...and a getter
749 2
    return self::$_enabledMode;
750
  }
751
752
  /**
753
   * Stashes or sets all settings at once
754
   *
755
   * @param array|null $settings Array of all settings to be set or null to set none
756
   *
757
   * @return array Current settings
758
   */
759 2
  public static function settings(array $settings = null)
760
  {
761 2
    static $keys = array(
762
        'delayedMode',
763
        '_enabledMode',
764
        'aliases',
765
        'appRootDirs',
766
        'cliColors',
767
        'displayCalledFrom',
768
        'expandedByDefault',
769
        'fileLinkFormat',
770
        'maxLevels',
771
        'maxStrLength',
772
        'returnOutput',
773
        'theme',
774
    );
775
776 2
    $out = array();
777 2
    foreach ($keys as $key) {
778
      /** @noinspection PhpVariableVariableInspection */
779 2
      $out[$key] = self::$$key;
780
    }
781
782 2
    if ($settings !== null) {
783 2
      $in = array_intersect_key($settings, array_flip($keys));
784 2
      foreach ($in as $key => $val) {
785
        /** @noinspection PhpVariableVariableInspection */
786 2
        self::$$key = $val;
787
      }
788
    }
789
790 2
    return $out;
791
  }
792
793
  /**
794
   * @param string $file
795
   * @param int    $line
796
   *
797
   * @return mixed
798
   */
799
  public static function getIdeLink($file, $line)
800
  {
801
    return str_replace(array('%f', '%l'), array($file, $line), self::$fileLinkFormat);
802
  }
803
804
  /**
805
   * generic path display callback, can be configured in the settings; purpose is to show relevant path info and hide
806
   * as much of the path as possible.
807
   *
808
   * @param string $file
809
   *
810
   * @return string
811
   */
812
  public static function shortenPath($file)
813
  {
814
    $file = str_replace('\\', '/', $file);
815
    $shortenedName = $file;
816
    $replaced = false;
817
    if (is_array(self::$appRootDirs)) {
818
      foreach (self::$appRootDirs as $path => $replaceString) {
819
        if (empty($path)) {
820
          continue;
821
        }
822
823
        $path = str_replace('\\', '/', $path);
824
825
        if (strpos($file, $path) === 0) {
826
          $shortenedName = $replaceString . substr($file, strlen($path));
827
          $replaced = true;
828
          break;
829
        }
830
      }
831
    }
832
833
    # fallback to find common path with Kint dir
834
    if (!$replaced) {
835
      $pathParts = explode('/', str_replace('\\', '/', KINT_DIR));
836
      $fileParts = explode('/', $file);
837
      $i = 0;
838
      foreach ($fileParts as $i => $filePart) {
839
        if (!isset($pathParts[$i]) || $pathParts[$i] !== $filePart) {
840
          break;
841
        }
842
      }
843
844
      $shortenedName = ($i ? '.../' : '') . implode('/', array_slice($fileParts, $i));
845
    }
846
847
    return $shortenedName;
848
  }
849
850
  /**
851
   * Prints a debug backtrace, same as Kint::dump(1)
852
   *
853
   * @param array $trace [OPTIONAL] you can pass your own trace, otherwise, `debug_backtrace` will be called
854
   *
855
   * @return mixed
856
   */
857
  public static function trace($trace = null)
858
  {
859
    if (!self::enabled()) {
860
      return '';
861
    }
862
863
    return self::dump(isset($trace) ? $trace : debug_backtrace(true));
864
  }
865
}
866