diff --git a/UPGRADE-3.1.md b/UPGRADE-3.1.md index bea905adc9640..969c5ef7991fd 100644 --- a/UPGRADE-3.1.md +++ b/UPGRADE-3.1.md @@ -19,6 +19,25 @@ Form * Support for data objects that implements both `Traversable` and `ArrayAccess` in `ResizeFormListener::preSubmit` method has been deprecated and will be removed in Symfony 4.0. + + * Using callable strings as choice options in ChoiceType has been deprecated + in favor of `PropertyPath` in Symfony 4.0 use a "\Closure" instead. + + Before: + + ```php + 'choice_value' => new PropertyPath('range'), + 'choice_label' => 'strtoupper', + ``` + + After: + + ```php + 'choice_value' => 'range', + 'choice_label' => function ($choice) { + return strtoupper($choice); + }, + ``` FrameworkBundle --------------- diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index dce77e76eb169..2e5f22379a4bb 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -16,6 +16,26 @@ Form * Support for data objects that implements both `Traversable` and `ArrayAccess` in `ResizeFormListener::preSubmit` method has been removed. + + * Using callable strings as choice options in ChoiceType is not supported + anymore in favor of passing PropertyPath instances. + + Before: + + ```php + 'choice_value' => new PropertyPath('range'), + 'choice_label' => 'strtoupper', + ``` + + After: + + ```php + 'choice_value' => 'range', + 'choice_label' => function ($choice) { + return strtoupper($choice); + }, + ``` + FrameworkBundle --------------- diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index db7cf28fdbaaf..57667a3aca964 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -7,6 +7,9 @@ CHANGELOG * deprecated the "choices_as_values" option of ChoiceType * deprecated support for data objects that implements both `Traversable` and `ArrayAccess` in `ResizeFormListener::preSubmit` method + + * Using callable strings as choice options in `ChoiceType` has been deprecated + and will be used as `PropertyPath` instead of callable in Symfony 4.0. 3.0.0 ----- diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index 130cd49f78bd2..384fc07c0a4f3 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -86,6 +86,8 @@ public function createListFromChoices($choices, $value = null) { if (is_string($value) && !is_callable($value)) { $value = new PropertyPath($value); + } elseif (is_string($value) && is_callable($value)) { + @trigger_error('Passing callable strings is deprecated since version 3.1 and PropertyAccessDecorator will treat them as property paths in 4.0. You should use a "\Closure" instead.', E_USER_DEPRECATED); } if ($value instanceof PropertyPath) { @@ -117,6 +119,8 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, $value = nul { if (is_string($value) && !is_callable($value)) { $value = new PropertyPath($value); + } elseif (is_string($value) && is_callable($value)) { + @trigger_error('Passing callable strings is deprecated since version 3.1 and PropertyAccessDecorator will treat them as property paths in 4.0. You should use a "\Closure" instead.', E_USER_DEPRECATED); } if ($value instanceof PropertyPath) { @@ -153,6 +157,8 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, if (is_string($label) && !is_callable($label)) { $label = new PropertyPath($label); + } elseif (is_string($label) && is_callable($label)) { + @trigger_error('Passing callable strings is deprecated since version 3.1 and PropertyAccessDecorator will treat them as property paths in 4.0. You should use a "\Closure" instead.', E_USER_DEPRECATED); } if ($label instanceof PropertyPath) { @@ -163,6 +169,8 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, if (is_string($preferredChoices) && !is_callable($preferredChoices)) { $preferredChoices = new PropertyPath($preferredChoices); + } elseif (is_string($preferredChoices) && is_callable($preferredChoices)) { + @trigger_error('Passing callable strings is deprecated since version 3.1 and PropertyAccessDecorator will treat them as property paths in 4.0. You should use a "\Closure" instead.', E_USER_DEPRECATED); } if ($preferredChoices instanceof PropertyPath) { @@ -178,6 +186,8 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, if (is_string($index) && !is_callable($index)) { $index = new PropertyPath($index); + } elseif (is_string($index) && is_callable($index)) { + @trigger_error('Passing callable strings is deprecated since version 3.1 and PropertyAccessDecorator will treat them as property paths in 4.0. You should use a "\Closure" instead.', E_USER_DEPRECATED); } if ($index instanceof PropertyPath) { @@ -188,6 +198,8 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, if (is_string($groupBy) && !is_callable($groupBy)) { $groupBy = new PropertyPath($groupBy); + } elseif (is_string($groupBy) && is_callable($groupBy)) { + @trigger_error('Passing callable strings is deprecated since version 3.1 and PropertyAccessDecorator will treat them as property paths in 4.0. You should use a "\Closure" instead.', E_USER_DEPRECATED); } if ($groupBy instanceof PropertyPath) { @@ -202,6 +214,8 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, if (is_string($attr) && !is_callable($attr)) { $attr = new PropertyPath($attr); + } elseif (is_string($attr) && is_callable($attr)) { + @trigger_error('Passing callable strings is deprecated since version 3.1 and PropertyAccessDecorator will treat them as property paths in 4.0. You should use a "\Closure" instead.', E_USER_DEPRECATED); } if ($attr instanceof PropertyPath) { diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/PropertyAccessDecoratorTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/PropertyAccessDecoratorTest.php index b9a77c633b730..e7e9c76da2ddb 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/PropertyAccessDecoratorTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/PropertyAccessDecoratorTest.php @@ -63,6 +63,21 @@ public function testCreateFromChoicesPropertyPathInstance() $this->assertSame(array('value'), $this->factory->createListFromChoices($choices, new PropertyPath('property'))); } + /** + * @group legacy + */ + public function testCreateFromChoicesPropertyPathWithCallableString() + { + $choices = array('foo' => 'bar'); + + $this->decoratedFactory->expects($this->once()) + ->method('createListFromChoices') + ->with($choices, 'end') + ->willReturn('RESULT'); + + $this->assertSame('RESULT', $this->factory->createListFromChoices($choices, 'end')); + } + public function testCreateFromLoaderPropertyPath() { $loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface'); @@ -77,6 +92,21 @@ public function testCreateFromLoaderPropertyPath() $this->assertSame('value', $this->factory->createListFromLoader($loader, 'property')); } + /** + * @group legacy + */ + public function testCreateFromLoaderPropertyPathWithCallableString() + { + $loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface'); + + $this->decoratedFactory->expects($this->once()) + ->method('createListFromLoader') + ->with($loader, 'end') + ->willReturn('RESULT'); + + $this->assertSame('RESULT', $this->factory->createListFromLoader($loader, 'end')); + } + // https://github.com/symfony/symfony/issues/5494 public function testCreateFromChoicesAssumeNullIfValuePropertyPathUnreadable() { @@ -138,6 +168,24 @@ public function testCreateViewPreferredChoicesAsPropertyPath() )); } + /** + * @group legacy + */ + public function testCreateViewPreferredChoicesAsPropertyPathWithCallableString() + { + $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); + + $this->decoratedFactory->expects($this->once()) + ->method('createView') + ->with($list, 'end') + ->willReturn('RESULT'); + + $this->assertSame('RESULT',$this->factory->createView( + $list, + 'end' + )); + } + public function testCreateViewPreferredChoicesAsPropertyPathInstance() { $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); @@ -191,6 +239,25 @@ public function testCreateViewLabelsAsPropertyPath() )); } + /** + * @group legacy + */ + public function testCreateViewLabelsAsPropertyPathWithCallableString() + { + $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); + + $this->decoratedFactory->expects($this->once()) + ->method('createView') + ->with($list, null, 'end') + ->willReturn('RESULT'); + + $this->assertSame('RESULT', $this->factory->createView( + $list, + null, // preferred choices + 'end' + )); + } + public function testCreateViewLabelsAsPropertyPathInstance() { $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); @@ -228,6 +295,26 @@ public function testCreateViewIndicesAsPropertyPath() )); } + /** + * @group legacy + */ + public function testCreateViewIndicesAsPropertyPathWithCallableString() + { + $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); + + $this->decoratedFactory->expects($this->once()) + ->method('createView') + ->with($list, null, null, 'end') + ->willReturn('RESULT'); + + $this->assertSame('RESULT', $this->factory->createView( + $list, + null, // preferred choices + null, // label + 'end' + )); + } + public function testCreateViewIndicesAsPropertyPathInstance() { $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); @@ -267,6 +354,27 @@ public function testCreateViewGroupsAsPropertyPath() )); } + /** + * @group legacy + */ + public function testCreateViewGroupsAsPropertyPathWithCallableString() + { + $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); + + $this->decoratedFactory->expects($this->once()) + ->method('createView') + ->with($list, null, null, null, 'end') + ->willReturn('RESULT'); + + $this->assertSame('RESULT', $this->factory->createView( + $list, + null, // preferred choices + null, // label + null, // index + 'end' + )); + } + public function testCreateViewGroupsAsPropertyPathInstance() { $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); @@ -329,6 +437,28 @@ public function testCreateViewAttrAsPropertyPath() )); } + /** + * @group legacy + */ + public function testCreateViewAttrAsPropertyPathWithCallableString() + { + $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface'); + + $this->decoratedFactory->expects($this->once()) + ->method('createView') + ->with($list, null, null, null, null, 'end') + ->willReturn('RESULT'); + + $this->assertSame('RESULT', $this->factory->createView( + $list, + null, // preferred choices + null, // label + null, // inde + null, // groups + 'end' + )); + } + public function testCreateViewAttrAsPropertyPathInstance() { $list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');