[Form] Added support for caching choice lists based on options
This commit is contained in:
parent
fcb833f26d
commit
b25973cc2e
@ -20,6 +20,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader;
|
||||
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
|
||||
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
@ -40,9 +41,9 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
private $idReaders = [];
|
||||
|
||||
/**
|
||||
* @var DoctrineChoiceLoader[]
|
||||
* @var EntityLoaderInterface[]
|
||||
*/
|
||||
private $choiceLoaders = [];
|
||||
private $entityLoaders = [];
|
||||
|
||||
/**
|
||||
* Creates the label for a choice.
|
||||
@ -115,43 +116,26 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
$choiceLoader = function (Options $options) {
|
||||
// Unless the choices are given explicitly, load them on demand
|
||||
if (null === $options['choices']) {
|
||||
$hash = null;
|
||||
$qbParts = null;
|
||||
// If there is no QueryBuilder we can safely cache
|
||||
$vary = [$options['em'], $options['class']];
|
||||
|
||||
// If there is no QueryBuilder we can safely cache DoctrineChoiceLoader,
|
||||
// also if concrete Type can return important QueryBuilder parts to generate
|
||||
// hash key we go for it as well
|
||||
if (!$options['query_builder'] || null !== $qbParts = $this->getQueryBuilderPartsForCachingHash($options['query_builder'])) {
|
||||
$hash = CachingFactoryDecorator::generateHash([
|
||||
$options['em'],
|
||||
$options['class'],
|
||||
$qbParts,
|
||||
]);
|
||||
|
||||
if (isset($this->choiceLoaders[$hash])) {
|
||||
return $this->choiceLoaders[$hash];
|
||||
}
|
||||
// hash key we go for it as well, otherwise fallback on the instance
|
||||
if ($options['query_builder']) {
|
||||
$vary[] = $this->getQueryBuilderPartsForCachingHash($options['query_builder']) ?? $options['query_builder'];
|
||||
}
|
||||
|
||||
if (null !== $options['query_builder']) {
|
||||
$entityLoader = $this->getLoader($options['em'], $options['query_builder'], $options['class']);
|
||||
} else {
|
||||
$queryBuilder = $options['em']->getRepository($options['class'])->createQueryBuilder('e');
|
||||
$entityLoader = $this->getLoader($options['em'], $queryBuilder, $options['class']);
|
||||
}
|
||||
|
||||
$doctrineChoiceLoader = new DoctrineChoiceLoader(
|
||||
return ChoiceList::loader($this, new DoctrineChoiceLoader(
|
||||
$options['em'],
|
||||
$options['class'],
|
||||
$options['id_reader'],
|
||||
$entityLoader
|
||||
);
|
||||
|
||||
if (null !== $hash) {
|
||||
$this->choiceLoaders[$hash] = $doctrineChoiceLoader;
|
||||
}
|
||||
|
||||
return $doctrineChoiceLoader;
|
||||
$this->getCachedEntityLoader(
|
||||
$options['em'],
|
||||
$options['query_builder'] ?? $options['em']->getRepository($options['class'])->createQueryBuilder('e'),
|
||||
$options['class'],
|
||||
$vary
|
||||
)
|
||||
), $vary);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -162,7 +146,7 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
// field name. We can only use numeric IDs as names, as we cannot
|
||||
// guarantee that a non-numeric ID contains a valid form name
|
||||
if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isIntId()) {
|
||||
return [__CLASS__, 'createChoiceName'];
|
||||
return ChoiceList::fieldName($this, [__CLASS__, 'createChoiceName']);
|
||||
}
|
||||
|
||||
// Otherwise, an incrementing integer is used as name automatically
|
||||
@ -176,7 +160,7 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
$choiceValue = function (Options $options) {
|
||||
// If the entity has a single-column ID, use that ID as value
|
||||
if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isSingleId()) {
|
||||
return [$options['id_reader'], 'getIdValue'];
|
||||
return ChoiceList::value($this, [$options['id_reader'], 'getIdValue'], $options['id_reader']);
|
||||
}
|
||||
|
||||
// Otherwise, an incrementing integer is used as value automatically
|
||||
@ -214,27 +198,13 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
// Set the "id_reader" option via the normalizer. This option is not
|
||||
// supposed to be set by the user.
|
||||
$idReaderNormalizer = function (Options $options) {
|
||||
$hash = CachingFactoryDecorator::generateHash([
|
||||
$options['em'],
|
||||
$options['class'],
|
||||
]);
|
||||
|
||||
// The ID reader is a utility that is needed to read the object IDs
|
||||
// when generating the field values. The callback generating the
|
||||
// field values has no access to the object manager or the class
|
||||
// of the field, so we store that information in the reader.
|
||||
// The reader is cached so that two choice lists for the same class
|
||||
// (and hence with the same reader) can successfully be cached.
|
||||
if (!isset($this->idReaders[$hash])) {
|
||||
$classMetadata = $options['em']->getClassMetadata($options['class']);
|
||||
$this->idReaders[$hash] = new IdReader($options['em'], $classMetadata);
|
||||
}
|
||||
|
||||
if ($this->idReaders[$hash]->isSingleId()) {
|
||||
return $this->idReaders[$hash];
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->getCachedIdReader($options['em'], $options['class']);
|
||||
};
|
||||
|
||||
$resolver->setDefaults([
|
||||
@ -242,7 +212,7 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
'query_builder' => null,
|
||||
'choices' => null,
|
||||
'choice_loader' => $choiceLoader,
|
||||
'choice_label' => [__CLASS__, 'createChoiceLabel'],
|
||||
'choice_label' => ChoiceList::label($this, [__CLASS__, 'createChoiceLabel']),
|
||||
'choice_name' => $choiceName,
|
||||
'choice_value' => $choiceValue,
|
||||
'id_reader' => null, // internal
|
||||
@ -274,6 +244,27 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->choiceLoaders = [];
|
||||
$this->entityLoaders = [];
|
||||
}
|
||||
|
||||
private function getCachedIdReader(ObjectManager $manager, string $class): ?IdReader
|
||||
{
|
||||
$hash = CachingFactoryDecorator::generateHash([$manager, $class]);
|
||||
|
||||
if (isset($this->idReaders[$hash])) {
|
||||
return $this->idReaders[$hash];
|
||||
}
|
||||
|
||||
$idReader = new IdReader($manager, $manager->getClassMetadata($class));
|
||||
|
||||
// don't cache the instance for composite ids that cannot be optimized
|
||||
return $this->idReaders[$hash] = $idReader->isSingleId() ? $idReader : null;
|
||||
}
|
||||
|
||||
private function getCachedEntityLoader(ObjectManager $manager, $queryBuilder, string $class, array $vary): EntityLoaderInterface
|
||||
{
|
||||
$hash = CachingFactoryDecorator::generateHash($vary);
|
||||
|
||||
return $this->entityLoaders[$hash] ?? ($this->entityLoaders[$hash] = $this->getLoader($manager, $queryBuilder, $class));
|
||||
}
|
||||
}
|
||||
|
@ -1205,13 +1205,13 @@ class EntityTypeTest extends BaseTypeTest
|
||||
'property3' => 2,
|
||||
]);
|
||||
|
||||
$choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader');
|
||||
$choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader');
|
||||
$choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader');
|
||||
$choiceList1 = $form->get('property1')->getConfig()->getAttribute('choice_list');
|
||||
$choiceList2 = $form->get('property2')->getConfig()->getAttribute('choice_list');
|
||||
$choiceList3 = $form->get('property3')->getConfig()->getAttribute('choice_list');
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1);
|
||||
$this->assertSame($choiceLoader1, $choiceLoader2);
|
||||
$this->assertSame($choiceLoader1, $choiceLoader3);
|
||||
$this->assertInstanceOf('Symfony\Component\Form\ChoiceList\LazyChoiceList', $choiceList1);
|
||||
$this->assertSame($choiceList1, $choiceList2);
|
||||
$this->assertSame($choiceList1, $choiceList3);
|
||||
}
|
||||
|
||||
public function testLoaderCachingWithParameters()
|
||||
@ -1265,13 +1265,13 @@ class EntityTypeTest extends BaseTypeTest
|
||||
'property3' => 2,
|
||||
]);
|
||||
|
||||
$choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader');
|
||||
$choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader');
|
||||
$choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader');
|
||||
$choiceList1 = $form->get('property1')->getConfig()->getAttribute('choice_list');
|
||||
$choiceList2 = $form->get('property2')->getConfig()->getAttribute('choice_list');
|
||||
$choiceList3 = $form->get('property3')->getConfig()->getAttribute('choice_list');
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1);
|
||||
$this->assertSame($choiceLoader1, $choiceLoader2);
|
||||
$this->assertSame($choiceLoader1, $choiceLoader3);
|
||||
$this->assertInstanceOf('Symfony\Component\Form\ChoiceList\LazyChoiceList', $choiceList1);
|
||||
$this->assertSame($choiceList1, $choiceList2);
|
||||
$this->assertSame($choiceList1, $choiceList3);
|
||||
}
|
||||
|
||||
protected function createRegistryMock($name, $em)
|
||||
|
@ -4,6 +4,7 @@ CHANGELOG
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* Added a `ChoiceList` facade to leverage explicit choice list caching based on options
|
||||
* 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.
|
||||
* Added default `inputmode` attribute to Search, Email and Tel form types.
|
||||
|
135
src/Symfony/Component/Form/ChoiceList/ChoiceList.php
Normal file
135
src/Symfony/Component/Form/ChoiceList/ChoiceList.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceAttr;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFieldName;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A set of convenient static methods to create cacheable choice list options.
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class ChoiceList
|
||||
{
|
||||
/**
|
||||
* Creates a cacheable loader from any callable providing iterable choices.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param callable $choices A callable that must return iterable choices or grouped choices
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the loader
|
||||
*/
|
||||
public static function lazy($formType, callable $choices, $vary = null): ChoiceLoader
|
||||
{
|
||||
return self::loader($formType, new CallbackChoiceLoader($choices), $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a loader to make it cacheable.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param ChoiceLoaderInterface $loader A loader responsible for creating loading choices or grouped choices
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the loader
|
||||
*/
|
||||
public static function loader($formType, ChoiceLoaderInterface $loader, $vary = null): ChoiceLoader
|
||||
{
|
||||
return new ChoiceLoader($formType, $loader, $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a "choice_value" callback to make it cacheable.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param callable $value Any pseudo callable to create a unique string value from a choice
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the callback
|
||||
*/
|
||||
public static function value($formType, $value, $vary = null): ChoiceValue
|
||||
{
|
||||
return new ChoiceValue($formType, $value, $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a "choice_label" option to make it cacheable.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param callable|false $label Any pseudo callable to create a label from a choice or false to discard it
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the option
|
||||
*/
|
||||
public static function label($formType, $label, $vary = null): ChoiceLabel
|
||||
{
|
||||
return new ChoiceLabel($formType, $label, $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a "choice_name" callback to make it cacheable.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param callable $fieldName Any pseudo callable to create a field name from a choice
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the callback
|
||||
*/
|
||||
public static function fieldName($formType, $fieldName, $vary = null): ChoiceFieldName
|
||||
{
|
||||
return new ChoiceFieldName($formType, $fieldName, $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a "choice_attr" option to make it cacheable.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param callable|array $attr Any pseudo callable or array to create html attributes from a choice
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the option
|
||||
*/
|
||||
public static function attr($formType, $attr, $vary = null): ChoiceAttr
|
||||
{
|
||||
return new ChoiceAttr($formType, $attr, $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a "group_by" callback to make it cacheable.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param callable $groupBy Any pseudo callable to return a group name from a choice
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the callback
|
||||
*/
|
||||
public static function groupBy($formType, $groupBy, $vary = null): GroupBy
|
||||
{
|
||||
return new GroupBy($formType, $groupBy, $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a "preferred_choices" option to make it cacheable.
|
||||
*
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param callable|array $preferred Any pseudo callable or array to return a group name from a choice
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the option
|
||||
*/
|
||||
public static function preferred($formType, $preferred, $vary = null): PreferredChoice
|
||||
{
|
||||
return new PreferredChoice($formType, $preferred, $vary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not be instantiated.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A template decorator for static {@see ChoiceType} options.
|
||||
*
|
||||
* Used as fly weight for {@see CachingFactoryDecorator}.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
abstract class AbstractStaticOption
|
||||
{
|
||||
private static $options = [];
|
||||
|
||||
/** @var bool|callable|string|array|\Closure|ChoiceLoaderInterface */
|
||||
private $option;
|
||||
|
||||
/**
|
||||
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
|
||||
* @param mixed $option Any pseudo callable, array, string or bool to define a choice list option
|
||||
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the option
|
||||
*/
|
||||
final public function __construct($formType, $option, $vary = null)
|
||||
{
|
||||
if (!$formType instanceof FormTypeInterface && !$formType instanceof FormTypeExtensionInterface) {
|
||||
throw new \TypeError(sprintf('Expected an instance of "%s" or "%s", but got "%s".', FormTypeInterface::class, FormTypeExtensionInterface::class, \is_object($formType) ? \get_class($formType) : \gettype($formType)));
|
||||
}
|
||||
|
||||
$hash = CachingFactoryDecorator::generateHash([static::class, $formType, $vary]);
|
||||
|
||||
$this->option = self::$options[$hash] ?? self::$options[$hash] = $option;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
final public function getOption()
|
||||
{
|
||||
return $this->option;
|
||||
}
|
||||
|
||||
final public static function reset(): void
|
||||
{
|
||||
self::$options = [];
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
|
||||
* which configures a "choice_attr" option.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class ChoiceAttr extends AbstractStaticOption
|
||||
{
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
|
||||
* which configures a "choice_name" callback.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class ChoiceFieldName extends AbstractStaticOption
|
||||
{
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
|
||||
* which configures a "choice_label" option.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class ChoiceLabel extends AbstractStaticOption
|
||||
{
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A cacheable wrapper for {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
|
||||
* which configures a "choice_loader" option.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class ChoiceLoader extends AbstractStaticOption implements ChoiceLoaderInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadChoiceList(callable $value = null)
|
||||
{
|
||||
return $this->getOption()->loadChoiceList($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadChoicesForValues(array $values, callable $value = null)
|
||||
{
|
||||
return $this->getOption()->loadChoicesForValues($values, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadValuesForChoices(array $choices, callable $value = null)
|
||||
{
|
||||
$this->getOption()->loadValuesForChoices($choices, $value);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
|
||||
* which configures a "choice_value" callback.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class ChoiceValue extends AbstractStaticOption
|
||||
{
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
|
||||
* which configures a "group_by" callback.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class GroupBy extends AbstractStaticOption
|
||||
{
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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\Factory\Cache;
|
||||
|
||||
use Symfony\Component\Form\FormTypeExtensionInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* A cacheable wrapper for any {@see FormTypeInterface} or {@see FormTypeExtensionInterface}
|
||||
* which configures a "preferred_choices" option.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
final class PreferredChoice extends AbstractStaticOption
|
||||
{
|
||||
}
|
@ -20,6 +20,7 @@ use Symfony\Contracts\Service\ResetInterface;
|
||||
* Caches the choice lists created by the decorated factory.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @author Jules Pietri <jules@heahprod.com>
|
||||
*/
|
||||
class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterface
|
||||
{
|
||||
@ -86,8 +87,13 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf
|
||||
$choices = iterator_to_array($choices);
|
||||
}
|
||||
|
||||
// The value is not validated on purpose. The decorated factory may
|
||||
// decide which values to accept and which not.
|
||||
// Only cache per value when needed. The value is not validated on purpose.
|
||||
// The decorated factory may decide which values to accept and which not.
|
||||
if ($value instanceof Cache\ChoiceValue) {
|
||||
$value = $value->getOption();
|
||||
} elseif ($value) {
|
||||
return $this->decoratedFactory->createListFromChoices($choices, $value);
|
||||
}
|
||||
|
||||
$hash = self::generateHash([$choices, $value], 'fromChoices');
|
||||
|
||||
@ -103,6 +109,24 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf
|
||||
*/
|
||||
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
|
||||
{
|
||||
$cache = true;
|
||||
|
||||
if ($loader instanceof Cache\ChoiceLoader) {
|
||||
$loader = $loader->getOption();
|
||||
} else {
|
||||
$cache = false;
|
||||
}
|
||||
|
||||
if ($value instanceof Cache\ChoiceValue) {
|
||||
$value = $value->getOption();
|
||||
} elseif ($value) {
|
||||
$cache = false;
|
||||
}
|
||||
|
||||
if (!$cache) {
|
||||
return $this->decoratedFactory->createListFromLoader($loader, $value);
|
||||
}
|
||||
|
||||
$hash = self::generateHash([$loader, $value], 'fromLoader');
|
||||
|
||||
if (!isset($this->lists[$hash])) {
|
||||
@ -117,8 +141,42 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf
|
||||
*/
|
||||
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
|
||||
{
|
||||
// The input is not validated on purpose. This way, the decorated
|
||||
// factory may decide which input to accept and which not.
|
||||
$cache = true;
|
||||
|
||||
if ($preferredChoices instanceof Cache\PreferredChoice) {
|
||||
$preferredChoices = $preferredChoices->getOption();
|
||||
} elseif ($preferredChoices) {
|
||||
$cache = false;
|
||||
}
|
||||
|
||||
if ($label instanceof Cache\ChoiceLabel) {
|
||||
$label = $label->getOption();
|
||||
} elseif (null !== $label) {
|
||||
$cache = false;
|
||||
}
|
||||
|
||||
if ($index instanceof Cache\ChoiceFieldName) {
|
||||
$index = $index->getOption();
|
||||
} elseif ($index) {
|
||||
$cache = false;
|
||||
}
|
||||
|
||||
if ($groupBy instanceof Cache\GroupBy) {
|
||||
$groupBy = $groupBy->getOption();
|
||||
} elseif ($groupBy) {
|
||||
$cache = false;
|
||||
}
|
||||
|
||||
if ($attr instanceof Cache\ChoiceAttr) {
|
||||
$attr = $attr->getOption();
|
||||
} elseif ($attr) {
|
||||
$cache = false;
|
||||
}
|
||||
|
||||
if (!$cache) {
|
||||
return $this->decoratedFactory->createView($list, $preferredChoices, $label, $index, $groupBy, $attr);
|
||||
}
|
||||
|
||||
$hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr]);
|
||||
|
||||
if (!isset($this->views[$hash])) {
|
||||
@ -139,5 +197,6 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf
|
||||
{
|
||||
$this->lists = [];
|
||||
$this->views = [];
|
||||
Cache\AbstractStaticOption::reset();
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,13 @@ namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceAttr;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceFieldName;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceValue;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\GroupBy;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\Cache\PreferredChoice;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
|
||||
@ -324,13 +331,13 @@ class ChoiceType extends AbstractType
|
||||
|
||||
$resolver->setAllowedTypes('choices', ['null', 'array', '\Traversable']);
|
||||
$resolver->setAllowedTypes('choice_translation_domain', ['null', 'bool', 'string']);
|
||||
$resolver->setAllowedTypes('choice_loader', ['null', 'Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface']);
|
||||
$resolver->setAllowedTypes('choice_label', ['null', 'bool', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath']);
|
||||
$resolver->setAllowedTypes('choice_name', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath']);
|
||||
$resolver->setAllowedTypes('choice_value', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath']);
|
||||
$resolver->setAllowedTypes('choice_attr', ['null', 'array', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath']);
|
||||
$resolver->setAllowedTypes('preferred_choices', ['array', '\Traversable', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath']);
|
||||
$resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath']);
|
||||
$resolver->setAllowedTypes('choice_loader', ['null', 'Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', ChoiceLoader::class]);
|
||||
$resolver->setAllowedTypes('choice_label', ['null', 'bool', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceLabel::class]);
|
||||
$resolver->setAllowedTypes('choice_name', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceFieldName::class]);
|
||||
$resolver->setAllowedTypes('choice_value', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceValue::class]);
|
||||
$resolver->setAllowedTypes('choice_attr', ['null', 'array', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', ChoiceAttr::class]);
|
||||
$resolver->setAllowedTypes('preferred_choices', ['array', '\Traversable', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', PreferredChoice::class]);
|
||||
$resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath', GroupBy::class]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader;
|
||||
use Symfony\Component\Intl\Countries;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
@ -29,9 +30,9 @@ class CountryType extends AbstractType
|
||||
$choiceTranslationLocale = $options['choice_translation_locale'];
|
||||
$alpha3 = $options['alpha3'];
|
||||
|
||||
return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale, $alpha3) {
|
||||
return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale, $alpha3) {
|
||||
return array_flip($alpha3 ? Countries::getAlpha3Names($choiceTranslationLocale) : Countries::getNames($choiceTranslationLocale));
|
||||
});
|
||||
}), [$choiceTranslationLocale, $alpha3]);
|
||||
},
|
||||
'choice_translation_domain' => false,
|
||||
'choice_translation_locale' => null,
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader;
|
||||
use Symfony\Component\Intl\Currencies;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
@ -28,9 +29,9 @@ class CurrencyType extends AbstractType
|
||||
'choice_loader' => function (Options $options) {
|
||||
$choiceTranslationLocale = $options['choice_translation_locale'];
|
||||
|
||||
return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) {
|
||||
return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) {
|
||||
return array_flip(Currencies::getNames($choiceTranslationLocale));
|
||||
});
|
||||
}), $choiceTranslationLocale);
|
||||
},
|
||||
'choice_translation_domain' => false,
|
||||
'choice_translation_locale' => null,
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader;
|
||||
use Symfony\Component\Form\Exception\LogicException;
|
||||
use Symfony\Component\Intl\Exception\MissingResourceException;
|
||||
@ -32,7 +33,7 @@ class LanguageType extends AbstractType
|
||||
$useAlpha3Codes = $options['alpha3'];
|
||||
$choiceSelfTranslation = $options['choice_self_translation'];
|
||||
|
||||
return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale, $useAlpha3Codes, $choiceSelfTranslation) {
|
||||
return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale, $useAlpha3Codes, $choiceSelfTranslation) {
|
||||
if (true === $choiceSelfTranslation) {
|
||||
foreach (Languages::getLanguageCodes() as $alpha2Code) {
|
||||
try {
|
||||
@ -47,7 +48,7 @@ class LanguageType extends AbstractType
|
||||
}
|
||||
|
||||
return array_flip($languagesList);
|
||||
});
|
||||
}), [$choiceTranslationLocale, $useAlpha3Codes, $choiceSelfTranslation]);
|
||||
},
|
||||
'choice_translation_domain' => false,
|
||||
'choice_translation_locale' => null,
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader;
|
||||
use Symfony\Component\Intl\Locales;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
@ -28,9 +29,9 @@ class LocaleType extends AbstractType
|
||||
'choice_loader' => function (Options $options) {
|
||||
$choiceTranslationLocale = $options['choice_translation_locale'];
|
||||
|
||||
return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) {
|
||||
return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) {
|
||||
return array_flip(Locales::getNames($choiceTranslationLocale));
|
||||
});
|
||||
}), $choiceTranslationLocale);
|
||||
},
|
||||
'choice_translation_domain' => false,
|
||||
'choice_translation_locale' => null,
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader;
|
||||
use Symfony\Component\Form\Exception\LogicException;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer;
|
||||
@ -49,14 +49,14 @@ class TimezoneType extends AbstractType
|
||||
if ($options['intl']) {
|
||||
$choiceTranslationLocale = $options['choice_translation_locale'];
|
||||
|
||||
return new IntlCallbackChoiceLoader(function () use ($input, $choiceTranslationLocale) {
|
||||
return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($input, $choiceTranslationLocale) {
|
||||
return self::getIntlTimezones($input, $choiceTranslationLocale);
|
||||
});
|
||||
}), [$input, $choiceTranslationLocale]);
|
||||
}
|
||||
|
||||
return new CallbackChoiceLoader(function () use ($input) {
|
||||
return ChoiceList::lazy($this, function () use ($input) {
|
||||
return self::getPhpTimezones($input);
|
||||
});
|
||||
}, $input);
|
||||
},
|
||||
'choice_translation_domain' => false,
|
||||
'choice_translation_locale' => null,
|
||||
|
@ -14,8 +14,12 @@ namespace Symfony\Component\Form\Tests\ChoiceList\Factory;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
@ -134,7 +138,7 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$list = new ArrayChoiceList([]);
|
||||
$closure = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
$this->decoratedFactory->expects($this->exactly(2))
|
||||
->method('createListFromChoices')
|
||||
->with($choices, $closure)
|
||||
->willReturn($list);
|
||||
@ -143,6 +147,23 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices, $closure));
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesSameValueClosureUseCache()
|
||||
{
|
||||
$choices = [1];
|
||||
$list = new ArrayChoiceList([]);
|
||||
$formType = $this->createMock(FormTypeInterface::class);
|
||||
$valueCallback = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with($choices, $valueCallback)
|
||||
->willReturn($list)
|
||||
;
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices, ChoiceList::value($formType, $valueCallback)));
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices, ChoiceList::value($formType, function () {})));
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesDifferentValueClosure()
|
||||
{
|
||||
$choices = [1];
|
||||
@ -168,14 +189,37 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
{
|
||||
$loader = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface')->getMock();
|
||||
$list = new ArrayChoiceList([]);
|
||||
$list2 = new ArrayChoiceList([]);
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromLoader')
|
||||
->with($loader)
|
||||
->willReturn($list)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromLoader')
|
||||
->with($loader)
|
||||
->willReturn($list2)
|
||||
;
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader));
|
||||
$this->assertSame($list2, $this->factory->createListFromLoader($loader));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderSameLoaderUseCache()
|
||||
{
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$loader = $this->createMock(ChoiceLoaderInterface::class);
|
||||
$list = new ArrayChoiceList([]);
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromLoader')
|
||||
->with($loader)
|
||||
->willReturn($list);
|
||||
->willReturn($list)
|
||||
;
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader(ChoiceList::loader($type, $loader)));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader(ChoiceList::loader($type, $this->createMock(ChoiceLoaderInterface::class))));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderDifferentLoader()
|
||||
@ -201,21 +245,53 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
public function testCreateFromLoaderSameValueClosure()
|
||||
{
|
||||
$loader = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface')->getMock();
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = new ArrayChoiceList([]);
|
||||
$list2 = new ArrayChoiceList([]);
|
||||
$closure = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $closure)
|
||||
->willReturn($list)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $closure)
|
||||
->willReturn($list2)
|
||||
;
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromLoader(ChoiceList::loader($type, $loader), $closure));
|
||||
$this->assertSame($list2, $this->factory->createListFromLoader(ChoiceList::loader($type, $this->createMock(ChoiceLoaderInterface::class)), $closure));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderSameValueClosureUseCache()
|
||||
{
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$loader = $this->createMock(ChoiceLoaderInterface::class);
|
||||
$list = new ArrayChoiceList([]);
|
||||
$closure = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $closure)
|
||||
->willReturn($list);
|
||||
->willReturn($list)
|
||||
;
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader, $closure));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader, $closure));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader(
|
||||
ChoiceList::loader($type, $loader),
|
||||
ChoiceList::value($type, $closure)
|
||||
));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader(
|
||||
ChoiceList::loader($type, $this->createMock(ChoiceLoaderInterface::class)),
|
||||
ChoiceList::value($type, function () {})
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderDifferentValueClosure()
|
||||
{
|
||||
$loader = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface')->getMock();
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list1 = new ArrayChoiceList([]);
|
||||
$list2 = new ArrayChoiceList([]);
|
||||
$closure1 = function () {};
|
||||
@ -230,8 +306,8 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
->with($loader, $closure2)
|
||||
->willReturn($list2);
|
||||
|
||||
$this->assertSame($list1, $this->factory->createListFromLoader($loader, $closure1));
|
||||
$this->assertSame($list2, $this->factory->createListFromLoader($loader, $closure2));
|
||||
$this->assertSame($list1, $this->factory->createListFromLoader(ChoiceList::loader($type, $loader), $closure1));
|
||||
$this->assertSame($list2, $this->factory->createListFromLoader(ChoiceList::loader($type, $this->createMock(ChoiceLoaderInterface::class)), $closure2));
|
||||
}
|
||||
|
||||
public function testCreateViewSamePreferredChoices()
|
||||
@ -239,14 +315,38 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$preferred = ['a'];
|
||||
$list = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\ChoiceListInterface')->getMock();
|
||||
$view = new ChoiceListView();
|
||||
$view2 = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->willReturn($view)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->willReturn($view2)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view2, $this->factory->createView($list, $preferred));
|
||||
}
|
||||
|
||||
public function testCreateViewSamePreferredChoicesUseCache()
|
||||
{
|
||||
$preferred = ['a'];
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = $this->createMock(ChoiceListInterface::class);
|
||||
$view = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->willReturn($view);
|
||||
->willReturn($view)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view, $this->factory->createView($list, ChoiceList::preferred($type, $preferred)));
|
||||
$this->assertSame($view, $this->factory->createView($list, ChoiceList::preferred($type, ['a'])));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentPreferredChoices()
|
||||
@ -275,14 +375,38 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$preferred = function () {};
|
||||
$list = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\ChoiceListInterface')->getMock();
|
||||
$view = new ChoiceListView();
|
||||
$view2 = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->willReturn($view)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->willReturn($view2)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view2, $this->factory->createView($list, $preferred));
|
||||
}
|
||||
|
||||
public function testCreateViewSamePreferredChoicesClosureUseCache()
|
||||
{
|
||||
$preferredCallback = function () {};
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = $this->createMock(ChoiceListInterface::class);
|
||||
$view = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->willReturn($view);
|
||||
->with($list, $preferredCallback)
|
||||
->willReturn($view)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view, $this->factory->createView($list, ChoiceList::preferred($type, $preferredCallback)));
|
||||
$this->assertSame($view, $this->factory->createView($list, ChoiceList::preferred($type, function () {})));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentPreferredChoicesClosure()
|
||||
@ -311,14 +435,38 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$labels = function () {};
|
||||
$list = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\ChoiceListInterface')->getMock();
|
||||
$view = new ChoiceListView();
|
||||
$view2 = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, $labels)
|
||||
->willReturn($view)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, $labels)
|
||||
->willReturn($view2)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, $labels));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, $labels));
|
||||
}
|
||||
|
||||
public function testCreateViewSameLabelClosureUseCache()
|
||||
{
|
||||
$labelsCallback = function () {};
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = $this->createMock(ChoiceListInterface::class);
|
||||
$view = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, $labels)
|
||||
->willReturn($view);
|
||||
->with($list, null, $labelsCallback)
|
||||
->willReturn($view)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, $labels));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, $labels));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, ChoiceList::label($type, $labelsCallback)));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, ChoiceList::label($type, function () {})));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentLabelClosure()
|
||||
@ -347,14 +495,38 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$index = function () {};
|
||||
$list = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\ChoiceListInterface')->getMock();
|
||||
$view = new ChoiceListView();
|
||||
$view2 = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, $index)
|
||||
->willReturn($view)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, $index)
|
||||
->willReturn($view2)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, $index));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, $index));
|
||||
}
|
||||
|
||||
public function testCreateViewSameIndexClosureUseCache()
|
||||
{
|
||||
$indexCallback = function () {};
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = $this->createMock(ChoiceListInterface::class);
|
||||
$view = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, $index)
|
||||
->willReturn($view);
|
||||
->with($list, null, null, $indexCallback)
|
||||
->willReturn($view)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, $index));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, $index));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, ChoiceList::fieldName($type, $indexCallback)));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, ChoiceList::fieldName($type, function () {})));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentIndexClosure()
|
||||
@ -383,14 +555,38 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$groupBy = function () {};
|
||||
$list = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\ChoiceListInterface')->getMock();
|
||||
$view = new ChoiceListView();
|
||||
$view2 = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $groupBy)
|
||||
->willReturn($view)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $groupBy)
|
||||
->willReturn($view2)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, $groupBy));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, null, $groupBy));
|
||||
}
|
||||
|
||||
public function testCreateViewSameGroupByClosureUseCache()
|
||||
{
|
||||
$groupByCallback = function () {};
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = $this->createMock(ChoiceListInterface::class);
|
||||
$view = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $groupBy)
|
||||
->willReturn($view);
|
||||
->with($list, null, null, null, $groupByCallback)
|
||||
->willReturn($view)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, $groupBy));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, $groupBy));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, ChoiceList::groupBy($type, $groupByCallback)));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, ChoiceList::groupBy($type, function () {})));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentGroupByClosure()
|
||||
@ -419,14 +615,37 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$attr = ['class' => 'foobar'];
|
||||
$list = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\ChoiceListInterface')->getMock();
|
||||
$view = new ChoiceListView();
|
||||
$view2 = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->willReturn($view)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->willReturn($view2)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
}
|
||||
|
||||
public function testCreateViewSameAttributesUseCache()
|
||||
{
|
||||
$attr = ['class' => 'foobar'];
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = $this->createMock(ChoiceListInterface::class);
|
||||
$view = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->willReturn($view);
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, ChoiceList::attr($type, $attr)));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, ChoiceList::attr($type, ['class' => 'foobar'])));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentAttributes()
|
||||
@ -455,14 +674,37 @@ class CachingFactoryDecoratorTest extends TestCase
|
||||
$attr = function () {};
|
||||
$list = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\ChoiceListInterface')->getMock();
|
||||
$view = new ChoiceListView();
|
||||
$view2 = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->willReturn($view)
|
||||
;
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->willReturn($view2)
|
||||
;
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
}
|
||||
|
||||
public function testCreateViewSameAttributesClosureUseCache()
|
||||
{
|
||||
$attrCallback = function () {};
|
||||
$type = $this->createMock(FormTypeInterface::class);
|
||||
$list = $this->createMock(ChoiceListInterface::class);
|
||||
$view = new ChoiceListView();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->with($list, null, null, null, null, $attrCallback)
|
||||
->willReturn($view);
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, $attr));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, ChoiceList::attr($type, $attrCallback)));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, null, ChoiceList::attr($type, function () {})));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentAttributesClosure()
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Component\Form\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class LazyChoiceTypeExtension extends AbstractTypeExtension
|
||||
@ -24,7 +24,7 @@ class LazyChoiceTypeExtension extends AbstractTypeExtension
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('choice_loader', new CallbackChoiceLoader(function () {
|
||||
$resolver->setDefault('choice_loader', ChoiceList::lazy($this, function () {
|
||||
return [
|
||||
'Lazy A' => 'lazy_a',
|
||||
'Lazy B' => 'lazy_b',
|
||||
|
Reference in New Issue
Block a user