login consent app sql

This commit is contained in:
2022-05-03 08:54:45 +02:00
parent e7253acfd8
commit f9a6535906
1652 changed files with 187600 additions and 45 deletions

View File

@ -0,0 +1,238 @@
<?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;
/**
* A list of choices with arbitrary data types.
*
* The user of this class is responsible for assigning string values to the
* choices annd for their uniqueness.
* Both the choices and their values are passed to the constructor.
* Each choice must have a corresponding value (with the same key) in
* the values array.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ArrayChoiceList implements ChoiceListInterface
{
/**
* The choices in the list.
*
* @var array
*/
protected $choices;
/**
* The values indexed by the original keys.
*
* @var array
*/
protected $structuredValues;
/**
* The original keys of the choices array.
*
* @var int[]|string[]
*/
protected $originalKeys;
protected $valueCallback;
/**
* Creates a list with the given choices and values.
*
* The given choice array must have the same array keys as the value array.
*
* @param iterable $choices The selectable choices
* @param callable|null $value The callable for creating the value
* for a choice. If `null` is passed,
* incrementing integers are used as
* values
*/
public function __construct(iterable $choices, callable $value = null)
{
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
if (null === $value && $this->castableToString($choices)) {
$value = function ($choice) {
return false === $choice ? '0' : (string) $choice;
};
}
if (null !== $value) {
// If a deterministic value generator was passed, use it later
$this->valueCallback = $value;
} else {
// Otherwise generate incrementing integers as values
$i = 0;
$value = function () use (&$i) {
return $i++;
};
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
$this->flatten($choices, $value, $choicesByValues, $keysByValues, $structuredValues);
$this->choices = $choicesByValues;
$this->originalKeys = $keysByValues;
$this->structuredValues = $structuredValues;
}
/**
* {@inheritdoc}
*/
public function getChoices()
{
return $this->choices;
}
/**
* {@inheritdoc}
*/
public function getValues()
{
return array_map('strval', array_keys($this->choices));
}
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
return $this->structuredValues;
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
return $this->originalKeys;
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
$choices = [];
foreach ($values as $i => $givenValue) {
if (\array_key_exists($givenValue, $this->choices)) {
$choices[$i] = $this->choices[$givenValue];
}
}
return $choices;
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
$values = [];
// Use the value callback to compare choices by their values, if present
if ($this->valueCallback) {
$givenValues = [];
foreach ($choices as $i => $givenChoice) {
$givenValues[$i] = (string) ($this->valueCallback)($givenChoice);
}
return array_intersect($givenValues, array_keys($this->choices));
}
// Otherwise compare choices by identity
foreach ($choices as $i => $givenChoice) {
foreach ($this->choices as $value => $choice) {
if ($choice === $givenChoice) {
$values[$i] = (string) $value;
break;
}
}
}
return $values;
}
/**
* Flattens an array into the given output variables.
*
* @param array $choices The array to flatten
* @param callable $value The callable for generating choice values
* @param array|null $choicesByValues The flattened choices indexed by the
* corresponding values
* @param array|null $keysByValues The original keys indexed by the
* corresponding values
* @param array|null $structuredValues The values indexed by the original keys
*
* @internal
*/
protected function flatten(array $choices, callable $value, ?array &$choicesByValues, ?array &$keysByValues, ?array &$structuredValues)
{
if (null === $choicesByValues) {
$choicesByValues = [];
$keysByValues = [];
$structuredValues = [];
}
foreach ($choices as $key => $choice) {
if (\is_array($choice)) {
$this->flatten($choice, $value, $choicesByValues, $keysByValues, $structuredValues[$key]);
continue;
}
$choiceValue = (string) $value($choice);
$choicesByValues[$choiceValue] = $choice;
$keysByValues[$choiceValue] = $key;
$structuredValues[$key] = $choiceValue;
}
}
/**
* Checks whether the given choices can be cast to strings without
* generating duplicates.
* This method is responsible for preventing conflict between scalar values
* and the empty value.
*/
private function castableToString(array $choices, array &$cache = []): bool
{
foreach ($choices as $choice) {
if (\is_array($choice)) {
if (!$this->castableToString($choice, $cache)) {
return false;
}
continue;
} elseif (!is_scalar($choice)) {
return false;
}
// prevent having false casted to the empty string by isset()
$choice = false === $choice ? '0' : (string) $choice;
if (isset($cache[$choice])) {
return false;
}
$cache[$choice] = true;
}
return true;
}
}

View File

@ -0,0 +1,159 @@
<?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\ChoiceFilter;
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLabel;
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader;
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceTranslationParameters;
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);
}
/**
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
* @param callable $filter Any pseudo callable to filter a choice list
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the callback
*/
public static function filter($formType, $filter, $vary = null): ChoiceFilter
{
return new ChoiceFilter($formType, $filter, $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 "choice_translation_parameters" option to make it cacheable.
*
* @param FormTypeInterface|FormTypeExtensionInterface $formType A form type or type extension configuring a cacheable choice list
* @param callable|array $translationParameters Any pseudo callable or array to create translation parameters from a choice
* @param mixed|null $vary Dynamic data used to compute a unique hash when caching the option
*/
public static function translationParameters($formType, $translationParameters, $vary = null): ChoiceTranslationParameters
{
return new ChoiceTranslationParameters($formType, $translationParameters, $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()
{
}
}

View File

@ -0,0 +1,140 @@
<?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;
/**
* A list of choices that can be selected in a choice field.
*
* A choice list assigns unique string values to each of a list of choices.
* These string values are displayed in the "value" attributes in HTML and
* submitted back to the server.
*
* The acceptable data types for the choices depend on the implementation.
* Values must always be strings and (within the list) free of duplicates.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceListInterface
{
/**
* Returns all selectable choices.
*
* @return array The selectable choices indexed by the corresponding values
*/
public function getChoices();
/**
* Returns the values for the choices.
*
* The values are strings that do not contain duplicates:
*
* $form->add('field', 'choice', [
* 'choices' => [
* 'Decided' => ['Yes' => true, 'No' => false],
* 'Undecided' => ['Maybe' => null],
* ],
* ]);
*
* In this example, the result of this method is:
*
* [
* 'Yes' => '0',
* 'No' => '1',
* 'Maybe' => '2',
* ]
*
* Null and false MUST NOT conflict when being casted to string.
* For this some default incremented values SHOULD be computed.
*
* @return string[]
*/
public function getValues();
/**
* Returns the values in the structure originally passed to the list.
*
* Contrary to {@link getValues()}, the result is indexed by the original
* keys of the choices. If the original array contained nested arrays, these
* nested arrays are represented here as well:
*
* $form->add('field', 'choice', [
* 'choices' => [
* 'Decided' => ['Yes' => true, 'No' => false],
* 'Undecided' => ['Maybe' => null],
* ],
* ]);
*
* In this example, the result of this method is:
*
* [
* 'Decided' => ['Yes' => '0', 'No' => '1'],
* 'Undecided' => ['Maybe' => '2'],
* ]
*
* Nested arrays do not make sense in a view format unless
* they are used as a convenient way of grouping.
* If the implementation does not intend to support grouped choices,
* this method SHOULD be equivalent to {@link getValues()}.
* The $groupBy callback parameter SHOULD be used instead.
*
* @return string[]
*/
public function getStructuredValues();
/**
* Returns the original keys of the choices.
*
* The original keys are the keys of the choice array that was passed in the
* "choice" option of the choice type. Note that this array may contain
* duplicates if the "choice" option contained choice groups:
*
* $form->add('field', 'choice', [
* 'choices' => [
* 'Decided' => [true, false],
* 'Undecided' => [null],
* ],
* ]);
*
* In this example, the original key 0 appears twice, once for `true` and
* once for `null`.
*
* @return int[]|string[] The original choice keys indexed by the
* corresponding choice values
*/
public function getOriginalKeys();
/**
* Returns the choices corresponding to the given values.
*
* The choices are returned with the same keys and in the same order as the
* corresponding values in the given array.
*
* @param string[] $values An array of choice values. Non-existing values in
* this array are ignored
*
* @return array
*/
public function getChoicesForValues(array $values);
/**
* Returns the values corresponding to the given choices.
*
* The values are returned with the same keys and in the same order as the
* corresponding choices in the given array.
*
* @param array $choices An array of choices. Non-existing choices in this
* array are ignored
*
* @return string[]
*/
public function getValuesForChoices(array $choices);
}

View File

@ -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, get_debug_type($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 = [];
}
}

View File

@ -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
{
}

View File

@ -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
{
}

View File

@ -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_filter" option.
*
* @internal
*
* @author Jules Pietri <jules@heahprod.com>
*/
final class ChoiceFilter extends AbstractStaticOption
{
}

View File

@ -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
{
}

View File

@ -0,0 +1,52 @@
<?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\ChoiceListInterface;
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): ChoiceListInterface
{
return $this->getOption()->loadChoiceList($value);
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, callable $value = null): array
{
return $this->getOption()->loadChoicesForValues($values, $value);
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, callable $value = null): array
{
return $this->getOption()->loadValuesForChoices($choices, $value);
}
}

View File

@ -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_translation_parameters" option.
*
* @internal
*
* @author Vincent Langlet <vincentlanglet@users.noreply.github.com>
*/
final class ChoiceTranslationParameters extends AbstractStaticOption
{
}

View File

@ -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
{
}

View File

@ -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
{
}

View File

@ -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
{
}

View File

@ -0,0 +1,254 @@
<?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;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Contracts\Service\ResetInterface;
/**
* Caches the choice lists created by the decorated factory.
*
* To cache a list based on its options, arguments must be decorated
* by a {@see Cache\AbstractStaticOption} implementation.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Jules Pietri <jules@heahprod.com>
*/
class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterface
{
private $decoratedFactory;
/**
* @var ChoiceListInterface[]
*/
private $lists = [];
/**
* @var ChoiceListView[]
*/
private $views = [];
/**
* Generates a SHA-256 hash for the given value.
*
* Optionally, a namespace string can be passed. Calling this method will
* the same values, but different namespaces, will return different hashes.
*
* @param mixed $value The value to hash
*
* @return string The SHA-256 hash
*
* @internal
*/
public static function generateHash($value, string $namespace = ''): string
{
if (\is_object($value)) {
$value = spl_object_hash($value);
} elseif (\is_array($value)) {
array_walk_recursive($value, function (&$v) {
if (\is_object($v)) {
$v = spl_object_hash($v);
}
});
}
return hash('sha256', $namespace.':'.serialize($value));
}
public function __construct(ChoiceListFactoryInterface $decoratedFactory)
{
$this->decoratedFactory = $decoratedFactory;
}
/**
* Returns the decorated factory.
*
* @return ChoiceListFactoryInterface
*/
public function getDecoratedFactory()
{
return $this->decoratedFactory;
}
/**
* {@inheritdoc}
*
* @param mixed $value
* @param mixed $filter
*/
public function createListFromChoices(iterable $choices, $value = null/*, $filter = null*/)
{
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
$cache = true;
// Only cache per value and filter 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) {
$cache = false;
}
if ($filter instanceof Cache\ChoiceFilter) {
$filter = $filter->getOption();
} elseif ($filter) {
$cache = false;
}
if (!$cache) {
return $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
}
$hash = self::generateHash([$choices, $value, $filter], 'fromChoices');
if (!isset($this->lists[$hash])) {
$this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
}
return $this->lists[$hash];
}
/**
* {@inheritdoc}
*
* @param mixed $value
* @param mixed $filter
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null/*, $filter = null*/)
{
$filter = \func_num_args() > 2 ? func_get_arg(2) : 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 ($filter instanceof Cache\ChoiceFilter) {
$filter = $filter->getOption();
} elseif ($filter) {
$cache = false;
}
if (!$cache) {
return $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
}
$hash = self::generateHash([$loader, $value, $filter], 'fromLoader');
if (!isset($this->lists[$hash])) {
$this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
}
return $this->lists[$hash];
}
/**
* {@inheritdoc}
*
* @param mixed $preferredChoices
* @param mixed $label
* @param mixed $index
* @param mixed $groupBy
* @param mixed $attr
* @param mixed $labelTranslationParameters
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/)
{
$labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : [];
$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 ($labelTranslationParameters instanceof Cache\ChoiceTranslationParameters) {
$labelTranslationParameters = $labelTranslationParameters->getOption();
} elseif ([] !== $labelTranslationParameters) {
$cache = false;
}
if (!$cache) {
return $this->decoratedFactory->createView(
$list,
$preferredChoices,
$label,
$index,
$groupBy,
$attr,
$labelTranslationParameters
);
}
$hash = self::generateHash([$list, $preferredChoices, $label, $index, $groupBy, $attr, $labelTranslationParameters]);
if (!isset($this->views[$hash])) {
$this->views[$hash] = $this->decoratedFactory->createView(
$list,
$preferredChoices,
$label,
$index,
$groupBy,
$attr,
$labelTranslationParameters
);
}
return $this->views[$hash];
}
public function reset()
{
$this->lists = [];
$this->views = [];
Cache\AbstractStaticOption::reset();
}
}

View File

@ -0,0 +1,88 @@
<?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;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
/**
* Creates {@link ChoiceListInterface} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceListFactoryInterface
{
/**
* Creates a choice list for the given choices.
*
* The choices should be passed in the values of the choices array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as only argument.
* Null may be passed when the choice list contains the empty value.
*
* @param callable|null $filter The callable filtering the choices
*
* @return ChoiceListInterface
*/
public function createListFromChoices(iterable $choices, callable $value = null/*, callable $filter = null*/);
/**
* Creates a choice list that is loaded with the given loader.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as only argument.
* Null may be passed when the choice list contains the empty value.
*
* @param callable|null $filter The callable filtering the choices
*
* @return ChoiceListInterface
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null/*, callable $filter = null*/);
/**
* Creates a view for the given choice list.
*
* Callables may be passed for all optional arguments. The callables receive
* the choice as first and the array key as the second argument.
*
* * The callable for the label and the name should return the generated
* label/choice name.
* * The callable for the preferred choices should return true or false,
* depending on whether the choice should be preferred or not.
* * The callable for the grouping should return the group name or null if
* a choice should not be grouped.
* * The callable for the attributes should return an array of HTML
* attributes that will be inserted in the tag of the choice.
*
* If no callable is passed, the labels will be generated from the choice
* keys. The view indices will be generated using an incrementing integer
* by default.
*
* The preferred choices can also be passed as array. Each choice that is
* contained in that array will be marked as preferred.
*
* The attributes can be passed as multi-dimensional array. The keys should
* match the keys of the choices. The values should be arrays of HTML
* attributes that should be added to the respective choice.
*
* @param array|callable|null $preferredChoices The preferred choices
* @param callable|false|null $label The callable generating the choice labels;
* pass false to discard the label
* @param array|callable|null $attr The callable generating the HTML attributes
* @param array|callable $labelTranslationParameters The parameters used to translate the choice labels
*
* @return ChoiceListView
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/);
}

View File

@ -0,0 +1,322 @@
<?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;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Translation\TranslatableMessage;
/**
* Default implementation of {@link ChoiceListFactoryInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Jules Pietri <jules@heahprod.com>
*/
class DefaultChoiceListFactory implements ChoiceListFactoryInterface
{
/**
* {@inheritdoc}
*
* @param callable|null $filter
*/
public function createListFromChoices(iterable $choices, callable $value = null/*, callable $filter = null*/)
{
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
if ($filter) {
// filter the choice list lazily
return $this->createListFromLoader(new FilterChoiceLoaderDecorator(
new CallbackChoiceLoader(static function () use ($choices) {
return $choices;
}
), $filter), $value);
}
return new ArrayChoiceList($choices, $value);
}
/**
* {@inheritdoc}
*
* @param callable|null $filter
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, callable $value = null/*, callable $filter = null*/)
{
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
if ($filter) {
$loader = new FilterChoiceLoaderDecorator($loader, $filter);
}
return new LazyChoiceList($loader, $value);
}
/**
* {@inheritdoc}
*
* @param array|callable $labelTranslationParameters The parameters used to translate the choice labels
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, callable $index = null, callable $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/)
{
$labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : [];
$preferredViews = [];
$preferredViewsOrder = [];
$otherViews = [];
$choices = $list->getChoices();
$keys = $list->getOriginalKeys();
if (!\is_callable($preferredChoices)) {
if (empty($preferredChoices)) {
$preferredChoices = null;
} else {
// make sure we have keys that reflect order
$preferredChoices = array_values($preferredChoices);
$preferredChoices = static function ($choice) use ($preferredChoices) {
return array_search($choice, $preferredChoices, true);
};
}
}
// The names are generated from an incrementing integer by default
if (null === $index) {
$index = 0;
}
// If $groupBy is a callable returning a string
// choices are added to the group with the name returned by the callable.
// If $groupBy is a callable returning an array
// choices are added to the groups with names returned by the callable
// If the callable returns null, the choice is not added to any group
if (\is_callable($groupBy)) {
foreach ($choices as $value => $choice) {
self::addChoiceViewsGroupedByCallable(
$groupBy,
$choice,
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$preferredChoices,
$preferredViews,
$preferredViewsOrder,
$otherViews
);
}
// Remove empty group views that may have been created by
// addChoiceViewsGroupedByCallable()
foreach ($preferredViews as $key => $view) {
if ($view instanceof ChoiceGroupView && 0 === \count($view->choices)) {
unset($preferredViews[$key]);
}
}
foreach ($otherViews as $key => $view) {
if ($view instanceof ChoiceGroupView && 0 === \count($view->choices)) {
unset($otherViews[$key]);
}
}
foreach ($preferredViewsOrder as $key => $groupViewsOrder) {
if ($groupViewsOrder) {
$preferredViewsOrder[$key] = min($groupViewsOrder);
} else {
unset($preferredViewsOrder[$key]);
}
}
} else {
// Otherwise use the original structure of the choices
self::addChoiceViewsFromStructuredValues(
$list->getStructuredValues(),
$label,
$choices,
$keys,
$index,
$attr,
$labelTranslationParameters,
$preferredChoices,
$preferredViews,
$preferredViewsOrder,
$otherViews
);
}
uksort($preferredViews, static function ($a, $b) use ($preferredViewsOrder): int {
return isset($preferredViewsOrder[$a], $preferredViewsOrder[$b])
? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b]
: 0;
});
return new ChoiceListView($otherViews, $preferredViews);
}
private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews)
{
// $value may be an integer or a string, since it's stored in the array
// keys. We want to guarantee it's a string though.
$key = $keys[$value];
$nextIndex = \is_int($index) ? $index++ : $index($choice, $key, $value);
// BC normalize label to accept a false value
if (null === $label) {
// If the labels are null, use the original choice key by default
$label = (string) $key;
} elseif (false !== $label) {
// If "choice_label" is set to false and "expanded" is true, the value false
// should be passed on to the "label" option of the checkboxes/radio buttons
$dynamicLabel = $label($choice, $key, $value);
if (false === $dynamicLabel) {
$label = false;
} elseif ($dynamicLabel instanceof TranslatableMessage) {
$label = $dynamicLabel;
} else {
$label = (string) $dynamicLabel;
}
}
$view = new ChoiceView(
$choice,
$value,
$label,
// The attributes may be a callable or a mapping from choice indices
// to nested arrays
\is_callable($attr) ? $attr($choice, $key, $value) : ($attr[$key] ?? []),
// The label translation parameters may be a callable or a mapping from choice indices
// to nested arrays
\is_callable($labelTranslationParameters) ? $labelTranslationParameters($choice, $key, $value) : ($labelTranslationParameters[$key] ?? [])
);
// $isPreferred may be null if no choices are preferred
if (null !== $isPreferred && false !== $preferredKey = $isPreferred($choice, $key, $value)) {
$preferredViews[$nextIndex] = $view;
$preferredViewsOrder[$nextIndex] = $preferredKey;
}
$otherViews[$nextIndex] = $view;
}
private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews)
{
foreach ($values as $key => $value) {
if (null === $value) {
continue;
}
// Add the contents of groups to new ChoiceGroupView instances
if (\is_array($value)) {
$preferredViewsForGroup = [];
$otherViewsForGroup = [];
self::addChoiceViewsFromStructuredValues(
$value,
$label,
$choices,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViewsForGroup,
$preferredViewsOrder,
$otherViewsForGroup
);
if (\count($preferredViewsForGroup) > 0) {
$preferredViews[$key] = new ChoiceGroupView($key, $preferredViewsForGroup);
}
if (\count($otherViewsForGroup) > 0) {
$otherViews[$key] = new ChoiceGroupView($key, $otherViewsForGroup);
}
continue;
}
// Add ungrouped items directly
self::addChoiceView(
$choices[$value],
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViews,
$preferredViewsOrder,
$otherViews
);
}
}
private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews)
{
$groupLabels = $groupBy($choice, $keys[$value], $value);
if (null === $groupLabels) {
// If the callable returns null, don't group the choice
self::addChoiceView(
$choice,
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViews,
$preferredViewsOrder,
$otherViews
);
return;
}
$groupLabels = \is_array($groupLabels) ? array_map('strval', $groupLabels) : [(string) $groupLabels];
foreach ($groupLabels as $groupLabel) {
// Initialize the group views if necessary. Unnecessarily built group
// views will be cleaned up at the end of createView()
if (!isset($preferredViews[$groupLabel])) {
$preferredViews[$groupLabel] = new ChoiceGroupView($groupLabel);
$otherViews[$groupLabel] = new ChoiceGroupView($groupLabel);
}
if (!isset($preferredViewsOrder[$groupLabel])) {
$preferredViewsOrder[$groupLabel] = [];
}
self::addChoiceView(
$choice,
$value,
$label,
$keys,
$index,
$attr,
$labelTranslationParameters,
$isPreferred,
$preferredViews[$groupLabel]->choices,
$preferredViewsOrder[$groupLabel],
$otherViews[$groupLabel]->choices
);
}
}
}

View File

@ -0,0 +1,239 @@
<?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;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyPathInterface;
/**
* Adds property path support to a choice list factory.
*
* Pass the decorated factory to the constructor:
*
* $decorator = new PropertyAccessDecorator($factory);
*
* You can now pass property paths for generating choice values, labels, view
* indices, HTML attributes and for determining the preferred choices and the
* choice groups:
*
* // extract values from the $value property
* $list = $createListFromChoices($objects, 'value');
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class PropertyAccessDecorator implements ChoiceListFactoryInterface
{
private $decoratedFactory;
private $propertyAccessor;
public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null)
{
$this->decoratedFactory = $decoratedFactory;
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
/**
* Returns the decorated factory.
*
* @return ChoiceListFactoryInterface
*/
public function getDecoratedFactory()
{
return $this->decoratedFactory;
}
/**
* {@inheritdoc}
*
* @param mixed $value
* @param mixed $filter
*
* @return ChoiceListInterface
*/
public function createListFromChoices(iterable $choices, $value = null/*, $filter = null*/)
{
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
if (\is_string($value)) {
$value = new PropertyPath($value);
}
if ($value instanceof PropertyPathInterface) {
$accessor = $this->propertyAccessor;
$value = function ($choice) use ($accessor, $value) {
// The callable may be invoked with a non-object/array value
// when such values are passed to
// ChoiceListInterface::getValuesForChoices(). Handle this case
// so that the call to getValue() doesn't break.
return \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null;
};
}
if (\is_string($filter)) {
$filter = new PropertyPath($filter);
}
if ($filter instanceof PropertyPath) {
$accessor = $this->propertyAccessor;
$filter = static function ($choice) use ($accessor, $filter) {
return (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter);
};
}
return $this->decoratedFactory->createListFromChoices($choices, $value, $filter);
}
/**
* {@inheritdoc}
*
* @param mixed $value
* @param mixed $filter
*
* @return ChoiceListInterface
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null/*, $filter = null*/)
{
$filter = \func_num_args() > 2 ? func_get_arg(2) : null;
if (\is_string($value)) {
$value = new PropertyPath($value);
}
if ($value instanceof PropertyPathInterface) {
$accessor = $this->propertyAccessor;
$value = function ($choice) use ($accessor, $value) {
// The callable may be invoked with a non-object/array value
// when such values are passed to
// ChoiceListInterface::getValuesForChoices(). Handle this case
// so that the call to getValue() doesn't break.
return \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null;
};
}
if (\is_string($filter)) {
$filter = new PropertyPath($filter);
}
if ($filter instanceof PropertyPath) {
$accessor = $this->propertyAccessor;
$filter = static function ($choice) use ($accessor, $filter) {
return (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter);
};
}
return $this->decoratedFactory->createListFromLoader($loader, $value, $filter);
}
/**
* {@inheritdoc}
*
* @param mixed $preferredChoices
* @param mixed $label
* @param mixed $index
* @param mixed $groupBy
* @param mixed $attr
* @param mixed $labelTranslationParameters
*
* @return ChoiceListView
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null/*, $labelTranslationParameters = []*/)
{
$labelTranslationParameters = \func_num_args() > 6 ? func_get_arg(6) : [];
$accessor = $this->propertyAccessor;
if (\is_string($label)) {
$label = new PropertyPath($label);
}
if ($label instanceof PropertyPathInterface) {
$label = function ($choice) use ($accessor, $label) {
return $accessor->getValue($choice, $label);
};
}
if (\is_string($preferredChoices)) {
$preferredChoices = new PropertyPath($preferredChoices);
}
if ($preferredChoices instanceof PropertyPathInterface) {
$preferredChoices = function ($choice) use ($accessor, $preferredChoices) {
try {
return $accessor->getValue($choice, $preferredChoices);
} catch (UnexpectedTypeException $e) {
// Assume not preferred if not readable
return false;
}
};
}
if (\is_string($index)) {
$index = new PropertyPath($index);
}
if ($index instanceof PropertyPathInterface) {
$index = function ($choice) use ($accessor, $index) {
return $accessor->getValue($choice, $index);
};
}
if (\is_string($groupBy)) {
$groupBy = new PropertyPath($groupBy);
}
if ($groupBy instanceof PropertyPathInterface) {
$groupBy = function ($choice) use ($accessor, $groupBy) {
try {
return $accessor->getValue($choice, $groupBy);
} catch (UnexpectedTypeException $e) {
// Don't group if path is not readable
return null;
}
};
}
if (\is_string($attr)) {
$attr = new PropertyPath($attr);
}
if ($attr instanceof PropertyPathInterface) {
$attr = function ($choice) use ($accessor, $attr) {
return $accessor->getValue($choice, $attr);
};
}
if (\is_string($labelTranslationParameters)) {
$labelTranslationParameters = new PropertyPath($labelTranslationParameters);
}
if ($labelTranslationParameters instanceof PropertyPath) {
$labelTranslationParameters = static function ($choice) use ($accessor, $labelTranslationParameters) {
return $accessor->getValue($choice, $labelTranslationParameters);
};
}
return $this->decoratedFactory->createView(
$list,
$preferredChoices,
$label,
$index,
$groupBy,
$attr,
$labelTranslationParameters
);
}
}

View File

@ -0,0 +1,103 @@
<?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\Loader\ChoiceLoaderInterface;
/**
* A choice list that loads its choices lazily.
*
* The choices are fetched using a {@link ChoiceLoaderInterface} instance.
* If only {@link getChoicesForValues()} or {@link getValuesForChoices()} is
* called, the choice list is only loaded partially for improved performance.
*
* Once {@link getChoices()} or {@link getValues()} is called, the list is
* loaded fully.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LazyChoiceList implements ChoiceListInterface
{
private $loader;
/**
* The callable creating string values for each choice.
*
* If null, choices are cast to strings.
*
* @var callable|null
*/
private $value;
/**
* Creates a lazily-loaded list using the given loader.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param callable|null $value The callable generating the choice values
*/
public function __construct(ChoiceLoaderInterface $loader, callable $value = null)
{
$this->loader = $loader;
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function getChoices()
{
return $this->loader->loadChoiceList($this->value)->getChoices();
}
/**
* {@inheritdoc}
*/
public function getValues()
{
return $this->loader->loadChoiceList($this->value)->getValues();
}
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
return $this->loader->loadChoiceList($this->value)->getStructuredValues();
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
return $this->loader->loadChoiceList($this->value)->getOriginalKeys();
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
return $this->loader->loadChoicesForValues($values, $this->value);
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
return $this->loader->loadValuesForChoices($choices, $this->value);
}
}

View File

@ -0,0 +1,87 @@
<?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;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
/**
* @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): ChoiceListInterface
{
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);
}
}

View File

@ -0,0 +1,35 @@
<?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;
/**
* Loads an {@link ArrayChoiceList} instance from a callable returning iterable choices.
*
* @author Jules Pietri <jules@heahprod.com>
*/
class CallbackChoiceLoader extends AbstractChoiceLoader
{
private $callback;
/**
* @param callable $callback The callable returning iterable choices
*/
public function __construct(callable $callback)
{
$this->callback = $callback;
}
protected function loadChoices(): iterable
{
return ($this->callback)();
}
}

View File

@ -0,0 +1,76 @@
<?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\ChoiceListInterface;
/**
* Loads a choice list.
*
* The methods {@link loadChoicesForValues()} and {@link loadValuesForChoices()}
* can be used to load the list only partially in cases where a fully-loaded
* list is not necessary.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceLoaderInterface
{
/**
* Loads a list of choices.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as only argument.
* Null may be passed when the choice list contains the empty value.
*
* @param callable|null $value The callable which generates the values
* from choices
*
* @return ChoiceListInterface
*/
public function loadChoiceList(callable $value = null);
/**
* Loads the choices corresponding to the given values.
*
* The choices are returned with the same keys and in the same order as the
* corresponding values in the given array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as only argument.
* Null may be passed when the choice list contains the empty value.
*
* @param string[] $values An array of choice values. Non-existing
* values in this array are ignored
* @param callable|null $value The callable generating the choice values
*
* @return array
*/
public function loadChoicesForValues(array $values, callable $value = null);
/**
* Loads the values corresponding to the given choices.
*
* The values are returned with the same keys and in the same order as the
* corresponding choices in the given array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as only argument.
* Null may be passed when the choice list contains the empty value.
*
* @param array $choices An array of choices. Non-existing choices in
* this array are ignored
* @param callable|null $value The callable generating the choice values
*
* @return string[]
*/
public function loadValuesForChoices(array $choices, callable $value = null);
}

View File

@ -0,0 +1,63 @@
<?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;
/**
* A decorator to filter choices only when they are loaded or partially loaded.
*
* @author Jules Pietri <jules@heahprod.com>
*/
class FilterChoiceLoaderDecorator extends AbstractChoiceLoader
{
private $decoratedLoader;
private $filter;
public function __construct(ChoiceLoaderInterface $loader, callable $filter)
{
$this->decoratedLoader = $loader;
$this->filter = $filter;
}
protected function loadChoices(): iterable
{
$list = $this->decoratedLoader->loadChoiceList();
if (array_values($list->getValues()) === array_values($structuredValues = $list->getStructuredValues())) {
return array_filter(array_combine($list->getOriginalKeys(), $list->getChoices()), $this->filter);
}
foreach ($structuredValues as $group => $values) {
if ($values && $filtered = array_filter($list->getChoicesForValues($values), $this->filter)) {
$choices[$group] = $filtered;
}
// filter empty groups
}
return $choices ?? [];
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, callable $value = null): array
{
return array_filter($this->decoratedLoader->loadChoicesForValues($values, $value), $this->filter);
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, callable $value = null): array
{
return $this->decoratedLoader->loadValuesForChoices(array_filter($choices, $this->filter), $value);
}
}

View File

@ -0,0 +1,44 @@
<?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;
/**
* Callback choice loader optimized for Intl choice types.
*
* @author Jules Pietri <jules@heahprod.com>
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class IntlCallbackChoiceLoader extends CallbackChoiceLoader
{
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, callable $value = null)
{
return parent::loadChoicesForValues(array_filter($values), $value);
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, callable $value = null)
{
$choices = array_filter($choices);
// If no callable is set, choices are the same as values
if (null === $value) {
return $choices;
}
return parent::loadValuesForChoices($choices, $value);
}
}

View File

@ -0,0 +1,47 @@
<?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\View;
/**
* Represents a group of choices in templates.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @implements \IteratorAggregate<array-key, ChoiceGroupView|ChoiceView>
*/
class ChoiceGroupView implements \IteratorAggregate
{
public $label;
public $choices;
/**
* Creates a new choice group view.
*
* @param array<array-key, ChoiceGroupView|ChoiceView> $choices the choice views in the group
*/
public function __construct(string $label, array $choices = [])
{
$this->label = $label;
$this->choices = $choices;
}
/**
* {@inheritdoc}
*
* @return \Traversable<array-key, ChoiceGroupView|ChoiceView>
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->choices);
}
}

View File

@ -0,0 +1,59 @@
<?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\View;
/**
* Represents a choice list in templates.
*
* A choice list contains choices and optionally preferred choices which are
* displayed in the very beginning of the list. Both choices and preferred
* choices may be grouped in {@link ChoiceGroupView} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ChoiceListView
{
public $choices;
public $preferredChoices;
/**
* Creates a new choice list view.
*
* @param ChoiceGroupView[]|ChoiceView[] $choices The choice views
* @param ChoiceGroupView[]|ChoiceView[] $preferredChoices the preferred choice views
*/
public function __construct(array $choices = [], array $preferredChoices = [])
{
$this->choices = $choices;
$this->preferredChoices = $preferredChoices;
}
/**
* Returns whether a placeholder is in the choices.
*
* A placeholder must be the first child element, not be in a group and have an empty value.
*
* @return bool
*/
public function hasPlaceholder()
{
if ($this->preferredChoices) {
$firstChoice = reset($this->preferredChoices);
return $firstChoice instanceof ChoiceView && '' === $firstChoice->value;
}
$firstChoice = reset($this->choices);
return $firstChoice instanceof ChoiceView && '' === $firstChoice->value;
}
}

View File

@ -0,0 +1,54 @@
<?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\View;
use Symfony\Component\Translation\TranslatableMessage;
/**
* Represents a choice in templates.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ChoiceView
{
public $label;
public $value;
public $data;
/**
* Additional attributes for the HTML tag.
*/
public $attr;
/**
* Additional parameters used to translate the label.
*/
public $labelTranslationParameters;
/**
* Creates a new choice view.
*
* @param mixed $data The original choice
* @param string $value The view representation of the choice
* @param string|TranslatableMessage|false $label The label displayed to humans; pass false to discard the label
* @param array $attr Additional attributes for the HTML tag
* @param array $labelTranslationParameters Additional parameters used to translate the label
*/
public function __construct($data, string $value, $label, array $attr = [], array $labelTranslationParameters = [])
{
$this->data = $data;
$this->value = $value;
$this->label = $label;
$this->attr = $attr;
$this->labelTranslationParameters = $labelTranslationParameters;
}
}