diff --git a/src/Symfony/Component/Form/ChoiceField.php b/src/Symfony/Component/Form/ChoiceField.php index 10363edcff17..600a486e98d1 100644 --- a/src/Symfony/Component/Form/ChoiceField.php +++ b/src/Symfony/Component/Form/ChoiceField.php @@ -40,6 +40,13 @@ class ChoiceField extends HybridField */ protected $preferredChoices = array(); + /** + * Stores the choices + * You should only access this property through getInitializedChoices() + * @var array + */ + protected $initializedChoices = array(); + protected function configure() { $this->addRequiredOption('choices'); @@ -47,8 +54,12 @@ protected function configure() $this->addOption('multiple', false); $this->addOption('expanded', false); - if (!is_array($this->getOption('choices'))) { - throw new InvalidOptionsException('The choices option must be an array', array('choices')); + parent::configure(); + + $choices = $this->getOption('choices'); + + if (!is_array($choices) && !$choices instanceof \Closure) { + throw new InvalidOptionsException('The choices option must be an array or a closure', array('choices')); } if (!is_array($this->getOption('preferred_choices'))) { @@ -62,7 +73,7 @@ protected function configure() if ($this->isExpanded()) { $this->setFieldMode(self::GROUP); - $choices = $this->getOption('choices'); + $choices = $this->getInitializedChoices(); foreach ($this->preferredChoices as $choice => $_) { $this->add($this->newChoiceField($choice, $choices[$choice])); @@ -76,8 +87,6 @@ protected function configure() } else { $this->setFieldMode(self::FIELD); } - - parent::configure(); } public function getName() @@ -95,19 +104,52 @@ public function getName() return $name; } + /** + * Initializes the choices + * + * If the choices were given as a closure, the closure is executed now. + * + * @return array + */ + protected function initializeChoices() + { + $this->initializedChoices = $this->getOption('choices'); + + if ($this->initializedChoices instanceof \Closure) { + $this->initializedChoices = $this->initializedChoices->__invoke(); + } + } + + /** + * Returns the choices + * + * If the choices were given as a closure, the closure is executed on + * the first call of this method. + * + * @return array + */ + protected function getInitializedChoices() + { + if (!$this->initializedChoices) { + $this->initializeChoices(); + } + + return $this->initializedChoices; + } + public function getPreferredChoices() { - return array_intersect_key($this->getOption('choices'), $this->preferredChoices); + return array_intersect_key($this->getInitializedChoices(), $this->preferredChoices); } public function getOtherChoices() { - return array_diff_key($this->getOption('choices'), $this->preferredChoices); + return array_diff_key($this->getInitializedChoices(), $this->preferredChoices); } public function getLabel($choice) { - $choices = $this->getOption('choices'); + $choices = $this->getInitializedChoices(); return isset($choices[$choice]) ? $choices[$choice] : null; } @@ -183,7 +225,7 @@ protected function transform($value) { if ($this->isExpanded()) { $value = parent::transform($value); - $choices = $this->getOption('choices'); + $choices = $this->getInitializedChoices(); foreach ($choices as $choice => $_) { $choices[$choice] = $this->isMultipleChoice() diff --git a/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php b/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php index 99ad01115f3d..43a277f5c0c9 100644 --- a/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php +++ b/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php @@ -67,12 +67,35 @@ public function testConfigurePreferredChoicesWithNonArray() )); } - public function testBindSingleNonExpanded() + public function getChoicesVariants() + { + $choices = $this->choices; + + return array( + array($choices), + array(function () use ($choices) { return $choices; }), + ); + } + + public function getNumericChoicesVariants() + { + $choices = $this->numericChoices; + + return array( + array($choices), + array(function () use ($choices) { return $choices; }), + ); + } + + /** + * @dataProvider getChoicesVariants + */ + public function testBindSingleNonExpanded($choices) { $field = new ChoiceField('name', array( 'multiple' => false, 'expanded' => false, - 'choices' => $this->choices, + 'choices' => $choices, )); $field->bind('b'); @@ -81,12 +104,15 @@ public function testBindSingleNonExpanded() $this->assertEquals('b', $field->getDisplayedData()); } - public function testBindMultipleNonExpanded() + /** + * @dataProvider getChoicesVariants + */ + public function testBindMultipleNonExpanded($choices) { $field = new ChoiceField('name', array( 'multiple' => true, 'expanded' => false, - 'choices' => $this->choices, + 'choices' => $choices, )); $field->bind(array('a', 'b')); @@ -95,12 +121,15 @@ public function testBindMultipleNonExpanded() $this->assertEquals(array('a', 'b'), $field->getDisplayedData()); } - public function testBindSingleExpanded() + /** + * @dataProvider getChoicesVariants + */ + public function testBindSingleExpanded($choices) { $field = new ChoiceField('name', array( 'multiple' => false, 'expanded' => true, - 'choices' => $this->choices, + 'choices' => $choices, )); $field->bind('b'); @@ -119,12 +148,15 @@ public function testBindSingleExpanded() $this->assertSame(array('a' => '', 'b' => '1', 'c' => '', 'd' => '', 'e' => ''), $field->getDisplayedData()); } - public function testBindSingleExpandedNumericChoices() + /** + * @dataProvider getNumericChoicesVariants + */ + public function testBindSingleExpandedNumericChoices($choices) { $field = new ChoiceField('name', array( 'multiple' => false, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $choices, )); $field->bind('1'); @@ -143,12 +175,15 @@ public function testBindSingleExpandedNumericChoices() $this->assertSame(array(0 => '', 1 => '1', 2 => '', 3 => '', 4 => ''), $field->getDisplayedData()); } - public function testBindMultipleExpanded() + /** + * @dataProvider getChoicesVariants + */ + public function testBindMultipleExpanded($choices) { $field = new ChoiceField('name', array( 'multiple' => true, 'expanded' => true, - 'choices' => $this->choices, + 'choices' => $choices, )); $field->bind(array('a' => 'a', 'b' => 'b')); @@ -167,12 +202,15 @@ public function testBindMultipleExpanded() $this->assertSame(array('a' => '1', 'b' => '1', 'c' => '', 'd' => '', 'e' => ''), $field->getDisplayedData()); } - public function testBindMultipleExpandedNumericChoices() + /** + * @dataProvider getNumericChoicesVariants + */ + public function testBindMultipleExpandedNumericChoices($choices) { $field = new ChoiceField('name', array( 'multiple' => true, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $choices, )); $field->bind(array(1 => 1, 2 => 2));