From 06b3825ee54f32e1aaf51a0a46ba0dd192fc70ff Mon Sep 17 00:00:00 2001 From: dualfroz Date: Fri, 4 Sep 2026 09:21:56 +0200 Subject: [PATCH] fix: do not read an undefined offset when a value argument is last The argument parser looked up the next command-line token via $argv[$key + 1] guarded only by count($argv) === 0. The remaining arguments array is not reindexed after earlier arguments are removed, so a value-taking argument placed last (after an unconsumed token) read an undefined offset and raised an Undefined array key warning under PHP 8, and the argument was set to empty instead of falling back to its default. Check that the next offset exists with isset(). --- src/Argument/Parser.php | 4 +++- tests/Argument/ManagerTest.php | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Argument/Parser.php b/src/Argument/Parser.php index 66bd209..e65fbd0 100644 --- a/src/Argument/Parser.php +++ b/src/Argument/Parser.php @@ -261,7 +261,9 @@ class Parser } if (is_null($value)) { - if (count($argv) === 0) { + // The array is not reindexed as arguments are removed, so check the next + // offset itself rather than the overall count. + if (!isset($argv[$key + 1])) { return $argv; } diff --git a/tests/Argument/ManagerTest.php b/tests/Argument/ManagerTest.php index 15648e1..495efe4 100644 --- a/tests/Argument/ManagerTest.php +++ b/tests/Argument/ManagerTest.php @@ -87,6 +87,29 @@ class ManagerTest extends TestCase $this->assertEquals('', $this->manager->get('foo')); } + public function testItParsesAValueTakingArgumentThatIsLastWithoutAWarning() + { + $this->manager->add([ + 'name' => ['prefix' => 'n', 'longPrefix' => 'name', 'defaultValue' => 'default-name'], + ]); + + set_error_handler(static function (int $errno, string $errstr): bool { + throw new \ErrorException($errstr, 0, $errno); + }); + + try { + // "-n" is the last element but not at a reindexed offset, so reading the + // next offset must not raise "Undefined array key". + $this->manager->parse(['command', 'somefile', '-n']); + } finally { + restore_error_handler(); + } + + // No value was supplied for -n, so it must fall back to its default. + $this->assertFalse($this->manager->provided('name')); + $this->assertEquals('default-name', $this->manager->get('name')); + } + public function testItStoresTrailingInArray() { $this->manager->add([