8000 feature #35338 Added support for using the "{{ label }}" placeholder … · symfony/symfony@ccfc4ba · GitHub
[go: up one dir, main page]

Skip to content

Commit ccfc4ba

Browse files
committed
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 ------- 0d9f442 Added support for using the "{{ label }}" placeholder in constraint messages
2 parents 32b82b8 + 0d9f442 commit ccfc4ba

File tree

6 files changed

+221
-12
lines changed

6 files changed

+221
-12
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/form.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,12 @@
126126
->args([service('request_stack')])
127127

128128
->set('form.type_extension.form.validator', FormTypeValidatorExtension::class)
129-
->args([service('validator')])
129+
->args([
130+
service('validator'),
131+
true,
132+
service('twig.form.renderer')->ignoreOnInvalid(),
133+
service('translator')->ignoreOnInvalid(),
134+
])
130135
->tag('form.type_extension', ['extended-type' => FormType::class])
131136

132137
->set('form.type_extension.repeated.validator', RepeatedTypeValidatorExtension::class)

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ CHANGELOG
44
5.2.0
55
-----
66

7-
* added `FormErrorNormalizer`
7+
* added `FormErrorNormalizer`
8+
* Added support for using the `{{ label }}` placeholder in constraint messages, which is replaced in the `ViolationMapper` by the corresponding field form label.
89

910
5.1.0
1011
-----

src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener;
1616
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper;
1717
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\Form\FormRendererInterface;
1819
use Symfony\Component\OptionsResolver\Options;
1920
use Symfony\Component\OptionsResolver\OptionsResolver;
2021
use Symfony\Component\Validator\Validator\ValidatorInterface;
22+
use Symfony\Contracts\Translation\TranslatorInterface;
2123

2224
/**
2325
* @author Bernhard Schussek <bschussek@gmail.com>
@@ -28,10 +30,10 @@ class FormTypeValidatorExtension extends BaseValidatorExtension
2830
private $violationMapper;
2931
private $legacyErrorMessages;
3032

31-
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true)
33+
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true, FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null)
3234
{
3335
$this->validator = $validator;
34-
$this->violationMapper = new ViolationMapper();
36+
$this->violationMapper = new ViolationMapper($formRenderer, $translator);
3537
$this->legacyErrorMessages = $legacyErrorMessages;
3638
}
3739

src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313

1414
use Symfony\Component\Form\AbstractExtension;
1515
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
16+
use Symfony\Component\Form\FormRendererInterface;
1617
use Symfony\Component\Validator\Constraints\Traverse;
1718
use Symfony\Component\Validator\Mapping\ClassMetadata;
1819
use Symfony\Component\Validator\Validator\ValidatorInterface;
20+
use Symfony\Contracts\Translation\TranslatorInterface;
1921

2022
/**
2123
* Extension supporting the Symfony Validator component in forms.
@@ -25,9 +27,11 @@
2527
class ValidatorExtension extends AbstractExtension
2628
{
2729
private $validator;
30+
private $formRenderer;
31+
private $translator;
2832
private $legacyErrorMessages;
2933

30-
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true)
34+
public function __construct(ValidatorInterface $validator, bool $legacyErrorMessages = true, FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null)
3135
{
3236
$this->legacyErrorMessages = $legacyErrorMessages;
3337

@@ -43,6 +47,8 @@ public function __construct(ValidatorInterface $validator, bool $legacyErrorMess
4347
$metadata->addConstraint(new Traverse(false));
4448

4549
$this->validator = $validator;
50+
$this->formRenderer = $formRenderer;
51+
$this->translator = $translator;
4652
}
4753

4854
public function loadTypeGuesser()
@@ -53,7 +59,7 @@ public function loadTypeGuesser()
5359
protected function loadTypeExtensions()
5460
{
5561
return [
56-
new Type\FormTypeValidatorExtension($this->validator, $this->legacyErrorMessages),
62+
new Type\FormTypeValidatorExtension($this->validator, $this->legacyErrorMessages, $this->formRenderer, $this->translator),
5763
new Type\RepeatedTypeValidatorExtension(),
5864
new Type\SubmitTypeValidatorExtension(),
5965
];

src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,28 @@
1313

1414
use Symfony\Component\Form\FormError;
1515
use Symfony\Component\Form\FormInterface;
16+
use Symfony\Component\Form\FormRendererInterface;
1617
use Symfony\Component\Form\Util\InheritDataAwareIterator;
1718
use Symfony\Component\PropertyAccess\PropertyPathBuilder;
1819
use Symfony\Component\PropertyAccess\PropertyPathIterator;
1920
use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface;
2021
use Symfony\Component\Validator\ConstraintViolation;
22+
use Symfony\Contracts\Translation\TranslatorInterface;
2123

2224
/**
2325
* @author Bernhard Schussek <bschussek@gmail.com>
2426
*/
2527
class ViolationMapper implements ViolationMapperInterface
2628
{
27-
/**
28-
* @var bool
29-
*/
30-
private $allowNonSynchronized;
29+
private $formRenderer;
30+
private $translator;
31+
private $allowNonSynchronized = false;
32+
33+
public function __construct(FormRendererInterface $formRenderer = null, TranslatorInterface $translator = null)
34+
{
35+
$this->formRenderer = $formRenderer;
36+
$this->translator = $translator;
37+
}
3138

3239
/**
3340
* {@inheritdoc}
@@ -124,9 +131,49 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form
124131

125132
// Only add the error if the form is synchronized
126133
if ($this->acceptsErrors($scope)) {
134+
$labelFormat = $scope->getConfig()->getOption('label_format');
135+
136+
if (null !== $labelFormat) {
137+
$label = str_replace(
138+
[
139+
'%name%',
140+
'%id%',
141+
],
142+
[
143+
$scope->getName(),
144+
(string) $scope->getPropertyPath(),
145+
],
146+
$labelFormat
147+
);
148+
} else {
149+
$label = $scope->getConfig()->getOption('label');
150+
}
151+
152+
if (null === $label && null !== $this->formRenderer) {
153+
$label = $this->formRenderer->humanize($scope->getName());
154+
} elseif (null === $label) {
155+
$label = $scope->getName();
156+
}
157+
158+
if (false !== $label && null !== $this->translator) {
159+
$label = $this->translator->trans(
160+
$label,
161+
$scope->getConfig()->getOption('label_translation_parameters', []),
162+
$scope->getConfig()->getOption('translation_domain')
163+
);
164+
}
165+
166+
$message = $violation->getMessage();
167+
$messageTemplate = $violation->getMessageTemplate();
168+
169+
if (false !== $label) {
170+
$message = str_replace('{{ label }}', $label, $message);
171+
$messageTemplate = str_replace('{{ label }}', $label, $messageTemplate);
172+
}
173+
127174
$scope->addError(new FormError(
128-
$violation->getMessage(),
129-
$violation->getMessageTemplate(),
175+
$message,
176+
$messageTemplate,
130177
$violation->getParameters(),
131178
$violation->getPlural(),
132179
$violation

src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
use Symfony\Component\Form\FormConfigBuilder;
2323
use Symfony\Component\Form\FormError;
2424
use Symfony\Component\Form\FormInterface;
25+
use Symfony\Component\Form\FormRenderer;
2526
use Symfony\Component\Form\Tests\Extension\Validator\ViolationMapper\Fixtures\Issue;
2627
use Symfony\Component\PropertyAccess\PropertyPath;
2728
use Symfony\Component\Validator\ConstraintViolation;
2829
use Symfony\Component\Validator\ConstraintViolationInterface;
30+
use Symfony\Contracts\Translation\TranslatorInterface;
2931

3032
/**
3133
* @author Bernhard Schussek <bschussek@gmail.com>
@@ -1590,4 +1592,150 @@ public function testBacktrackIfSeveralSubFormsWithSamePropertyPath()
15901592
$this->assertEquals([$this->getFormError($violation2, $grandChild2)], iterator_to_array($grandChild2->getErrors()), $grandChild2->getName().' should have an error, but has none');
15911593
$this->assertEquals([$this->getFormError($violation3, $grandChild3)], iterator_to_array($grandChild3->getErrors()), $grandChild3->getName().' should have an error, but has none');
15921594
}
1595+
1596+
public function testMessageWithLabel1()
1597+
{
1598+
$renderer = $this->getMockBuilder(FormRenderer::class)
1599+
->setMethods(null)
1600+
->disableOriginalConstructor()
1601+
->getMock()
1602+
;
1603+
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
1604+
$translator->expects($this->any())->method('trans')->willReturnMap([
1605+
['Name', [], null, null, 'Custom Name'],
1606+
]);
1607+
$this->mapper = new ViolationMapper($renderer, $translator);
1608+
1609+
$parent = $this->getForm('parent');
1610+
$child = $this->getForm('name', 'name');
1611+
$parent->add($child);
1612+
1613+
$parent->submit([]);
1614+
1615+
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.name', null);
1616+
$this->mapper->mapViolation($violation, $parent);
1617+
1618+
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
1619+
1620+
$errors = iterator_to_array($child->getErrors());
1621+
if (isset($errors[0])) {
1622+
/** @var FormError $error */
1623+
$error = $errors[0];
1624+
$this->assertSame('Message Custom Name', $error->getMessage());
1625+
}
1626+
}
1627+
1628+
public function testMessageWithLabel2()
1629+
{
1630+
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
1631+
$translator->expects($this->any())->method('trans')->willReturnMap([
1632+
['options_label', [], null, null, 'Translated Label'],
1633+
]);
1634+
$this->mapper = new ViolationMapper(null, $translator);
1635+
1636+
$parent = $this->getForm('parent');
1637+
1638+
$config = new FormConfigBuilder('name', null, $this->dispatcher, [
1639+
'error_mapping' => [],
1640+
'label' => 'options_label',
1641+
]);
1642+
$config->setMapped(true);
1643+
$config->setInheritData(false);
1644+
$config->setPropertyPath('name');
1645+
$config->setCompound(true);
1646+
$config->setDataMapper(new PropertyPathMapper());
1647+
1648+
$child = new Form($config);
1649+
$parent->add($child);
1650+
1651+
$parent->submit([]);
1652+
1653+
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.name', null);
1654+
$this->mapper->mapViolation($violation, $parent);
1655+
1656+
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
1657+
1658+
$errors = iterator_to_array($child->getErrors());
1659+
if (isset($errors[0])) {
1660+
/** @var FormError $error */
1661+
$error = $errors[0];
1662+
$this->assertSame('Message Translated Label', $error->getMessage());
1663+
}
1664+
}
1665+
1666+
public function testMessageWithLabelFormat1()
1667+
{
1668+
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
1669+
$translator->expects($this->any())->method('trans')->willReturnMap([
1670+
['form.custom', [], null, null, 'Translated 1st Custom Label'],
1671+
]);
1672+
$this->mapper = new ViolationMapper(null, $translator);
1673+
1674+
$parent = $this->getForm('parent');
1675+
1676+
$config = new FormConfigBuilder('custom', null, $this->dispatcher, [
1677+
'error_mapping' => [],
1678+
'label_format' => 'form.%name%',
1679+
]);
1680+
$config->setMapped(true);
1681+
$config->setInheritData(false);
1682+
$config->setPropertyPath('custom');
1683+
$config->setCompound(true);
1684+
$config->setDataMapper(new PropertyPathMapper());
1685+
1686+
$child = new Form($config);
1687+
$parent->add($child);
1688+
1689+
$parent->submit([]);
1690+
1691+
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.custom', null);
1692+
$this->mapper->mapViolation($violation, $parent);
1693+
1694+
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
1695+
1696+
$errors = iterator_to_array($child->getErrors());
1697+
if (isset($errors[0])) {
1698+
/** @var FormError $error */
1699+
$error = $errors[0];
1700+
$this->assertSame('Message Translated 1st Custom Label', $error->getMessage());
1701+
}
1702+
}
1703+
1704+
public function testMessageWithLabelFormat2()
1705+
{
1706+
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
1707+
$translator->expects($this->any())->method('trans')->willReturnMap([
1708+
['form_custom-id', [], null, null, 'Translated 2nd Custom Label'],
1709+
]);
1710+
$this->mapper = new ViolationMapper(null, $translator);
1711+
1712+
$parent = $this->getForm('parent');
1713+
1714+
$config = new FormConfigBuilder('custom-id', null, $this->dispatcher, [
1715+
'error_mapping' => [],
1716+
'label_format' => 'form_%id%',
1717+
]);
1718+
$config->setMapped(true);
1719+
$config->setInheritData(false);
1720+
$config->setPropertyPath('custom-id');
1721+
$config->setCompound(true);
1722+
$config->setDataMapper(new PropertyPathMapper());
1723+
1724+
$child = new Form($config);
1725+
$parent->add($child);
1726+
1727+
$parent->submit([]);
1728+
1729+
$violation = new ConstraintViolation('Message {{ label }}', null, [], null, 'data.custom-id', null);
1730+
$this->mapper->mapViolation($violation, $parent);
1731+
1732+
$this->assertCount(1, $child->getErrors(), $child->getName().' should have an error, but has none');
1733+
1734+
$errors = iterator_to_array($child->getErrors());
1735+
if (isset($errors[0])) {
1736+
/** @var FormError $error */
1737+
$error = $errors[0];
1738+
$this->assertSame('Message Translated 2nd Custom Label', $error->getMessage());
1739+
}
1740+
}
15931741
}

0 commit comments

Comments
 (0)
0