Skip to content

Commit 1e52a9a

Browse files
committed
[make:validator] Add --target option that allows restricting automatically where the constraint class can be used
1 parent d3e8169 commit 1e52a9a

File tree

8 files changed

+210
-0
lines changed

8 files changed

+210
-0
lines changed

config/help/MakeValidator.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@ The <info>%command.name%</info> command generates a new validation constraint.
33
<info>php %command.full_name% EnabledValidator</info>
44

55
If the argument is missing, the command will ask for the constraint class name interactively.
6+
7+
The <info>--target</info> option allows you to restrict where the constraint class can be used:
8+
9+
<info>php %command.full_name% EnabledValidator --target=class</info>
10+
<info>php %command.full_name% EnabledValidator --target=method</info>
11+
<info>php %command.full_name% EnabledValidator --target=property</info>
12+
13+
If the option is invalid, the command will ask for the target type interactively.

src/Maker/MakeValidator.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
use Symfony\Bundle\MakerBundle\InputConfiguration;
1818
use Symfony\Bundle\MakerBundle\Str;
1919
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
20+
use Symfony\Bundle\MakerBundle\Validator\TargetEnum;
2021
use Symfony\Component\Console\Command\Command;
22+
use Symfony\Component\Console\Helper\QuestionHelper;
2123
use Symfony\Component\Console\Input\InputArgument;
2224
use Symfony\Component\Console\Input\InputInterface;
25+
use Symfony\Component\Console\Input\InputOption;
26+
use Symfony\Component\Console\Question\ChoiceQuestion;
2327
use Symfony\Component\Validator\Constraint;
2428
use Symfony\Component\Validator\ConstraintValidator;
2529
use Symfony\Component\Validator\Validation;
@@ -45,10 +49,30 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
4549
{
4650
$command
4751
->addArgument('name', InputArgument::OPTIONAL, 'The name of the validator class (e.g. <fg=yellow>EnabledValidator</>)')
52+
->addOption('target', null, InputOption::VALUE_REQUIRED, 'The target of the constraint class')
4853
->setHelp($this->getHelpFileContents('MakeValidator.txt'))
4954
;
5055
}
5156

57+
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
58+
{
59+
$target = $input->getOption('target');
60+
$supportedTargets = TargetEnum::values();
61+
62+
if (!$target || \in_array($target, $supportedTargets)) {
63+
return;
64+
}
65+
66+
$helper = new QuestionHelper();
67+
$question = new ChoiceQuestion(
68+
'Target type',
69+
$supportedTargets
70+
);
71+
72+
$target = $helper->ask($input, $io->getOutput(), $question);
73+
$input->setOption('target', $target);
74+
}
75+
5276
/** @return void */
5377
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
5478
{
@@ -77,6 +101,9 @@ class: \sprintf('Validator\\%s', Str::removeSuffix($validatorClassData->getClass
77101
$generator->generateClassFromClassData(
78102
$constraintDataClass,
79103
'validator/Constraint.tpl.php',
104+
[
105+
'target' => $input->getOption('target'),
106+
]
80107
);
81108

82109
$generator->writeChanges();

src/Validator/TargetEnum.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Validator;
13+
14+
enum TargetEnum: string
15+
{
16+
case CLASS_TARGET = 'class';
17+
case METHOD_TARGET = 'method';
18+
case PROPERTY_TARGET = 'property';
19+
20+
/**
21+
* @return array<string>
22+
*/
23+
public static function values(): array
24+
{
25+
return array_column(self::cases(), 'value');
26+
}
27+
}

templates/validator/Constraint.tpl.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,20 @@
44

55
<?= $class_data->getUseStatements(); ?>
66

7+
<?php switch ($target): ?>
8+
<?php case 'class': ?>
9+
#[\Attribute(\Attribute::IS_REPEATABLE)]
10+
<?php break; ?>
11+
<?php case 'method': ?>
12+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
13+
<?php break; ?>
14+
<?php case 'property': ?>
15+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
16+
<?php break; ?>
17+
<?php default: ?>
718
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
19+
<?php endswitch; ?>
20+
821
<?= $class_data->getClassDeclaration(); ?>
922

1023
{
@@ -19,4 +32,11 @@ public function __construct(
1932
) {
2033
parent::__construct([], $groups, $payload);
2134
}
35+
36+
<?php if ('class' === $target): ?>
37+
public function getTargets(): string
38+
{
39+
return self::CLASS_CONSTRAINT;
40+
}
41+
<?php endif; ?>
2242
}

tests/Maker/MakeValidatorTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\MakerBundle\Maker\MakeValidator;
1515
use Symfony\Bundle\MakerBundle\Test\MakerTestCase;
1616
use Symfony\Bundle\MakerBundle\Test\MakerTestRunner;
17+
use Symfony\Bundle\MakerBundle\Validator\TargetEnum;
1718

1819
class MakeValidatorTest extends MakerTestCase
1920
{
@@ -46,5 +47,64 @@ public function getTestDetails(): \Generator
4647
self::assertSame(file_get_contents($expectedVoterPath), file_get_contents($generatedVoter));
4748
}),
4849
];
50+
51+
yield 'it_makes_validator_constraint_with_target' => [$this->createMakerTest()
52+
->run(function (MakerTestRunner $runner) {
53+
$targets = $this->getTargets();
54+
55+
foreach ($targets as $target => $className) {
56+
$runner->runMaker(
57+
[
58+
// Validator name.
59+
$className,
60+
],
61+
"--target=$target"
62+
);
63+
64+
$expectedPath = \dirname(__DIR__)."/fixtures/make-validator/expected/$className.php";
65+
$generatedPath = $runner->getPath("src/Validator/$className.php");
66+
67+
self::assertSame(file_get_contents($expectedPath), file_get_contents($generatedPath));
68+
}
69+
}),
70+
];
71+
72+
yield 'it_makes_validator_constraint_with_target_invalid' => [$this->createMakerTest()
73+
->run(function (MakerTestRunner $runner) {
74+
$targets = $this->getTargets();
75+
$keys = array_keys($targets);
76+
77+
foreach ($targets as $target => $className) {
78+
$choice = array_search($target, $keys);
79+
80+
$runner->runMaker(
81+
[
82+
// Validator name.
83+
$className,
84+
// Target type.
85+
$choice,
86+
],
87+
'--target=invalid'
88+
);
89+
90+
$expectedPath = \dirname(__DIR__)."/fixtures/make-validator/expected/$className.php";
91+
$generatedPath = $runner->getPath("src/Validator/$className.php");
92+
93+
self::assertSame(file_get_contents($expectedPath), file_get_contents($generatedPath));
94+
}
95+
}),
96+
];
97+
}
98+
99+
/**
100+
* @return array<value-of<TargetEnum>, string>
101+
*/
102+
private function getTargets(): array
103+
{
104+
return [
105+
'class' => 'WithTargetClass',
106+
'method' => 'WithTargetMethod',
107+
'property' => 'WithTargetProperty',
108+
];
49109
}
50110
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace App\Validator;
4+
5+
use Symfony\Component\Validator\Constraint;
6+
7+
#[\Attribute(\Attribute::IS_REPEATABLE)]
8+
final class WithTargetClass extends Constraint
9+
{
10+
public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.';
11+
12+
// You can use #[HasNamedArguments] to make some constraint options required.
13+
// All configurable options must be passed to the constructor.
14+
public function __construct(
15+
public string $mode = 'strict',
16+
?array $groups = null,
17+
mixed $payload = null
18+
) {
19+
parent::__construct([], $groups, $payload);
20+
}
21+
22+
public function getTargets(): string
23+
{
24+
return self::CLASS_CONSTRAINT;
25+
}
26+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Validator;
4+
5+
use Symfony\Component\Validator\Constraint;
6+
7+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
8+
final class WithTargetMethod extends Constraint
9+
{
10+
public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.';
11+
12+
// You can use #[HasNamedArguments] to make some constraint options required.
13+
// All configurable options must be passed to the constructor.
14+
public function __construct(
15+
public string $mode = 'strict',
16+
?array $groups = null,
17+
mixed $payload = null
18+
) {
19+
parent::__construct([], $groups, $payload);
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Validator;
4+
5+
use Symfony\Component\Validator\Constraint;
6+
7+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
8+
final class WithTargetProperty extends Constraint
9+
{
10+
public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.';
11+
12+
// You can use #[HasNamedArguments] to make some constraint options required.
13+
// All configurable options must be passed to the constructor.
14+
public function __construct(
15+
public string $mode = 'strict',
16+
?array $groups = null,
17+
mixed $payload = null
18+
) {
19+
parent::__construct([], $groups, $payload);
20+
}
21+
}

0 commit comments

Comments
 (0)