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 @@
= $class_data->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)]
+
+
= $class_data->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 @@
+