feature #35338 Added support for using the "{{ label }}" placeholder in constraint messages (a-menshchikov)
This PR was squashed before being merged into the 5.2-dev branch.
Discussion
----------
Added support for using the "{{ label }}" placeholder in constraint messages
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | #12238
| License | MIT
| Doc PR |
- [ ] Add docs PR
Commits
-------
0d9f44235c
Added support for using the "{{ label }}" placeholder in constraint messages
This commit is contained in:
commit
ccfc4ba269
@ -126,7 +126,12 @@ return static function (ContainerConfigurator $container) {
|
||||
->args([service('request_stack')])
|
||||
|
||||
->set('form.type_extension.form.validator', FormTypeValidatorExtension::class)
|
||||
->args([service('validator')])
|
||||
->args([
|
||||
service('validator'),
|
||||
true,
|
||||
service('twig.form.renderer')->ignoreOnInvalid(),
|
||||
service('translator')->ignoreOnInvalid(),
|
||||
])
|
||||
->tag('form.type_extension', ['extended-type' => FormType::class])
|
||||
|
||||
->set('form.type_extension.repeated.validator', RepeatedTypeValidatorExtension::class)
|
||||
|
@ -4,7 +4,8 @@ CHANGELOG
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* added `FormErrorNormalizer`
|
||||
* added `FormErrorNormalizer`
|
||||
* Added support for using the `{{ label }}` placeholder in constraint messages, which is replaced in the `ViolationMapper` by the corresponding field form label.
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
@ -15,9 +15,11 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener;
|
||||
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormRendererInterface;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
@ -28,10 +30,10 @@ class FormTypeValidatorExtension extends BaseValidatorExtension
|
||||
private $violationMapper;
|
||||
private $legacyErrorMessages;
|
||||
|
||||
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true)
|
||||
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true, FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null)
|
||||
{
|
||||
$this->validator = $validator;
|
||||
$this->violationMapper = new ViolationMapper();
|
||||
$this->violationMapper = new ViolationMapper($formRenderer, $translator);
|
||||
$this->legacyErrorMessages = $legacyErrorMessages;
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,11 @@ namespace Symfony\Component\Form\Extension\Validator;
|
||||
|
||||
use Symfony\Component\Form\AbstractExtension;
|
||||
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
|
||||
use Symfony\Component\Form\FormRendererInterface;
|
||||
use Symfony\Component\Validator\Constraints\Traverse;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* Extension supporting the Symfony Validator component in forms.
|
||||
@ -25,9 +27,11 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
class ValidatorExtension extends AbstractExtension
|
||||
{
|
||||
private $validator;
|
||||
private $formRenderer;
|
||||
private $translator;
|
||||
private $legacyErrorMessages;
|
||||
|
||||
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true)
|
||||
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true, FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null)
|
||||
{
|
||||
$this->legacyErrorMessages = $legacyErrorMessages;
|
||||
|
||||
@ -43,6 +47,8 @@ class ValidatorExtension extends AbstractExtension
|
||||
$metadata->addConstraint(new Traverse(false));
|
||||
|
||||
$this->validator = $validator;
|
||||
$this->formRenderer = $formRenderer;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function loadTypeGuesser()
|
||||
@ -53,7 +59,7 @@ class ValidatorExtension extends AbstractExtension
|
||||
protected function loadTypeExtensions()
|
||||
{
|
||||
return [
|
||||
new Type\FormTypeValidatorExtension($this->validator, $this->legacyErrorMessages),
|
||||
new Type\FormTypeValidatorExtension($this->validator, $this->legacyErrorMessages, $this->formRenderer, $this->translator),
|
||||
new Type\RepeatedTypeValidatorExtension(),
|
||||
new Type\SubmitTypeValidatorExtension(),
|
||||
];
|
||||
|
@ -13,21 +13,28 @@ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
|
||||
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormRendererInterface;
|
||||
use Symfony\Component\Form\Util\InheritDataAwareIterator;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathBuilder;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIterator;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ViolationMapper implements ViolationMapperInterface
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $allowNonSynchronized;
|
||||
private $formRenderer;
|
||||
private $translator;
|
||||
private $allowNonSynchronized = false;
|
||||
|
||||
public function __construct(FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null)
|
||||
{
|
||||
$this->formRenderer = $formRenderer;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@ -124,9 +131,49 @@ class ViolationMapper implements ViolationMapperInterface
|
||||
|
||||
// Only add the error if the form is synchronized
|
||||
if ($this->acceptsErrors($scope)) {
|
||||
$labelFormat = $scope->getConfig()->getOption('label_format');
|
||||
|
||||
if (null !== $labelFormat) {
|
||||
$label = str_replace(
|
||||
[
|
||||
'%name%',
|
||||
'%id%',
|
||||
],
|
||||
[
|
||||
$scope->getName(),
|
||||
(string) $scope->getPropertyPath(),
|
||||
],
|
||||
$labelFormat
|
||||
);
|
||||
} else {
|
||||
$label = $scope->getConfig()->getOption('label');
|
||||
}
|
||||
|
||||
if (null === $label && null !== $this->formRenderer) {
|
||||
$label = $this->formRenderer->humanize($scope->getName());
|
||||
} elseif (null === $label) {
|
||||
$label = $scope->getName();
|
||||
}
|
||||
|
||||
if (false !== $label && null !== $this->translator) {
|
||||
$label = $this->translator->trans(
|
||||
$label,
|
||||
$scope->getConfig()->getOption('label_translation_parameters', []),
|
||||
$scope->getConfig()->getOption('translation_domain')
|
||||
);
|
||||
}
|
||||
|
||||
$message = $violation->getMessage();
|
||||
$messageTemplate = $violation->getMessageTemplate();
|
||||
|
||||
if (false !== $label) {
|
||||
$message = str_replace('{{ label }}', $label, $message);
|
||||
$messageTemplate = str_replace('{{ label }}', $label, $messageTemplate);
|
||||
}
|
||||
|
||||
$scope->addError(new FormError(
|
||||
$violation->getMessage(),
|
||||
$violation->getMessageTemplate(),
|
||||
$message,
|
||||
$messageTemplate,
|
||||
$violation->getParameters(),
|
||||
$violation->getPlural(),
|
||||
$violation
|
||||
|
@ -22,10 +22,12 @@ use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FormConfigBuilder;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormRenderer;
|
||||
use Symfony\Component\Form\Tests\Extension\Validator\ViolationMapper\Fixtures\Issue;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
@ -1590,4 +1592,150 @@ class ViolationMapperTest extends TestCase
|
||||
$this->assertEquals([$this->getFormError($violation2, $grandChild2)], iterator_to_array($grandChild2->getErrors()), $grandChild2->getName().' should have an error, but has none');
|
||||
$this->assertEquals([$this->getFormError($violation3, $grandChild3)], iterator_to_array($grandChild3->getErrors()), $grandChild3->getName().' should have an error, but has none');
|
||||
}
|
||||
|
||||
public function testMessageWithLabel1()
|
||||
{
|
||||
$renderer = $this->getMockBuilder(FormRenderer::class)
|
||||
->setMethods(null)
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
;
|
||||
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
||||
$translator->expects($this->any())->method('trans')->willReturnMap([
|
||||
['Name', [], null, null, 'Custom Name'],
|
||||
]);
|
||||
$this->mapper = new ViolationMapper($renderer, $translator);
|
||||
|
||||
$parent = $this->getForm('parent');
|
||||
$child = $this->getForm('name', 'name');
|
||||
$parent->add($child);
|
||||
|
||||
$parent->submit([]);
|
||||
|
||||
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.name', null);
|
||||
$this->mapper->mapViolation($violation, $parent);
|
||||
|
||||
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
|
||||
|
||||
$errors = iterator_to_array($child->getErrors());
|
||||
if (isset($errors[0])) {
|
||||
/** @var FormError $error */
|
||||
$error = $errors[0];
|
||||
$this->assertSame('Message Custom Name', $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testMessageWithLabel2()
|
||||
{
|
||||
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
||||
$translator->expects($this->any())->method('trans')->willReturnMap([
|
||||
['options_label', [], null, null, 'Translated Label'],
|
||||
]);
|
||||
$this->mapper = new ViolationMapper(null, $translator);
|
||||
|
||||
$parent = $this->getForm('parent');
|
||||
|
||||
$config = new FormConfigBuilder('name', null, $this->dispatcher, [
|
||||
'error_mapping' => [],
|
||||
'label' => 'options_label',
|
||||
]);
|
||||
$config->setMapped(true);
|
||||
$config->setInheritData(false);
|
||||
$config->setPropertyPath('name');
|
||||
$config->setCompound(true);
|
||||
$config->setDataMapper(new PropertyPathMapper());
|
||||
|
||||
$child = new Form($config);
|
||||
$parent->add($child);
|
||||
|
||||
$parent->submit([]);
|
||||
|
||||
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.name', null);
|
||||
$this->mapper->mapViolation($violation, $parent);
|
||||
|
||||
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
|
||||
|
||||
$errors = iterator_to_array($child->getErrors());
|
||||
if (isset($errors[0])) {
|
||||
/** @var FormError $error */
|
||||
$error = $errors[0];
|
||||
$this->assertSame('Message Translated Label', $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testMessageWithLabelFormat1()
|
||||
{
|
||||
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
||||
$translator->expects($this->any())->method('trans')->willReturnMap([
|
||||
['form.custom', [], null, null, 'Translated 1st Custom Label'],
|
||||
]);
|
||||
$this->mapper = new ViolationMapper(null, $translator);
|
||||
|
||||
$parent = $this->getForm('parent');
|
||||
|
||||
$config = new FormConfigBuilder('custom', null, $this->dispatcher, [
|
||||
'error_mapping' => [],
|
||||
'label_format' => 'form.%name%',
|
||||
]);
|
||||
$config->setMapped(true);
|
||||
$config->setInheritData(false);
|
||||
$config->setPropertyPath('custom');
|
||||
$config->setCompound(true);
|
||||
$config->setDataMapper(new PropertyPathMapper());
|
||||
|
||||
$child = new Form($config);
|
||||
$parent->add($child);
|
||||
|
||||
$parent->submit([]);
|
||||
|
||||
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.custom', null);
|
||||
$this->mapper->mapViolation($violation, $parent);
|
||||
|
||||
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
|
||||
|
||||
$errors = iterator_to_array($child->getErrors());
|
||||
if (isset($errors[0])) {
|
||||
/** @var FormError $error */
|
||||
$error = $errors[0];
|
||||
$this->assertSame('Message Translated 1st Custom Label', $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testMessageWithLabelFormat2()
|
||||
{
|
||||
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
||||
$translator->expects($this->any())->method('trans')->willReturnMap([
|
||||
['form_custom-id', [], null, null, 'Translated 2nd Custom Label'],
|
||||
]);
|
||||
$this->mapper = new ViolationMapper(null, $translator);
|
||||
|
||||
$parent = $this->getForm('parent');
|
||||
|
||||
$config = new FormConfigBuilder('custom-id', null, $this->dispatcher, [
|
||||
'error_mapping' => [],
|
||||
'label_format' => 'form_%id%',
|
||||
]);
|
||||
$config->setMapped(true);
|
||||
$config->setInheritData(false);
|
||||
$config->setPropertyPath('custom-id');
|
||||
$config->setCompound(true);
|
||||
$config->setDataMapper(new PropertyPathMapper());
|
||||
|
||||
$child = new Form($config);
|
||||
$parent->add($child);
|
||||
|
||||
$parent->submit([]);
|
||||
|
||||
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.custom-id', null);
|
||||
$this->mapper->mapViolation($violation, $parent);
|
||||
|
||||
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
|
||||
|
||||
$errors = iterator_to_array($child->getErrors());
|
||||
if (isset($errors[0])) {
|
||||
/** @var FormError $error */
|
||||
$error = $errors[0];
|
||||
$this->assertSame('Message Translated 2nd Custom Label', $error->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user