diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
index 74042a7b41a31..d50d4cad2e268 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
@@ -84,6 +84,13 @@ class EntityChoiceList extends ObjectChoiceList
*/
private $preferredEntities = array();
+ /**
+ * Whether or not the choice list shoud be loaded.
+ *
+ * @var Boolean
+ */
+ private $lazy;
+
/**
* Creates a new entity choice list.
*
@@ -97,22 +104,27 @@ class EntityChoiceList extends ObjectChoiceList
* to group the choices. Only allowed if
* the choices are given as flat array.
* @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths.
+ * @param Boolean $lazy Whether or not the choice list shoud be loaded.
*/
- public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null)
+ public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null, $lazy = false)
{
$this->em = $manager;
$this->entityLoader = $entityLoader;
$this->classMetadata = $manager->getClassMetadata($class);
$this->class = $this->classMetadata->getName();
$this->loaded = is_array($entities) || $entities instanceof \Traversable;
+ $this->lazy = $lazy;
$this->preferredEntities = $preferredEntities;
$identifier = $this->classMetadata->getIdentifierFieldNames();
if (1 === count($identifier)) {
- $this->idField = $identifier[0];
- $this->idAsValue = true;
-
+ if (!$lazy) {
+ $this->idField = $identifier[0];
+ $this->idAsValue = true;
+ } else {
+ $this->idField = (string) $labelPath;
+ }
if (in_array($this->classMetadata->getTypeOfField($this->idField), array('integer', 'smallint', 'bigint'))) {
$this->idAsIndex = true;
}
@@ -124,7 +136,7 @@ public function __construct(ObjectManager $manager, $class, $labelPath = null, E
$entities = array();
}
- parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor);
+ parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, $labelPath, $propertyAccessor);
}
/**
@@ -215,6 +227,10 @@ public function getChoicesForValues(array $values)
return $this->entityLoader->getEntitiesByIds($this->idField, $values);
}
+ if ($this->lazy) {
+ return $this->em->getRepository($this->class)->findBy(array($this->idField => $values));
+ }
+
$this->load();
}
@@ -237,13 +253,13 @@ public function getValuesForChoices(array $entities)
// know that the IDs are used as values
// Attention: This optimization does not check choices for existence
- if ($this->idAsValue) {
+ if ($this->idAsValue || $this->lazy) {
$values = array();
foreach ($entities as $entity) {
if ($entity instanceof $this->class) {
// Make sure to convert to the right format
- $values[] = $this->fixValue(current($this->getIdentifierValues($entity)));
+ $values[] = $this->fixValue($this->createValue($entity));
}
}
@@ -379,6 +395,10 @@ protected function fixIndex($index)
*/
private function load()
{
+ if ($this->lazy) {
+ return;
+ }
+
if ($this->entityLoader) {
$entities = $this->entityLoader->getEntities();
} else {
diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
index 81623966c1087..f86005e998ada 100644
--- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
+++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
@@ -12,6 +12,7 @@
namespace Symfony\Bridge\Doctrine\Form\Type;
use Doctrine\Common\Persistence\ManagerRegistry;
+use Symfony\Component\Form\Exception\LogicException;
use Symfony\Component\Form\Exception\RuntimeException;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\FormBuilderInterface;
@@ -50,6 +51,9 @@ public function __construct(ManagerRegistry $registry, PropertyAccessorInterface
public function buildForm(FormBuilderInterface $builder, array $options)
{
+ if ('text' === $options['widget'] && !$options['property']) {
+ throw new LogicException('When using a text widget, the "property" option must be defined.');
+ }
if ($options['multiple']) {
$builder
->addEventSubscriber(new MergeDoctrineCollectionListener())
@@ -116,7 +120,8 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
$loaderHash,
$choiceHashes,
$preferredChoiceHashes,
- $groupByHash
+ $groupByHash,
+ 'text' === $options['widget']
)));
if (!isset($choiceListCache[$hash])) {
@@ -128,7 +133,8 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
$options['choices'],
$options['preferred_choices'],
$options['group_by'],
- $propertyAccessor
+ $propertyAccessor,
+ 'text' === $options['widget']
);
}
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 453c29c65b218..e6be2a21f8e01 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
@@ -46,10 +46,12 @@
{% block choice_widget %}
{% spaceless %}
- {% if expanded %}
+ {% if widget == 'checkbox' or widget == 'radio' %}
{{ block('choice_widget_expanded') }}
+ {% elseif widget == 'select' %}
+ {{ block('choice_widget_select') }}
{% else %}
- {{ block('choice_widget_collapsed') }}
+ {{ block('form_widget_simple') }}
{% endif %}
{% endspaceless %}
{% endblock choice_widget %}
@@ -65,7 +67,7 @@
{% endspaceless %}
{% endblock choice_widget_expanded %}
-{% block choice_widget_collapsed %}
+{% block choice_widget_select %}
{% spaceless %}
{% endspaceless %}
-{% endblock choice_widget_collapsed %}
+{% endblock choice_widget_select %}
{% block choice_widget_options %}
{% spaceless %}
@@ -388,3 +390,7 @@
{% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
{% endspaceless %}
{% endblock button_attributes %}
+
+{# Deprecated in Symfony 2.4, to be removed in 3.0 #}
+
+{% block choice_widget_collapsed %}{{ block('choice_widget_select') }}{% endblock %}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValuesToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValuesToStringTransformer.php
new file mode 100644
index 0000000000000..4c67cbb628200
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValuesToStringTransformer.php
@@ -0,0 +1,80 @@
+
+ *
+ * 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\DataTransformer;
+
+use Symfony\Component\Form\Exception\TransformationFailedException;
+
+use Symfony\Component\Form\DataTransformerInterface;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+
+/**
+ * Converts an array of values to a string with multiple values separated by a delimiter.
+ *
+ * @author Bilal Amarni
+ */
+class ValuesToStringTransformer implements DataTransformerInterface
+{
+ private $delimiter;
+ private $trim;
+
+ public function __construct($delimiter, $trim)
+ {
+ $this->delimiter = $delimiter;
+ $this->trim = $trim;
+ }
+
+ /**
+ * @param array $array
+ *
+ * @return string
+ *
+ * @throws UnexpectedTypeException if the given value is not an array
+ */
+ public function transform($array)
+ {
+ if (null === $array) {
+ return array();
+ }
+
+ if (!is_array($array)) {
+ throw new UnexpectedTypeException($array, 'array');
+ }
+
+ return implode($this->delimiter, $array);
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return array
+ *
+ * @throws UnexpectedTypeException if the given value is not a string
+ */
+ public function reverseTransform($string)
+ {
+ if (empty($string)) {
+ return array();
+ }
+
+ if (!is_string($string)) {
+ throw new UnexpectedTypeException($string, 'string');
+ }
+
+ $values = explode($this->delimiter, $string);
+
+ if ($this->trim) {
+ $values = array_map('trim', $values);
+ }
+
+ return $values;
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 9a3fdef12b81d..2c504fe61cfeb 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -25,6 +25,7 @@
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToBooleanArrayTransformer;
+use Symfony\Component\Form\Extension\Core\DataTransformer\ValuesToStringTransformer;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
@@ -45,7 +46,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
throw new LogicException('Either the option "choices" or "choice_list" must be set.');
}
- if ($options['expanded']) {
+ if (in_array($options['widget'], array('radio', 'checkbox'))) {
// Initialize all choices before doing the index check below.
// This helps in cases where index checks are optimized for non
// initialized choice lists. For example, when using an SQL driver,
@@ -68,7 +69,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$this->addSubForms($builder, $preferredViews, $options);
$this->addSubForms($builder, $remainingViews, $options);
- if ($options['multiple']) {
+ if ('checkbox' === $options['widget']) {
$builder->addViewTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list']));
$builder->addEventSubscriber(new FixCheckboxInputListener($options['choice_list']), 10);
} else {
@@ -78,6 +79,9 @@ public function buildForm(FormBuilderInterface $builder, array $options)
} else {
if ($options['multiple']) {
$builder->addViewTransformer(new ChoicesToValuesTransformer($options['choice_list']));
+ if ('text' === $options['widget']) {
+ $builder->addViewTransformer(new ValuesToStringTransformer($options['delimiter'], $options['trim']));
+ }
} else {
$builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list']));
}
@@ -96,6 +100,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace($view->vars, array(
+ 'widget' => $options['widget'],
'multiple' => $options['multiple'],
'expanded' => $options['expanded'],
'preferred_choices' => $options['choice_list']->getPreferredViews(),
@@ -124,7 +129,7 @@ public function buildView(FormView $view, FormInterface $form, array $options)
$view->vars['empty_value'] = $options['empty_value'];
}
- if ($options['multiple'] && !$options['expanded']) {
+ if ($options['multiple'] && 'select' === $options['widget']) {
// Add "[]" to the name in case a select tag with multiple options is
// displayed. Otherwise only one of the selected options is sent in the
// POST request.
@@ -137,12 +142,12 @@ public function buildView(FormView $view, FormInterface $form, array $options)
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
- if ($options['expanded']) {
+ if (in_array($options['widget'], array('radio', 'checkbox'))) {
// Radio buttons should have the same name as the parent
$childName = $view->vars['full_name'];
// Checkboxes should append "[]" to allow multiple selection
- if ($options['multiple']) {
+ if ('checkbox' === $options['widget']) {
$childName .= '[]';
}
@@ -174,7 +179,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
};
$emptyData = function (Options $options) {
- if ($options['multiple'] || $options['expanded']) {
+ if ($options['multiple'] || 'radio' === $options['widget']) {
return array();
}
@@ -192,7 +197,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
} elseif (false === $emptyValue) {
// an empty value should be added but the user decided otherwise
return null;
- } elseif ($options['expanded'] && '' === $emptyValue) {
+ } elseif ('radio' === $options['widget'] && '' === $emptyValue) {
// never use an empty label for radio buttons
return 'None';
}
@@ -201,13 +206,38 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
return $emptyValue;
};
+ $widgetDefault = function (Options $options) {
+ if ($options['expanded']) {
+ if ($options['multiple']) {
+ return 'checkbox';
+ }
+
+ return 'radio';
+ }
+
+ return 'select';
+ };
+
+ $multipleNormalizer = function (Options $options, $multiple) {
+ if (in_array($options['widget'], array('radio', 'checkbox'))) {
+ return ('checkbox' === $options['widget']);
+ }
+
+ return $multiple;
+ };
+
+ $expandedNormalizer = function (Options $options) {
+ return in_array($options['widget'], array('radio', 'checkbox'));
+ };
+
$compound = function (Options $options) {
- return $options['expanded'];
+ return in_array($options['widget'], array('radio', 'checkbox'));
};
$resolver->setDefaults(array(
+ 'widget' => $widgetDefault,
+ 'delimiter' => ',',
'multiple' => false,
- 'expanded' => false,
'choice_list' => $choiceList,
'choices' => array(),
'preferred_choices' => array(),
@@ -219,15 +249,23 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
// is manually set to an object.
// See https://github.com/symfony/symfony/pull/5582
'data_class' => null,
+ // Deprecated, to be removed in 3.0
+ 'expanded' => false,
));
$resolver->setNormalizers(array(
'empty_value' => $emptyValueNormalizer,
+ 'multiple' => $multipleNormalizer,
+ 'expanded' => $expandedNormalizer, // For BC
));
$resolver->setAllowedTypes(array(
'choice_list' => array('null', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'),
));
+
+ $resolver->setAllowedValues(array(
+ 'widget' => array('select', 'checkbox', 'radio', 'text')
+ ));
}
/**
@@ -258,16 +296,13 @@ private function addSubForms(FormBuilderInterface $builder, array $choiceViews,
'translation_domain' => $options['translation_domain'],
);
- if ($options['multiple']) {
- $choiceType = 'checkbox';
+ if ('checkbox' === $options['widget']) {
// The user can check 0 or more checkboxes. If required
// is true, he is required to check all of them.
$choiceOpts['required'] = false;
- } else {
- $choiceType = 'radio';
}
- $builder->add($i, $choiceType, $choiceOpts);
+ $builder->add($i, $options['widget'], $choiceOpts);
}
}
}