renderBlock('widget_container_attributes') ?>>
renderBlock('input', array('type' => isset($type) ? $type : "url")) ?>
+renderBlock('form_widget_single_control', array('type' => isset($type) ? $type : "url")) ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php
new file mode 100644
index 0000000000000..fe4afa01d2e61
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php
@@ -0,0 +1,9 @@
+id="escape($id) ?>"
+name="escape($full_name) ?>"
+readonly="readonly"
+disabled="disabled"
+required="required"
+maxlength="escape($max_length) ?>"
+pattern="escape($pattern) ?>"
+ $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php
new file mode 100644
index 0000000000000..a36cfbbdfc462
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php
@@ -0,0 +1,2 @@
+id="escape($id) ?>"
+ $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php
index 05d8c4aea89d3..1a889760ca869 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_errors.html.php
@@ -1,4 +1,26 @@
-hasChildren()): ?>
+
+
+
+
+ getMessagePluralization()) {
+ echo $view['translator']->trans(
+ $error->getMessageTemplate(),
+ $error->getMessageParameters(),
+ 'validators'
+ );
+ } else {
+ echo $view['translator']->transChoice(
+ $error->getMessageTemplate(),
+ $error->getMessagePluralization(),
+ $error->getMessageParameters(),
+ 'validators'
+ );
+ }?>
+
+
+
+
0): ?>
@@ -26,26 +48,4 @@
-
-
-
-
- getMessagePluralization()) {
- echo $view['translator']->trans(
- $error->getMessageTemplate(),
- $error->getMessageParameters(),
- 'validators'
- );
- } else {
- echo $view['translator']->transChoice(
- $error->getMessageTemplate(),
- $error->getMessagePluralization(),
- $error->getMessageParameters(),
- 'validators'
- );
- }?>
-
-
-
\ No newline at end of file
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php
index 9262a1bae10b3..5526a03fe9cc5 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_row.html.php
@@ -3,7 +3,7 @@
label($form, isset($label) ? $label : null) ?>
- hasChildren()): ?>
+
errors($form) ?>
widget($form) ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget.html.php
deleted file mode 100644
index 171f1eb40c74e..0000000000000
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget.html.php
+++ /dev/null
@@ -1,8 +0,0 @@
-hasChildren()): ?>
-renderBlock('container_attributes') ?>>
- renderBlock('form_rows') ?>
- rest($form) ?>
-
-
-renderBlock('input')?>
-
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php
new file mode 100644
index 0000000000000..9c90f15e58fc9
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php
@@ -0,0 +1,4 @@
+renderBlock('widget_container_attributes') ?>>
+ renderBlock('form_rows') ?>
+ rest($form) ?>
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/input.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/form_widget_single_control.html.php
similarity index 60%
rename from src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/input.html.php
rename to src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/form_widget_single_control.html.php
index 64f43ddf6eb1b..6506f9140eeab 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/input.html.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Parent/form_widget_single_control.html.php
@@ -1,2 +1,2 @@
- renderBlock('attributes') ?> value="" rel="theme" />
+ renderBlock('widget_attributes') ?> value="" rel="theme" />
diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md
index f6f32633e5f1d..818b43e969bca 100644
--- a/src/Symfony/Component/Form/CHANGELOG.md
+++ b/src/Symfony/Component/Form/CHANGELOG.md
@@ -25,16 +25,12 @@ CHANGELOG
* [BC BREAK] removed EntitiesToArrayTransformer and EntityToIdTransformer.
The former has been replaced by CollectionToArrayTransformer in combination
with EntityChoiceList, the latter is not required in the core anymore.
-
* [BC BREAK] renamed
-
* ArrayToBooleanChoicesTransformer to ChoicesToBooleanArrayTransformer
* ScalarToBooleanChoicesTransformer to ChoiceToBooleanArrayTransformer
* ArrayToChoicesTransformer to ChoicesToValuesTransformer
* ScalarToChoiceTransformer to ChoiceToValueTransformer
-
- to be consistent with the naming in ChoiceListInterface.
-
+ to be consistent with the naming in ChoiceListInterface.
* [BC BREAK] removed FormUtil::toArrayKey() and FormUtil::toArrayKeys().
They were merged into ChoiceList and have no public equivalent anymore.
* choice fields now throw a FormException if neither the "choices" nor the
@@ -62,8 +58,15 @@ CHANGELOG
by event subscribers
* simplified CSRF protection and removed the csrf type
* deprecated FieldType and merged it into FormType
- * [BC BREAK] renamed "field_*" theme blocks to "form_*" and "field_widget" to
- "input"
+ * [BC BREAK] renamed theme blocks
+ * "field_*" to "form_*"
+ * "field_widget" to "form_widget_single_control"
+ * "widget_choice_options" to "choice_widget_options"
+ * "generic_label" to "form_label"
+ * added theme blocks "form_widget_compound", "choice_widget_expanded" and
+ "choice_widget_collapsed" to make theming more modular
* ValidatorTypeGuesser now guesses "collection" for array type constraint
* added method `guessPattern` to FormTypeGuesserInterface to guess which pattern to use in the HTML5 attribute "pattern"
* deprecated method `guessMinLength` in favor of `guessPattern`
+ * labels don't display field attributes anymore. Label attributes can be
+ passed in the "label_attr" option/variable
\ No newline at end of file
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php
index 8ab2c6843d0ae..b99b5892aeb0b 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php
@@ -46,11 +46,14 @@ public function buildView(FormView $view, FormInterface $form)
*/
public function getDefaultOptions()
{
+ $emptyData = function (FormInterface $form, $clientData) {
+ return $clientData;
+ };
+
return array(
- 'value' => '1',
- 'empty_data' => function (FormInterface $form, $clientData) {
- return $clientData;
- },
+ 'value' => '1',
+ 'empty_data' => $emptyData,
+ 'single_control' => true,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 1a8ba2d1be6a2..3e21af5ad1800 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -165,6 +165,10 @@ public function getDefaultOptions()
return $options['required'] ? null : '';
};
+ $singleControl = function (Options $options) {
+ return !$options['expanded'];
+ };
+
return array(
'multiple' => false,
'expanded' => false,
@@ -174,6 +178,7 @@ public function getDefaultOptions()
'empty_data' => $emptyData,
'empty_value' => $emptyValue,
'error_bubbling' => false,
+ 'single_control' => $singleControl,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
index f428054edee74..2308059058b12 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
@@ -21,6 +21,7 @@
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer;
+use Symfony\Component\Form\Options;
class DateTimeType extends AbstractType
{
@@ -130,6 +131,10 @@ public function buildView(FormView $view, FormInterface $form)
*/
public function getDefaultOptions()
{
+ $singleControl = function (Options $options) {
+ return $options['widget'] === 'single_text';
+ };
+
return array(
'input' => 'datetime',
'data_timezone' => null,
@@ -158,6 +163,7 @@ public function getDefaultOptions()
// representation is not \DateTime, but an array, we need to unset
// this option.
'data_class' => null,
+ 'single_control' => $singleControl,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
index c506714c7ffd3..38b23e436ab01 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
@@ -11,18 +11,18 @@
namespace Symfony\Component\Form\Extension\Core\Type;
-use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
-
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\Exception\CreationException;
use Symfony\Component\Form\FormView;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\ReversedTransformer;
+use Symfony\Component\Form\Options;
class DateType extends AbstractType
{
@@ -163,6 +163,10 @@ public function buildViewBottomUp(FormView $view, FormInterface $form)
*/
public function getDefaultOptions()
{
+ $singleControl = function (Options $options) {
+ return $options['widget'] === 'single_text';
+ };
+
return array(
'years' => range(date('Y') - 5, date('Y') + 5),
'months' => range(1, 12),
@@ -182,6 +186,7 @@ public function getDefaultOptions()
// representation is not \DateTime, but an array, we need to unset
// this option.
'data_class' => null,
+ 'single_control' => $singleControl,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
index 468c65a0f359a..d57176ec672b1 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
@@ -38,6 +38,16 @@ public function buildViewBottomUp(FormView $view, FormInterface $form)
;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultOptions()
+ {
+ return array(
+ 'single_control' => true,
+ );
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
index ec5870acff7d3..0dea6d113c96c 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
@@ -40,8 +40,13 @@ public function buildForm(FormBuilder $builder, array $options)
} else {
$options['property_path'] = new PropertyPath($options['property_path']);
}
+
if (!is_array($options['attr'])) {
- throw new FormException('The "attr" option must be "array".');
+ throw new FormException('The "attr" option must be an "array".');
+ }
+
+ if (!is_array($options['label_attr'])) {
+ throw new FormException('The "label_attr" option must be an "array".');
}
$builder
@@ -56,11 +61,13 @@ public function buildForm(FormBuilder $builder, array $options)
->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))
- ->setAttribute('attr', $options['attr'] ?: array())
+ ->setAttribute('attr', $options['attr'])
+ ->setAttribute('label_attr', $options['label_attr'])
->setAttribute('invalid_message', $options['invalid_message'])
->setAttribute('invalid_message_parameters', $options['invalid_message_parameters'])
->setAttribute('translation_domain', $options['translation_domain'])
->setAttribute('virtual', $options['virtual'])
+ ->setAttribute('single_control', $options['single_control'])
->setData($options['data'])
->setDataMapper(new PropertyPathMapper($options['data_class']))
->addEventSubscriber(new ValidationListener())
@@ -125,6 +132,8 @@ public function buildView(FormView $view, FormInterface $form)
->set('label', $form->getAttribute('label'))
->set('multipart', false)
->set('attr', $form->getAttribute('attr'))
+ ->set('label_attr', $form->getAttribute('label_attr'))
+ ->set('single_control', $form->getAttribute('single_control'))
->set('types', $types)
->set('translation_domain', $form->getAttribute('translation_domain'))
;
@@ -183,7 +192,13 @@ public function getDefaultOptions()
return '';
};
};
-
+
+ // For any form that is not represented by a single HTML control,
+ // errors should bubble up by default
+ $errorBubbling = function (Options $options) {
+ return !$options['single_control'];
+ };
+
return array(
'data' => null,
'data_class' => $dataClass,
@@ -196,11 +211,13 @@ public function getDefaultOptions()
'pattern' => null,
'property_path' => null,
'by_reference' => true,
- 'error_bubbling' => false,
+ 'error_bubbling' => $errorBubbling,
'error_mapping' => array(),
'label' => null,
'attr' => array(),
+ 'label_attr' => array(),
'virtual' => false,
+ 'single_control' => false,
'invalid_message' => 'This value is not valid.',
'invalid_message_parameters' => array(),
'translation_domain' => 'messages',
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php b/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php
index 71066250624af..49562d9fd87ef 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php
@@ -25,6 +25,7 @@ public function getDefaultOptions()
'required' => false,
// Pass errors to the parent
'error_bubbling' => true,
+ 'single_control' => true,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php
index 9beaed51029bf..293c01d3cd7cb 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php
@@ -37,10 +37,11 @@ public function getDefaultOptions()
{
return array(
// default precision is locale specific (usually around 3)
- 'precision' => null,
- 'grouping' => false,
+ 'precision' => null,
+ 'grouping' => false,
// Integer cast rounds towards 0, so do the same when displaying fractions
- 'rounding_mode' => \NumberFormatter::ROUND_DOWN,
+ 'rounding_mode' => \NumberFormatter::ROUND_DOWN,
+ 'single_control' => true,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php b/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php
index 1f87f3a2a6872..560cbe59b562d 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php
@@ -51,10 +51,11 @@ public function buildView(FormView $view, FormInterface $form)
public function getDefaultOptions()
{
return array(
- 'precision' => 2,
- 'grouping' => false,
- 'divisor' => 1,
- 'currency' => 'EUR',
+ 'precision' => 2,
+ 'grouping' => false,
+ 'divisor' => 1,
+ 'currency' => 'EUR',
+ 'single_control' => true,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php
index 21ea17046e760..993df32809561 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php
@@ -36,9 +36,10 @@ public function getDefaultOptions()
{
return array(
// default precision is locale specific (usually around 3)
- 'precision' => null,
- 'grouping' => false,
- 'rounding_mode' => \NumberFormatter::ROUND_HALFUP,
+ 'precision' => null,
+ 'grouping' => false,
+ 'rounding_mode' => \NumberFormatter::ROUND_HALFUP,
+ 'single_control' => true,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php
index ea6550869a244..0ee0af39e320d 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php
@@ -31,8 +31,9 @@ public function buildForm(FormBuilder $builder, array $options)
public function getDefaultOptions()
{
return array(
- 'precision' => 0,
- 'type' => 'fractional',
+ 'precision' => 0,
+ 'type' => 'fractional',
+ 'single_control' => true,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TextType.php b/src/Symfony/Component/Form/Extension/Core/Type/TextType.php
index 28003119340d1..f454160d3ec61 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/TextType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/TextType.php
@@ -27,6 +27,16 @@ public function buildForm(FormBuilder $builder, array $options)
;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultOptions()
+ {
+ return array(
+ 'single_control' => true,
+ );
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
index 546220d0ab318..42cd7f42e536a 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
@@ -20,6 +20,7 @@
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
use Symfony\Component\Form\FormView;
+use Symfony\Component\Form\Options;
class TimeType extends AbstractType
{
@@ -136,6 +137,10 @@ public function buildView(FormView $view, FormInterface $form)
*/
public function getDefaultOptions()
{
+ $singleControl = function (Options $options) {
+ return $options['widget'] === 'single_text';
+ };
+
return array(
'hours' => range(0, 23),
'minutes' => range(0, 59),
@@ -155,6 +160,7 @@ public function getDefaultOptions()
// representation is not \DateTime, but an array, we need to unset
// this option.
'data_class' => null,
+ 'single_control' => $singleControl,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php
index dd74d87b2eaea..da8a07d1af32c 100644
--- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php
+++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php
@@ -63,8 +63,8 @@ public function onBindClientData(FilterDataEvent $event)
$form = $event->getForm();
$data = $event->getData();
- if ($form->isRoot() && $form->hasChildren() && isset($data[$this->fieldName])) {
- if (!$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) {
+ if ($form->isRoot() && !$form->getAttribute('single_control')) {
+ if (!isset($data[$this->fieldName]) || !$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
}
diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php
index 8145e8e5755d6..c26324b41e2a1 100644
--- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php
+++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php
@@ -64,7 +64,7 @@ public function buildForm(FormBuilder $builder, array $options)
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
- if (!$view->hasParent() && $view->hasChildren() && $form->hasAttribute('csrf_field_name')) {
+ if (!$view->hasParent() && !$form->getAttribute('single_control') && $form->hasAttribute('csrf_field_name')) {
$name = $form->getAttribute('csrf_field_name');
$csrfProvider = $form->getAttribute('csrf_provider');
$intention = $form->getAttribute('csrf_intention');
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index 5669e0ead4956..f75cd4b0a128e 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -225,10 +225,7 @@ public function __construct($name, EventDispatcherInterface $dispatcher,
$this->validators = $validators;
$this->required = (Boolean) $required;
$this->disabled = (Boolean) $disabled;
- // NULL is the default meaning:
- // bubble up if the form has children (complex forms)
- // don't bubble up if the form has no children (primitive fields)
- $this->errorBubbling = null === $errorBubbling ? null : (Boolean) $errorBubbling;
+ $this->errorBubbling = (Boolean) $errorBubbling;
$this->emptyData = $emptyData;
$this->attributes = $attributes;
@@ -665,7 +662,7 @@ public function addError(FormError $error)
*/
public function getErrorBubbling()
{
- return null === $this->errorBubbling ? $this->hasChildren() : $this->errorBubbling;
+ return $this->errorBubbling;
}
/**
diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
index 07813061886ba..6f0675cba5804 100644
--- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
@@ -277,6 +277,20 @@ public function testCollection()
);
}
+ public function testEmptyCollection()
+ {
+ $form = $this->factory->createNamed('collection', 'name', array(), array(
+ 'type' => 'text',
+ ));
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/div
+ [./input[@type="hidden"][@id="name__token"]]
+ [count(./div)=0]
+'
+ );
+ }
+
public function testCollectionRow()
{
$collection = $this->factory->createNamedBuilder(
diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
index c75ba7c7a4ff3..7be8193cfe39d 100644
--- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
@@ -155,7 +155,7 @@ public function testLabelOnForm()
$this->assertMatchesXpath($html,
'/label
- [@class=" required"]
+ [@class="required"]
[.="[trans]Name[/trans]"]
'
);
@@ -204,7 +204,7 @@ public function testLabelWithCustomTextPassedAsOptionAndDirectly()
);
}
- public function testLabelWithCustomOptionsPassedDirectly()
+ public function testLabelDoesNotRenderFieldAttributes()
{
$form = $this->factory->createNamed('text', 'name');
$html = $this->renderLabel($form->createView(), null, array(
@@ -215,6 +215,23 @@ public function testLabelWithCustomOptionsPassedDirectly()
$this->assertMatchesXpath($html,
'/label
+ [@for="name"]
+ [@class="required"]
+'
+ );
+ }
+
+ public function testLabelWithCustomOptionsPassedDirectly()
+ {
+ $form = $this->factory->createNamed('text', 'name');
+ $html = $this->renderLabel($form->createView(), null, array(
+ 'label_attr' => array(
+ 'class' => 'my&class'
+ ),
+ ));
+
+ $this->assertMatchesXpath($html,
+'/label
[@for="name"]
[@class="my&class required"]
'
@@ -225,7 +242,7 @@ public function testLabelWithCustomTextAndCustomOptionsPassedDirectly()
{
$form = $this->factory->createNamed('text', 'name');
$html = $this->renderLabel($form->createView(), 'Custom label', array(
- 'attr' => array(
+ 'label_attr' => array(
'class' => 'my&class'
),
));
diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
index f6edbce25a440..9bf77d9c59ee4 100644
--- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
@@ -170,6 +170,20 @@ public function testCollection()
);
}
+ public function testEmptyCollection()
+ {
+ $form = $this->factory->createNamed('collection', 'name', array(), array(
+ 'type' => 'text',
+ ));
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/table
+ [./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="name__token"]]]
+ [count(./tr[./td/input])=1]
+'
+ );
+ }
+
public function testForm()
{
$view = $this->factory->createNamedBuilder('form', 'name')
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php
index 8275c61455999..6df8130e383fd 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php
@@ -568,4 +568,32 @@ public function testCreateViewDoNoMarkItAsRendered()
$this->assertFalse($view->isRendered());
}
+
+ public function testErrorBubblingIfNoSingleControl()
+ {
+ $form = $this->factory->create('form', null, array(
+ 'single_control' => false,
+ ));
+
+ $this->assertTrue($form->getErrorBubbling());
+ }
+
+ public function testNoErrorBubblingIfSingleControl()
+ {
+ $form = $this->factory->create('form', null, array(
+ 'single_control' => true,
+ ));
+
+ $this->assertFalse($form->getErrorBubbling());
+ }
+
+ public function testOverrideErrorBubbling()
+ {
+ $form = $this->factory->create('form', null, array(
+ 'single_control' => true,
+ 'error_bubbling' => true,
+ ));
+
+ $this->assertTrue($form->getErrorBubbling());
+ }
}
diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
index 4424f81e14140..0fdea7d438e5b 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
@@ -56,43 +56,42 @@ protected function getExtensions()
));
}
- public function testCsrfProtectionByDefaultIfRootAndChildren()
+ public function testCsrfProtectionByDefaultIfRootAndNotSingleControl()
{
$view = $this->factory
- ->createBuilder('form', null, array(
+ ->create('form', null, array(
'csrf_field_name' => 'csrf',
+ 'single_control' => false,
))
- ->add($this->factory->createNamedBuilder('form', 'child'))
- ->getForm()
->createView();
$this->assertTrue($view->hasChild('csrf'));
}
- public function testNoCsrfProtectionByDefaultIfChildrenButNotRoot()
+ public function testNoCsrfProtectionByDefaultIfNotSingleControlButNotRoot()
{
$view = $this->factory
->createNamedBuilder('form', 'root')
->add($this->factory
->createNamedBuilder('form', 'form', null, array(
'csrf_field_name' => 'csrf',
+ 'single_control' => false,
))
- ->add($this->factory->createNamedBuilder('form', 'child'))
)
->getForm()
- ->get('form')
- ->createView();
+ ->createView()
+ ->getChild('form');
$this->assertFalse($view->hasChild('csrf'));
}
- public function testNoCsrfProtectionByDefaultIfRootButNoChildren()
+ public function testNoCsrfProtectionByDefaultIfRootButSingleControl()
{
$view = $this->factory
- ->createBuilder('form', null, array(
+ ->create('form', null, array(
'csrf_field_name' => 'csrf',
+ 'single_control' => true,
))
- ->getForm()
->createView();
$this->assertFalse($view->hasChild('csrf'));
@@ -101,12 +100,11 @@ public function testNoCsrfProtectionByDefaultIfRootButNoChildren()
public function testCsrfProtectionCanBeDisabled()
{
$view = $this->factory
- ->createBuilder('form', null, array(
+ ->create('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_protection' => false,
+ 'single_control' => false,
))
- ->add($this->factory->createNamedBuilder('form', 'child'))
- ->getForm()
->createView();
$this->assertFalse($view->hasChild('csrf'));
@@ -120,13 +118,12 @@ public function testGenerateCsrfToken()
->will($this->returnValue('token'));
$view = $this->factory
- ->createBuilder('form', null, array(
+ ->create('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
- 'intention' => '%INTENTION%'
+ 'intention' => '%INTENTION%',
+ 'single_control' => false,
))
- ->add($this->factory->createNamedBuilder('form', 'child'))
- ->getForm()
->createView();
$this->assertEquals('token', $view->getChild('csrf')->get('value'));
@@ -143,7 +140,7 @@ public function provideBoolean()
/**
* @dataProvider provideBoolean
*/
- public function testValidateTokenOnBindIfRootAndChildren($valid)
+ public function testValidateTokenOnBindIfRootAndNotSingleControl($valid)
{
$this->csrfProvider->expects($this->once())
->method('isCsrfTokenValid')
@@ -151,13 +148,12 @@ public function testValidateTokenOnBindIfRootAndChildren($valid)
->will($this->returnValue($valid));
$form = $this->factory
- ->createBuilder('form', null, array(
+ ->create('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
- 'intention' => '%INTENTION%'
- ))
- ->add($this->factory->createNamedBuilder('form', 'child'))
- ->getForm();
+ 'intention' => '%INTENTION%',
+ 'single_control' => false,
+ ));
$form->bind(array(
'child' => 'foobar',
@@ -171,7 +167,32 @@ public function testValidateTokenOnBindIfRootAndChildren($valid)
$this->assertSame($valid, $form->isValid());
}
- public function testDontValidateTokenIfChildrenButNoRoot()
+ public function testFailIfRootAndNotSingleControlAndTokenMissing()
+ {
+ $this->csrfProvider->expects($this->never())
+ ->method('isCsrfTokenValid');
+
+ $form = $this->factory
+ ->create('form', null, array(
+ 'csrf_field_name' => 'csrf',
+ 'csrf_provider' => $this->csrfProvider,
+ 'intention' => '%INTENTION%',
+ 'single_control' => false,
+ ));
+
+ $form->bind(array(
+ 'child' => 'foobar',
+ // token is missing
+ ));
+
+ // Remove token from data
+ $this->assertSame(array('child' => 'foobar'), $form->getData());
+
+ // Validate accordingly
+ $this->assertFalse($form->isValid());
+ }
+
+ public function testDontValidateTokenIfNotSingleControlButNoRoot()
{
$this->csrfProvider->expects($this->never())
->method('isCsrfTokenValid');
@@ -182,9 +203,9 @@ public function testDontValidateTokenIfChildrenButNoRoot()
->createNamedBuilder('form', 'form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
- 'intention' => '%INTENTION%'
+ 'intention' => '%INTENTION%',
+ 'single_control' => false,
))
- ->add($this->factory->createNamedBuilder('form', 'child'))
)
->getForm()
->get('form');
@@ -195,18 +216,18 @@ public function testDontValidateTokenIfChildrenButNoRoot()
));
}
- public function testDontValidateTokenIfRootButNoChildren()
+ public function testDontValidateTokenIfRootButSingleControl()
{
$this->csrfProvider->expects($this->never())
->method('isCsrfTokenValid');
$form = $this->factory
- ->createBuilder('form', null, array(
+ ->create('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
- 'intention' => '%INTENTION%'
- ))
- ->getForm();
+ 'intention' => '%INTENTION%',
+ 'single_control' => true,
+ ));
$form->bind(array(
'csrf' => 'token',
diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php
index 206fa5eccf396..4b8eb3f3a9329 100644
--- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php
+++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php
@@ -608,41 +608,8 @@ public function testCreateNamedBuilderFromParentBuilder()
$this->assertEquals($parentBuilder, $builder->getParent());
}
- public function testUnknownOptions()
- {
- $type = new \Symfony\Component\Form\Extension\Core\Type\TextType();
-
- $factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension()));
-
- $this->setExpectedException('Symfony\Component\Form\Exception\InvalidOptionException',
- 'The options "invalid", "unknown" do not exist. Known options are: ' .
- '"attr", "by_reference", "data", "data_class", "disabled", ' .
- '"empty_data", "error_bubbling", "error_mapping", "invalid_message", ' .
- '"invalid_message_parameters", "label", "max_length", "pattern", ' .
- '"property_path", "read_only", "required", "translation_domain", ' .
- '"trim"'
- );
- $factory->createNamedBuilder($type, "text", "value", array("invalid" => "opt", "unknown" => "opt"));
- }
-
- public function testUnknownOption()
- {
- $type = new \Symfony\Component\Form\Extension\Core\Type\TextType();
-
- $factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension()));
-
- $this->setExpectedException('Symfony\Component\Form\Exception\InvalidOptionException',
- 'The option "unknown" does not exist. Known options are: "attr", ' .
- '"by_reference", "data", "data_class", "disabled", "empty_data", ' .
- '"error_bubbling", "error_mapping", "invalid_message", ' .
- '"invalid_message_parameters", "label", "max_length", "pattern", ' .
- '"property_path", "read_only", "required", "translation_domain", ' .
- '"trim"'
- );
- $factory->createNamedBuilder($type, "text", "value", array("unknown" => "opt"));
- }
- public function testFieldTypeCreatesDefaultValueForEmptyDataOption()
+ public function testFormTypeCreatesDefaultValueForEmptyDataOption()
{
$factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension()));
diff --git a/src/Symfony/Component/Form/Tests/FormTest.php b/src/Symfony/Component/Form/Tests/FormTest.php
index 08ef363e4bcf4..b7b7cb0803db7 100644
--- a/src/Symfony/Component/Form/Tests/FormTest.php
+++ b/src/Symfony/Component/Form/Tests/FormTest.php
@@ -133,61 +133,6 @@ public function testDataIsInitializedEmpty()
$this->assertSame('bar', $form->getClientData());
}
- public function testErrorsBubbleUpIfEnabled()
- {
- $error = new FormError('Error!');
- $parent = $this->form;
- $form = $this->getBuilder()->setErrorBubbling(true)->getForm();
-
- $form->setParent($parent);
- $form->addError($error);
-
- $this->assertEquals(array(), $form->getErrors());
- $this->assertEquals(array($error), $parent->getErrors());
- }
-
- public function testErrorsDontBubbleUpIfDisabled()
- {
- $error = new FormError('Error!');
- $parent = $this->form;
- $form = $this->getBuilder()->setErrorBubbling(false)->getForm();
-
- $form->setParent($parent);
- $form->addError($error);
-
- $this->assertEquals(array($error), $form->getErrors());
- $this->assertEquals(array(), $parent->getErrors());
- }
-
- public function testErrorsBubbleUpIfNullAndChildren()
- {
- $error = new FormError('Error!');
- $parent = $this->form;
- $form = $this->getBuilder()
- ->setErrorBubbling(null)
- ->add($this->getBuilder('child'))
- ->getForm();
-
- $form->setParent($parent);
- $form->addError($error);
-
- $this->assertEquals(array(), $form->getErrors());
- $this->assertEquals(array($error), $parent->getErrors());
- }
-
- public function testErrorsDontBubbleUpIfNullAndNoChildren()
- {
- $error = new FormError('Error!');
- $parent = $this->form;
- $form = $this->getBuilder()->setErrorBubbling(null)->getForm();
-
- $form->setParent($parent);
- $form->addError($error);
-
- $this->assertEquals(array($error), $form->getErrors());
- $this->assertEquals(array(), $parent->getErrors());
- }
-
public function testValidIfAllChildrenAreValid()
{
$this->form->add($this->getValidForm('firstName'));