diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md
index 0af744d7c3ada..b949f534f493e 100644
--- a/UPGRADE-2.1.md
+++ b/UPGRADE-2.1.md
@@ -576,6 +576,30 @@
{% endblock %}
````
+ * Custom styling of individual rows of a collection form has been removed for
+ performance reasons. Instead, all rows now have the same block name, where
+ the word "entry" replaces the previous occurence of the row index.
+
+ Before:
+
+ ```
+ {% block _author_tags_0_label %}
+ {# ... #}
+ {% endblock %}
+
+ {% block _author_tags_1_label %}
+ {# ... #}
+ {% endblock %}
+ ```
+
+ After:
+
+ ```
+ {% block _author_tags_entry_label %}
+ {# ... #}
+ {% endblock %}
+ ```
+
#### Other BC Breaks
* The order of the first two arguments of the methods `createNamed` and
diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
index 310198864be09..f77965f689f24 100644
--- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
@@ -245,7 +245,7 @@ protected function render(FormView $view, $section, array $variables = array())
$this->varStack[$rendering]['variables'] = array_replace_recursive($this->varStack[$rendering]['variables'], $variables);
} else {
$types = $view->getVar('types');
- $types[] = $custom;
+ $types[] = $view->getVar('full_block_name');
$typeIndex = count($types) - 1;
$this->varStack[$rendering] = array(
'variables' => array_replace_recursive($view->getVars(), $variables),
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/custom_widgets.html.twig b/src/Symfony/Bridge/Twig/Tests/Extension/custom_widgets.html.twig
index 6b9c0bbbb4fd8..12fd7c66d8245 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/custom_widgets.html.twig
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/custom_widgets.html.twig
@@ -1,5 +1,16 @@
{% block _text_id_widget %}
-
- {{ form_widget(form) }}
-
+{% spaceless %}
+
+ {{ form_widget(form) }}
+
+{% endspaceless %}
{% endblock _text_id_widget %}
+
+{% block _name_entry_label %}
+{% spaceless %}
+ {% if label is empty %}
+ {% set label = name|humanize %}
+ {% endif %}
+
+{% endspaceless %}
+{% endblock _name_entry_label %}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php
index fd8c3deeab23f..a2b5afc1d5a40 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php
@@ -246,7 +246,7 @@ protected function renderSection(FormView $view, $section, array $variables = ar
$variables = array_replace_recursive($this->varStack[$rendering]['variables'], $variables);
} else {
$types = $view->getVar('types');
- $types[] = $custom;
+ $types[] = $view->getVar('full_block_name');
$typeIndex = count($types) - 1;
$variables = array_replace_recursive($view->getVars(), $variables);
$this->varStack[$rendering]['types'] = $types;
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php
new file mode 100644
index 0000000000000..0efecf0c721ef
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php
@@ -0,0 +1,2 @@
+humanize($name); } ?>
+
diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md
index cbc37f07660f5..88a19bc07ecff 100644
--- a/src/Symfony/Component/Form/CHANGELOG.md
+++ b/src/Symfony/Component/Form/CHANGELOG.md
@@ -170,3 +170,5 @@ CHANGELOG
* `getExtensions`
* `setExtensions`
* ChoiceType now caches its created choice lists to improve performance
+ * [BC BREAK] Rows of a collection field cannot be themed individually anymore. All rows in the collection
+ field now have the same block names, which contains "entry" where it previously contained the row index.
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php
index 6b0d1ec1ee845..cc88d3a686d97 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php
@@ -16,6 +16,7 @@
use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener;
+use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CollectionType extends AbstractType
@@ -73,6 +74,12 @@ public function finishView(FormViewInterface $view, FormInterface $form, array $
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
+ $optionsFilter = function (Options $options, $value) {
+ $value['block_name'] = 'entry';
+
+ return $value;
+ };
+
$resolver->setDefaults(array(
'allow_add' => false,
'allow_delete' => false,
@@ -81,6 +88,10 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
'type' => 'text',
'options' => array(),
));
+
+ $resolver->setFilters(array(
+ 'options' => $optionsFilter,
+ ));
}
/**
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
index 0ef0def253525..0f7f9a2332ac9 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
@@ -59,6 +59,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
public function buildView(FormViewInterface $view, FormInterface $form, array $options)
{
$name = $form->getName();
+ $blockName = $options['block_name'] ?: $form->getName();
$readOnly = $options['read_only'];
$translationDomain = $options['translation_domain'];
@@ -67,23 +68,30 @@ public function buildView(FormViewInterface $view, FormInterface $form, array $o
throw new FormException('Form node with empty name can be used only as root form node.');
}
- if ('' !== ($parentFullName = $view->getParent()->getVar('full_name'))) {
- $id = sprintf('%s_%s', $view->getParent()->getVar('id'), $name);
+ $parentView = $view->getParent();
+
+ if ('' !== ($parentFullName = $parentView->getVar('full_name'))) {
+ $id = sprintf('%s_%s', $parentView->getVar('id'), $name);
$fullName = sprintf('%s[%s]', $parentFullName, $name);
+ $fullBlockName = sprintf('%s_%s', $parentView->getVar('full_block_name'), $blockName);
} else {
$id = $name;
$fullName = $name;
+ $fullBlockName = '_' . $blockName;
}
- // Complex fields are read-only if themselves or their parent is.
- $readOnly = $readOnly || $view->getParent()->getVar('read_only');
+ // Complex fields are read-only if they themselves or their parents are.
+ if (!$readOnly) {
+ $readOnly = $parentView->getVar('read_only');
+ }
if (!$translationDomain) {
- $translationDomain = $view->getParent()->getVar('translation_domain');
+ $translationDomain = $parentView->getVar('translation_domain');
}
} else {
$id = $name;
$fullName = $name;
+ $fullBlockName = '_' . $blockName;
// Strip leading underscores and digits. These are allowed in
// form names, but not in HTML4 ID attributes.
@@ -105,6 +113,7 @@ public function buildView(FormViewInterface $view, FormInterface $form, array $o
'id' => $id,
'name' => $name,
'full_name' => $fullName,
+ 'full_block_name' => $fullBlockName,
'read_only' => $readOnly,
'errors' => $form->getErrors(),
'valid' => $form->isBound() ? $form->isValid() : true,
@@ -177,7 +186,14 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
return false !== $options['property_path'];
};
+ // If data is given, the form is locked to that data
+ // (independent of its value)
+ $resolver->setOptional(array(
+ 'data',
+ ));
+
$resolver->setDefaults(array(
+ 'block_name' => null,
'data_class' => $dataClass,
'empty_data' => $emptyData,
'trim' => true,
@@ -198,12 +214,6 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
'translation_domain' => null,
));
- // If data is given, the form is locked to that data
- // (independent of its value)
- $resolver->setOptional(array(
- 'data',
- ));
-
$resolver->setAllowedTypes(array(
'attr' => 'array',
'label_attr' => 'array',
diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
index d4ae48ee4a4d3..01c8d96968900 100644
--- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
@@ -557,6 +557,27 @@ public function testThemeInheritance($parentTheme, $childTheme)
]
/following-sibling::input[@type="hidden"]
]
+'
+ );
+ }
+
+ /**
+ * The block "_name_child_label" should be overridden in the theme of the
+ * implemented driver.
+ */
+ public function testCollectionRowWithCustomBlock()
+ {
+ $collection = array('one', 'two', 'three');
+ $form = $this->factory->createNamedBuilder('name', 'collection', $collection)
+ ->getForm();
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/div
+ [
+ ./div[./label[.="Custom label: [trans]0[/trans]"]]
+ /following-sibling::div[./label[.="Custom label: [trans]1[/trans]"]]
+ /following-sibling::div[./label[.="Custom label: [trans]2[/trans]"]]
+ ]
'
);
}
diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
index 801f6a8ae6438..1436d835d0fbc 100644
--- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
@@ -338,6 +338,27 @@ public function testRepeatedWithCustomOptions()
]
]
[count(.//input)=3]
+'
+ );
+ }
+
+ /**
+ * The block "_name_child_label" should be overridden in the theme of the
+ * implemented driver.
+ */
+ public function testCollectionRowWithCustomBlock()
+ {
+ $collection = array('one', 'two', 'three');
+ $form = $this->factory->createNamedBuilder('name', 'collection', $collection)
+ ->getForm();
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/table
+ [
+ ./tr[./td/label[.="Custom label: [trans]0[/trans]"]]
+ /following-sibling::tr[./td/label[.="Custom label: [trans]1[/trans]"]]
+ /following-sibling::tr[./td/label[.="Custom label: [trans]2[/trans]"]]
+ ]
'
);
}