Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fa0a5e3

Browse filesBrowse files
committed
Added choice_filter option to ChoiceType
1 parent 3d2124e commit fa0a5e3
Copy full SHA for fa0a5e3

File tree

11 files changed

+221
-18
lines changed
Filter options

11 files changed

+221
-18
lines changed

‎src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php
+19-1Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,26 @@
1414
use Doctrine\Common\Persistence\ObjectManager;
1515
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
1616
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
17+
use Symfony\Component\Form\ChoiceList\Loader\ChoiceFilterInterface;
1718
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
1819

1920
/**
2021
* Loads choices using a Doctrine object manager.
2122
*
2223
* @author Bernhard Schussek <bschussek@gmail.com>
2324
*/
24-
class DoctrineChoiceLoader implements ChoiceLoaderInterface
25+
class DoctrineChoiceLoader implements ChoiceLoaderInterface, ChoiceFilterInterface
2526
{
2627
private $manager;
2728
private $class;
2829
private $idReader;
2930
private $objectLoader;
3031

32+
/**
33+
* @var callable
34+
*/
35+
private $choiceFilter;
36+
3137
/**
3238
* @var ChoiceListInterface
3339
*/
@@ -68,6 +74,10 @@ public function loadChoiceList($value = null)
6874
? $this->objectLoader->getEntities()
6975
: $this->manager->getRepository($this->class)->findAll();
7076

77+
if (null !== $this->choiceFilter) {
78+
$objects = array_filter($objects, $this->choiceFilter, ARRAY_FILTER_USE_BOTH);
79+
}
80+
7181
return $this->choiceList = new ArrayChoiceList($objects, $value);
7282
}
7383

@@ -146,4 +156,12 @@ public function loadChoicesForValues(array $values, $value = null)
146156

147157
return $this->loadChoiceList($value)->getChoicesForValues($values);
148158
}
159+
160+
/**
161+
* {@inheritdoc}
162+
*/
163+
public function setChoiceFilter(callable $choiceFilter)
164+
{
165+
$this->choiceFilter = $choiceFilter;
166+
}
149167
}

‎src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,4 +1482,22 @@ public function testSetDataNonEmptyArraySubmitNullMultiple()
14821482
$this->assertEquals(array(), $form->getNormData());
14831483
$this->assertSame(array(), $form->getViewData(), 'View data is always an array');
14841484
}
1485+
1486+
public function testChoiceFilterOption()
1487+
{
1488+
$entity1 = new SingleIntIdEntity(1, 'Foo');
1489+
$entity2 = new SingleIntIdEntity(2, 'Bar');
1490+
1491+
$this->persist(array($entity1, $entity2));
1492+
1493+
$field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array(
1494+
'em' => 'default',
1495+
'class' => self::SINGLE_IDENT_CLASS,
1496+
'choice_filter' => function (SingleIntIdEntity $entity) {
1497+
return 'Bar' === $entity->name;
1498+
},
1499+
));
1500+
1501+
$this->assertEquals(array(2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
1502+
}
14851503
}

‎src/Symfony/Bridge/Doctrine/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/composer.json
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"require-dev": {
2828
"symfony/stopwatch": "~3.4|~4.0",
2929
"symfony/dependency-injection": "~3.4|~4.0",
30-
"symfony/form": "~3.4|~4.0",
30+
"symfony/form": "^4.2",
3131
"symfony/http-kernel": "~3.4|~4.0",
3232
"symfony/property-access": "~3.4|~4.0",
3333
"symfony/property-info": "~3.4|~4.0",
@@ -45,7 +45,8 @@
4545
},
4646
"conflict": {
4747
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
48-
"symfony/dependency-injection": "<3.4"
48+
"symfony/dependency-injection": "<3.4",
49+
"symfony/form": "<4.2"
4950
},
5051
"suggest": {
5152
"symfony/form": "",

‎src/Symfony/Component/Form/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/CHANGELOG.md
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ CHANGELOG
88
* added `Symfony\Component\Form\ClearableErrorsInterface`
99
* deprecated calling `FormRenderer::searchAndRenderBlock` for fields which were already rendered
1010
* deprecated the `scale` option of the `IntegerType`
11+
* added `Symfony\Component\Form\ChoiceList\Loader\ChoiceFilterInterface`
12+
* added `choice_filter` option to `ChoiceType`
1113

1214
4.1.0
1315
-----

‎src/Symfony/Component/Form/ChoiceList/Loader/CallbackChoiceLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/ChoiceList/Loader/CallbackChoiceLoader.php
+21-2Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818
*
1919
* @author Jules Pietri <jules@heahprod.com>
2020
*/
21-
class CallbackChoiceLoader implements ChoiceLoaderInterface
21+
class CallbackChoiceLoader implements ChoiceLoaderInterface, ChoiceFilterInterface
2222
{
2323
private $callback;
2424

25+
/**
26+
* @var callable
27+
*/
28+
private $choiceFilter;
29+
2530
/**
2631
* The loaded choice list.
2732
*
@@ -46,7 +51,13 @@ public function loadChoiceList($value = null)
4651
return $this->choiceList;
4752
}
4853

49-
return $this->choiceList = new ArrayChoiceList(\call_user_func($this->callback), $value);
54+
$choices = \call_user_func($this->callback);
55+
56+
if (null !== $this->choiceFilter) {
57+
$choices = array_filter($choices, $this->choiceFilter, ARRAY_FILTER_USE_BOTH);
58+
}
59+
60+
return $this->choiceList = new ArrayChoiceList($choices, $value);
5061
}
5162

5263
/**
@@ -74,4 +85,12 @@ public function loadValuesForChoices(array $choices, $value = null)
7485

7586
return $this->loadChoiceList($value)->getValuesForChoices($choices);
7687
}
88+
89+
/**
90+
* {@inheritdoc}
91+
*/
92+
public function setChoiceFilter(callable $choiceFilter)
93+
{
94+
$this->choiceFilter = $choiceFilter;
95+
}
7796
}
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\ChoiceList\Loader;
13+
14+
/**
15+
* @author Yonel Ceruto <yonelceruto@gmail.com>
16+
*/
17+
interface ChoiceFilterInterface
18+
{
19+
/**
20+
* @param callable $choiceFilter The callable returning a filtered array of choices
21+
*/
22+
public function setChoiceFilter(callable $choiceFilter);
23+
}

‎src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
1818
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
1919
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
20+
use Symfony\Component\Form\ChoiceList\Loader\ChoiceFilterInterface;
2021
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
2122
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
2223
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
24+
use Symfony\Component\Form\Exception\RuntimeException;
2325
use Symfony\Component\Form\Exception\TransformationFailedException;
2426
use Symfony\Component\Form\Extension\Core\DataMapper\CheckboxListMapper;
2527
use Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper;
@@ -265,6 +267,16 @@ public function configureOptions(OptionsResolver $resolver)
265267
return $options['required'] ? null : '';
266268
};
267269

270+
$choiceFilterNormalizer = function (Options $options, $choiceFilter) {
271+
if (null !== $choiceFilter && !\is_callable($choiceFilter)) {
272+
return function ($choice) use ($choiceFilter) {
273+
return \in_array($choice, $choiceFilter, true);
274+
};
275+
}
276+
277+
return $choiceFilter;
278+
};
279+
268280
$placeholderNormalizer = function (Options $options, $placeholder) {
269281
if ($options['multiple']) {
270282
// never use an empty value for this case
@@ -301,6 +313,7 @@ public function configureOptions(OptionsResolver $resolver)
301313
'expanded' => false,
302314
'choices' => array(),
303315
'choice_loader' => null,
316+
'choice_filter' => null,
304317
'choice_label' => null,
305318
'choice_name' => null,
306319
'choice_value' => null,
@@ -319,12 +332,14 @@ public function configureOptions(OptionsResolver $resolver)
319332
'trim' => false,
320333
));
321334

335+
$resolver->setNormalizer('choice_filter', $choiceFilterNormalizer);
322336
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
323337
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
324338

325339
$resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable'));
326340
$resolver->setAllowedTypes('choice_translation_domain', array('null', 'bool', 'string'));
327341
$resolver->setAllowedTypes('choice_loader', array('null', 'Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface'));
342+
$resolver->setAllowedTypes('choice_filter', array('null', 'array', 'callable'));
328343
$resolver->setAllowedTypes('choice_label', array('null', 'bool', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
329344
$resolver->setAllowedTypes('choice_name', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
330345
$resolver->setAllowedTypes('choice_value', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
@@ -390,6 +405,14 @@ private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceV
390405
private function createChoiceList(array $options)
391406
{
392407
if (null !== $options['choice_loader']) {
408+
if (null !== $options['choice_filter']) {
409+
if (!$options['choice_loader'] instanceof ChoiceFilterInterface) {
410+
throw new RuntimeException(sprintf('The choice loader "%s" must implement "%s" to use the "choice_filter" option.', \get_class($options['choice_loader']), ChoiceFilterInterface::class));
411+
}
412+
413+
$options['choice_loader']->setChoiceFilter($options['choice_filter']);
414+
}
415+
393416
return $this->choiceListFactory->createListFromLoader(
394417
$options['choice_loader'],
395418
$options['choice_value']
@@ -399,6 +422,10 @@ private function createChoiceList(array $options)
399422
// Harden against NULL values (like in EntityType and ModelType)
400423
$choices = null !== $options['choices'] ? $options['choices'] : array();
401424

425+
if (null !== $options['choice_filter'] && 0 !== \count($choices)) {
426+
$choices = array_filter($choices, $options['choice_filter'], ARRAY_FILTER_USE_BOTH);
427+
}
428+
402429
return $this->choiceListFactory->createListFromChoices($choices, $options['choice_value']);
403430
}
404431

‎src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php
+92Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
1313

14+
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
15+
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
1416
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
1517
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
1618

@@ -2052,4 +2054,94 @@ public function provideTrimCases()
20522054
'Multiple expanded' => array(true, true),
20532055
);
20542056
}
2057+
2058+
/**
2059+
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
2060+
*/
2061+
public function testChoiceFilterOptionExpectsCallable()
2062+
{
2063+
$this->factory->create(static::TESTED_TYPE, null, array(
2064+
'choice_filter' => new \stdClass(),
2065+
));
2066+
}
2067+
2068+
/**
2069+
* @expectedException \Symfony\Component\Form\Exception\RuntimeException
2070+
*/
2071+
public function testChoiceFilterOptionExpectsChoiceFilterInterface()
2072+
{
2073+
$this->factory->create(static::TESTED_TYPE, null, array(
2074+
'choice_loader' => new class() implements ChoiceLoaderInterface {
2075+
public function loadChoiceList($value = null)
2076+
{
2077+
}
2078+
2079+
public function loadChoicesForValues(array $values, $value = null)
2080+
{
2081+
}
2082+
2083+
public function loadValuesForChoices(array $choices, $value = null)
2084+
{
2085+
}
2086+
},
2087+
'choice_filter' => function ($choice) {},
2088+
));
2089+
}
2090+
2091+
public function testClosureChoiceFilterOptionWithChoiceLoaderOption()
2092+
{
2093+
$form = $this->factory->create(static::TESTED_TYPE, null, array(
2094+
// defined by superclass
2095+
'choice_loader' => new CallbackChoiceLoader(function () {
2096+
return $this->choices;
2097+
}),
2098+
// defined by subclass or userland
2099+
'choice_filter' => function ($choice) {
2100+
return \in_array($choice, array('b', 'c'), true);
2101+
},
2102+
));
2103+
2104+
$options = array();
2105+
foreach ($form->createView()->vars['choices'] as $choiceView) {
2106+
$options[$choiceView->label] = $choiceView->value;
2107+
}
2108+
2109+
$this->assertSame(array('Fabien' => 'b', 'Kris' => 'c'), $options);
2110+
}
2111+
2112+
public function testStaticChoiceFilterOptionWithChoiceLoaderOption()
2113+
{
2114+
$form = $this->factory->create(static::TESTED_TYPE, null, array(
2115+
// defined by superclass
2116+
'choice_loader' => new CallbackChoiceLoader(function () {
2117+
return $this->choices;
2118+
}),
2119+
// defined by subclass or userland
2120+
'choice_filter' => array('b', 'c'),
2121+
));
2122+
2123+
$options = array();
2124+
foreach ($form->createView()->vars['choices'] as $choiceView) {
2125+
$options[$choiceView->label] = $choiceView->value;
2126+
}
2127+
2128+
$this->assertSame(array('Fabien' => 'b', 'Kris' => 'c'), $options);
2129+
}
2130+
2131+
public function testChoiceFilterOptionWithChoicesOption()
2132+
{
2133+
$form = $this->factory->create(static::TESTED_TYPE, null, array(
2134+
// defined by superclass
2135+
'choices' => $this->choices,
2136+
// defined by subclass or userland
2137+
'choice_filter' => array('b', 'c'),
2138+
));
2139+
2140+
$options = array();
2141+
foreach ($form->createView()->vars['choices'] as $choiceView) {
2142+
$options[$choiceView->label] = $choiceView->value;
2143+
}
2144+
2145+
$this->assertSame(array('Fabien' => 'b', 'Kris' => 'c'), $options);
2146+
}
20552147
}

‎src/Symfony/Component/Form/Tests/Fixtures/Author.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Fixtures/Author.php
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ private function getPrivateGetter()
4242
return 'foobar';
4343
}
4444

45-
public function setAustralian($australian)
45+
public function setAustralian($australian): self
4646
{
4747
$this->australian = $australian;
48+
49+
return $this;
4850
}
4951

5052
public function isAustralian()

‎src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"options": {
55
"own": [
66
"choice_attr",
7+
"choice_filter",
78
"choice_label",
89
"choice_loader",
910
"choice_name",

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.