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)