diff --git a/src/Attributes/Argument.php b/src/Attributes/Argument.php index f41be69..8dc9d87 100644 --- a/src/Attributes/Argument.php +++ b/src/Attributes/Argument.php @@ -4,7 +4,7 @@ use Attribute; -#[Attribute(Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_PARAMETER)] final class Argument { public function __construct() diff --git a/src/Attributes/Description.php b/src/Attributes/Description.php index 723e52d..9fb8a06 100644 --- a/src/Attributes/Description.php +++ b/src/Attributes/Description.php @@ -4,7 +4,7 @@ use Attribute; -#[Attribute(Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_PARAMETER)] final class Description { public function __construct( diff --git a/src/Attributes/Name.php b/src/Attributes/Name.php index c05ca81..e119f09 100644 --- a/src/Attributes/Name.php +++ b/src/Attributes/Name.php @@ -4,7 +4,7 @@ use Attribute; -#[Attribute(Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_PARAMETER)] final class Name { public function __construct(public readonly string $name) diff --git a/src/Attributes/Option.php b/src/Attributes/Option.php index 8feb116..7994967 100644 --- a/src/Attributes/Option.php +++ b/src/Attributes/Option.php @@ -4,7 +4,7 @@ use Attribute; -#[Attribute(Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_PARAMETER)] final class Option { public function __construct() diff --git a/src/Attributes/Shortcut.php b/src/Attributes/Shortcut.php index 26f7850..9b9125c 100644 --- a/src/Attributes/Shortcut.php +++ b/src/Attributes/Shortcut.php @@ -4,7 +4,7 @@ use Attribute; -#[Attribute(Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_PARAMETER)] final class Shortcut { public function __construct(public readonly string $shortcut) diff --git a/src/Factory.php b/src/Factory.php index e57f292..5caa850 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -5,7 +5,7 @@ use Baethon\Symfony\Console\Input\Attributes\Argument; use Baethon\Symfony\Console\Input\Attributes\Option; use ReflectionClass; -use ReflectionProperty; +use ReflectionParameter; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -80,13 +80,16 @@ function (ParameterDefinition $parameter) { */ private function collectByType(ReflectionClass $reflection, string $attribute): array { + $constructor = $reflection->getConstructor() + ?? throw new \InvalidArgumentException('Unable to finc constructor of '.$reflection->getName()); + $properties = array_filter( - $reflection->getProperties(ReflectionProperty::IS_PUBLIC), - fn (ReflectionProperty $item) => $item->getAttributes($attribute) !== [], + $constructor->getParameters(), + fn (ReflectionParameter $item) => $item->getAttributes($attribute) !== [], ); return array_map( - ParameterDefinition::fromReflectionProperty(...), + ParameterDefinition::fromReflectionParameter(...), $properties, ); } diff --git a/src/ParameterDefinition.php b/src/ParameterDefinition.php index 2d14189..5d03bbd 100644 --- a/src/ParameterDefinition.php +++ b/src/ParameterDefinition.php @@ -5,7 +5,7 @@ use Baethon\Symfony\Console\Input\Attributes\Description; use Baethon\Symfony\Console\Input\Attributes\Name; use Baethon\Symfony\Console\Input\Attributes\Shortcut; -use ReflectionProperty; +use ReflectionParameter; use ReflectionType; final class ParameterDefinition @@ -22,22 +22,24 @@ public function __construct( // } - public static function fromReflectionProperty(ReflectionProperty $property): ParameterDefinition + public static function fromReflectionParameter(ReflectionParameter $parameter): ParameterDefinition { - $type = $property->getType(); - $defaultValue = $property->getDefaultValue(); + $type = $parameter->getType(); + $defaultValue = $parameter->isOptional() + ? $parameter->getDefaultValue() + : null; return new self( - name: self::findAttribute($property, Name::class) - ?->name ?? $property->getName(), + name: self::findAttribute($parameter, Name::class) + ?->name ?? $parameter->getName(), required: match (true) { ! is_null($defaultValue) => false, $type?->allowsNull() => false, default => true, }, - description: self::findAttribute($property, Description::class) + description: self::findAttribute($parameter, Description::class) ?->description, - shortcut: self::findAttribute($property, Shortcut::class) + shortcut: self::findAttribute($parameter, Shortcut::class) ?->shortcut, list: ($type instanceof ReflectionType && $type->getName() === 'array'), option: ($type instanceof ReflectionType && $type->getName() === 'bool'), @@ -51,7 +53,7 @@ public static function fromReflectionProperty(ReflectionProperty $property): Par * @param class-string $attribute * @return ?T */ - private static function findAttribute(ReflectionProperty $property, string $attribute) + private static function findAttribute(ReflectionParameter $property, string $attribute) { $attributes = $property->getAttributes($attribute); diff --git a/tests/Unit/FactoryTest.php b/tests/Unit/FactoryTest.php index 073f1de..e6ac2f2 100644 --- a/tests/Unit/FactoryTest.php +++ b/tests/Unit/FactoryTest.php @@ -14,10 +14,12 @@ expect($actual)->toEqual($expected); })->with([ 'boolean flag' => [ - new class + new class(true) { - #[Option] - public bool $test; + public function __construct( + #[Option] + public bool $test, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_NONE), @@ -25,10 +27,12 @@ ], 'string required option' => [ - new class + new class('') { - #[Option] - public string $test; + public function __construct( + #[Option] + public string $test, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_REQUIRED), @@ -36,10 +40,12 @@ ], 'string option' => [ - new class + new class('') { - #[Option] - public ?string $test; + public function __construct( + #[Option] + public ?string $test, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_OPTIONAL), @@ -47,10 +53,12 @@ ], 'array required option' => [ - new class + new class([]) { - #[Option] - public array $test; + public function __construct( + #[Option] + public array $test, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), @@ -58,10 +66,12 @@ ], 'array option' => [ - new class + new class([]) { - #[Option] - public ?array $test; + public function __construct( + #[Option] + public ?array $test, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY), @@ -69,11 +79,13 @@ ], 'shortcut' => [ - new class + new class(true) { - #[Option] - #[Shortcut('t')] - public bool $test; + public function __construct( + #[Option] + #[Shortcut('t')] + public bool $test, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_NONE, shortcut: 't'), @@ -81,11 +93,13 @@ ], 'description' => [ - new class + new class(true) { - #[Option] - #[Description('Lorem ipsum')] - public bool $test; + public function __construct( + #[Option] + #[Description('Lorem ipsum')] + public bool $test, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_NONE, description: 'Lorem ipsum'), @@ -93,16 +107,18 @@ ], 'multiple options' => [ - new class + new class(true, '', []) { - #[Option] - public bool $test; + public function __construct( + #[Option] + public bool $test, - #[Option] - public ?string $name; + #[Option] + public ?string $name, - #[Option] - public array $roles; + #[Option] + public array $roles, + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_NONE), @@ -112,10 +128,12 @@ ], 'default value' => [ - new class + new class('') { - #[Option] - public string $test = 'Lorem ipsum'; + public function __construct( + #[Option] + public string $test = 'Lorem ipsum', + ) {} }, [ new InputOption('test', mode: InputOption::VALUE_OPTIONAL, default: 'Lorem ipsum'), @@ -124,12 +142,14 @@ ]); it('doesnt pick non-options', function () { - $inputDto = new class + $inputDto = new class('', '') { - #[Argument] - public string $test; + public function __construct( + #[Argument] + public string $test, - public string $foo; + public string $foo, + ) {} }; $actual = (new Factory)->createOptions($inputDto); @@ -137,16 +157,18 @@ }); }); -define('InputArgument', function () { +describe('InputArgument', function () { it('creates list of arguments', function ($inputDto, array $expected) { $actual = (new Factory)->createArguments($inputDto); expect($actual)->toEqual($expected); })->with([ 'string required option' => [ - new class + new class('') { - #[Argument] - public string $test; + public function __construct( + #[Argument] + public string $test, + ) {} }, [ new InputArgument('test', mode: InputArgument::REQUIRED), @@ -154,10 +176,12 @@ ], 'string option' => [ - new class + new class('') { - #[Argument] - public ?string $test; + public function __construct( + #[Argument] + public ?string $test, + ) {} }, [ new InputArgument('test', mode: InputArgument::OPTIONAL), @@ -165,10 +189,12 @@ ], 'array required option' => [ - new class + new class([]) { - #[Argument] - public array $test; + public function __construct( + #[Argument] + public array $test, + ) {} }, [ new InputArgument('test', mode: InputArgument::REQUIRED | InputArgument::IS_ARRAY), @@ -176,10 +202,12 @@ ], 'array option' => [ - new class + new class([]) { - #[Argument] - public ?array $test; + public function __construct( + #[Argument] + public ?array $test, + ) {} }, [ new InputArgument('test', mode: InputArgument::OPTIONAL | InputArgument::IS_ARRAY), @@ -187,11 +215,13 @@ ], 'description' => [ - new class + new class('') { - #[Argument] - #[Description('Lorem ipsum')] - public string $test; + public function __construct( + #[Argument] + #[Description('Lorem ipsum')] + public string $test, + ) {} }, [ new InputArgument('test', mode: InputArgument::REQUIRED, description: 'Lorem ipsum'), @@ -199,16 +229,18 @@ ], 'multiple options' => [ - new class + new class('', '', []) { - #[Argument] - public string $test; + public function __construct( + #[Argument] + public string $test, - #[Argument] - public ?string $name; + #[Argument] + public ?string $name, - #[Argument] - public array $roles; + #[Argument] + public array $roles, + ) {} }, [ new InputArgument('test', mode: InputArgument::REQUIRED), @@ -218,10 +250,12 @@ ], 'default value' => [ - new class + new class('') { - #[Argument] - public string $test = 'Lorem ipsum'; + public function __construct( + #[Argument] + public string $test = 'Lorem ipsum', + ) {} }, [ new InputArgument('test', mode: InputArgument::OPTIONAL, default: 'Lorem ipsum'), @@ -230,12 +264,14 @@ ]); it('doesnt pick non-arguments', function () { - $inputDto = new class + $inputDto = new class('', '') { - #[Option] - public string $test; + public function __construct( + #[Option] + public string $test, - public string $bar; + public string $bar, + ) {} }; $actual = (new Factory)->createArguments($inputDto); diff --git a/tests/Unit/ParameterDefinitionTest.php b/tests/Unit/ParameterDefinitionTest.php index 159a036..89f7a5a 100644 --- a/tests/Unit/ParameterDefinitionTest.php +++ b/tests/Unit/ParameterDefinitionTest.php @@ -6,15 +6,21 @@ use Baethon\Symfony\Console\Input\ParameterDefinition; it('extracts definition using attributes', function ($dto, ParameterDefinition $expected, string $property = 'test') { - $property = (new ReflectionClass($dto))->getProperty($property); + $constructor = (new ReflectionClass($dto))->getConstructor(); + $parameter = array_find( + $constructor->getParameters(), + fn (ReflectionParameter $item) => $item->getName() === $property + ); - expect(ParameterDefinition::fromReflectionProperty($property)) + expect(ParameterDefinition::fromReflectionParameter($parameter)) ->toEqual($expected); })->with([ 'required value' => [ - new class + new class('') { - public string $test; + public function __construct( + public string $test, + ) {} }, new ParameterDefinition( name: 'test', @@ -22,9 +28,11 @@ ), ], 'optional value' => [ - new class + new class(null) { - public ?string $test; + public function __construct( + public ?string $test, + ) {} }, new ParameterDefinition( name: 'test', @@ -32,10 +40,12 @@ ), ], 'with description' => [ - new class + new class('') { - #[Description('Foo')] - public string $test; + public function __construct( + #[Description('Foo')] + public string $test, + ) {} }, new ParameterDefinition( name: 'test', @@ -44,10 +54,12 @@ ), ], 'with shortcut' => [ - new class + new class('') { - #[Shortcut('f')] - public string $test; + public function __construct( + #[Shortcut('f')] + public string $test, + ) {} }, new ParameterDefinition( name: 'test', @@ -56,10 +68,12 @@ ), ], 'with name' => [ - new class + new class('') { - #[Name('foo')] - public string $test; + public function __construct( + #[Name('foo')] + public string $test, + ) {} }, new ParameterDefinition( name: 'foo', @@ -67,9 +81,11 @@ ), ], 'as array' => [ - new class + new class([]) { - public array $test; + public function __construct( + public array $test, + ) {} }, new ParameterDefinition( name: 'test', @@ -78,9 +94,11 @@ ), ], 'as option' => [ - new class + new class(true) { - public bool $test; + public function __construct( + public bool $test, + ) {} }, new ParameterDefinition( name: 'test', @@ -91,7 +109,9 @@ 'default value' => [ new class { - public string $test = 'Test'; + public function __construct( + public string $test = 'Test', + ) {} }, new ParameterDefinition( name: 'test',