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 c296699

Browse filesBrowse files
committed
[Form] Added a "choice_filter" option to ChoiceType
1 parent 269c4a2 commit c296699
Copy full SHA for c296699

18 files changed

+552
-35
lines changed

‎UPGRADE-5.1.md

Copy file name to clipboardExpand all lines: UPGRADE-5.1.md
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Form
2323
is deprecated. The method will be added to the interface in 6.0.
2424
* Implementing the `FormConfigBuilderInterface` without implementing the `setIsEmptyCallback()` method
2525
is deprecated. The method will be added to the interface in 6.0.
26+
* Not defining a third `callable|null $filter` argument to `ChoiceListFactoryInterface::createListFromChoices` and `ChoiceListFactoryInterface::createListFromLoader` methods is deprecated.
27+
The argument will de defined in 6.0.
2628

2729
FrameworkBundle
2830
---------------

‎UPGRADE-6.0.md

Copy file name to clipboardExpand all lines: UPGRADE-6.0.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Form
2121

2222
* Added the `getIsEmptyCallback()` method to the `FormConfigInterface`.
2323
* Added the `setIsEmptyCallback()` method to the `FormConfigBuilderInterface`.
24+
* Added a third `callable|null $filter` argument to `ChoiceListFactoryInterface::createListFromChoices` and `ChoiceListFactoryInterface::createListFromLoader` methods.
2425

2526
FrameworkBundle
2627
---------------

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.1.0
55
-----
66

7+
* Added a `choice_filter` option to `ChoiceType`
78
* Added a `ChoiceList` facade to leverage explicit choice list caching based on options
89
* Added an `AbstractChoiceLoader` to simplify implementations and handle global optimizations
910
* The `view_timezone` option defaults to the `model_timezone` if no `reference_date` is configured.

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/ChoiceList/ChoiceList.php
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceAttr;
1515
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFieldName;
16+
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFilter;
1617
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel;
1718
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader;
1819
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue;
@@ -66,6 +67,16 @@ public static function value($formType, $value, $vary = null): ChoiceValue
6667
return new ChoiceValue($formType, $value, $vary);
6768
}
6869

70+
/**
71+
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
72+
* @param callable $filter Any pseudo callable to filter a choice list
73+
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the callback
74+
*/
75+
public static function filter($formType, $filter, $vary = null): ChoiceFilter
76+
{
77+
return new ChoiceFilter($formType, $filter, $vary);
78+
}
79+
6980
/**
7081
* Decorates a "choice_label" option to make it cacheable.
7182
*
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\Factory\Cache;
13+
14+
use Symfony\Component\Form\FormTypeInterface;
15+
16+
/**
17+
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
18+
* which configures a "choice_filter" option.
19+
*
20+
* @internal
21+
*
22+
* @author Jules Pietri <jules@heahprod.com>
23+
*/
24+
final class ChoiceFilter extends AbstractStaticOption
25+
{
26+
}

‎src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php
+25-9Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,34 @@ public function getDecoratedFactory()
8181
/**
8282
* {@inheritdoc}
8383
*/
84-
public function createListFromChoices(iterable $choices, $value = null)
84+
public function createListFromChoices(iterable $choices, $value = null, $filter = null)
8585
{
8686
if ($choices instanceof \Traversable) {
8787
$choices = iterator_to_array($choices);
8888
}
8989

90-
// Only cache per value when needed. The value is not validated on purpose.
90+
$cache = true;
91+
// Only cache per value and filter when needed. The value is not validated on purpose.
9192
// The decorated factory may decide which values to accept and which not.
9293
if ($value instanceof Cache\ChoiceValue) {
9394
$value = $value->getOption();
9495
} elseif ($value) {
95-
return $this->decoratedFactory->createListFromChoices($choices, $value);
96+
$cache = false;
97+
}
98+
if ($filter instanceof Cache\ChoiceFilter) {
99+
$filter = $filter->getOption();
100+
} elseif ($filter) {
101+
$cache = false;
102+
}
103+
104+
if (!$cache) {
105+
return $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
96106
}
97107

98-
$hash = self::generateHash([$choices, $value], 'fromChoices');
108+
$hash = self::generateHash([$choices, $value, $filter], 'fromChoices');
99109

100110
if (!isset($this->lists[$hash])) {
101-
$this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value);
111+
$this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
102112
}
103113

104114
return $this->lists[$hash];
@@ -107,7 +117,7 @@ public function createListFromChoices(iterable $choices, $value = null)
107117
/**
108118
* {@inheritdoc}
109119
*/
110-
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
120+
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null, $filter = null)
111121
{
112122
$cache = true;
113123

@@ -123,14 +133,20 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, $value = nul
123133
$cache = false;
124134
}
125135

136+
if ($filter instanceof Cache\ChoiceFilter) {
137+
$filter = $filter->getOption();
138+
} elseif ($filter) {
139+
$cache = false;
140+
}
141+
126142
if (!$cache) {
127-
return $this->decoratedFactory->createListFromLoader($loader, $value);
143+
return $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
128144
}
129145

130-
$hash = self::generateHash([$loader, $value], 'fromLoader');
146+
$hash = self::generateHash([$loader, $value, $filter], 'fromLoader');
131147

132148
if (!isset($this->lists[$hash])) {
133-
$this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value);
149+
$this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
134150
}
135151

136152
return $this->lists[$hash];

‎src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php
+8-2Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ interface ChoiceListFactoryInterface
3131
* The callable receives the choice as only argument.
3232
* Null may be passed when the choice list contains the empty value.
3333
*
34+
* @param callable|null $filter The callable filtering the choices
35+
* (will be added in Symfony 6.0)
36+
*
3437
* @return ChoiceListInterface The choice list
3538
*/
36-
public function createListFromChoices(iterable $choices, callable $value = null);
39+
public function createListFromChoices(iterable $choices, callable $value = null/*, $filter = null*/);
3740

3841
/**
3942
* Creates a choice list that is loaded with the given loader.
@@ -42,9 +45,12 @@ public function createListFromChoices(iterable $choices, callable $value = null)
4245
* The callable receives the choice as only argument.
4346
* Null may be passed when the choice list contains the empty value.
4447
*
48+
* @param callable|null $filter The callable filtering the choices
49+
* (will be added in Symfony 6.0)
50+
*
4551
* @return ChoiceListInterface The choice list
4652
*/
47-
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null);
53+
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null/*, $filter = null*/);
4854

4955
/**
5056
* Creates a view for the given choice list.

‎src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php
+31-2Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
1616
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
1717
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
18+
use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator;
1819
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
1920
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
2021
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
@@ -29,16 +30,44 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
2930
/**
3031
* {@inheritdoc}
3132
*/
32-
public function createListFromChoices(iterable $choices, callable $value = null)
33+
public function createListFromChoices(iterable $choices, callable $value = null, callable $filter = null)
3334
{
35+
if ($filter) {
36+
if ($choices instanceof \Traversable) {
37+
$choices = iterator_to_array($choices);
38+
}
39+
40+
foreach ($choices as $group => $choiceOrGroup) {
41+
if (!\is_array($choiceOrGroup)) {
42+
$choices = array_filter($choices, $filter);
43+
44+
break;
45+
}
46+
47+
// choices are structured, filter by group
48+
if ($filtered = array_filter($choiceOrGroup, $filter)) {
49+
$choices[$group] = $filtered;
50+
51+
continue;
52+
}
53+
54+
// also filter empty groups
55+
unset($choices[$group]);
56+
}
57+
}
58+
3459
return new ArrayChoiceList($choices, $value);
3560
}
3661

3762
/**
3863
* {@inheritdoc}
3964
*/
40-
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null)
65+
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null, callable $filter = null)
4166
{
67+
if ($filter) {
68+
$loader = new FilterChoiceLoaderDecorator($loader, $filter);
69+
}
70+
4271
return new LazyChoiceList($loader, $value);
4372
}
4473

‎src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php
+38-8Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ public function getDecoratedFactory()
5959
/**
6060
* {@inheritdoc}
6161
*
62-
* @param callable|string|PropertyPath|null $value The callable or path for
63-
* generating the choice values
62+
* @param callable|string|PropertyPath|null $value The callable or path for
63+
* generating the choice values
64+
* @param callable|string|PropertyPath|null $filter The callable or path for
65+
* filtering the choices
6466
*
6567
* @return ChoiceListInterface The choice list
6668
*/
67-
public function createListFromChoices(iterable $choices, $value = null)
69+
public function createListFromChoices(iterable $choices, $value = null, $filter = null)
6870
{
6971
if (\is_string($value)) {
7072
$value = new PropertyPath($value);
@@ -81,18 +83,33 @@ public function createListFromChoices(iterable $choices, $value = null)
8183
};
8284
}
8385

84-
return $this->decoratedFactory->createListFromChoices($choices, $value);
86+
if (\is_string($filter)) {
87+
$filter = new PropertyPath($filter);
88+
}
89+
90+
if ($filter instanceof PropertyPath) {
91+
$accessor = $this->propertyAccessor;
92+
$filter = static function ($choice) use ($accessor, $filter) {
93+
if (\is_object($choice) || \is_array($choice)) {
94+
return (bool) $accessor->getValue($choice, $filter);
95+
}
96+
};
97+
}
98+
99+
return $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
85100
}
86101

87102
/**
88103
* {@inheritdoc}
89104
*
90-
* @param callable|string|PropertyPath|null $value The callable or path for
91-
* generating the choice values
105+
* @param callable|string|PropertyPath|null $value The callable or path for
106+
* generating the choice values
107+
* @param callable|string|PropertyPath|null $filter The callable or path for
108+
* filtering the choices
92109
*
93110
* @return ChoiceListInterface The choice list
94111
*/
95-
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
112+
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null, $filter = null)
96113
{
97114
if (\is_string($value)) {
98115
$value = new PropertyPath($value);
@@ -109,7 +126,20 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, $value = nul
109126
};
110127
}
111128

112-
return $this->decoratedFactory->createListFromLoader($loader, $value);
129+
if (\is_string($filter)) {
130+
$filter = new PropertyPath($filter);
131+
}
132+
133+
if ($filter instanceof PropertyPath) {
134+
$accessor = $this->propertyAccessor;
135+
$filter = static function ($choice) use ($accessor, $filter) {
136+
if (\is_object($choice) || \is_array($choice)) {
137+
return (bool) $accessor->getValue($choice, $filter);
138+
}
139+
};
140+
}
141+
142+
return $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
113143
}
114144

115145
/**
+63Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
* A decorator to filter choices only when they are loaded or partially loaded.
16+
*
17+
* @author Jules Pietri <jules@heahprod.com>
18+
*/
19+
class FilterChoiceLoaderDecorator extends AbstractChoiceLoader
20+
{
21+
private $decoratedLoader;
22+
private $filter;
23+
24+
public function __construct(ChoiceLoaderInterface $loader, callable $filter)
25+
{
26+
$this->decoratedLoader = $loader;
27+
$this->filter = $filter;
28+
}
29+
30+
protected function loadChoices(): iterable
31+
{
32+
$list = $this->decoratedLoader->loadChoiceList();
33+
34+
if (array_values($list->getValues()) === array_values($structuredValues = $list->getStructuredValues())) {
35+
return array_filter(array_combine($list->getOriginalKeys(), $list->getChoices()), $this->filter);
36+
}
37+
38+
foreach ($structuredValues as $group => $values) {
39+
if ($filtered = array_filter($list->getChoicesForValues($values), $this->filter)) {
40+
$choices[$group] = $filtered;
41+
}
42+
// filter empty groups
43+
}
44+
45+
return $choices ?? [];
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function loadChoicesForValues(array $values, callable $value = null): array
52+
{
53+
return array_filter($this->decoratedLoader->loadChoicesForValues($values, $value), $this->filter);
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function loadValuesForChoices(array $choices, callable $value = null): array
60+
{
61+
return $this->decoratedLoader->loadValuesForChoices(array_filter($choices, $this->filter), $value);
62+
}
63+
}

0 commit comments

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