diff --git a/config/help/MakeValidator.txt b/config/help/MakeValidator.txt index 64a7be774..0331afab5 100644 --- a/config/help/MakeValidator.txt +++ b/config/help/MakeValidator.txt @@ -3,3 +3,11 @@ The %command.name% command generates a new validation constraint. php %command.full_name% EnabledValidator If the argument is missing, the command will ask for the constraint class name interactively. + +The --target option allows you to restrict where the constraint class can be used: + + php %command.full_name% EnabledValidator --target=class + php %command.full_name% EnabledValidator --target=method + php %command.full_name% EnabledValidator --target=property + +If the option is invalid, the command will ask for the target type interactively. diff --git a/src/Maker/MakeValidator.php b/src/Maker/MakeValidator.php index f638a12d5..767fca0f7 100644 --- a/src/Maker/MakeValidator.php +++ b/src/Maker/MakeValidator.php @@ -17,9 +17,13 @@ use Symfony\Bundle\MakerBundle\InputConfiguration; use Symfony\Bundle\MakerBundle\Str; use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; +use Symfony\Bundle\MakerBundle\Validator\TargetEnum; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Validation; @@ -45,10 +49,30 @@ public function configureCommand(Command $command, InputConfiguration $inputConf { $command ->addArgument('name', InputArgument::OPTIONAL, 'The name of the validator class (e.g. EnabledValidator)') + ->addOption('target', null, InputOption::VALUE_REQUIRED, 'The target of the constraint class') ->setHelp($this->getHelpFileContents('MakeValidator.txt')) ; } + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + $target = $input->getOption('target'); + $supportedTargets = TargetEnum::values(); + + if (!$target || \in_array($target, $supportedTargets)) { + return; + } + + $helper = new QuestionHelper(); + $question = new ChoiceQuestion( + 'Target type', + $supportedTargets + ); + + $target = $helper->ask($input, $io->getOutput(), $question); + $input->setOption('target', $target); + } + /** @return void */ public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) { @@ -77,6 +101,9 @@ class: \sprintf('Validator\\%s', Str::removeSuffix($validatorClassData->getClass $generator->generateClassFromClassData( $constraintDataClass, 'validator/Constraint.tpl.php', + [ + 'target' => $input->getOption('target'), + ] ); $generator->writeChanges(); diff --git a/src/Validator/TargetEnum.php b/src/Validator/TargetEnum.php new file mode 100644 index 000000000..377176d5b --- /dev/null +++ b/src/Validator/TargetEnum.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Validator; + +enum TargetEnum: string +{ + case CLASS_TARGET = 'class'; + case METHOD_TARGET = 'method'; + case PROPERTY_TARGET = 'property'; + + /** + * @return array + */ + public static function values(): array + { + return array_column(self::cases(), 'value'); + } +} diff --git a/templates/validator/Constraint.tpl.php b/templates/validator/Constraint.tpl.php index 0774095ed..80ab68385 100644 --- a/templates/validator/Constraint.tpl.php +++ b/templates/validator/Constraint.tpl.php @@ -4,7 +4,20 @@ getUseStatements(); ?> + + +#[\Attribute(\Attribute::IS_REPEATABLE)] + + +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] + + +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] + + #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] + + getClassDeclaration(); ?> { @@ -19,4 +32,11 @@ public function __construct( ) { parent::__construct([], $groups, $payload); } + + + public function getTargets(): string + { + return self::CLASS_CONSTRAINT; + } + } diff --git a/tests/Maker/MakeValidatorTest.php b/tests/Maker/MakeValidatorTest.php index acc551b0c..f8e11af6b 100644 --- a/tests/Maker/MakeValidatorTest.php +++ b/tests/Maker/MakeValidatorTest.php @@ -14,6 +14,7 @@ use Symfony\Bundle\MakerBundle\Maker\MakeValidator; use Symfony\Bundle\MakerBundle\Test\MakerTestCase; use Symfony\Bundle\MakerBundle\Test\MakerTestRunner; +use Symfony\Bundle\MakerBundle\Validator\TargetEnum; class MakeValidatorTest extends MakerTestCase { @@ -46,5 +47,64 @@ public function getTestDetails(): \Generator self::assertSame(file_get_contents($expectedVoterPath), file_get_contents($generatedVoter)); }), ]; + + yield 'it_makes_validator_constraint_with_target' => [$this->createMakerTest() + ->run(function (MakerTestRunner $runner) { + $targets = $this->getTargets(); + + foreach ($targets as $target => $className) { + $runner->runMaker( + [ + // Validator name. + $className, + ], + "--target=$target" + ); + + $expectedPath = \dirname(__DIR__)."/fixtures/make-validator/expected/$className.php"; + $generatedPath = $runner->getPath("src/Validator/$className.php"); + + self::assertSame(file_get_contents($expectedPath), file_get_contents($generatedPath)); + } + }), + ]; + + yield 'it_makes_validator_constraint_with_target_invalid' => [$this->createMakerTest() + ->run(function (MakerTestRunner $runner) { + $targets = $this->getTargets(); + $keys = array_keys($targets); + + foreach ($targets as $target => $className) { + $choice = array_search($target, $keys); + + $runner->runMaker( + [ + // Validator name. + $className, + // Target type. + $choice, + ], + '--target=invalid' + ); + + $expectedPath = \dirname(__DIR__)."/fixtures/make-validator/expected/$className.php"; + $generatedPath = $runner->getPath("src/Validator/$className.php"); + + self::assertSame(file_get_contents($expectedPath), file_get_contents($generatedPath)); + } + }), + ]; + } + + /** + * @return array, string> + */ + private function getTargets(): array + { + return [ + 'class' => 'WithTargetClass', + 'method' => 'WithTargetMethod', + 'property' => 'WithTargetProperty', + ]; } } diff --git a/tests/fixtures/make-validator/expected/WithTargetClass.php b/tests/fixtures/make-validator/expected/WithTargetClass.php new file mode 100644 index 000000000..8ac027b00 --- /dev/null +++ b/tests/fixtures/make-validator/expected/WithTargetClass.php @@ -0,0 +1,26 @@ +