8000 [Form] Added a "choice_filter" option to ChoiceType · symfony/symfony@ed2c312 · GitHub
[go: up one dir, main page]

Skip to content

Commit ed2c312

Browse files
committed
[Form] Added a "choice_filter" option to ChoiceType
1 parent df1caae commit ed2c312

20 files changed

+710
-40
lines changed

UPGRADE-5.1.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ 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+
* Added argument `callable|null $filter` to `ChoiceListFactoryInterface::createListFromChoices()` and `createListFromLoader()` - not defining them is deprecated.
2627

2728
FrameworkBundle
2829
---------------

UPGRADE-6.0.md

Lines 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 argument `callable|null $filter` to `ChoiceListFactoryInterface::createListFromChoices()` and `createListFromLoader()`.
2425

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

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ CHANGELOG
44
5.1.0
55
-----
66

7+
* Added a `choice_filter` option to `ChoiceType`
8+
* Added argument `callable|null $filter` to `ChoiceListFactoryInterface::createListFromChoices()` and `createListFromLoader()` - not defining them is deprecated.
79
* Added a `ChoiceList` facade to leverage explicit choice list caching based on options
810
* Added an `AbstractChoiceLoader` to simplify implementations and handle global optimizations
911
* The `view_timezone` option defaults to the `model_timezone` if no `reference_date` is configured.

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

Lines 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
*
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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\FormTypeExtensionInterface;
15+
use Symfony\Component\Form\FormTypeInterface;
16+
17+
/**
18+
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
19+
* which configures a "choice_filter" option.
20+
*
21+
* @internal
22+
*
23+
* @author Jules Pietri <jules@heahprod.com>
24+
*/
25+
final class ChoiceFilter extends AbstractStaticOption
26+
{
27+
}

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

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
/**
2020
* Caches the choice lists created by the decorated factory.
2121
*
22+
* To cache a list based on its options, arguments must be decorated
23+
* by a {@see Cache\AbstractStaticOption} implementation.
24+
*
2225
* @author Bernhard Schussek <bschussek@gmail.com>
2326
* @author Jules Pietri <jules@heahprod.com>
2427
*/
@@ -80,35 +83,61 @@ public function getDecoratedFactory()
8083

8184
/**
8285
* {@inheritdoc}
86+
*
87+
* @param callable|Cache\ChoiceValue|null $value The callable or static option for
88+
* generating the choice values
89+
* @param callable|Cache\ChoiceFilter|null $filter The callable or static option for
90+
* filtering the choices
8391
*/
84-
public function createListFromChoices(iterable $choices, $value = null)
92+
public function createListFromChoices(iterable $choices, $value = null/*, $filter = null*/)
8593
{
94+
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
95+
8696
if ($choices instanceof \Traversable) {
8797
$choices = iterator_to_array($choices);
8898
}
8999

90-
// Only cache per value when needed. The value is not validated on purpose.
100+
$cache = true;
101+
// Only cache per value and filter when needed. The value is not validated on purpose.
91102
// The decorated factory may decide which values to accept and which not.
92103
if ($value instanceof Cache\ChoiceValue) {
93104
$value = $value->getOption();
94105
} elseif ($value) {
95-
return $this->decoratedFactory->createListFromChoices($choices, $value);
106+
$cache = false;
107+
}
108+
if ($filter instanceof Cache\ChoiceFilter) {
109+
$filter = $filter->getOption();
110+
} elseif ($filter) {
111+
$cache = false;
112+
}
113+
114+
if (!$cache) {
115+
return $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
96116
}
97117

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

100120
if (!isset($this->lists[$hash])) {
101-
$this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value);
121+
$this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
102122
}
103123

104124
return $this->lists[$hash];
105125
}
106126

107127
/**
108128
* {@inheritdoc}
129+
*
130+
* @param ChoiceLoaderInterface|Cache\ChoiceLoader $loader The loader or static loader to load
131+
* the choices lazily
132+
* @param callable|Cache\ChoiceValue|null $value The callable or static option for
133+
* generating the choice values
134+
* @param callable|Cache\ChoiceFilter|null $filter The callable or static option for
135+
* filtering the choices
109136
*/
110-
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
137+
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null/*, $filter = null*/)
111138
{
139+
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
140+
112141
$cache = true;
113142

114143
if ($loader instanceof Cache\ChoiceLoader) {
@@ -123,21 +152,33 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, $value = nul
123152
$cache = false;
124153
}
125154

155+
if ($filter instanceof Cache\ChoiceFilter) {
156+
$filter = $filter->getOption();
157+
} elseif ($filter) {
158+
$cache = false;
159+
}
160+
126161
if (!$cache) {
127-
return $this->decoratedFactory->createListFromLoader($loader, $value);
162+
return $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
128163
}
129164

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

132167
if (!isset($this->lists[$hash])) {
133-
$this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value);
168+
$this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
134169
}
135170

136171
return $this->lists[$hash];
137172
}
138173

139174
/**
140175
* {@inheritdoc}
176+
*
177+
* @param array|callable|Cache\PreferredChoice|null $preferredChoices The preferred choices
178+
* @param callable|false|Cache\ChoiceLabel|null $label The option or static option generating the choice labels
179+
* @param callable|Cache\ChoiceFieldName|null $index The option or static option generating the view indices
180+
* @param callable|Cache\GroupBy|null $groupBy The option or static option generating the group names
181+
* @param array|callable|Cache\ChoiceAttr|null $attr The option or static option generating the HTML attributes
141182
*/
142183
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
143184
{

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ 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+
*
3436
* @return ChoiceListInterface The choice list
3537
*/
36-
public function createListFromChoices(iterable $choices, callable $value = null);
38+
public function createListFromChoices(iterable $choices, callable $value = null/*, callable $filter = null*/);
3739

3840
/**
3941
* Creates a choice list that is loaded with the given loader.
@@ -42,9 +44,11 @@ public function createListFromChoices(iterable $choices, callable $value = null)
4244
* The callable receives the choice as only argument.
4345
* Null may be passed when the choice list contains the empty value.
4446
*
47+
* @param callable|null $filter The callable filtering the choices
48+
*
4549
* @return ChoiceListInterface The choice list
4650
*/
47-
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null);
51+
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null/*, callable $filter = null*/);
4852

4953
/**
5054
* Creates a view for the given choice list.

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
1515
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
1616
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
17+
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
1718
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
19+
use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator;
1820
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
1921
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
2022
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
@@ -23,22 +25,44 @@
2325
* Default implementation of {@link ChoiceListFactoryInterface}.
2426
*
2527
* @author Bernhard Schussek <bschussek@gmail.com>
28+
* @author Jules Pietri <jules@heahprod.com>
2629
*/
2730
class DefaultChoiceListFactory implements ChoiceListFactoryInterface
2831
{
2932
/**
3033
* {@inheritdoc}
34+
*
35+
* @param callable|null $filter
3136
*/
32-
public function createListFromChoices(iterable $choices, callable $value = null)
37+
public function createListFromChoices(iterable $choices, callable $value = null/*, callable $filter = null*/)
3338
{
39+
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
40+
41+
if ($filter) {
42+
// filter the choice list lazily
43+
return $this->createListFromLoader(new FilterChoiceLoaderDecorator(
44+
new CallbackChoiceLoader(static function () use ($choices) {
45+
return $choices;
46+
}
47+
), $filter), $value);
48+
}
49+
3450
return new ArrayChoiceList($choices, $value);
3551
}
3652

3753
/**
3854
* {@inheritdoc}
55+
*
56+
* @param callable|null $filter
3957
*/
40-
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null)
58+
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null/*, callable $filter = null*/)
4159
{
60+
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
61+
62+
if ($filter) {
63+
$loader = new FilterChoiceLoaderDecorator($loader, $filter);
64+
}
65+
4266
return new LazyChoiceList($loader, $value);
4367
}
4468

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

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,17 @@ 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
{
71+
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
72+
6973
if (\is_string($value)) {
7074
$value = new PropertyPath($value);
7175
}
@@ -81,19 +85,34 @@ public function createListFromChoices(iterable $choices, $value = null)
8185
};
8286
}
8387

84-
return $this->decoratedFactory->createListFromChoices($choices, $value);
88+
if (\is_string($filter)) {
89+
$filter = new PropertyPath($filter);
90+
}
91+
92+
if ($filter instanceof PropertyPath) {
93+
$accessor = $this->propertyAccessor;
94+
$filter = static function ($choice) use ($accessor, $filter) {
95+
return (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter);
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
{
114+
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
115+
97116
if (\is_string($value)) {
98117
$value = new PropertyPath($value);
99118
}
@@ -109,7 +128,18 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, $value = nul
109128
};
110129
}
111130

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

115145
/**

0 commit comments

Comments
 (0)
0