Skip to content

Commit dfec5bc

Browse files
committed
[Console] Improve invokable command attribute exceptions
The exception messages now include the method: > The option parameter "$a" of "App\Command\SendSalesReportsCommand::__invoke()" must declare a default value.
1 parent 94f4d7a commit dfec5bc

File tree

3 files changed

+36
-20
lines changed

3 files changed

+36
-20
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: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
class InvokableCommandTest extends TestCase
2929
{
30+
private const FUNCTION_NAME = 'Symfony\Component\Console\Tests\Command\InvokableCommandTest::Symfony\Component\Console\Tests\Command\{closure}()';
31+
3032
public function testCommandInputArgumentDefinition()
3133
{
3234
$command = new Command('foo');
@@ -138,7 +140,7 @@ public function testInvalidArgumentType()
138140
$command->setCode(function (#[Argument] object $any) {});
139141

140142
$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.');
143+
$this->expectExceptionMessage(\sprintf('The type "object" on parameter "$any" of "%s" is not supported as a command argument. Only "string", "bool", "int", "float", "array" types are allowed.', self::FUNCTION_NAME));
142144

143145
$command->getDefinition();
144146
}
@@ -149,7 +151,7 @@ public function testInvalidOptionType()
149151
$command->setCode(function (#[Option] ?object $any = null) {});
150152

151153
$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.');
154+
$this->expectExceptionMessage(\sprintf('The type "object" on parameter "$any" of "%s" is not supported as a command option. Only "string", "bool", "int", "float", "array" types are allowed.', self::FUNCTION_NAME));
153155

154156
$command->getDefinition();
155157
}
@@ -337,39 +339,39 @@ public static function provideInvalidOptionDefinitions(): \Generator
337339
{
338340
yield 'no-default' => [
339341
function (#[Option] string $a) {},
340-
'The option parameter "$a" must declare a default value.',
342+
\sprintf('The option parameter "$a" of "%s" must declare a default value.', self::FUNCTION_NAME),
341343
];
342344
yield 'nullable-bool-default-true' => [
343345
function (#[Option] ?bool $a = true) {},
344-
'The option parameter "$a" must not be nullable when it has a default boolean value.',
346+
\sprintf('The option parameter "$a" of "%s" must not be nullable when it has a default boolean value.', self::FUNCTION_NAME),
345347
];
346348
yield 'nullable-bool-default-false' => [
347349
function (#[Option] ?bool $a = false) {},
348-
'The option parameter "$a" must not be nullable when it has a default boolean value.',
350+
\sprintf('The option parameter "$a" of "%s" must not be nullable when it has a default boolean value.', self::FUNCTION_NAME),
349351
];
350352
yield 'invalid-union-type' => [
351353
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.',
354+
\sprintf('The union type for parameter "$a" of "%s" is not supported as a command option. Only "bool|string", "bool|int", "bool|float" types are allowed.', self::FUNCTION_NAME),
353355
];
354356
yield 'union-type-cannot-allow-null' => [
355357
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.',
358+
\sprintf('The union type for parameter "$a" of "%s" is not supported as a command option. Only "bool|string", "bool|int", "bool|float" types are allowed.', self::FUNCTION_NAME),
357359
];
358360
yield 'union-type-default-true' => [
359361
function (#[Option] string|bool $a = true) {},
360-
'The option parameter "$a" must have a default value of false.',
362+
\sprintf('The option parameter "$a" of "%s" must have a default value of false.', self::FUNCTION_NAME),
361363
];
362364
yield 'union-type-default-string' => [
363365
function (#[Option] string|bool $a = 'foo') {},
364-
'The option parameter "$a" must have a default value of false.',
366+
\sprintf('The option parameter "$a" of "%s" must have a default value of false.', self::FUNCTION_NAME),
365367
];
366368
yield 'nullable-string-not-null-default' => [
367369
function (#[Option] ?string $a = 'foo') {},
368-
'The option parameter "$a" must either be not-nullable or have a default of null.',
370+
\sprintf('The option parameter "$a" of "%s" must either be not-nullable or have a default of null.', self::FUNCTION_NAME),
369371
];
370372
yield 'nullable-array-not-null-default' => [
371373
function (#[Option] ?array $a = []) {},
372-
'The option parameter "$a" must either be not-nullable or have a default of null.',
374+
\sprintf('The option parameter "$a" of "%s" must either be not-nullable or have a default of null.', self::FUNCTION_NAME),
373375
];
374376
}
375377

0 commit comments

Comments
 (0)