diff --git a/UPGRADE-2.6.md b/UPGRADE-2.6.md index 2339eea368aa6..66163d0987b9e 100644 --- a/UPGRADE-2.6.md +++ b/UPGRADE-2.6.md @@ -55,6 +55,45 @@ Form ... {% endif %} ``` + + * The "cascade_validation" option was deprecated. Use the "constraints" + option together with the `Valid` constraint instead. Contrary to + "cascade_validation", "constraints" must be set on the respective child forms, + not the parent form. + + Before: + + ```php + $form = $this->createForm('form', $article, array('cascade_validation' => true)) + ->add('author', new AuthorType()) + ->getForm(); + ``` + + After: + + ```php + use Symfony\Component\Validator\Constraints\Valid; + + $form = $this->createForm('form', $article) + ->add('author', new AuthorType(), array( + 'constraints' => new Valid(), + )) + ->getForm(); + ``` + + Alternatively, you can set the `Valid` constraint in the model itself: + + ```php + use Symfony\Component\Validator\Constraints as Assert; + + class Article + { + /** + * @Assert\Valid + */ + private $author; + } + ``` Validator --------- diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 8ab60af3f447b..a5da6c30adabb 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -176,6 +176,11 @@ {{ block('form_widget_simple') }} {%- endblock email_widget -%} +{%- block range_widget -%} + {% set type = type|default('range') %} + {{- block('form_widget_simple') -}} +{%- endblock range_widget %} + {%- block button_widget -%} {%- if label is empty -%} {%- if label_format is not empty -%} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index 5090309101631..a7d09848e47a7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -113,6 +113,9 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php new file mode 100644 index 0000000000000..4c628f8e005bc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php @@ -0,0 +1 @@ +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'range')); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig index d44685471798a..2f5c5cf0fddba 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig @@ -57,6 +57,14 @@ {% endblock %} {% block panelContent %} + {% set filter = request.query.get('state', '-1') %} + {% set filterOptions = { + '-1': '', + (constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_DEFINED')): 'Defined messages', + (constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_MISSING')): 'Missing messages', + (constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK')): 'Fallback messages', + } %} +

Translation Stats

@@ -72,6 +80,22 @@ + + + +
Missing messages
{{ collector.countMissings }}
Filter +
+ + + +
+
@@ -83,7 +107,7 @@ Id Message Preview - {% for message in collector.messages %} + {% for message in collector.messages if message.state == filter or filter == '-1' %} {{ translator.state(message) }} {{ message.locale }} diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index a4c8aa1912b5f..330f0b1417e25 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -5,6 +5,9 @@ CHANGELOG ----- * deprecated option "read_only" in favor of "attr['readonly']" + * added the html5 "range" FormType + * deprecated the "cascade_validation" option in favor of setting "constraints" + with the Valid constraint 2.7.0 ----- diff --git a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php index 231994258e8d6..96e6c1961a803 100644 --- a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php @@ -63,6 +63,7 @@ protected function loadTypes() new Type\PasswordType(), new Type\PercentType(), new Type\RadioType(), + new Type\RangeType(), new Type\RepeatedType(), new Type\SearchType(), new Type\TextareaType(), diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php new file mode 100644 index 0000000000000..78909e643f5a7 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; + +class RangeType extends AbstractType +{ + /** + * {@inheritdoc} + */ + public function getParent() + { + return 'text'; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'range'; + } +} diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 19e9dd5d7033e..25a8f3a3edace 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -13,6 +13,7 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -63,6 +64,20 @@ public function validate($form, Constraint $constraint) // in the form $constraints = $config->getOption('constraints'); foreach ($constraints as $constraint) { + // For the "Valid" constraint, validate the data in all groups + if ($constraint instanceof Valid) { + if ($validator) { + $validator->atPath('data')->validate($form->getData(), $constraint, $groups); + } else { + // 2.4 API + $this->context->validateValue($form->getData(), $constraint, 'data', $groups); + } + + continue; + } + + // Otherwise validate a constraint only once for the first + // matching group foreach ($groups as $group) { if (in_array($group, $constraint->groups)) { if ($validator) { diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index 16030ae03ffb9..c086ec0e5e6f9 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -70,7 +70,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setDefaults(array( 'error_mapping' => array(), 'constraints' => array(), - 'cascade_validation' => false, + 'cascade_validation' => false, // deprecated 'invalid_message' => 'This value is not valid.', 'invalid_message_parameters' => array(), 'allow_extra_fields' => false, diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php index f78be3cb88648..ffebc9a68a8a6 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php @@ -1597,6 +1597,37 @@ public function testRadioWithValue() ); } + public function testRange() + { + $form = $this->factory->createNamed('name', 'range', 42, array('attr' => array('min' => 5))); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class')), +'/input + [@type="range"] + [@name="name"] + [@value="42"] + [@min="5"] + [@class="my&class form-control"] +' + ); + } + + public function testRangeWithMinMaxValues() + { + $form = $this->factory->createNamed('name', 'range', 42, array('attr' => array('min' => 5, 'max' => 57))); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class')), +'/input + [@type="range"] + [@name="name"] + [@value="42"] + [@min="5"] + [@max="57"] + [@class="my&class form-control"] +' + ); + } + public function testTextarea() { $form = $this->factory->createNamed('name', 'textarea', 'foo&bar', array( diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 60eb0346158d7..d5fc63de8e71e 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -1712,6 +1712,35 @@ public function testRadioWithValue() ); } + public function testRange() + { + $form = $this->factory->createNamed('name', 'range', 42, array('attr' => array('min' => 5))); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/input + [@type="range"] + [@name="name"] + [@value="42"] + [@min="5"] +' + ); + } + + public function testRangeWithMinMaxValues() + { + $form = $this->factory->createNamed('name', 'range', 42, array('attr' => array('min' => 5, 'max' => 57))); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/input + [@type="range"] + [@name="name"] + [@value="42"] + [@min="5"] + [@max="57"] +' + ); + } + public function testTextarea() { $form = $this->factory->createNamed('name', 'textarea', 'foo&bar', array( diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index ba40307c3a458..ab69cbda4e222 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest; use Symfony\Component\Validator\Validation; @@ -109,6 +110,52 @@ public function testValidateConstraints() $this->assertNoViolation(); } + public function testValidateIfParentWithCascadeValidation() + { + $object = $this->getMock('\stdClass'); + + $parent = $this->getBuilder('parent', null, array('cascade_validation' => true)) + ->setCompound(true) + ->setDataMapper($this->getDataMapper()) + ->getForm(); + $options = array('validation_groups' => array('group1', 'group2')); + $form = $this->getBuilder('name', '\stdClass', $options)->getForm(); + $parent->add($form); + + $form->setData($object); + + $this->expectValidateAt(0, 'data', $object, 'group1'); + $this->expectValidateAt(1, 'data', $object, 'group2'); + + $this->validator->validate($form, new Form()); + + $this->assertNoViolation(); + } + + public function testValidateIfChildWithValidConstraint() + { + $object = $this->getMock('\stdClass'); + + $parent = $this->getBuilder('parent') + ->setCompound(true) + ->setDataMapper($this->getDataMapper()) + ->getForm(); + $options = array( + 'validation_groups' => array('group1', 'group2'), + 'constraints' => array(new Valid()), + ); + $form = $this->getBuilder('name', '\stdClass', $options)->getForm(); + $parent->add($form); + + $form->setData($object); + + $this->expectValidateAt(0, 'data', $object, array('group1', 'group2')); + + $this->validator->validate($form, new Form()); + + $this->assertNoViolation(); + } + public function testDontValidateIfParentWithoutCascadeValidation() { $object = $this->getMock('\stdClass'); @@ -387,12 +434,13 @@ public function testUseValidationGroupOfClickedButton() { $object = $this->getMock('\stdClass'); - $parent = $this->getBuilder('parent', null, array('cascade_validation' => true)) + $parent = $this->getBuilder('parent') ->setCompound(true) ->setDataMapper($this->getDataMapper()) ->getForm(); $form = $this->getForm('name', '\stdClass', array( 'validation_groups' => 'form_group', + 'constraints' => array(new Valid()), )); $parent->add($form); @@ -402,7 +450,7 @@ public function testUseValidationGroupOfClickedButton() $parent->submit(array('name' => $object, 'submit' => '')); - $this->expectValidateAt(0, 'data', $object, 'button_group'); + $this->expectValidateAt(0, 'data', $object, array('button_group')); $this->validator->validate($form, new Form()); @@ -413,12 +461,13 @@ public function testDontUseValidationGroupOfUnclickedButton() { $object = $this->getMock('\stdClass'); - $parent = $this->getBuilder('parent', null, array('cascade_validation' => true)) + $parent = $this->getBuilder('parent') ->setCompound(true) ->setDataMapper($this->getDataMapper()) ->getForm(); $form = $this->getForm('name', '\stdClass', array( 'validation_groups' => 'form_group', + 'constraints' => array(new Valid()), )); $parent->add($form); @@ -428,7 +477,7 @@ public function testDontUseValidationGroupOfUnclickedButton() $form->setData($object); - $this->expectValidateAt(0, 'data', $object, 'form_group'); + $this->expectValidateAt(0, 'data', $object, array('form_group')); $this->validator->validate($form, new Form()); @@ -439,20 +488,18 @@ public function testUseInheritedValidationGroup() { $object = $this->getMock('\stdClass'); - $parentOptions = array( - 'validation_groups' => 'group', - 'cascade_validation' => true, - ); + $parentOptions = array('validation_groups' => 'group'); $parent = $this->getBuilder('parent', null, $parentOptions) ->setCompound(true) ->setDataMapper($this->getDataMapper()) ->getForm(); - $form = $this->getBuilder('name', '\stdClass')->getForm(); + $formOptions = array('constraints' => array(new Valid())); + $form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm(); $parent->add($form); $form->setData($object); - $this->expectValidateAt(0, 'data', $object, 'group'); + $this->expectValidateAt(0, 'data', $object, array('group')); $this->validator->validate($form, new Form()); @@ -463,21 +510,18 @@ public function testUseInheritedCallbackValidationGroup() { $object = $this->getMock('\stdClass'); - $parentOptions = array( - 'validation_groups' => array($this, 'getValidationGroups'), - 'cascade_validation' => true, - ); + $parentOptions = array('validation_groups' => array($this, 'getValidationGroups')); $parent = $this->getBuilder('parent', null, $parentOptions) ->setCompound(true) ->setDataMapper($this->getDataMapper()) ->getForm(); - $form = $this->getBuilder('name', '\stdClass')->getForm(); + $formOptions = array('constraints' => array(new Valid())); + $form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm(); $parent->add($form); $form->setData($object); - $this->expectValidateAt(0, 'data', $object, 'group1'); - $this->expectValidateAt(1, 'data', $object, 'group2'); + $this->expectValidateAt(0, 'data', $object, array('group1', 'group2')); $this->validator->validate($form, new Form()); @@ -492,19 +536,18 @@ public function testUseInheritedClosureValidationGroup() 'validation_groups' => function (FormInterface $form) { return array('group1', 'group2'); }, - 'cascade_validation' => true, ); $parent = $this->getBuilder('parent', null, $parentOptions) ->setCompound(true) ->setDataMapper($this->getDataMapper()) ->getForm(); - $form = $this->getBuilder('name', '\stdClass')->getForm(); + $formOptions = array('constraints' => array(new Valid())); + $form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm(); $parent->add($form); $form->setData($object); - $this->expectValidateAt(0, 'data', $object, 'group1'); - $this->expectValidateAt(1, 'data', $object, 'group2'); + $this->expectValidateAt(0, 'data', $object, array('group1', 'group2')); $this->validator->validate($form, new Form()); diff --git a/src/Symfony/Component/Translation/Loader/JsonFileLoader.php b/src/Symfony/Component/Translation/Loader/JsonFileLoader.php index a5b8a4424fc5f..ce4e91ff4fbee 100644 --- a/src/Symfony/Component/Translation/Loader/JsonFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/JsonFileLoader.php @@ -25,14 +25,6 @@ class JsonFileLoader extends FileLoader */ protected function loadResource($resource) { - if (!stream_is_local($resource)) { - throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); - } - - if (!file_exists($resource)) { - throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); - } - $messages = array(); if ($data = file_get_contents($resource)) { $messages = json_decode($data, true); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php index e23709f3fcfe6..20de505b58f4c 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php @@ -220,14 +220,24 @@ protected function expectNoValidate() protected function expectValidateAt($i, $propertyPath, $value, $group) { - $validator = $this->context->getValidator()->inContext($this->context); - $validator->expects($this->at(2 * $i)) - ->method('atPath') - ->with($propertyPath) - ->will($this->returnValue($validator)); - $validator->expects($this->at(2 * $i + 1)) - ->method('validate') - ->with($value, $this->logicalOr(null, array()), $group); + switch ($this->getApiVersion()) { + case Validation::API_VERSION_2_4: + $this->context->expects($this->at($i)) + ->method('validate') + ->with($value, $propertyPath, $group); + break; + case Validation::API_VERSION_2_5: + case Validation::API_VERSION_2_5_BC: + $validator = $this->context->getValidator()->inContext($this->context); + $validator->expects($this->at(2 * $i)) + ->method('atPath') + ->with($propertyPath) + ->will($this->returnValue($validator)); + $validator->expects($this->at(2 * $i + 1)) + ->method('validate') + ->with($value, $this->logicalOr(null, array(), $this->isInstanceOf('\Symfony\Component\Validator\Constraints\Valid')), $group); + break; + } } protected function expectValidateValueAt($i, $propertyPath, $value, $constraints, $group = null)