Passed
Push — master ( ba645d...9eba8c )
by Michael
07:09 queued 43s
created

Kint::dump()   F

Complexity

Conditions 40
Paths > 20000

Size

Total Lines 170
Code Lines 101

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 101
dl 0
loc 170
rs 0
c 0
b 0
f 0
cc 40
nc 215041
nop 1

4 Methods

Rating   Name   Duplication   Size   Complexity  
B Kint::createFromStatics() 0 28 9
A Kint::dumpVar() 0 4 1
B Kint::getBasesFromParamInfo() 0 50 8
A Kint::getStatics() 0 16 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
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2013 Jonathan Vollebregt ([email protected]), Rokas Šleinius ([email protected])
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
9
 * this software and associated documentation files (the "Software"), to deal in
10
 * the Software without restriction, including without limitation the rights to
11
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12
 * the Software, and to permit persons to whom the Software is furnished to do so,
13
 * subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in all
16
 * copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
 */
25
26
namespace Kint;
27
28
use InvalidArgumentException;
29
use Kint\Object\BasicObject;
30
use Kint\Parser\Parser;
31
use Kint\Parser\Plugin;
32
use Kint\Renderer\Renderer;
33
use Kint\Renderer\TextRenderer;
34
35
class Kint
36
{
37
    const MODE_RICH = 'r';
38
    const MODE_TEXT = 't';
39
    const MODE_CLI = 'c';
40
    const MODE_PLAIN = 'p';
41
42
    /**
43
     * @var mixed Kint mode
44
     *
45
     * false: Disabled
46
     * true: Enabled, default mode selection
47
     * other: Manual mode selection
48
     */
49
    public static $enabled_mode = true;
50
51
    /**
52
     * Default mode.
53
     *
54
     * @var string
55
     */
56
    public static $mode_default = self::MODE_RICH;
57
58
    /**
59
     * Default mode in CLI with cli_detection on.
60
     *
61
     * @var string
62
     */
63
    public static $mode_default_cli = self::MODE_CLI;
64
65
    /**
66
     * @var bool Return output instead of echoing
67
     */
68
    public static $return;
69
70
    /**
71
     * @var string format of the link to the source file in trace entries.
72
     *
73
     * Use %f for file path, %l for line number.
74
     *
75
     * [!] EXAMPLE (works with for phpStorm and RemoteCall Plugin):
76
     *
77
     * Kint::$file_link_format = 'http://localhost:8091/?message=%f:%l';
78
     */
79
    public static $file_link_format = '';
80
81
    /**
82
     * @var bool whether to display where kint was called from
83
     */
84
    public static $display_called_from = true;
85
86
    /**
87
     * @var array base directories of your application that will be displayed instead of the full path.
88
     *
89
     * Keys are paths, values are replacement strings
90
     *
91
     * [!] EXAMPLE (for Laravel 5):
92
     *
93
     * Kint::$app_root_dirs = [
94
     *     base_path() => '<BASE>',
95
     *     app_path() => '<APP>',
96
     *     config_path() => '<CONFIG>',
97
     *     database_path() => '<DATABASE>',
98
     *     public_path() => '<PUBLIC>',
99
     *     resource_path() => '<RESOURCE>',
100
     *     storage_path() => '<STORAGE>',
101
     * ];
102
     *
103
     * Defaults to [$_SERVER['DOCUMENT_ROOT'] => '<ROOT>']
104
     */
105
    public static $app_root_dirs = array();
106
107
    /**
108
     * @var int max array/object levels to go deep, if zero no limits are applied
109
     */
110
    public static $max_depth = 6;
111
112
    /**
113
     * @var bool expand all trees by default for rich view
114
     */
115
    public static $expanded = false;
116
117
    /**
118
     * @var bool enable detection when Kint is command line.
119
     *
120
     * Formats output with whitespace only; does not HTML-escape it
121
     */
122
    public static $cli_detection = true;
123
124
    /**
125
     * @var array Kint aliases. Add debug functions in Kint wrappers here to fix modifiers and backtraces
126
     */
127
    public static $aliases = array(
128
        array('Kint\\Kint', 'dump'),
129
        array('Kint\\Kint', 'trace'),
130
        array('Kint\\Kint', 'dumpArray'),
131
    );
132
133
    /**
134
     * @var array<mixed, string> Array of modes to renderer class names
135
     */
136
    public static $renderers = array(
137
        self::MODE_RICH => 'Kint\\Renderer\\RichRenderer',
138
        self::MODE_PLAIN => 'Kint\\Renderer\\PlainRenderer',
139
        self::MODE_TEXT => 'Kint\\Renderer\\TextRenderer',
140
        self::MODE_CLI => 'Kint\\Renderer\\CliRenderer',
141
    );
142
143
    public static $plugins = array(
144
        'Kint\\Parser\\ArrayObjectPlugin',
145
        'Kint\\Parser\\Base64Plugin',
146
        'Kint\\Parser\\BlacklistPlugin',
147
        'Kint\\Parser\\ClassMethodsPlugin',
148
        'Kint\\Parser\\ClassStaticsPlugin',
149
        'Kint\\Parser\\ClosurePlugin',
150
        'Kint\\Parser\\ColorPlugin',
151
        'Kint\\Parser\\DateTimePlugin',
152
        'Kint\\Parser\\FsPathPlugin',
153
        'Kint\\Parser\\IteratorPlugin',
154
        'Kint\\Parser\\JsonPlugin',
155
        'Kint\\Parser\\MicrotimePlugin',
156
        'Kint\\Parser\\SimpleXMLElementPlugin',
157
        'Kint\\Parser\\SplFileInfoPlugin',
158
        'Kint\\Parser\\SplObjectStoragePlugin',
159
        'Kint\\Parser\\StreamPlugin',
160
        'Kint\\Parser\\TablePlugin',
161
        'Kint\\Parser\\ThrowablePlugin',
162
        'Kint\\Parser\\TimestampPlugin',
163
        'Kint\\Parser\\TracePlugin',
164
        'Kint\\Parser\\XmlPlugin',
165
    );
166
167
    protected static $plugin_pool = array();
168
169
    protected $parser;
170
    protected $renderer;
171
172
    public function __construct(Parser $p, Renderer $r)
173
    {
174
        $this->parser = $p;
175
        $this->renderer = $r;
176
    }
177
178
    public function setParser(Parser $p)
179
    {
180
        $this->parser = $p;
181
    }
182
183
    public function getParser()
184
    {
185
        return $this->parser;
186
    }
187
188
    public function setRenderer(Renderer $r)
189
    {
190
        $this->renderer = $r;
191
    }
192
193
    public function getRenderer()
194
    {
195
        return $this->renderer;
196
    }
197
198
    public function setStatesFromStatics(array $statics)
199
    {
200
        $this->renderer->setStatics($statics);
201
202
        $this->parser->setDepthLimit(isset($statics['max_depth']) ? $statics['max_depth'] : false);
203
        $this->parser->clearPlugins();
204
205
        if (!isset($statics['plugins'])) {
206
            return;
207
        }
208
209
        $plugins = array();
210
211
        foreach ($statics['plugins'] as $plugin) {
212
            if ($plugin instanceof Plugin) {
213
                $plugins[] = $plugin;
214
            } elseif (\is_string($plugin) && \is_subclass_of($plugin, 'Kint\\Parser\\Plugin')) {
215
                if (!isset(self::$plugin_pool[$plugin])) {
216
                    $p = new $plugin();
217
                    self::$plugin_pool[$plugin] = $p;
218
                }
219
                $plugins[] = self::$plugin_pool[$plugin];
220
            }
221
        }
222
223
        $plugins = $this->renderer->filterParserPlugins($plugins);
224
225
        foreach ($plugins as $plugin) {
226
            $this->parser->addPlugin($plugin);
227
        }
228
    }
229
230
    public function setStatesFromCallInfo(array $info)
231
    {
232
        $this->renderer->setCallInfo($info);
233
234
        if (isset($info['modifiers']) && \is_array($info['modifiers']) && \in_array('+', $info['modifiers'], true)) {
235
            $this->parser->setDepthLimit(false);
236
        }
237
238
        $this->parser->setCallerClass(isset($info['caller']['class']) ? $info['caller']['class'] : null);
239
    }
240
241
    /**
242
     * Renders a list of vars including the pre and post renders.
243
     *
244
     * @param array         $vars Data to dump
245
     * @param BasicObject[] $base Base objects
246
     *
247
     * @return string
248
     */
249
    public function dumpAll(array $vars, array $base)
250
    {
251
        if (\array_keys($vars) !== \array_keys($base)) {
252
            throw new InvalidArgumentException('Kint::dumpAll requires arrays of identical size and keys as arguments');
253
        }
254
255
        $output = $this->renderer->preRender();
256
257
        if ($vars === array()) {
258
            $output .= $this->renderer->renderNothing();
259
        }
260
261
        foreach ($vars as $key => $arg) {
262
            if (!$base[$key] instanceof BasicObject) {
263
                throw new InvalidArgumentException('Kint::dumpAll requires all elements of the second argument to be BasicObject instances');
264
            }
265
            $output .= $this->dumpVar($arg, $base[$key]);
266
        }
267
268
        $output .= $this->renderer->postRender();
269
270
        return $output;
271
    }
272
273
    /**
274
     * Dumps and renders a var.
275
     *
276
     * @param mixed       $var  Data to dump
277
     * @param BasicObject $base Base object
278
     *
279
     * @return string
280
     */
281
    public function dumpVar(&$var, BasicObject $base)
282
    {
283
        return $this->renderer->render(
284
            $this->parser->parse($var, $base)
285
        );
286
    }
287
288
    /**
289
     * Gets all static settings at once.
290
     *
291
     * @return array Current static settings
292
     */
293
    public static function getStatics()
294
    {
295
        return array(
296
            'aliases' => self::$aliases,
297
            'app_root_dirs' => self::$app_root_dirs,
298
            'cli_detection' => self::$cli_detection,
299
            'display_called_from' => self::$display_called_from,
300
            'enabled_mode' => self::$enabled_mode,
301
            'expanded' => self::$expanded,
302
            'file_link_format' => self::$file_link_format,
303
            'max_depth' => self::$max_depth,
304
            'mode_default' => self::$mode_default,
305
            'mode_default_cli' => self::$mode_default_cli,
306
            'plugins' => self::$plugins,
307
            'renderers' => self::$renderers,
308
            'return' => self::$return,
309
        );
310
    }
311
312
    /**
313
     * Creates a Kint instances based on static settings.
314
     *
315
     * Also calls setStatesFromStatics for you
316
     *
317
     * @param array $statics array of statics as returned by getStatics
318
     *
319
     * @return null|\Kint\Kint
320
     */
321
    public static function createFromStatics(array $statics)
322
    {
323
        $mode = false;
324
325
        if (isset($statics['enabled_mode'])) {
326
            $mode = $statics['enabled_mode'];
327
328
            if (true === $statics['enabled_mode'] && isset($statics['mode_default'])) {
329
                $mode = $statics['mode_default'];
330
331
                if (PHP_SAPI === 'cli' && !empty($statics['cli_detection']) && isset($statics['mode_default_cli'])) {
332
                    $mode = $statics['mode_default_cli'];
333
                }
334
            }
335
        }
336
337
        if (!$mode) {
338
            return null;
339
        }
340
341
        if (!isset($statics['renderers'][$mode])) {
342
            $renderer = new TextRenderer();
343
        } else {
344
            /** @var Renderer */
345
            $renderer = new $statics['renderers'][$mode]();
346
        }
347
348
        return new self(new Parser(), $renderer);
349
    }
350
351
    /**
352
     * Creates base objects given parameter info.
353
     *
354
     * @param array $params Parameters as returned from getCallInfo
355
     * @param int   $argc   Number of arguments the helper was called with
356
     *
357
     * @return BasicObject[] Base objects for the arguments
358
     */
359
    public static function getBasesFromParamInfo(array $params, $argc)
360
    {
361
        static $blacklist = array(
362
            'null',
363
            'true',
364
            'false',
365
            'array(...)',
366
            'array()',
367
            '[...]',
368
            '[]',
369
            '(...)',
370
            '()',
371
            '"..."',
372
            'b"..."',
373
            "'...'",
374
            "b'...'",
375
        );
376
377
        $params = \array_values($params);
378
        $bases = array();
379
380
        for ($i = 0; $i < $argc; ++$i) {
381
            if (isset($params[$i])) {
382
                $param = $params[$i];
383
            } else {
384
                $param = null;
385
            }
386
387
            if (!isset($param['name']) || \is_numeric($param['name'])) {
388
                $name = null;
389
            } elseif (\in_array(\strtolower($param['name']), $blacklist, true)) {
390
                $name = null;
391
            } else {
392
                $name = $param['name'];
393
            }
394
395
            if (isset($param['path'])) {
396
                $access_path = $param['path'];
397
398
                if (!empty($param['expression'])) {
399
                    $access_path = '('.$access_path.')';
400
                }
401
            } else {
402
                $access_path = '$'.$i;
403
            }
404
405
            $bases[] = BasicObject::blank($name, $access_path);
406
        }
407
408
        return $bases;
409
    }
410
411
    /**
412
     * Gets call info from the backtrace, alias, and argument count.
413
     *
414
     * Aliases must be normalized beforehand (Utils::normalizeAliases)
415
     *
416
     * @param array   $aliases Call aliases as found in Kint::$aliases
417
     * @param array[] $trace   Backtrace
418
     * @param int     $argc    Number of arguments
419
     *
420
     * @return array{params:null|array, modifiers:array, callee:null|array, caller:null|array, trace:array[]} Call info
421
     */
422
    public static function getCallInfo(array $aliases, array $trace, $argc)
423
    {
424
        $found = false;
425
        $callee = null;
426
        $caller = null;
427
        $miniTrace = array();
428
429
        foreach ($trace as $index => $frame) {
430
            if (Utils::traceFrameIsListed($frame, $aliases)) {
431
                $found = true;
432
                $miniTrace = array();
433
            }
434
435
            if (!Utils::traceFrameIsListed($frame, array('spl_autoload_call'))) {
436
                $miniTrace[] = $frame;
437
            }
438
        }
439
440
        if ($found) {
441
            $callee = \reset($miniTrace) ?: null;
442
443
            /** @var null|array Psalm bug workaround */
444
            $caller = \next($miniTrace) ?: null;
445
        }
446
447
        foreach ($miniTrace as $index => $frame) {
448
            if ((0 === $index && $callee === $frame) || isset($frame['file'], $frame['line'])) {
449
                unset($frame['object'], $frame['args']);
450
                $miniTrace[$index] = $frame;
451
            } else {
452
                unset($miniTrace[$index]);
453
            }
454
        }
455
456
        $miniTrace = \array_values($miniTrace);
457
458
        $call = self::getSingleCall($callee ?: array(), $argc);
459
460
        $ret = array(
461
            'params' => null,
462
            'modifiers' => array(),
463
            'callee' => $callee,
464
            'caller' => $caller,
465
            'trace' => $miniTrace,
466
        );
467
468
        if ($call) {
469
            $ret['params'] = $call['parameters'];
470
            $ret['modifiers'] = $call['modifiers'];
471
        }
472
473
        return $ret;
474
    }
475
476
    /**
477
     * Dumps a backtrace.
478
     *
479
     * Functionally equivalent to Kint::dump(1) or Kint::dump(debug_backtrace(true))
480
     *
481
     * @return int|string
482
     */
483
    public static function trace()
484
    {
485
        if (!self::$enabled_mode) {
486
            return 0;
487
        }
488
489
        Utils::normalizeAliases(self::$aliases);
490
491
        $args = \func_get_args();
492
493
        $call_info = self::getCallInfo(self::$aliases, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), \count($args));
494
495
        $statics = self::getStatics();
496
497
        if (\in_array('~', $call_info['modifiers'], true)) {
498
            $statics['enabled_mode'] = self::MODE_TEXT;
499
        }
500
501
        $kintstance = self::createFromStatics($statics);
502
        if (!$kintstance) {
503
            // Should never happen
504
            return 0; // @codeCoverageIgnore
505
        }
506
507
        if (\in_array('-', $call_info['modifiers'], true)) {
508
            while (\ob_get_level()) {
509
                \ob_end_clean();
510
            }
511
        }
512
513
        $kintstance->setStatesFromStatics($statics);
514
        $kintstance->setStatesFromCallInfo($call_info);
515
516
        $trimmed_trace = array();
517
        $trace = \debug_backtrace(true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $options of debug_backtrace(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

517
        $trace = \debug_backtrace(/** @scrutinizer ignore-type */ true);
Loading history...
518
519
        foreach ($trace as $frame) {
520
            if (Utils::traceFrameIsListed($frame, self::$aliases)) {
521
                $trimmed_trace = array();
522
            }
523
524
            $trimmed_trace[] = $frame;
525
        }
526
527
        $output = $kintstance->dumpAll(
528
            array($trimmed_trace),
529
            array(BasicObject::blank('Kint\\Kint::trace()', 'debug_backtrace(true)'))
530
        );
531
532
        if (self::$return || \in_array('@', $call_info['modifiers'], true)) {
533
            return $output;
534
        }
535
536
        echo $output;
537
538
        if (\in_array('-', $call_info['modifiers'], true)) {
539
            \flush(); // @codeCoverageIgnore
540
        }
541
542
        return 0;
543
    }
544
545
    /**
546
     * Dumps some data.
547
     *
548
     * Functionally equivalent to Kint::dump(1) or Kint::dump(debug_backtrace(true))
549
     *
550
     * @return int|string
551
     */
552
    public static function dump()
553
    {
554
        if (!self::$enabled_mode) {
555
            return 0;
556
        }
557
558
        Utils::normalizeAliases(self::$aliases);
559
560
        $args = \func_get_args();
561
562
        $call_info = self::getCallInfo(self::$aliases, \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), \count($args));
563
564
        $statics = self::getStatics();
565
566
        if (\in_array('~', $call_info['modifiers'], true)) {
567
            $statics['enabled_mode'] = self::MODE_TEXT;
568
        }
569
570
        $kintstance = self::createFromStatics($statics);
571
        if (!$kintstance) {
572
            // Should never happen
573
            return 0; // @codeCoverageIgnore
574
        }
575
576
        if (\in_array('-', $call_info['modifiers'], true)) {
577
            while (\ob_get_level()) {
578
                \ob_end_clean();
579
            }
580
        }
581
582
        $kintstance->setStatesFromStatics($statics);
583
        $kintstance->setStatesFromCallInfo($call_info);
584
585
        // If the call is Kint::dump(1) then dump a backtrace instead
586
        if ($args === array(1) && (!isset($call_info['params'][0]['name']) || '1' === $call_info['params'][0]['name'])) {
587
            $args = \debug_backtrace(true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $options of debug_backtrace(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

587
            $args = \debug_backtrace(/** @scrutinizer ignore-type */ true);
Loading history...
588
            $trace = array();
589
590
            foreach ($args as $index => $frame) {
591
                if (Utils::traceFrameIsListed($frame, self::$aliases)) {
592
                    $trace = array();
593
                }
594
595
                $trace[] = $frame;
596
            }
597
598
            if (isset($call_info['callee']['function'])) {
599
                $tracename = $call_info['callee']['function'].'(1)';
600
                if (isset($call_info['callee']['class'], $call_info['callee']['type'])) {
601
                    $tracename = $call_info['callee']['class'].$call_info['callee']['type'].$tracename;
602
                }
603
            } else {
604
                $tracename = 'Kint\\Kint::dump(1)';
605
            }
606
607
            $tracebase = BasicObject::blank($tracename, 'debug_backtrace(true)');
608
609
            $output = $kintstance->dumpAll(array($trace), array($tracebase));
610
        } else {
611
            $bases = self::getBasesFromParamInfo(
612
                isset($call_info['params']) ? $call_info['params'] : array(),
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $call_info['params'] : array() can also be of type null; however, parameter $params of Kint\Kint::getBasesFromParamInfo() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

612
                /** @scrutinizer ignore-type */ isset($call_info['params']) ? $call_info['params'] : array(),
Loading history...
613
                \count($args)
614
            );
615
            $output = $kintstance->dumpAll($args, $bases);
616
        }
617
618
        if (self::$return || \in_array('@', $call_info['modifiers'], true)) {
619
            return $output;
620
        }
621
622
        echo $output;
623
624
        if (\in_array('-', $call_info['modifiers'], true)) {
625
            \flush(); // @codeCoverageIgnore
626
        }
627
628
        return 0;
629
    }
630
631
    /**
632
     * generic path display callback, can be configured in app_root_dirs; purpose is
633
     * to show relevant path info and hide as much of the path as possible.
634
     *
635
     * @param string $file
636
     *
637
     * @return string
638
     */
639
    public static function shortenPath($file)
640
    {
641
        $file = \array_values(\array_filter(\explode('/', \str_replace('\\', '/', $file)), 'strlen'));
642
643
        $longest_match = 0;
644
        $match = '/';
645
646
        foreach (self::$app_root_dirs as $path => $alias) {
647
            if (empty($path)) {
648
                continue;
649
            }
650
651
            $path = \array_values(\array_filter(\explode('/', \str_replace('\\', '/', $path)), 'strlen'));
652
653
            if (\array_slice($file, 0, \count($path)) === $path && \count($path) > $longest_match) {
654
                $longest_match = \count($path);
655
                $match = $alias;
656
            }
657
        }
658
659
        if ($longest_match) {
660
            $file = \array_merge(array($match), \array_slice($file, $longest_match));
661
662
            return \implode('/', $file);
663
        }
664
665
        // fallback to find common path with Kint dir
666
        $kint = \array_values(\array_filter(\explode('/', \str_replace('\\', '/', KINT_DIR)), 'strlen'));
667
668
        foreach ($file as $i => $part) {
669
            if (!isset($kint[$i]) || $kint[$i] !== $part) {
670
                return ($i ? '.../' : '/').\implode('/', \array_slice($file, $i));
671
            }
672
        }
673
674
        return '/'.\implode('/', $file);
675
    }
676
677
    public static function getIdeLink($file, $line)
678
    {
679
        return \str_replace(array('%f', '%l'), array($file, $line), self::$file_link_format);
680
    }
681
682
    /**
683
     * Returns specific function call info from a stack trace frame, or null if no match could be found.
684
     *
685
     * @param array $frame The stack trace frame in question
686
     * @param int   $argc  The amount of arguments received
687
     *
688
     * @return null|array{parameters:array, modifiers:array} params and modifiers, or null if a specific call could not be determined
689
     */
690
    protected static function getSingleCall(array $frame, $argc)
691
    {
692
        if (!isset($frame['file'], $frame['line'], $frame['function']) || !\is_readable($frame['file'])) {
693
            return null;
694
        }
695
696
        if (empty($frame['class'])) {
697
            $callfunc = $frame['function'];
698
        } else {
699
            $callfunc = array($frame['class'], $frame['function']);
700
        }
701
702
        $calls = CallFinder::getFunctionCalls(
703
            \file_get_contents($frame['file']),
704
            $frame['line'],
705
            $callfunc
706
        );
707
708
        $return = null;
709
710
        foreach ($calls as $call) {
711
            $is_unpack = false;
712
713
            // Handle argument unpacking as a last resort
714
            if (KINT_PHP56) {
715
                foreach ($call['parameters'] as $i => &$param) {
716
                    if (0 === \strpos($param['name'], '...')) {
717
                        if ($i < $argc && $i === \count($call['parameters']) - 1) {
718
                            for ($j = 1; $j + $i < $argc; ++$j) {
719
                                $call['parameters'][] = array(
720
                                    'name' => 'array_values('.\substr($param['name'], 3).')['.$j.']',
721
                                    'path' => 'array_values('.\substr($param['path'], 3).')['.$j.']',
722
                                    'expression' => false,
723
                                );
724
                            }
725
726
                            $param['name'] = 'reset('.\substr($param['name'], 3).')';
727
                            $param['path'] = 'reset('.\substr($param['path'], 3).')';
728
                            $param['expression'] = false;
729
                        } else {
730
                            $call['parameters'] = \array_slice($call['parameters'], 0, $i);
731
                        }
732
733
                        $is_unpack = true;
734
                        break;
735
                    }
736
737
                    if ($i >= $argc) {
738
                        continue 2;
739
                    }
740
                }
741
            }
742
743
            if ($is_unpack || \count($call['parameters']) === $argc) {
744
                if (null === $return) {
745
                    $return = $call;
746
                } else {
747
                    // If we have multiple calls on the same line with the same amount of arguments,
748
                    // we can't be sure which it is so just return null and let them figure it out
749
                    return null;
750
                }
751
            }
752
        }
753
754
        return $return;
755
    }
756
}
757