Skip to content

Commit

Permalink
feature #1589 [make:validator] generate final classes
Browse files Browse the repository at this point in the history
* [make:validator] generate final classes

- adds generated file comparison to test suite

* simplify class generation

* match expectations created in #1590
  • Loading branch information
jrushlow authored Sep 13, 2024
1 parent e7aeeb2 commit fb404cc
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 22 deletions.
30 changes: 30 additions & 0 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,36 @@ public function generateClass(string $className, string $templateName, array $va
return $targetPath;
}

/**
* Future replacement for generateClass().
*
* @internal
*
* @param string $templateName Template name in Resources/skeleton to use
* @param array $variables Array of variables to pass to the template
*
* @return string The path where the file will be created
*
* @throws \Exception
*/
final public function generateClassFromClassData(ClassData $classData, string $templateName, array $variables = []): string
{
$classData = $this->templateComponentGenerator->configureClass($classData);
$targetPath = $this->fileManager->getRelativePathForFutureClass($classData->getFullClassName());

if (null === $targetPath) {
throw new \LogicException(\sprintf('Could not determine where to locate the new class "%s", maybe try with a full namespace like "\\My\\Full\\Namespace\\%s"', $classData->getFullClassName(), $classData->getClassName()));
}

$variables = array_merge($variables, [
'class_data' => $classData,
]);

$this->addOperation($targetPath, $templateName, $variables);

return $targetPath;
}

/**
* Generate a normal file from a template.
*
Expand Down
30 changes: 19 additions & 11 deletions src/Maker/MakeValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Validation;

/**
Expand Down Expand Up @@ -49,26 +52,31 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
/** @return void */
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
{
$validatorClassNameDetails = $generator->createClassNameDetails(
$input->getArgument('name'),
'Validator\\',
'Validator'
$validatorClassData = ClassData::create(
class: \sprintf('Validator\\%s', $input->getArgument('name')),
suffix: 'Validator',
extendsClass: ConstraintValidator::class,
useStatements: [
Constraint::class,
],
);

$constraintFullClassName = Str::removeSuffix($validatorClassNameDetails->getFullName(), 'Validator');
$constraintDataClass = ClassData::create(
class: \sprintf('Validator\\%s', Str::removeSuffix($validatorClassData->getClassName(), 'Validator')),
extendsClass: Constraint::class,
);

$generator->generateClass(
$validatorClassNameDetails->getFullName(),
$generator->generateClassFromClassData(
$validatorClassData,
'validator/Validator.tpl.php',
[
'constraint_class_name' => Str::getShortClassName($constraintFullClassName),
'constraint_class_name' => $constraintDataClass->getClassName(),
]
);

$generator->generateClass(
$constraintFullClassName,
$generator->generateClassFromClassData(
$constraintDataClass,
'validator/Constraint.tpl.php',
[]
);

$generator->writeChanges();
Expand Down
5 changes: 2 additions & 3 deletions src/Maker/MakeVoter.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ class: \sprintf('Security\Voter\%s', $input->getArgument('name')),
]
);

$generator->generateClass(
$voterClassData->getFullClassName(),
$generator->generateClassFromClassData(
$voterClassData,
'security/Voter.tpl.php',
['class_data' => $voterClassData]
);

$generator->writeChanges();
Expand Down
6 changes: 3 additions & 3 deletions src/Resources/skeleton/validator/Constraint.tpl.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?= "<?php\n" ?>

namespace <?= $namespace; ?>;
namespace <?= $class_data->getNamespace(); ?>;

use Symfony\Component\Validator\Constraint;
<?= $class_data->getUseStatements(); ?>

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class <?= $class_name ?> extends Constraint
<?= $class_data->getClassDeclaration(); ?>
{
public string $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.';

Expand Down
10 changes: 5 additions & 5 deletions src/Resources/skeleton/validator/Validator.tpl.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?= "<?php\n" ?>

namespace <?= $namespace; ?>;
namespace <?= $class_data->getNamespace(); ?>;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
<?= $class_data->getUseStatements(); ?>

class <?= $class_name ?> extends ConstraintValidator
<?= $class_data->getClassDeclaration(); ?>
{
public function validate(mixed $value, Constraint $constraint): void
{
Expand All @@ -18,6 +17,7 @@ public function validate(mixed $value, Constraint $constraint): void
// TODO: implement the validation here
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $value)
->addViolation();
->addViolation()
;
}
}
12 changes: 12 additions & 0 deletions tests/Maker/MakeValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ public function getTestDetails(): \Generator
'FooBar',
]
);

// Validator
$expectedVoterPath = \dirname(__DIR__).'/fixtures/make-validator/expected/FooBarValidator.php';
$generatedVoter = $runner->getPath('src/Validator/FooBarValidator.php');

self::assertSame(file_get_contents($expectedVoterPath), file_get_contents($generatedVoter));

// Constraint
$expectedVoterPath = \dirname(__DIR__).'/fixtures/make-validator/expected/FooBar.php';
$generatedVoter = $runner->getPath('src/Validator/FooBar.php');

self::assertSame(file_get_contents($expectedVoterPath), file_get_contents($generatedVoter));
}),
];
}
Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/make-validator/expected/FooBar.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::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class FooBar extends Constraint
{
public string $message = 'The string "{{ string }}" 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);
}
}
24 changes: 24 additions & 0 deletions tests/fixtures/make-validator/expected/FooBarValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Validator;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

final class FooBarValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
/* @var FooBar $constraint */

if (null === $value || '' === $value) {
return;
}

// TODO: implement the validation here
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $value)
->addViolation()
;
}
}

0 comments on commit fb404cc

Please sign in to comment.