Skip to content

Commit c9d7c63

Browse files
kbondfabpot
authored andcommitted
[Console] Improve #[Argument]/#[Option] exception messages
1 parent 94f4d7a commit c9d7c63

File tree

3 files changed

+28
-26
lines changed

3 files changed

+28
-26
lines changed

src/Symfony/Component/Console/Attribute/Argument.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Argument
2626
private string|bool|int|float|array|null $default = null;
2727
private array|\Closure $suggestedValues;
2828
private ?int $mode = null;
29+
private string $function = '';
2930

3031
/**
3132
* Represents a console command <argument> definition.
@@ -52,17 +53,23 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
5253
return null;
5354
}
5455

56+
if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) {
57+
$self->function = $function->class.'::'.$function->name;
58+
} else {
59+
$self->function = $function->name;
60+
}
61+
5562
$type = $parameter->getType();
5663
$name = $parameter->getName();
5764

5865
if (!$type instanceof \ReflectionNamedType) {
59-
throw new LogicException(\sprintf('The parameter "$%s" must have a named type. Untyped, Union or Intersection types are not supported for command arguments.', $name));
66+
throw new LogicException(\sprintf('The parameter "$%s" of "%s()" must have a named type. Untyped, Union or Intersection types are not supported for command arguments.', $name, $self->function));
6067
}
6168

6269
$parameterTypeName = $type->getName();
6370

6471
if (!\in_array($parameterTypeName, self::ALLOWED_TYPES, true)) {
65-
throw new LogicException(\sprintf('The type "%s" of parameter "$%s" is not supported as a command argument. Only "%s" types are allowed.', $parameterTypeName, $name, implode('", "', self::ALLOWED_TYPES)));
72+
throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command argument. Only "%s" types are allowed.', $parameterTypeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES)));
6673
}
6774

6875
if (!$self->name) {

src/Symfony/Component/Console/Attribute/Option.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Option
2929
private ?int $mode = null;
3030
private string $typeName = '';
3131
private bool $allowNull = false;
32+
private string $function = '';
3233

3334
/**
3435
* Represents a console command --option definition.
@@ -57,11 +58,17 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
5758
return null;
5859
}
5960

61+
if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) {
62+
$self->function = $function->class.'::'.$function->name;
63+
} else {
64+
$self->function = $function->name;
65+
}
66+
6067
$name = $parameter->getName();
6168
$type = $parameter->getType();
6269

6370
if (!$parameter->isDefaultValueAvailable()) {
64-
throw new LogicException(\sprintf('The option parameter "$%s" must declare a default value.', $name));
71+
throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must declare a default value.', $name, $self->function));
6572
}
6673

6774
if (!$self->name) {
@@ -76,21 +83,21 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
7683
}
7784

7885
if (!$type instanceof \ReflectionNamedType) {
79-
throw new LogicException(\sprintf('The parameter "$%s" must have a named type. Untyped or Intersection types are not supported for command options.', $name));
86+
throw new LogicException(\sprintf('The parameter "$%s" of "%s()" must have a named type. Untyped or Intersection types are not supported for command options.', $name, $self->function));
8087
}
8188

8289
$self->typeName = $type->getName();
8390

8491
if (!\in_array($self->typeName, self::ALLOWED_TYPES, true)) {
85-
throw new LogicException(\sprintf('The type "%s" of parameter "$%s" is not supported as a command option. Only "%s" types are allowed.', $self->typeName, $name, implode('", "', self::ALLOWED_TYPES)));
92+
throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command option. Only "%s" types are allowed.', $self->typeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES)));
8693
}
8794

8895
if ('bool' === $self->typeName && $self->allowNull && \in_array($self->default, [true, false], true)) {
89-
throw new LogicException(\sprintf('The option parameter "$%s" must not be nullable when it has a default boolean value.', $name));
96+
throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must not be nullable when it has a default boolean value.', $name, $self->function));
9097
}
9198

9299
if ($self->allowNull && null !== $self->default) {
93-
throw new LogicException(\sprintf('The option parameter "$%s" must either be not-nullable or have a default of null.', $name));
100+
throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must either be not-nullable or have a default of null.', $name, $self->function));
94101
}
95102

96103
if ('bool' === $self->typeName) {
@@ -160,11 +167,11 @@ private function handleUnion(\ReflectionUnionType $type): self
160167
$this->typeName = implode('|', array_filter($types));
161168

162169
if (!\in_array($this->typeName, self::ALLOWED_UNION_TYPES, true)) {
163-
throw new LogicException(\sprintf('The union type for parameter "$%s" is not supported as a command option. Only "%s" types are allowed.', $this->name, implode('", "', self::ALLOWED_UNION_TYPES)));
170+
throw new LogicException(\sprintf('The union type for parameter "$%s" of "%s()" is not supported as a command option. Only "%s" types are allowed.', $this->name, $this->function, implode('", "', self::ALLOWED_UNION_TYPES)));
164171
}
165172

166173
if (false !== $this->default) {
167-
throw new LogicException(\sprintf('The option parameter "$%s" must have a default value of false.', $this->name));
174+
throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must have a default value of false.', $this->name, $this->function));
168175
}
169176

170177
$this->mode = InputOption::VALUE_OPTIONAL;

src/Symfony/Component/Console/Tests/Command/InvokableCommandTest.php

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ public function testInvalidArgumentType()
138138
$command->setCode(function (#[Argument] object $any) {});
139139

140140
$this->expectException(LogicException::class);
141-
$this->expectExceptionMessage('The type "object" of parameter "$any" is not supported as a command argument. Only "string", "bool", "int", "float", "array" types are allowed.');
142141

143142
$command->getDefinition();
144143
}
@@ -149,7 +148,6 @@ public function testInvalidOptionType()
149148
$command->setCode(function (#[Option] ?object $any = null) {});
150149

151150
$this->expectException(LogicException::class);
152-
$this->expectExceptionMessage('The type "object" of parameter "$any" is not supported as a command option. Only "string", "bool", "int", "float", "array" types are allowed.');
153151

154152
$command->getDefinition();
155153
}
@@ -322,54 +320,44 @@ public static function provideNonBinaryInputOptions(): \Generator
322320
/**
323321
* @dataProvider provideInvalidOptionDefinitions
324322
*/
325-
public function testInvalidOptionDefinition(callable $code, string $expectedMessage)
323+
public function testInvalidOptionDefinition(callable $code)
326324
{
327325
$command = new Command('foo');
328326
$command->setCode($code);
329327

330328
$this->expectException(LogicException::class);
331-
$this->expectExceptionMessage($expectedMessage);
332329

333330
$command->getDefinition();
334331
}
335332

336333
public static function provideInvalidOptionDefinitions(): \Generator
337334
{
338335
yield 'no-default' => [
339-
function (#[Option] string $a) {},
340-
'The option parameter "$a" must declare a default value.',
336+
function (#[Option] string $a) {}
341337
];
342338
yield 'nullable-bool-default-true' => [
343-
function (#[Option] ?bool $a = true) {},
344-
'The option parameter "$a" must not be nullable when it has a default boolean value.',
339+
function (#[Option] ?bool $a = true) {}
345340
];
346341
yield 'nullable-bool-default-false' => [
347-
function (#[Option] ?bool $a = false) {},
348-
'The option parameter "$a" must not be nullable when it has a default boolean value.',
342+
function (#[Option] ?bool $a = false) {}
349343
];
350344
yield 'invalid-union-type' => [
351-
function (#[Option] array|bool $a = false) {},
352-
'The union type for parameter "$a" is not supported as a command option. Only "bool|string", "bool|int", "bool|float" types are allowed.',
345+
function (#[Option] array|bool $a = false) {}
353346
];
354347
yield 'union-type-cannot-allow-null' => [
355348
function (#[Option] string|bool|null $a = null) {},
356-
'The union type for parameter "$a" is not supported as a command option. Only "bool|string", "bool|int", "bool|float" types are allowed.',
357349
];
358350
yield 'union-type-default-true' => [
359351
function (#[Option] string|bool $a = true) {},
360-
'The option parameter "$a" must have a default value of false.',
361352
];
362353
yield 'union-type-default-string' => [
363354
function (#[Option] string|bool $a = 'foo') {},
364-
'The option parameter "$a" must have a default value of false.',
365355
];
366356
yield 'nullable-string-not-null-default' => [
367357
function (#[Option] ?string $a = 'foo') {},
368-
'The option parameter "$a" must either be not-nullable or have a default of null.',
369358
];
370359
yield 'nullable-array-not-null-default' => [
371360
function (#[Option] ?array $a = []) {},
372-
'The option parameter "$a" must either be not-nullable or have a default of null.',
373361
];
374362
}
375363

0 commit comments

Comments
 (0)