• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHPCSUtils / 30255602737

27 Jul 2026 09:50AM UTC coverage: 99.719% (-0.05%) from 99.77%
30255602737

push

github

web-flow
Merge pull request #775 from PHPCSStandards/feature/abstractarraydeclarationsniff-getactualarraykey-improve-exception-handling

AbstractArrayDeclarationSniff::getActualArrayKey(): catch catchable exceptions

3 of 5 new or added lines in 1 file covered. (60.0%)

3906 of 3917 relevant lines covered (99.72%)

288.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.45
/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php
1
<?php
2
/**
3
 * PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4
 *
5
 * @package   PHPCSUtils
6
 * @copyright 2019-2020 PHPCSUtils Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCSStandards/PHPCSUtils
9
 */
10

11
namespace PHPCSUtils\AbstractSniffs;
12

13
use Exception;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Sniffs\Sniff;
16
use PHP_CodeSniffer\Util\Tokens;
17
use PHPCSUtils\Exceptions\LogicException;
18
use PHPCSUtils\Exceptions\UnexpectedTokenType;
19
use PHPCSUtils\Tokens\Collections;
20
use PHPCSUtils\Utils\Arrays;
21
use PHPCSUtils\Utils\Numbers;
22
use PHPCSUtils\Utils\PassedParameters;
23
use PHPCSUtils\Utils\TextStrings;
24
use Throwable;
25

26
/**
27
 * Abstract sniff to easily examine all parts of an array declaration.
28
 *
29
 * @since 1.0.0
30
 */
31
abstract class AbstractArrayDeclarationSniff implements Sniff
32
{
33

34
    /**
35
     * The stack pointer to the array keyword or the short array open token.
36
     *
37
     * @since 1.0.0
38
     *
39
     * @var int
40
     */
41
    protected $stackPtr;
42

43
    /**
44
     * The token stack for the current file being examined.
45
     *
46
     * @since 1.0.0
47
     *
48
     * @var array<int, array<string, mixed>>
49
     */
50
    protected $tokens;
51

52
    /**
53
     * The stack pointer to the array opener.
54
     *
55
     * @since 1.0.0
56
     *
57
     * @var int
58
     */
59
    protected $arrayOpener;
60

61
    /**
62
     * The stack pointer to the array closer.
63
     *
64
     * @since 1.0.0
65
     *
66
     * @var int
67
     */
68
    protected $arrayCloser;
69

70
    /**
71
     * A multi-dimentional array with information on each array item.
72
     *
73
     * The array index is 1-based and contains the following information on each array item:
74
     * ```php
75
     * 1 => array(
76
     *   'start' => int,    // The stack pointer to the first token in the array item.
77
     *   'end'   => int,    // The stack pointer to the last token in the array item.
78
     *   'raw'   => string, // A string with the contents of all tokens between `start` and `end`.
79
     *   'clean' => string, // Same as `raw`, but all comment tokens have been stripped out.
80
     * )
81
     * ```
82
     *
83
     * @since 1.0.0
84
     *
85
     * @var array<int, array<string, int|string>>
86
     */
87
    protected $arrayItems;
88

89
    /**
90
     * How many items are in the array.
91
     *
92
     * @since 1.0.0
93
     *
94
     * @var int
95
     */
96
    protected $itemCount = 0;
97

98
    /**
99
     * Whether or not the array is single line.
100
     *
101
     * @since 1.0.0
102
     *
103
     * @var bool
104
     */
105
    protected $singleLine;
106

107
    /**
108
     * List of tokens which can safely be used with an eval() expression.
109
     *
110
     * This list gets enhanced with additional token groups in the constructor.
111
     *
112
     * @since 1.0.0
113
     *
114
     * @var array<int|string, int|string>
115
     */
116
    private $acceptedTokens = [
117
        \T_NULL                     => \T_NULL,
118
        \T_TRUE                     => \T_TRUE,
119
        \T_FALSE                    => \T_FALSE,
120
        \T_LNUMBER                  => \T_LNUMBER,
121
        \T_DNUMBER                  => \T_DNUMBER,
122
        \T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
123
        \T_STRING_CONCAT            => \T_STRING_CONCAT,
124
        \T_BOOLEAN_NOT              => \T_BOOLEAN_NOT,
125
    ];
126

127
    /**
128
     * List of tokens which, when found directly before an open parenthesis, indicate a function call/callback.
129
     *
130
     * Note: some of these (end heredoc/nowdoc) can not result in valid code (parse error), but that's not our concern.
131
     *
132
     * @since 1.2.3
133
     *
134
     * @var array<int|string, int|string>
135
     */
136
    private $callbackIndicators = [
137
        \T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
138
        \T_END_HEREDOC              => \T_END_HEREDOC,
139
        \T_END_NOWDOC               => \T_END_NOWDOC,
140
        \T_CLOSE_PARENTHESIS        => \T_CLOSE_PARENTHESIS,
141
        \T_CLOSE_CURLY_BRACKET      => \T_CLOSE_CURLY_BRACKET,
142
        \T_CLOSE_SQUARE_BRACKET     => \T_CLOSE_SQUARE_BRACKET,
143
    ];
144

145
    /**
146
     * Set up this class.
147
     *
148
     * @since 1.0.0
149
     *
150
     * @codeCoverageIgnore
151
     *
152
     * @return void
153
     */
154
    final public function __construct()
155
    {
156
        // Enhance the list of accepted tokens.
157
        $this->acceptedTokens += Tokens::$assignmentTokens;
158
        $this->acceptedTokens += Tokens::$comparisonTokens;
159
        $this->acceptedTokens += Tokens::$arithmeticTokens;
160
        $this->acceptedTokens += Tokens::$operators;
161
        $this->acceptedTokens += Tokens::$booleanOperators;
162
        $this->acceptedTokens += Tokens::$castTokens;
163
        $this->acceptedTokens += Tokens::$bracketTokens;
164
        $this->acceptedTokens += Tokens::$heredocTokens;
165
        $this->acceptedTokens += Collections::ternaryOperators();
166
    }
167

168
    /**
169
     * Returns an array of tokens this test wants to listen for.
170
     *
171
     * @since 1.0.0
172
     *
173
     * @codeCoverageIgnore
174
     *
175
     * @return array<int|string>
176
     */
177
    public function register()
178
    {
179
        return Collections::arrayOpenTokensBC();
180
    }
181

182
    /**
183
     * Processes this test when one of its tokens is encountered.
184
     *
185
     * This method fills the properties with relevant information for examining the array
186
     * and then passes off to the {@see AbstractArrayDeclarationSniff::processArray()} method.
187
     *
188
     * @since 1.0.0
189
     *
190
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
191
     *                                               token was found.
192
     * @param int                         $stackPtr  The position in the PHP_CodeSniffer
193
     *                                               file's token stack where the token
194
     *                                               was found.
195
     *
196
     * @return void
197
     */
198
    final public function process(File $phpcsFile, $stackPtr)
120✔
199
    {
200
        try {
201
            $this->arrayItems = PassedParameters::getParameters($phpcsFile, $stackPtr);
120✔
202
        } catch (UnexpectedTokenType $e) {
48✔
203
            // Parse error, short list, real square open bracket or incorrectly tokenized short array token.
204
            return;
8✔
205
        }
206

207
        $openClose = Arrays::getOpenClose($phpcsFile, $stackPtr, true);
96✔
208
        if ($openClose === false) {
96✔
209
            // Parse error or live coding.
210
            return;
8✔
211
        }
212

213
        $this->stackPtr    = $stackPtr;
88✔
214
        $this->tokens      = $phpcsFile->getTokens();
88✔
215
        $this->arrayOpener = $openClose['opener'];
88✔
216
        $this->arrayCloser = $openClose['closer'];
88✔
217
        $this->itemCount   = \count($this->arrayItems);
88✔
218

219
        $this->singleLine = true;
88✔
220
        if ($this->tokens[$openClose['opener']]['line'] !== $this->tokens[$openClose['closer']]['line']) {
88✔
221
            $this->singleLine = false;
16✔
222
        }
4✔
223

224
        $this->processArray($phpcsFile);
88✔
225

226
        // Reset select properties between calls to this sniff to lower memory usage.
227
        $this->tokens     = [];
88✔
228
        $this->arrayItems = [];
88✔
229
    }
44✔
230

231
    /**
232
     * Process every part of the array declaration.
233
     *
234
     * Controller which calls the individual `process...()` methods for each part of the array.
235
     *
236
     * The method starts by calling the {@see AbstractArrayDeclarationSniff::processOpenClose()} method
237
     * and subsequently calls the following methods for each array item:
238
     *
239
     * Unkeyed arrays | Keyed arrays
240
     * -------------- | ------------
241
     * processNoKey() | processKey()
242
     * -              | processArrow()
243
     * processValue() | processValue()
244
     * processComma() | processComma()
245
     *
246
     * This is the default logic for the sniff, but can be overloaded in a concrete child class
247
     * if needed.
248
     *
249
     * @since 1.0.0
250
     *
251
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
252
     *                                               token was found.
253
     *
254
     * @return void
255
     */
256
    public function processArray(File $phpcsFile)
88✔
257
    {
258
        if ($this->processOpenClose($phpcsFile, $this->arrayOpener, $this->arrayCloser) === true) {
88✔
259
            return;
8✔
260
        }
261

262
        if ($this->itemCount === 0) {
80✔
263
            return;
8✔
264
        }
265

266
        foreach ($this->arrayItems as $itemNr => $arrayItem) {
72✔
267
            try {
268
                $arrowPtr = Arrays::getDoubleArrowPtr($phpcsFile, $arrayItem['start'], $arrayItem['end']);
72✔
269
            } catch (LogicException $e) {
24✔
270
                // Parse error: empty array item. Ignore.
271
                continue;
8✔
272
            }
273

274
            if ($arrowPtr !== false) {
72✔
275
                if ($this->processKey($phpcsFile, $arrayItem['start'], ($arrowPtr - 1), $itemNr) === true) {
40✔
276
                    return;
8✔
277
                }
278

279
                if ($this->processArrow($phpcsFile, $arrowPtr, $itemNr) === true) {
32✔
280
                    return;
8✔
281
                }
282

283
                if ($this->processValue($phpcsFile, ($arrowPtr + 1), $arrayItem['end'], $itemNr) === true) {
24✔
284
                    return;
12✔
285
                }
286
            } else {
6✔
287
                if ($this->processNoKey($phpcsFile, $arrayItem['start'], $itemNr) === true) {
64✔
288
                    return;
8✔
289
                }
290

291
                if ($this->processValue($phpcsFile, $arrayItem['start'], $arrayItem['end'], $itemNr) === true) {
56✔
292
                    return;
8✔
293
                }
294
            }
295

296
            $commaPtr = ($arrayItem['end'] + 1);
56✔
297
            if ($itemNr < $this->itemCount || $this->tokens[$commaPtr]['code'] === \T_COMMA) {
56✔
298
                if ($this->processComma($phpcsFile, $commaPtr, $itemNr) === true) {
56✔
299
                    return;
8✔
300
                }
301
            }
12✔
302
        }
12✔
303
    }
12✔
304

305
    /**
306
     * Process the array opener and closer.
307
     *
308
     * Optional method to be implemented in concrete child classes. By default, this method does nothing.
309
     *
310
     * @since 1.0.0
311
     *
312
     * @codeCoverageIgnore
313
     *
314
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
315
     *                                               token was found.
316
     * @param int                         $openPtr   The position of the array opener token in the token stack.
317
     * @param int                         $closePtr  The position of the array closer token in the token stack.
318
     *
319
     * @return true|void Returning `TRUE` will short-circuit the sniff and stop processing.
320
     *                   In effect, this means that the sniff will not examine the individual
321
     *                   array items if `TRUE` is returned.
322
     */
323
    public function processOpenClose(File $phpcsFile, $openPtr, $closePtr)
324
    {
325
    }
326

327
    /**
328
     * Process the tokens in an array key.
329
     *
330
     * Optional method to be implemented in concrete child classes. By default, this method does nothing.
331
     *
332
     * Note: The `$startPtr` and `$endPtr` do not discount whitespace or comments, but are all inclusive
333
     * to allow for examining all tokens in an array key.
334
     *
335
     * @since 1.0.0
336
     *
337
     * @codeCoverageIgnore
338
     *
339
     * @see \PHPCSUtils\AbstractSniffs\AbstractArrayDeclarationSniff::getActualArrayKey() Optional helper function.
340
     *
341
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
342
     *                                               token was found.
343
     * @param int                         $startPtr  The stack pointer to the first token in the "key" part of
344
     *                                               an array item.
345
     * @param int                         $endPtr    The stack pointer to the last token in the "key" part of
346
     *                                               an array item.
347
     * @param int                         $itemNr    Which item in the array is being handled.
348
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
349
     *
350
     * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
351
     *                   In effect, this means that the sniff will not examine the double arrow, the array
352
     *                   value or comma for this array item and will not process any array items after this one.
353
     */
354
    public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr)
355
    {
356
    }
357

358
    /**
359
     * Process an array item without an array key.
360
     *
361
     * Optional method to be implemented in concrete child classes. By default, this method does nothing.
362
     *
363
     * Note: This method is _not_ intended for processing the array _value_. Use the
364
     * {@see AbstractArrayDeclarationSniff::processValue()} method to implement processing of the array value.
365
     *
366
     * @since 1.0.0
367
     *
368
     * @codeCoverageIgnore
369
     *
370
     * @see \PHPCSUtils\AbstractSniffs\AbstractArrayDeclarationSniff::processValue() Method to process the array value.
371
     *
372
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
373
     *                                               token was found.
374
     * @param int                         $startPtr  The stack pointer to the first token in the array item,
375
     *                                               which in this case will be the first token of the array
376
     *                                               value part of the array item.
377
     * @param int                         $itemNr    Which item in the array is being handled.
378
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
379
     *
380
     * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
381
     *                   In effect, this means that the sniff will not examine the array value or
382
     *                   comma for this array item and will not process any array items after this one.
383
     */
384
    public function processNoKey(File $phpcsFile, $startPtr, $itemNr)
385
    {
386
    }
387

388
    /**
389
     * Process the double arrow.
390
     *
391
     * Optional method to be implemented in concrete child classes. By default, this method does nothing.
392
     *
393
     * @since 1.0.0
394
     *
395
     * @codeCoverageIgnore
396
     *
397
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
398
     *                                               token was found.
399
     * @param int                         $arrowPtr  The stack pointer to the double arrow for the array item.
400
     * @param int                         $itemNr    Which item in the array is being handled.
401
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
402
     *
403
     * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
404
     *                   In effect, this means that the sniff will not examine the array value or
405
     *                   comma for this array item and will not process any array items after this one.
406
     */
407
    public function processArrow(File $phpcsFile, $arrowPtr, $itemNr)
408
    {
409
    }
410

411
    /**
412
     * Process the tokens in an array value.
413
     *
414
     * Optional method to be implemented in concrete child classes. By default, this method does nothing.
415
     *
416
     * Note: The `$startPtr` and `$endPtr` do not discount whitespace or comments, but are all inclusive
417
     * to allow for examining all tokens in an array value.
418
     *
419
     * @since 1.0.0
420
     *
421
     * @codeCoverageIgnore
422
     *
423
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
424
     *                                               token was found.
425
     * @param int                         $startPtr  The stack pointer to the first token in the "value" part of
426
     *                                               an array item.
427
     * @param int                         $endPtr    The stack pointer to the last token in the "value" part of
428
     *                                               an array item.
429
     * @param int                         $itemNr    Which item in the array is being handled.
430
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
431
     *
432
     * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
433
     *                   In effect, this means that the sniff will not examine the comma for this
434
     *                   array item and will not process any array items after this one.
435
     */
436
    public function processValue(File $phpcsFile, $startPtr, $endPtr, $itemNr)
437
    {
438
    }
439

440
    /**
441
     * Process the comma after an array item.
442
     *
443
     * Optional method to be implemented in concrete child classes. By default, this method does nothing.
444
     *
445
     * @since 1.0.0
446
     *
447
     * @codeCoverageIgnore
448
     *
449
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
450
     *                                               token was found.
451
     * @param int                         $commaPtr  The stack pointer to the comma.
452
     * @param int                         $itemNr    Which item in the array is being handled.
453
     *                                               1-based, i.e. the first item is item 1, the second 2 etc.
454
     *
455
     * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
456
     *                   In effect, this means that the sniff will not process any array items
457
     *                   after this one.
458
     */
459
    public function processComma(File $phpcsFile, $commaPtr, $itemNr)
460
    {
461
    }
462

463
    /**
464
     * Determine what the actual array key would be.
465
     *
466
     * Helper function for processsing array keys in the processKey() function.
467
     * Using this method is up to the sniff implementation in the child class.
468
     *
469
     * @since 1.0.0
470
     *
471
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
472
     *                                               token was found.
473
     * @param int                         $startPtr  The stack pointer to the first token in the "key" part of
474
     *                                               an array item.
475
     * @param int                         $endPtr    The stack pointer to the last token in the "key" part of
476
     *                                               an array item.
477
     *
478
     * @return string|int|void The string or integer array key or void if the array key could not
479
     *                         reliably be determined.
480
     */
481
    public function getActualArrayKey(File $phpcsFile, $startPtr, $endPtr)
164✔
482
    {
483
        /*
484
         * Determine the value of the key.
485
         */
486
        $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true);
164✔
487
        $lastNonEmpty  = $phpcsFile->findPrevious(Tokens::$emptyTokens, $endPtr, null, true);
164✔
488

489
        $content = '';
164✔
490

491
        for ($i = $firstNonEmpty, $lastEffective = null;
164✔
492
            $i <= $lastNonEmpty;
164✔
493
            ($lastEffective = isset(Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false ? $i : $lastEffective), $i++
152✔
494
        ) {
30✔
495
            if (isset(Tokens::$commentTokens[$this->tokens[$i]['code']]) === true) {
164✔
496
                continue;
16✔
497
            }
498

499
            if ($this->tokens[$i]['code'] === \T_WHITESPACE) {
164✔
500
                $content .= ' ';
88✔
501
                continue;
88✔
502
            }
503

504
            // Handle FQN true/false/null for PHPCS 3.x.
505
            if ($this->tokens[$i]['code'] === \T_NS_SEPARATOR) {
164✔
506
                $nextNonEmpty   = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
16✔
507
                $nextNonEmptyLC = \strtolower($this->tokens[$nextNonEmpty]['content']);
16✔
508
                if ($nextNonEmpty !== false
8✔
509
                    && ($this->tokens[$nextNonEmpty]['code'] === \T_TRUE
16✔
510
                    || $this->tokens[$nextNonEmpty]['code'] === \T_FALSE
16✔
511
                    || $this->tokens[$nextNonEmpty]['code'] === \T_NULL)
14✔
512
                ) {
8✔
513
                    $content .= $this->tokens[$nextNonEmpty]['content'];
12✔
514
                    $i        = $nextNonEmpty;
12✔
515
                    continue;
12✔
516
                }
517
            }
2✔
518

519
            if (isset($this->acceptedTokens[$this->tokens[$i]['code']]) === false
164✔
520
                || \T_UNSET_CAST === $this->tokens[$i]['code']
164✔
521
            ) {
36✔
522
                // This is not a key we can evaluate. Might be a variable or constant.
523
                return;
24✔
524
            }
525

526
            // Take PHP 7.4 numeric literal separators into account.
527
            if ($this->tokens[$i]['code'] === \T_LNUMBER || $this->tokens[$i]['code'] === \T_DNUMBER) {
148✔
528
                $number   = Numbers::getCompleteNumber($phpcsFile, $i);
40✔
529
                $content .= $number['content'];
40✔
530
                $i        = $number['last_token'];
40✔
531
                continue;
40✔
532
            }
533

534
            /*
535
             * Make sure that when new/deprecated/removed casts are used in the code under scan and the sniff is run
536
             * on a PHP version which doesn't support the cast, the eval() won't cause a deprecation notice,
537
             * borking the scan of the file.
538
             *
539
             * - (unset) was deprecated in PHP 7.2 and removed in PHP 8.0;
540
             * - (real) was deprecated in PHP 7.4 and removed in PHP 8.0;
541
             * - (boolean) was deprecated in PHP 8.5 and will be removed in PHP 9.0;
542
             * - (integer) was deprecated in PHP 8.5 and will be removed in PHP 9.0;
543
             * - (double) was deprecated in PHP 8.5 and will be removed in PHP 9.0;
544
             * - (string) was deprecated in PHP 8.5 and will be removed in PHP 9.0;
545
             */
546
            if (\T_DOUBLE_CAST === $this->tokens[$i]['code']) {
148✔
547
                $content .= '(float)';
16✔
548
                continue;
16✔
549
            }
550

551
            if (\PHP_VERSION_ID >= 80500) {
148✔
552
                if (\T_INT_CAST === $this->tokens[$i]['code']) {
80✔
553
                    $content .= '(int)';
12✔
554
                    continue;
12✔
555
                }
556

557
                if (\T_BOOL_CAST === $this->tokens[$i]['code']) {
80✔
558
                    $content .= '(bool)';
4✔
559
                    continue;
4✔
560
                }
561

562
                if (\T_BINARY_CAST === $this->tokens[$i]['code']) {
80✔
563
                    $content .= '(string)';
8✔
564
                    continue;
8✔
565
                }
566
            }
567

568
            if (isset($lastEffective) === true
148✔
569
                && $this->tokens[$i]['code'] === \T_OPEN_PARENTHESIS
148✔
570
                && isset($this->callbackIndicators[$this->tokens[$lastEffective]['code']]) === true
148✔
571
            ) {
32✔
572
                // Bow out for potential function call in callback format.
573
                return;
56✔
574
            }
575

576
            // Account for heredoc with vars.
577
            if ($this->tokens[$i]['code'] === \T_START_HEREDOC) {
148✔
578
                $text = TextStrings::getCompleteTextString($phpcsFile, $i);
40✔
579

580
                // Check if there's a variable in the heredoc.
581
                if ($text !== TextStrings::stripEmbeds($text)) {
40✔
582
                    return;
8✔
583
                }
584

585
                for ($j = $i; $j <= $this->tokens[$i]['scope_closer']; $j++) {
32✔
586
                    $content .= $this->tokens[$j]['content'];
32✔
587
                }
8✔
588

589
                $i = $this->tokens[$i]['scope_closer'];
32✔
590
                continue;
32✔
591
            }
592

593
            $content .= $this->tokens[$i]['content'];
132✔
594
        }
28✔
595

596
        try {
597
            // The PHP_EOL is to prevent getting parse errors when the key is a heredoc/nowdoc.
598
            $key = @eval('return ' . $content . ';' . \PHP_EOL);
84✔
599
        } catch (Throwable $e) { // phpcs:ignore PHPCompatibility.Interfaces.NewInterfaces.throwableFound
36✔
600
            // The code didn't evaluate cleanly on PHP >= 7.0. Bow out.
601
            return;
20✔
NEW
602
        } catch (Exception $e) {
×
603
            // The code didn't evaluate cleanly on PHP < 7.0. Bow out.
NEW
604
            return;
×
605
        }
606

607
        /*
608
         * Ok, so now we know the base value of the key, let's determine whether it is
609
         * an acceptable index key for an array and if not, what it would turn into.
610
         */
611

612
        switch (\gettype($key)) {
64✔
613
            case 'NULL':
64✔
614
                // An array key of `null` will become an empty string.
615
                return '';
8✔
616

617
            case 'boolean':
64✔
618
                return ($key === true) ? 1 : 0;
16✔
619

620
            case 'integer':
64✔
621
                return $key;
24✔
622

623
            case 'double':
64✔
624
                return (int) $key; // Will automatically cut off the decimal part.
24✔
625

626
            case 'string':
64✔
627
                if (Numbers::isDecimalInt($key) === true) {
64✔
628
                    return (int) $key;
24✔
629
                }
630

631
                return $key;
40✔
632

633
            default:
634
                /*
635
                 * Shouldn't be possible. Either way, if it's not one of the above types,
636
                 * this is not a key we can handle.
637
                 */
638
                return; // @codeCoverageIgnore
639
        }
640
    }
641
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc