/
githubmirror
/
symfony
Обзор
Документация
Войти
/
githubmirror
/
symfony
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
8.2
src/Symfony/Component/Console/Tests/ArgumentResolver/ValueResolver/MapInputValueResolverTest.php
307 строк
11 KB
Robin Chalas
[Console] Add validation constraints support to `#[MapInput]`
23 мар 2026, 22:58
23 мар 2026, 22:58
9ceb0fb
Код
Авторство
О чём код?
<?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tests\ArgumentResolver\ValueResolver; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\ArgumentResolver\ValueResolver\BackedEnumValueResolver; use Symfony\Component\Console\ArgumentResolver\ValueResolver\BuiltinTypeValueResolver; use Symfony\Component\Console\ArgumentResolver\ValueResolver\DateTimeValueResolver; use Symfony\Component\Console\ArgumentResolver\ValueResolver\MapInputValueResolver; use Symfony\Component\Console\Attribute\Argument; use Symfony\Component\Console\Attribute\MapInput; use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Attribute\Reflection\ReflectionMember; use Symfony\Component\Console\Exception\InputValidationFailedException; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Validator\Validation; class MapInputValueResolverTest extends TestCase { public function testResolveMapInput() { $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver()); $input = new ArrayInput(['username' => 'john', '--email' => 'john@example.com'], new InputDefinition([ new InputArgument('username'), new InputOption('email'), ])); $command = new class { public function __invoke( #[MapInput] DummyInput $input, ) { } }; $reflection = new \ReflectionMethod($command, '__invoke'); $parameter = $reflection->getParameters()[0]; $member = new ReflectionMember($parameter); $result = $resolver->resolve('input', $input, $member); $this->assertCount(1, $result); $this->assertInstanceOf(DummyInput::class, $result[0]); $this->assertSame('john', $result[0]->username); $this->assertSame('john@example.com', $result[0]->email); } public function testDoesNotResolveWithoutAttribute() { $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver()); $input = new ArrayInput(['username' => 'john'], new InputDefinition([ new InputArgument('username'), ])); $function = static fn (string $username) => null; $reflection = new \ReflectionFunction($function); $parameter = $reflection->getParameters()[0]; $member = new ReflectionMember($parameter); $result = $resolver->resolve('username', $input, $member); $this->assertSame([], $result); } public function testDoesNotResolveBuiltinTypes() { $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver()); $input = new ArrayInput(['count' => '5'], new InputDefinition([ new InputArgument('count'), ])); $function = static fn (int $count) => null; $reflection = new \ReflectionFunction($function); $parameter = $reflection->getParameters()[0]; $member = new ReflectionMember($parameter); $result = $resolver->resolve('count', $input, $member); $this->assertSame([], $result); } public function testResolvesDateTimeAndBackedEnum() { $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver()); $input = new ArrayInput([ 'created-at' => '2024-01-15', '--status' => 'active', ], new InputDefinition([ new InputArgument('created-at'), new InputOption('status'), ])); $command = new class { public function __invoke( #[MapInput] DummyInputWithDateTimeAndEnum $input, ) { } }; $reflection = new \ReflectionMethod($command, '__invoke'); $parameter = $reflection->getParameters()[0]; $member = new ReflectionMember($parameter); $result = $resolver->resolve('input', $input, $member); $this->assertCount(1, $result); $this->assertInstanceOf(DummyInputWithDateTimeAndEnum::class, $result[0]); $this->assertInstanceOf(\DateTimeImmutable::class, $result[0]->createdAt); $this->assertSame('2024-01-15', $result[0]->createdAt->format('Y-m-d')); $this->assertSame(DummyStatus::Active, $result[0]->status); } public function testValidationPassesWithValidInput() { $validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator(); $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver(), $validator); $input = new ArrayInput(['username' => 'john', '--email' => 'john@example.com'], new InputDefinition([ new InputArgument('username'), new InputOption('email'), ])); $command = new class { public function __invoke( #[MapInput] DummyValidatedInput $input, ) { } }; $reflection = new \ReflectionMethod($command, '__invoke'); $member = new ReflectionMember($reflection->getParameters()[0]); $result = $resolver->resolve('input', $input, $member); $this->assertCount(1, $result); $this->assertSame('john', $result[0]->username); $this->assertSame('john@example.com', $result[0]->email); } public function testValidationFailsWithInvalidInput() { $validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator(); $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver(), $validator); $input = new ArrayInput(['username' => '', '--email' => 'not-an-email'], new InputDefinition([ new InputArgument('username'), new InputOption('email'), ])); $command = new class { public function __invoke( #[MapInput] DummyValidatedInput $input, ) { } }; $reflection = new \ReflectionMethod($command, '__invoke'); $member = new ReflectionMember($reflection->getParameters()[0]); try { $resolver->resolve('input', $input, $member); $this->fail('Expected InputValidationFailedException was not thrown.'); } catch (InputValidationFailedException $e) { $this->assertGreaterThan(0, \count($e->getViolations())); $this->assertStringContainsString('username:', $e->getMessage()); $this->assertStringContainsString('--email:', $e->getMessage()); $this->assertStringNotContainsString('Object(', $e->getMessage()); } } public function testValidationSkippedWhenNoValidator() { $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver()); $input = new ArrayInput(['username' => '', '--email' => 'not-an-email'], new InputDefinition([ new InputArgument('username'), new InputOption('email'), ])); $command = new class { public function __invoke( #[MapInput] DummyValidatedInput $input, ) { } }; $reflection = new \ReflectionMethod($command, '__invoke'); $member = new ReflectionMember($reflection->getParameters()[0]); $result = $resolver->resolve('input', $input, $member); $this->assertCount(1, $result); $this->assertSame('', $result[0]->username); } public function testValidationWithGroupsSkipsNonMatchingConstraints() { $validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator(); $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver(), $validator); $input = new ArrayInput(['username' => '', '--email' => 'john@example.com'], new InputDefinition([ new InputArgument('username'), new InputOption('email'), ])); $command = new class { public function __invoke( #[MapInput(validationGroups: ['strict'])] DummyGroupValidatedInput $input, ) { } }; $reflection = new \ReflectionMethod($command, '__invoke'); $member = new ReflectionMember($reflection->getParameters()[0]); $result = $resolver->resolve('input', $input, $member); $this->assertCount(1, $result); $this->assertSame('', $result[0]->username); } public function testValidationWithGroupsEnforcesMatchingConstraints() { $validator = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator(); $resolver = new MapInputValueResolver(new BuiltinTypeValueResolver(), new BackedEnumValueResolver(), new DateTimeValueResolver(), $validator); $input = new ArrayInput(['username' => '', '--email' => 'not-an-email'], new InputDefinition([ new InputArgument('username'), new InputOption('email'), ])); $command = new class { public function __invoke( #[MapInput(validationGroups: ['strict'])] DummyGroupValidatedInput $input, ) { } }; $reflection = new \ReflectionMethod($command, '__invoke'); $member = new ReflectionMember($reflection->getParameters()[0]); $this->expectException(InputValidationFailedException::class); $resolver->resolve('input', $input, $member); } } class DummyInput { #[Argument] public string $username; #[Option] public ?string $email = null; } class DummyInputWithDateTimeAndEnum { #[Argument] public \DateTimeImmutable $createdAt; #[Option] public DummyStatus $status = DummyStatus::Pending; } enum DummyStatus: string { case Pending = 'pending'; case Active = 'active'; case Inactive = 'inactive'; } class DummyValidatedInput { #[Argument] #[\Symfony\Component\Validator\Constraints\NotBlank] public string $username; #[Option] #[\Symfony\Component\Validator\Constraints\Email] public ?string $email = null; } class DummyGroupValidatedInput { #[Argument] #[\Symfony\Component\Validator\Constraints\NotBlank] public string $username; #[Option] #[\Symfony\Component\Validator\Constraints\Email(groups: ['strict'])] public ?string $email = null; }