Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/help/MakeValidator.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ The <info>%command.name%</info> command generates a new validation constraint.
<info>php %command.full_name% EnabledValidator</info>

If the argument is missing, the command will ask for the constraint class name interactively.

The <info>--target</info> option allows you to restrict where the constraint class can be used:

<info>php %command.full_name% EnabledValidator --target=class</info>
<info>php %command.full_name% EnabledValidator --target=method</info>
<info>php %command.full_name% EnabledValidator --target=property</info>

If the option is invalid, the command will ask for the target type interactively.
27 changes: 27 additions & 0 deletions src/Maker/MakeValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -45,10 +49,30 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
{
$command
->addArgument('name', InputArgument::OPTIONAL, 'The name of the validator class (e.g. <fg=yellow>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)
{
Expand Down Expand Up @@ -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();
Expand Down
27 changes: 27 additions & 0 deletions src/Validator/TargetEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Symfony MakerBundle package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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<string>
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}
20 changes: 20 additions & 0 deletions templates/validator/Constraint.tpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@

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

<?php switch ($target): ?>
<?php case 'class': ?>
#[\Attribute(\Attribute::IS_REPEATABLE)]
<?php break; ?>
<?php case 'method': ?>
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
<?php break; ?>
<?php case 'property': ?>
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
<?php break; ?>
<?php default: ?>
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
<?php endswitch; ?>

<?= $class_data->getClassDeclaration(); ?>

{
Expand All @@ -19,4 +32,11 @@ public function __construct(
) {
parent::__construct([], $groups, $payload);
}

<?php if ('class' === $target): ?>
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
<?php endif; ?>
}
60 changes: 60 additions & 0 deletions tests/Maker/MakeValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<value-of<TargetEnum>, string>
*/
private function getTargets(): array
{
return [
'class' => 'WithTargetClass',
'method' => 'WithTargetMethod',
'property' => 'WithTargetProperty',
];
}
}
26 changes: 26 additions & 0 deletions tests/fixtures/make-validator/expected/WithTargetClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Validator;

use Symfony\Component\Validator\Constraint;

#[\Attribute(\Attribute::IS_REPEATABLE)]
final class WithTargetClass extends Constraint
{
public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.';

// You can use #[HasNamedArguments] to make some constraint options required.
// All configurable options must be passed to the constructor.
public function __construct(
public string $mode = 'strict',
?array $groups = null,
mixed $payload = null
) {
parent::__construct([], $groups, $payload);
}

public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}
21 changes: 21 additions & 0 deletions tests/fixtures/make-validator/expected/WithTargetMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Validator;

use Symfony\Component\Validator\Constraint;

#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class WithTargetMethod extends Constraint
{
public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.';

// You can use #[HasNamedArguments] to make some constraint options required.
// All configurable options must be passed to the constructor.
public function __construct(
public string $mode = 'strict',
?array $groups = null,
mixed $payload = null
) {
parent::__construct([], $groups, $payload);
}
}
21 changes: 21 additions & 0 deletions tests/fixtures/make-validator/expected/WithTargetProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Validator;

use Symfony\Component\Validator\Constraint;

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
final class WithTargetProperty extends Constraint
{
public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.';

// You can use #[HasNamedArguments] to make some constraint options required.
// All configurable options must be passed to the constructor.
public function __construct(
public string $mode = 'strict',
?array $groups = null,
mixed $payload = null
) {
parent::__construct([], $groups, $payload);
}
}