Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ abstract class Command
* Option definitions.
*
* @var array<string,array<string,mixed>> Definitions keyed by option name,
* each with optional "type", "required" and "default" keys
* each with optional "type" ("string", "int", "float", "numeric" or
* "flag"), "required" and "default" keys. Only options declared as
* "flag" accept being passed without a value
*/
protected array $optionDefinitions = [];
/**
Expand Down Expand Up @@ -369,7 +371,17 @@ protected function validateDefinitions(array $definitions, array $values, string
if (!\is_string($type)) {
$type = 'string';
}
if ($type === 'string' || !\is_string($value)) {
if ($type === 'flag') {
continue;
}
if (!\is_string($value) || $type === 'string') {
if (!\is_string($value)) {
// A boolean (true) means the option was passed without a
// value, which is only valid for "flag" definitions
$errors[] = $translator
? $translator->render('cli', 'validation.type', [$label, (string) $key, $type])
: $label . ' "' . $key . '" must be of type ' . $type . '.';
}
continue;
}
$valid = match ($type) {
Expand Down
35 changes: 35 additions & 0 deletions tests/ValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,41 @@ public function testMissingRequiredOptionReportsAnError() : void
self::assertStringContainsString('option "count" is required', Stderr::getContents());
}

public function testTypedOptionWithoutValueReportsAnError() : void
{
$command = new ValidatedCommandMock($this->console);
$command->setOptionDefinitions([
'limit' => ['type' => 'int'],
]);
$this->console->addCommand($command);
$this->console->exec('validated 5 --limit');
self::assertStringContainsString('option "limit" must be of type int', Stderr::getContents());
self::assertStringNotContainsString('ran', Stdout::getContents());
}

public function testFlagOptionAllowsBeingPassedWithoutValue() : void
{
$command = new ValidatedCommandMock($this->console);
$command->setOptionDefinitions([
'all' => ['type' => 'flag'],
]);
$this->console->addCommand($command);
$this->console->exec('validated 5 --all');
self::assertStringContainsString('ran', Stdout::getContents());
self::assertTrue($this->console->getOption('all'));
}

public function testTypedOptionWithValueStillValidates() : void
{
$command = new ValidatedCommandMock($this->console);
$command->setOptionDefinitions([
'limit' => ['type' => 'int'],
]);
$this->console->addCommand($command);
$this->console->exec('validated 5 --limit=abc');
self::assertStringContainsString('option "limit" must be of type int', Stderr::getContents());
}

public function testValidationErrorsAreTranslatedToSpanish() : void
{
$console = new ConsoleMock(new Language('es'));
Expand Down
Loading