feature #34550 [Form] Added an AbstractChoiceLoader to simplify implementations and handle global optimizations (HeahDude)
This PR was merged into the 5.1-dev branch.
Discussion
----------
[Form] Added an AbstractChoiceLoader to simplify implementations and handle global optimizations
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | ~
| License | MIT
| Doc PR | ~
<!--
Replace this notice by a short README for your feature/bugfix. This will help people
understand your PR and can be used as a start for the documentation.
Additionally (see https://symfony.com/roadmap):
- Always add tests and ensure they pass.
- Never break backward compatibility (see https://symfony.com/bc).
- Bug fixes must be submitted against the lowest maintained branch where they apply
(lowest branches are regularly merged to upper ones so they get the fixes too.)
- Features and deprecations must be submitted against branch master.
-->
Taking over #33218 (taking over #30983)
The `ChoiceLoaderInterface` is not easy to understand/implement, while its goal is simple, lazy load an array of choices.
What may seem complicated is the how/what to optimize loading, we need to deal with a `$value` callback (which refers to the `choice_value` option allowing to transform a model choice to a unique string value that can be displayed/submitted).
We have now enough implementations in core to justify the need of an abstraction and provide a better DX in the process.
Before this PR we needed to implement 3 methods to create a loader:
- `loadChoiceList(?callable $value): ChoiceListInterface`
- `loadChoicesForValues(array $values, ?callable $value): array`
- `loadValuesForChoices(array $choices, ?callable $value): array`
and handle optimization, in each.
Now we only need to implement:
- `loadChoices(): iterable`
and optionnally:
- `doLoadChoicesForValues(array $values, ?callable $value): array`
if more optimization is needed to only load submitted values, (i.e the core intl loader prevents loading values that are the same as choices, and the doctrine one performs a `WHERE id IN ($ids)` query).
Commits
-------
1394df2dea
[Form] Added an AbstractChoiceLoader to simplify implementations and handle global optimizations
This commit is contained in:
commit
31fb219896
@ -12,27 +12,20 @@
|
|||||||
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
|
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
|
||||||
|
|
||||||
use Doctrine\Persistence\ObjectManager;
|
use Doctrine\Persistence\ObjectManager;
|
||||||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader;
|
||||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
|
||||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads choices using a Doctrine object manager.
|
* Loads choices using a Doctrine object manager.
|
||||||
*
|
*
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
*/
|
*/
|
||||||
class DoctrineChoiceLoader implements ChoiceLoaderInterface
|
class DoctrineChoiceLoader extends AbstractChoiceLoader
|
||||||
{
|
{
|
||||||
private $manager;
|
private $manager;
|
||||||
private $class;
|
private $class;
|
||||||
private $idReader;
|
private $idReader;
|
||||||
private $objectLoader;
|
private $objectLoader;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ChoiceListInterface
|
|
||||||
*/
|
|
||||||
private $choiceList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new choice loader.
|
* Creates a new choice loader.
|
||||||
*
|
*
|
||||||
@ -59,81 +52,57 @@ class DoctrineChoiceLoader implements ChoiceLoaderInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function loadChoiceList(callable $value = null)
|
protected function loadChoices(): iterable
|
||||||
{
|
{
|
||||||
if ($this->choiceList) {
|
return $this->objectLoader
|
||||||
return $this->choiceList;
|
|
||||||
}
|
|
||||||
|
|
||||||
$objects = $this->objectLoader
|
|
||||||
? $this->objectLoader->getEntities()
|
? $this->objectLoader->getEntities()
|
||||||
: $this->manager->getRepository($this->class)->findAll();
|
: $this->manager->getRepository($this->class)->findAll();
|
||||||
|
|
||||||
return $this->choiceList = new ArrayChoiceList($objects, $value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* @internal to be remove in Symfony 6
|
||||||
*/
|
*/
|
||||||
public function loadValuesForChoices(array $choices, callable $value = null)
|
protected function doLoadValuesForChoices(array $choices): array
|
||||||
{
|
{
|
||||||
// Performance optimization
|
|
||||||
if (empty($choices)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimize performance for single-field identifiers. We already
|
// Optimize performance for single-field identifiers. We already
|
||||||
// know that the IDs are used as values
|
// know that the IDs are used as values
|
||||||
$optimize = $this->idReader && (null === $value || \is_array($value) && $value[0] === $this->idReader);
|
|
||||||
|
|
||||||
// Attention: This optimization does not check choices for existence
|
// Attention: This optimization does not check choices for existence
|
||||||
if ($optimize && !$this->choiceList) {
|
if ($this->idReader) {
|
||||||
$values = [];
|
trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__);
|
||||||
|
|
||||||
// Maintain order and indices of the given objects
|
// Maintain order and indices of the given objects
|
||||||
|
$values = [];
|
||||||
foreach ($choices as $i => $object) {
|
foreach ($choices as $i => $object) {
|
||||||
if ($object instanceof $this->class) {
|
if ($object instanceof $this->class) {
|
||||||
// Make sure to convert to the right format
|
$values[$i] = $this->idReader->getIdValue($object);
|
||||||
$values[$i] = (string) $this->idReader->getIdValue($object);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $values;
|
return $values;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->loadChoiceList($value)->getValuesForChoices($choices);
|
return parent::doLoadValuesForChoices($choices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function doLoadChoicesForValues(array $values, ?callable $value): array
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function loadChoicesForValues(array $values, callable $value = null)
|
|
||||||
{
|
{
|
||||||
// Performance optimization
|
$legacy = $this->idReader && null === $value;
|
||||||
// Also prevents the generation of "WHERE id IN ()" queries through the
|
|
||||||
// object loader. At least with MySQL and on the development machine
|
if ($legacy) {
|
||||||
// this was tested on, no exception was thrown for such invalid
|
trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__);
|
||||||
// statements, consequently no test fails when this code is removed.
|
|
||||||
// https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
|
|
||||||
if (empty($values)) {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize performance in case we have an object loader and
|
// Optimize performance in case we have an object loader and
|
||||||
// a single-field identifier
|
// a single-field identifier
|
||||||
$optimize = $this->idReader && (null === $value || \is_array($value) && $this->idReader === $value[0]);
|
if (($legacy || \is_array($value) && $this->idReader === $value[0]) && $this->objectLoader) {
|
||||||
|
|
||||||
if ($optimize && !$this->choiceList && $this->objectLoader) {
|
|
||||||
$unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values);
|
|
||||||
$objectsById = [];
|
|
||||||
$objects = [];
|
$objects = [];
|
||||||
|
$objectsById = [];
|
||||||
|
|
||||||
// Maintain order and indices from the given $values
|
// Maintain order and indices from the given $values
|
||||||
// An alternative approach to the following loop is to add the
|
// An alternative approach to the following loop is to add the
|
||||||
// "INDEX BY" clause to the Doctrine query in the loader,
|
// "INDEX BY" clause to the Doctrine query in the loader,
|
||||||
// but I'm not sure whether that's doable in a generic fashion.
|
// but I'm not sure whether that's doable in a generic fashion.
|
||||||
foreach ($unorderedObjects as $object) {
|
foreach ($this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values) as $object) {
|
||||||
$objectsById[(string) $this->idReader->getIdValue($object)] = $object;
|
$objectsById[$this->idReader->getIdValue($object)] = $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($values as $i => $id) {
|
foreach ($values as $i => $id) {
|
||||||
@ -145,6 +114,6 @@ class DoctrineChoiceLoader implements ChoiceLoaderInterface
|
|||||||
return $objects;
|
return $objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->loadChoiceList($value)->getChoicesForValues($values);
|
return parent::doLoadChoicesForValues($values, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,12 +84,12 @@ class IdReader
|
|||||||
*
|
*
|
||||||
* This method assumes that the object has a single-column ID.
|
* This method assumes that the object has a single-column ID.
|
||||||
*
|
*
|
||||||
* @return mixed The ID value
|
* @return string The ID value
|
||||||
*/
|
*/
|
||||||
public function getIdValue(object $object = null)
|
public function getIdValue(object $object = null)
|
||||||
{
|
{
|
||||||
if (!$object) {
|
if (!$object) {
|
||||||
return null;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->om->contains($object)) {
|
if (!$this->om->contains($object)) {
|
||||||
@ -104,7 +104,7 @@ class IdReader
|
|||||||
$idValue = $this->associationIdReader->getIdValue($idValue);
|
$idValue = $this->associationIdReader->getIdValue($idValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $idValue;
|
return (string) $idValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,6 +100,10 @@ class DoctrineChoiceLoaderTest extends TestCase
|
|||||||
->method('getClassMetadata')
|
->method('getClassMetadata')
|
||||||
->with($this->class)
|
->with($this->class)
|
||||||
->willReturn(new ClassMetadata($this->class));
|
->willReturn(new ClassMetadata($this->class));
|
||||||
|
$this->repository->expects($this->any())
|
||||||
|
->method('findAll')
|
||||||
|
->willReturn([$this->obj1, $this->obj2, $this->obj3])
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadChoiceList()
|
public function testLoadChoiceList()
|
||||||
@ -186,6 +190,11 @@ class DoctrineChoiceLoaderTest extends TestCase
|
|||||||
$this->assertSame([], $loader->loadValuesForChoices([]));
|
$this->assertSame([], $loader->loadValuesForChoices([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*
|
||||||
|
* @expectedDeprecation Since symfony/doctrine-bridge 5.1: Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don't pass the IdReader to "Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader" or define the "choice_value" option instead.
|
||||||
|
*/
|
||||||
public function testLoadValuesForChoicesDoesNotLoadIfSingleIntId()
|
public function testLoadValuesForChoicesDoesNotLoadIfSingleIntId()
|
||||||
{
|
{
|
||||||
$loader = new DoctrineChoiceLoader(
|
$loader = new DoctrineChoiceLoader(
|
||||||
@ -205,7 +214,7 @@ class DoctrineChoiceLoaderTest extends TestCase
|
|||||||
$this->assertSame(['2'], $loader->loadValuesForChoices([$this->obj2]));
|
$this->assertSame(['2'], $loader->loadValuesForChoices([$this->obj2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadValuesForChoicesLoadsIfSingleIntIdAndValueGiven()
|
public function testLoadValuesForChoicesDoesNotLoadIfSingleIntIdAndValueGiven()
|
||||||
{
|
{
|
||||||
$loader = new DoctrineChoiceLoader(
|
$loader = new DoctrineChoiceLoader(
|
||||||
$this->om,
|
$this->om,
|
||||||
@ -216,7 +225,7 @@ class DoctrineChoiceLoaderTest extends TestCase
|
|||||||
$choices = [$this->obj1, $this->obj2, $this->obj3];
|
$choices = [$this->obj1, $this->obj2, $this->obj3];
|
||||||
$value = function (\stdClass $object) { return $object->name; };
|
$value = function (\stdClass $object) { return $object->name; };
|
||||||
|
|
||||||
$this->repository->expects($this->once())
|
$this->repository->expects($this->never())
|
||||||
->method('findAll')
|
->method('findAll')
|
||||||
->willReturn($choices);
|
->willReturn($choices);
|
||||||
|
|
||||||
@ -254,8 +263,7 @@ class DoctrineChoiceLoaderTest extends TestCase
|
|||||||
{
|
{
|
||||||
$loader = new DoctrineChoiceLoader(
|
$loader = new DoctrineChoiceLoader(
|
||||||
$this->om,
|
$this->om,
|
||||||
$this->class,
|
$this->class
|
||||||
$this->idReader
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$choices = [$this->obj1, $this->obj2, $this->obj3];
|
$choices = [$this->obj1, $this->obj2, $this->obj3];
|
||||||
@ -285,7 +293,12 @@ class DoctrineChoiceLoaderTest extends TestCase
|
|||||||
$this->assertSame([], $loader->loadChoicesForValues([]));
|
$this->assertSame([], $loader->loadChoicesForValues([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoadChoicesForValuesLoadsOnlyChoicesIfSingleIntId()
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*
|
||||||
|
* @expectedDeprecation Not defining explicitly the IdReader as value callback when query can be optimized has been deprecated in 5.1. Don't pass the IdReader to "Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader" or define the choice_value instead.
|
||||||
|
*/
|
||||||
|
public function legacyTestLoadChoicesForValuesLoadsOnlyChoicesIfValueUseIdReader()
|
||||||
{
|
{
|
||||||
$loader = new DoctrineChoiceLoader(
|
$loader = new DoctrineChoiceLoader(
|
||||||
$this->om,
|
$this->om,
|
||||||
@ -321,6 +334,42 @@ class DoctrineChoiceLoaderTest extends TestCase
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testLoadChoicesForValuesLoadsOnlyChoicesIfValueUseIdReader()
|
||||||
|
{
|
||||||
|
$loader = new DoctrineChoiceLoader(
|
||||||
|
$this->om,
|
||||||
|
$this->class,
|
||||||
|
$this->idReader,
|
||||||
|
$this->objectLoader
|
||||||
|
);
|
||||||
|
|
||||||
|
$choices = [$this->obj2, $this->obj3];
|
||||||
|
|
||||||
|
$this->idReader->expects($this->any())
|
||||||
|
->method('getIdField')
|
||||||
|
->willReturn('idField');
|
||||||
|
|
||||||
|
$this->repository->expects($this->never())
|
||||||
|
->method('findAll');
|
||||||
|
|
||||||
|
$this->objectLoader->expects($this->once())
|
||||||
|
->method('getEntitiesByIds')
|
||||||
|
->with('idField', [4 => '3', 7 => '2'])
|
||||||
|
->willReturn($choices);
|
||||||
|
|
||||||
|
$this->idReader->expects($this->any())
|
||||||
|
->method('getIdValue')
|
||||||
|
->willReturnMap([
|
||||||
|
[$this->obj2, '2'],
|
||||||
|
[$this->obj3, '3'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[4 => $this->obj3, 7 => $this->obj2],
|
||||||
|
$loader->loadChoicesForValues([4 => '3', 7 => '2'], [$this->idReader, 'getIdValue']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public function testLoadChoicesForValuesLoadsAllIfSingleIntIdAndValueGiven()
|
public function testLoadChoicesForValuesLoadsAllIfSingleIntIdAndValueGiven()
|
||||||
{
|
{
|
||||||
$loader = new DoctrineChoiceLoader(
|
$loader = new DoctrineChoiceLoader(
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
"symfony/stopwatch": "^4.4|^5.0",
|
"symfony/stopwatch": "^4.4|^5.0",
|
||||||
"symfony/config": "^4.4|^5.0",
|
"symfony/config": "^4.4|^5.0",
|
||||||
"symfony/dependency-injection": "^4.4|^5.0",
|
"symfony/dependency-injection": "^4.4|^5.0",
|
||||||
"symfony/form": "^5.0",
|
"symfony/form": "^5.1",
|
||||||
"symfony/http-kernel": "^5.0",
|
"symfony/http-kernel": "^5.0",
|
||||||
"symfony/messenger": "^4.4|^5.0",
|
"symfony/messenger": "^4.4|^5.0",
|
||||||
"symfony/property-access": "^4.4|^5.0",
|
"symfony/property-access": "^4.4|^5.0",
|
||||||
@ -49,7 +49,7 @@
|
|||||||
"conflict": {
|
"conflict": {
|
||||||
"phpunit/phpunit": "<5.4.3",
|
"phpunit/phpunit": "<5.4.3",
|
||||||
"symfony/dependency-injection": "<4.4",
|
"symfony/dependency-injection": "<4.4",
|
||||||
"symfony/form": "<5",
|
"symfony/form": "<5.1",
|
||||||
"symfony/http-kernel": "<5",
|
"symfony/http-kernel": "<5",
|
||||||
"symfony/messenger": "<4.4",
|
"symfony/messenger": "<4.4",
|
||||||
"symfony/property-info": "<5",
|
"symfony/property-info": "<5",
|
||||||
|
@ -4,6 +4,7 @@ CHANGELOG
|
|||||||
5.1.0
|
5.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* Added an `AbstractChoiceLoader` to simplify implementations and handle global optimizations
|
||||||
* The `view_timezone` option defaults to the `model_timezone` if no `reference_date` is configured.
|
* The `view_timezone` option defaults to the `model_timezone` if no `reference_date` is configured.
|
||||||
* Added default `inputmode` attribute to Search, Email and Tel form types.
|
* Added default `inputmode` attribute to Search, Email and Tel form types.
|
||||||
* Implementing the `FormConfigInterface` without implementing the `getIsEmptyCallback()` method
|
* Implementing the `FormConfigInterface` without implementing the `getIsEmptyCallback()` method
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Form\ChoiceList\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jules Pietri <jules@heahprod.com>
|
||||||
|
*/
|
||||||
|
abstract class AbstractChoiceLoader implements ChoiceLoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The loaded choice list.
|
||||||
|
*
|
||||||
|
* @var ArrayChoiceList
|
||||||
|
*/
|
||||||
|
private $choiceList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @final
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadChoiceList(callable $value = null)
|
||||||
|
{
|
||||||
|
return $this->choiceList ?? ($this->choiceList = new ArrayChoiceList($this->loadChoices(), $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadChoicesForValues(array $values, callable $value = null)
|
||||||
|
{
|
||||||
|
if (!$values) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->choiceList) {
|
||||||
|
return $this->choiceList->getChoicesForValues($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->doLoadChoicesForValues($values, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadValuesForChoices(array $choices, callable $value = null)
|
||||||
|
{
|
||||||
|
if (!$choices) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value) {
|
||||||
|
// if a value callback exists, use it
|
||||||
|
return array_map($value, $choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->choiceList) {
|
||||||
|
return $this->choiceList->getValuesForChoices($choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->doLoadValuesForChoices($choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function loadChoices(): iterable;
|
||||||
|
|
||||||
|
protected function doLoadChoicesForValues(array $values, ?callable $value): array
|
||||||
|
{
|
||||||
|
return $this->loadChoiceList($value)->getChoicesForValues($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doLoadValuesForChoices(array $choices): array
|
||||||
|
{
|
||||||
|
return $this->loadChoiceList()->getValuesForChoices($choices);
|
||||||
|
}
|
||||||
|
}
|
@ -11,67 +11,25 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Form\ChoiceList\Loader;
|
namespace Symfony\Component\Form\ChoiceList\Loader;
|
||||||
|
|
||||||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an {@link ArrayChoiceList} instance from a callable returning an array of choices.
|
* Loads an {@link ArrayChoiceList} instance from a callable returning iterable choices.
|
||||||
*
|
*
|
||||||
* @author Jules Pietri <jules@heahprod.com>
|
* @author Jules Pietri <jules@heahprod.com>
|
||||||
*/
|
*/
|
||||||
class CallbackChoiceLoader implements ChoiceLoaderInterface
|
class CallbackChoiceLoader extends AbstractChoiceLoader
|
||||||
{
|
{
|
||||||
private $callback;
|
private $callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The loaded choice list.
|
* @param callable $callback The callable returning iterable choices
|
||||||
*
|
|
||||||
* @var ArrayChoiceList
|
|
||||||
*/
|
|
||||||
private $choiceList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable $callback The callable returning an array of choices
|
|
||||||
*/
|
*/
|
||||||
public function __construct(callable $callback)
|
public function __construct(callable $callback)
|
||||||
{
|
{
|
||||||
$this->callback = $callback;
|
$this->callback = $callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function loadChoices(): iterable
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function loadChoiceList(callable $value = null)
|
|
||||||
{
|
{
|
||||||
if (null !== $this->choiceList) {
|
return ($this->callback)();
|
||||||
return $this->choiceList;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->choiceList = new ArrayChoiceList(($this->callback)(), $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function loadChoicesForValues(array $values, callable $value = null)
|
|
||||||
{
|
|
||||||
// Optimize
|
|
||||||
if (empty($values)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->loadChoiceList($value)->getChoicesForValues($values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function loadValuesForChoices(array $choices, callable $value = null)
|
|
||||||
{
|
|
||||||
// Optimize
|
|
||||||
if (empty($choices)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->loadChoiceList($value)->getValuesForChoices($choices);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,7 @@ class IntlCallbackChoiceLoader extends CallbackChoiceLoader
|
|||||||
*/
|
*/
|
||||||
public function loadChoicesForValues(array $values, callable $value = null)
|
public function loadChoicesForValues(array $values, callable $value = null)
|
||||||
{
|
{
|
||||||
// Optimize
|
return parent::loadChoicesForValues(array_filter($values), $value);
|
||||||
$values = array_filter($values);
|
|
||||||
if (empty($values)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->loadChoiceList($value)->getChoicesForValues($values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,17 +32,13 @@ class IntlCallbackChoiceLoader extends CallbackChoiceLoader
|
|||||||
*/
|
*/
|
||||||
public function loadValuesForChoices(array $choices, callable $value = null)
|
public function loadValuesForChoices(array $choices, callable $value = null)
|
||||||
{
|
{
|
||||||
// Optimize
|
|
||||||
$choices = array_filter($choices);
|
$choices = array_filter($choices);
|
||||||
if (empty($choices)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no callable is set, choices are the same as values
|
// If no callable is set, choices are the same as values
|
||||||
if (null === $value) {
|
if (null === $value) {
|
||||||
return $choices;
|
return $choices;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->loadChoiceList($value)->getValuesForChoices($choices);
|
return parent::loadValuesForChoices($choices, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user