8000 feature #35733 [Form] Added a "choice_filter" option to ChoiceType (H… · symfony/symfony@e0bddee · GitHub
[go: up one dir, main page]

Skip to content

Commit e0bddee

Browse files
committed
feature #35733 [Form] Added a "choice_filter" option to ChoiceType (HeahDude)
This PR was merged into the 5.1-dev branch. Discussion ---------- [Form] Added a "choice_filter" option to ChoiceType | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | yes | Tickets | Fix #32657 | License | MIT | Doc PR | symfony/symfony-docs#13223 Finally opening this PR for a very old branch, based on both #34550 (merged) and #30994 (merged). ~Until #30994 is merged, this PR should better be reviewed by commits. Thanks!~ Commits ------- ed2c312 [Form] Added a "choice_filter" option to ChoiceType
2 parents 0fb0371 + ed2c312 commit e0bddee

File tree

20 files changed

+710
-40
lines changed
  • ChoiceList
  • Extension/Core/Type
  • Tests
  • 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
    F438
    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