[Form] Refactored choice lists to support dynamic label, value, index and attribute generation
This commit is contained in:
parent
75c8a2ba21
commit
03efce1b56
@ -11,17 +11,20 @@
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
|
||||
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
use Symfony\Component\Form\Exception\StringCastException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* A choice list presenting a list of Doctrine entities as choices.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link EntityChoiceLoader} instead.
|
||||
*/
|
||||
class EntityChoiceList extends ObjectChoiceList
|
||||
{
|
||||
|
@ -0,0 +1,267 @@
|
||||
<?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\Bridge\Doctrine\Form\ChoiceList;
|
||||
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Loads choices using a Doctrine object manager.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class EntityChoiceLoader implements ChoiceLoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var ChoiceListFactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var ObjectManager
|
||||
*/
|
||||
private $manager;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $class;
|
||||
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
private $classMetadata;
|
||||
|
||||
/**
|
||||
* @var null|EntityLoaderInterface
|
||||
*/
|
||||
private $entityLoader;
|
||||
|
||||
/**
|
||||
* The identifier field, unless the identifier is composite
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
private $idField = null;
|
||||
|
||||
/**
|
||||
* Whether to use the identifier for value generation
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $compositeId = true;
|
||||
|
||||
/**
|
||||
* @var ChoiceListInterface
|
||||
*/
|
||||
private $choiceList;
|
||||
|
||||
/**
|
||||
* Returns the value of the identifier field of an entity.
|
||||
*
|
||||
* Doctrine must know about this entity, that is, the entity must already
|
||||
* be persisted or added to the identity map before. Otherwise an
|
||||
* exception is thrown.
|
||||
*
|
||||
* This method assumes that the entity has a single-column identifier and
|
||||
* will return a single value instead of an array.
|
||||
*
|
||||
* @param object $object The entity for which to get the identifier
|
||||
*
|
||||
* @return int|string The identifier value
|
||||
*
|
||||
* @throws RuntimeException If the entity does not exist in Doctrine's identity map
|
||||
*
|
||||
* @internal Should not be accessed by user-land code. This method is public
|
||||
* only to be usable as callback.
|
||||
*/
|
||||
public static function getIdValue(ObjectManager $om, ClassMetadata $classMetadata, $object)
|
||||
{
|
||||
if (!$om->contains($object)) {
|
||||
throw new RuntimeException(
|
||||
'Entities passed to the choice field must be managed. Maybe '.
|
||||
'persist them in the entity manager?'
|
||||
);
|
||||
}
|
||||
|
||||
$om->initializeObject($object);
|
||||
|
||||
return current($classMetadata->getIdentifierValues($object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new choice loader.
|
||||
*
|
||||
* Optionally, an implementation of {@link EntityLoaderInterface} can be
|
||||
* passed which optimizes the entity loading for one of the Doctrine
|
||||
* mapper implementations.
|
||||
*
|
||||
* @param ChoiceListFactoryInterface $factory The factory for creating
|
||||
* the loaded choice list
|
||||
* @param ObjectManager $manager The object manager
|
||||
* @param string $class The entity class name
|
||||
* @param null|EntityLoaderInterface $entityLoader The entity loader
|
||||
*/
|
||||
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, EntityLoaderInterface $entityLoader = null)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->manager = $manager;
|
||||
$this->classMetadata = $manager->getClassMetadata($class);
|
||||
$this->class = $this->classMetadata->getName();
|
||||
$this->entityLoader = $entityLoader;
|
||||
|
||||
$identifier = $this->classMetadata->getIdentifierFieldNames();
|
||||
|
||||
if (1 === count($identifier)) {
|
||||
$this->idField = $identifier[0];
|
||||
$this->compositeId = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadChoiceList($value = null)
|
||||
{
|
||||
if ($this->choiceList) {
|
||||
return $this->choiceList;
|
||||
}
|
||||
|
||||
$entities = $this->entityLoader
|
||||
? $this->entityLoader->getEntities()
|
||||
: $this->manager->getRepository($this->class)->findAll();
|
||||
|
||||
// If the class has a multi-column identifier, we cannot index the
|
||||
// entities by their IDs
|
||||
if ($this->compositeId) {
|
||||
$this->choiceList = $this->factory->createListFromChoices($entities, $value);
|
||||
|
||||
return $this->choiceList;
|
||||
}
|
||||
|
||||
// Index the entities by ID
|
||||
$entitiesById = array();
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$id = self::getIdValue($this->manager, $this->classMetadata, $entity);
|
||||
$entitiesById[$id] = $entity;
|
||||
}
|
||||
|
||||
$this->choiceList = $this->factory->createListFromChoices($entitiesById, $value);
|
||||
|
||||
return $this->choiceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the values corresponding to the given entities.
|
||||
*
|
||||
* The values are returned with the same keys and in the same order as the
|
||||
* corresponding entities in the given array.
|
||||
*
|
||||
* Optionally, a callable can be passed for generating the choice values.
|
||||
* The callable receives the entity as first and the array key as the second
|
||||
* argument.
|
||||
*
|
||||
* @param array $entities An array of entities. Non-existing entities
|
||||
* in this array are ignored
|
||||
* @param null|callable $value The callable generating the choice values
|
||||
*
|
||||
* @return string[] An array of choice values
|
||||
*/
|
||||
public function loadValuesForChoices(array $entities, $value = null)
|
||||
{
|
||||
// Performance optimization
|
||||
if (empty($entities)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Optimize performance for single-field identifiers. We already
|
||||
// know that the IDs are used as values
|
||||
|
||||
// Attention: This optimization does not check choices for existence
|
||||
if (!$this->choiceList && !$this->compositeId) {
|
||||
$values = array();
|
||||
|
||||
// Maintain order and indices of the given entities
|
||||
foreach ($entities as $i => $entity) {
|
||||
if ($entity instanceof $this->class) {
|
||||
// Make sure to convert to the right format
|
||||
$values[$i] = (string) self::getIdValue($this->manager, $this->classMetadata, $entity);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
return $this->loadChoiceList($value)->getValuesForChoices($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the entities corresponding to the given values.
|
||||
*
|
||||
* The entities 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 entity as first and the array key as the second
|
||||
* argument.
|
||||
*
|
||||
* @param string[] $values An array of choice values. Non-existing
|
||||
* values in this array are ignored
|
||||
* @param null|callable $value The callable generating the choice values
|
||||
*
|
||||
* @return array An array of entities
|
||||
*/
|
||||
public function loadChoicesForValues(array $values, $value = null)
|
||||
{
|
||||
// Performance optimization
|
||||
// Also prevents the generation of "WHERE id IN ()" queries through the
|
||||
// entity loader. At least with MySQL and on the development machine
|
||||
// this was tested on, no exception was thrown for such invalid
|
||||
// statements, consequently no test fails when this code is removed.
|
||||
// https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
|
||||
if (empty($values)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Optimize performance in case we have an entity loader and
|
||||
// a single-field identifier
|
||||
if (!$this->choiceList && !$this->compositeId && $this->entityLoader) {
|
||||
$unorderedEntities = $this->entityLoader->getEntitiesByIds($this->idField, $values);
|
||||
$entitiesById = array();
|
||||
$entities = array();
|
||||
|
||||
// Maintain order and indices from the given $values
|
||||
// An alternative approach to the following loop is to add the
|
||||
// "INDEX BY" clause to the Doctrine query in the loader,
|
||||
// but I'm not sure whether that's doable in a generic fashion.
|
||||
foreach ($unorderedEntities as $entity) {
|
||||
$id = self::getIdValue($this->manager, $this->classMetadata, $entity);
|
||||
$entitiesById[$id] = $entity;
|
||||
}
|
||||
|
||||
foreach ($values as $i => $id) {
|
||||
if (isset($entitiesById[$id])) {
|
||||
$entities[$i] = $entitiesById[$id];
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
return $this->loadChoiceList($value)->getChoicesForValues($values);
|
||||
}
|
||||
}
|
@ -17,7 +17,10 @@ use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* Getting Entities through the ORM QueryBuilder.
|
||||
* Loads entities using a {@link QueryBuilder} instance.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ORMQueryBuilderLoader implements EntityLoaderInterface
|
||||
{
|
||||
|
@ -14,21 +14,38 @@ namespace Symfony\Bridge\Doctrine\Form;
|
||||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractExtension;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
class DoctrineOrmExtension extends AbstractExtension
|
||||
{
|
||||
protected $registry;
|
||||
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
/**
|
||||
* @var ChoiceListFactoryInterface
|
||||
*/
|
||||
private $choiceListFactory;
|
||||
|
||||
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
|
||||
}
|
||||
|
||||
protected function loadTypes()
|
||||
{
|
||||
return array(
|
||||
new EntityType($this->registry, PropertyAccess::createPropertyAccessor()),
|
||||
new EntityType($this->registry, $this->propertyAccessor, $this->choiceListFactory),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -12,17 +12,20 @@
|
||||
namespace Symfony\Bridge\Doctrine\Form\Type;
|
||||
|
||||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceLoader;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
|
||||
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
|
||||
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
abstract class DoctrineType extends AbstractType
|
||||
@ -33,19 +36,19 @@ abstract class DoctrineType extends AbstractType
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @var ChoiceListFactoryInterface
|
||||
*/
|
||||
private $choiceListCache = array();
|
||||
private $choiceListFactory;
|
||||
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
* @var EntityChoiceLoader[]
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
private $choiceLoaders = array();
|
||||
|
||||
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null)
|
||||
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
$this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(new DefaultChoiceListFactory(), $propertyAccessor);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
@ -60,86 +63,79 @@ abstract class DoctrineType extends AbstractType
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$choiceListCache = &$this->choiceListCache;
|
||||
$registry = $this->registry;
|
||||
$propertyAccessor = $this->propertyAccessor;
|
||||
$choiceListFactory = $this->choiceListFactory;
|
||||
$choiceLoaders = &$this->choiceLoaders;
|
||||
$type = $this;
|
||||
|
||||
$loader = function (Options $options) use ($type) {
|
||||
$queryBuilder = (null !== $options['query_builder'])
|
||||
? $options['query_builder']
|
||||
: $options['em']->getRepository($options['class'])->createQueryBuilder('e');
|
||||
|
||||
return $type->getLoader($options['em'], $queryBuilder, $options['class']);
|
||||
};
|
||||
|
||||
$choiceList = function (Options $options) use (&$choiceListCache, $propertyAccessor) {
|
||||
// Support for closures
|
||||
$propertyHash = is_object($options['property'])
|
||||
? spl_object_hash($options['property'])
|
||||
: $options['property'];
|
||||
|
||||
$choiceHashes = $options['choices'];
|
||||
|
||||
// Support for recursive arrays
|
||||
if (is_array($choiceHashes)) {
|
||||
// A second parameter ($key) is passed, so we cannot use
|
||||
// spl_object_hash() directly (which strictly requires
|
||||
// one parameter)
|
||||
array_walk_recursive($choiceHashes, function (&$value) {
|
||||
$value = spl_object_hash($value);
|
||||
});
|
||||
} elseif ($choiceHashes instanceof \Traversable) {
|
||||
$hashes = array();
|
||||
foreach ($choiceHashes as $value) {
|
||||
$hashes[] = spl_object_hash($value);
|
||||
}
|
||||
|
||||
$choiceHashes = $hashes;
|
||||
}
|
||||
|
||||
$preferredChoiceHashes = $options['preferred_choices'];
|
||||
|
||||
if (is_array($preferredChoiceHashes)) {
|
||||
array_walk_recursive($preferredChoiceHashes, function (&$value) {
|
||||
$value = spl_object_hash($value);
|
||||
});
|
||||
}
|
||||
|
||||
// Support for custom loaders (with query builders)
|
||||
$loaderHash = is_object($options['loader'])
|
||||
? spl_object_hash($options['loader'])
|
||||
: $options['loader'];
|
||||
|
||||
// Support for closures
|
||||
$groupByHash = is_object($options['group_by'])
|
||||
? spl_object_hash($options['group_by'])
|
||||
: $options['group_by'];
|
||||
|
||||
$hash = hash('sha256', json_encode(array(
|
||||
spl_object_hash($options['em']),
|
||||
$options['class'],
|
||||
$propertyHash,
|
||||
$loaderHash,
|
||||
$choiceHashes,
|
||||
$preferredChoiceHashes,
|
||||
$groupByHash,
|
||||
)));
|
||||
|
||||
if (!isset($choiceListCache[$hash])) {
|
||||
$choiceListCache[$hash] = new EntityChoiceList(
|
||||
$choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders, $type) {
|
||||
// Unless the choices are given explicitly, load them on demand
|
||||
if (null === $options['choices']) {
|
||||
$hash = CachingFactoryDecorator::generateHash(array(
|
||||
$options['em'],
|
||||
$options['class'],
|
||||
$options['property'],
|
||||
$options['query_builder'],
|
||||
$options['loader'],
|
||||
$options['choices'],
|
||||
$options['preferred_choices'],
|
||||
$options['group_by'],
|
||||
$propertyAccessor
|
||||
));
|
||||
|
||||
if (!isset($choiceLoaders[$hash])) {
|
||||
if ($options['loader']) {
|
||||
$loader = $options['loader'];
|
||||
} elseif (null !== $options['query_builder']) {
|
||||
$loader = $type->getLoader($options['em'], $options['query_builder'], $options['class']);
|
||||
} else {
|
||||
$queryBuilder = $options['em']->getRepository($options['class'])->createQueryBuilder('e');
|
||||
$loader = $type->getLoader($options['em'], $queryBuilder, $options['class']);
|
||||
}
|
||||
|
||||
$choiceLoaders[$hash] = new EntityChoiceLoader(
|
||||
$choiceListFactory,
|
||||
$options['em'],
|
||||
$options['class'],
|
||||
$loader
|
||||
);
|
||||
}
|
||||
|
||||
return $choiceListCache[$hash];
|
||||
return $choiceLoaders[$hash];
|
||||
}
|
||||
};
|
||||
|
||||
$choiceLabel = function (Options $options) {
|
||||
// BC with the "property" option
|
||||
if ($options['property']) {
|
||||
return $options['property'];
|
||||
}
|
||||
|
||||
// BC: use __toString() by default
|
||||
return function ($entity) {
|
||||
return (string) $entity;
|
||||
};
|
||||
};
|
||||
|
||||
$choiceName = function (Options $options) {
|
||||
/** @var ObjectManager $om */
|
||||
$om = $options['em'];
|
||||
$classMetadata = $om->getClassMetadata($options['class']);
|
||||
$ids = $classMetadata->getIdentifierFieldNames();
|
||||
$idType = $classMetadata->getTypeOfField(current($ids));
|
||||
|
||||
// If the entity has a single-column, numeric ID, use that ID as
|
||||
// field name
|
||||
if (1 === count($ids) && in_array($idType, array('integer', 'smallint', 'bigint'))) {
|
||||
return function ($entity, $id) {
|
||||
return $id;
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, an incrementing integer is used as name automatically
|
||||
};
|
||||
|
||||
// The choices are always indexed by ID (see "choices" normalizer
|
||||
// and EntityChoiceLoader), unless the ID is composite. Then they
|
||||
// are indexed by an incrementing integer.
|
||||
// Use the ID/incrementing integer as choice value.
|
||||
$choiceValue = function ($entity, $key) {
|
||||
return $key;
|
||||
};
|
||||
|
||||
$emNormalizer = function (Options $options, $em) use ($registry) {
|
||||
@ -165,19 +161,50 @@ abstract class DoctrineType extends AbstractType
|
||||
return $em;
|
||||
};
|
||||
|
||||
$choicesNormalizer = function (Options $options, $entities) {
|
||||
if (null === $entities || 0 === count($entities)) {
|
||||
return $entities;
|
||||
}
|
||||
|
||||
// Make sure that the entities are indexed by their ID
|
||||
/** @var ObjectManager $om */
|
||||
$om = $options['em'];
|
||||
$classMetadata = $om->getClassMetadata($options['class']);
|
||||
$ids = $classMetadata->getIdentifierFieldNames();
|
||||
|
||||
// We cannot use composite IDs as indices. In that case, keep the
|
||||
// given indices
|
||||
if (count($ids) > 1) {
|
||||
return $entities;
|
||||
}
|
||||
|
||||
$entitiesById = array();
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$id = EntityChoiceLoader::getIdValue($om, $classMetadata, $entity);
|
||||
$entitiesById[$id] = $entity;
|
||||
}
|
||||
|
||||
return $entitiesById;
|
||||
};
|
||||
|
||||
$resolver->setDefaults(array(
|
||||
'em' => null,
|
||||
'property' => null,
|
||||
'property' => null, // deprecated, use "choice_label"
|
||||
'query_builder' => null,
|
||||
'loader' => $loader,
|
||||
'loader' => null, // deprecated, use "choice_loader"
|
||||
'choices' => null,
|
||||
'choice_list' => $choiceList,
|
||||
'group_by' => null,
|
||||
'choices_as_values' => true,
|
||||
'choice_loader' => $choiceLoader,
|
||||
'choice_label' => $choiceLabel,
|
||||
'choice_name' => $choiceName,
|
||||
'choice_value' => $choiceValue,
|
||||
));
|
||||
|
||||
$resolver->setRequired(array('class'));
|
||||
|
||||
$resolver->setNormalizer('em', $emNormalizer);
|
||||
$resolver->setNormalizer('choices', $choicesNormalizer);
|
||||
|
||||
$resolver->setAllowedTypes('em', array('null', 'string', 'Doctrine\Common\Persistence\ObjectManager'));
|
||||
$resolver->setAllowedTypes('loader', array('null', 'Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface'));
|
||||
|
@ -11,21 +11,23 @@
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Tests\Form\Type;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
|
||||
use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\Forms;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\Forms;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
class EntityTypeTest extends TypeTestCase
|
||||
@ -37,12 +39,12 @@ class EntityTypeTest extends TypeTestCase
|
||||
const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity';
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject|ManagerRegistry
|
||||
*/
|
||||
private $emRegistry;
|
||||
|
||||
@ -131,7 +133,7 @@ class EntityTypeTest extends TypeTestCase
|
||||
'property' => 'name',
|
||||
));
|
||||
|
||||
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
|
||||
$this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
public function testSetDataToUninitializedEntityWithNonRequiredToString()
|
||||
@ -147,7 +149,7 @@ class EntityTypeTest extends TypeTestCase
|
||||
'required' => false,
|
||||
));
|
||||
|
||||
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
|
||||
$this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder()
|
||||
@ -166,7 +168,7 @@ class EntityTypeTest extends TypeTestCase
|
||||
'query_builder' => $qb,
|
||||
));
|
||||
|
||||
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
|
||||
$this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -249,7 +251,7 @@ class EntityTypeTest extends TypeTestCase
|
||||
$field->submit(null);
|
||||
|
||||
$this->assertNull($field->getData());
|
||||
$this->assertSame(array(), $field->getViewData());
|
||||
$this->assertNull($field->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitSingleNonExpandedNull()
|
||||
@ -510,7 +512,7 @@ class EntityTypeTest extends TypeTestCase
|
||||
|
||||
$field->submit('2');
|
||||
|
||||
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
|
||||
$this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
|
||||
$this->assertTrue($field->isSynchronized());
|
||||
$this->assertSame($entity2, $field->getData());
|
||||
$this->assertSame('2', $field->getViewData());
|
||||
@ -537,9 +539,14 @@ class EntityTypeTest extends TypeTestCase
|
||||
|
||||
$this->assertSame('2', $field->getViewData());
|
||||
$this->assertEquals(array(
|
||||
'Group1' => array(1 => new ChoiceView($item1, '1', 'Foo'), 2 => new ChoiceView($item2, '2', 'Bar')),
|
||||
'Group2' => array(3 => new ChoiceView($item3, '3', 'Baz')),
|
||||
'4' => new ChoiceView($item4, '4', 'Boo!'),
|
||||
'Group1' => new ChoiceGroupView('Group1', array(
|
||||
1 => new ChoiceView('Foo', '1', $item1),
|
||||
2 => new ChoiceView('Bar', '2', $item2),
|
||||
)),
|
||||
'Group2' => new ChoiceGroupView('Group2', array(
|
||||
3 => new ChoiceView('Baz', '3', $item3),
|
||||
)),
|
||||
4 => new ChoiceView('Boo!', '4', $item4),
|
||||
), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
@ -558,8 +565,8 @@ class EntityTypeTest extends TypeTestCase
|
||||
'property' => 'name',
|
||||
));
|
||||
|
||||
$this->assertEquals(array(3 => new ChoiceView($entity3, '3', 'Baz'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['preferred_choices']);
|
||||
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo')), $field->createView()->vars['choices']);
|
||||
$this->assertEquals(array(3 => new ChoiceView('Baz', '3', $entity3), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['preferred_choices']);
|
||||
$this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1)), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
public function testOverrideChoicesWithPreferredChoices()
|
||||
@ -578,8 +585,8 @@ class EntityTypeTest extends TypeTestCase
|
||||
'property' => 'name',
|
||||
));
|
||||
|
||||
$this->assertEquals(array(3 => new ChoiceView($entity3, '3', 'Baz')), $field->createView()->vars['preferred_choices']);
|
||||
$this->assertEquals(array(2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
|
||||
$this->assertEquals(array(3 => new ChoiceView('Baz', '3', $entity3)), $field->createView()->vars['preferred_choices']);
|
||||
$this->assertEquals(array(2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier()
|
||||
@ -833,6 +840,30 @@ class EntityTypeTest extends TypeTestCase
|
||||
$this->assertCount(1, $loaders);
|
||||
}
|
||||
|
||||
public function testCacheChoiceLists()
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
|
||||
$this->persist(array($entity1));
|
||||
|
||||
$field1 = $this->factory->createNamed('name', 'entity', null, array(
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'required' => false,
|
||||
'property' => 'name',
|
||||
));
|
||||
|
||||
$field2 = $this->factory->createNamed('name', 'entity', null, array(
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'required' => false,
|
||||
'property' => 'name',
|
||||
));
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $field1->getConfig()->getOption('choice_list'));
|
||||
$this->assertSame($field1->getConfig()->getOption('choice_list'), $field2->getConfig()->getOption('choice_list'));
|
||||
}
|
||||
|
||||
protected function createRegistryMock($name, $em)
|
||||
{
|
||||
$registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry');
|
||||
|
@ -13,7 +13,7 @@ namespace Symfony\Bridge\Twig\Extension;
|
||||
|
||||
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
|
||||
use Symfony\Bridge\Twig\Form\TwigRendererInterface;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
|
||||
/**
|
||||
* FormExtension extends Twig with form capabilities.
|
||||
|
@ -79,7 +79,8 @@
|
||||
{{- block('choice_widget_options') -}}
|
||||
</optgroup>
|
||||
{%- else -%}
|
||||
<option value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.label|trans({}, translation_domain) }}</option>
|
||||
{% set attr = choice.attr %}
|
||||
<option value="{{ choice.value }}" {{ block('attributes') }}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.label|trans({}, translation_domain) }}</option>
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
{%- endblock choice_widget_options -%}
|
||||
@ -355,3 +356,16 @@
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endblock button_attributes -%}
|
||||
|
||||
{% block attributes -%}
|
||||
{%- for attrname, attrvalue in attr -%}
|
||||
{{- " " -}}
|
||||
{%- if attrname in ['placeholder', 'title'] -%}
|
||||
{{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}"
|
||||
{%- elseif attrvalue is sameas(true) -%}
|
||||
{{- attrname }}="{{ attrname }}"
|
||||
{%- elseif attrvalue is not sameas(false) -%}
|
||||
{{- attrname }}="{{ attrvalue }}"
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endblock attributes -%}
|
||||
|
@ -1,11 +1,13 @@
|
||||
<?php $translatorHelper = $view['translator']; // outside of the loop for performance reasons! ?>
|
||||
<?php use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
|
||||
$translatorHelper = $view['translator']; // outside of the loop for performance reasons! ?>
|
||||
<?php $formHelper = $view['form']; ?>
|
||||
<?php foreach ($choices as $index => $choice): ?>
|
||||
<?php if (is_array($choice)): ?>
|
||||
<?php if (is_array($choice) || $choice instanceof ChoiceGroupView): ?>
|
||||
<optgroup label="<?php echo $view->escape($translatorHelper->trans($index, array(), $translation_domain)) ?>">
|
||||
<?php echo $formHelper->block($form, 'choice_widget_options', array('choices' => $choice)) ?>
|
||||
</optgroup>
|
||||
<?php else: ?>
|
||||
<option value="<?php echo $view->escape($choice->value) ?>"<?php if ($is_selected($choice->value, $value)): ?> selected="selected"<?php endif?>><?php echo $view->escape($translatorHelper->trans($choice->label, array(), $translation_domain)) ?></option>
|
||||
<option value="<?php echo $view->escape($choice->value) ?>" <?php echo $view['form']->block($form, 'attributes', array('attr' => $choice->attr)) ?><?php if ($is_selected($choice->value, $value)): ?> selected="selected"<?php endif?>><?php echo $view->escape($translatorHelper->trans($choice->label, array(), $translation_domain)) ?></option>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
|
136
src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php
Normal file
136
src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?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\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A list of choices with arbitrary data types.
|
||||
*
|
||||
* The user of this class is responsible for assigning string values to the
|
||||
* choices. Both the choices and their values are passed to the constructor.
|
||||
* Each choice must have a corresponding value (with the same array key) in
|
||||
* the value array.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ArrayChoiceList implements ChoiceListInterface
|
||||
{
|
||||
/**
|
||||
* The choices in the list.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $choices = array();
|
||||
|
||||
/**
|
||||
* The values of the choices.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $values = array();
|
||||
|
||||
/**
|
||||
* Creates a list with the given choices and values.
|
||||
*
|
||||
* The given choice array must have the same array keys as the value array.
|
||||
*
|
||||
* @param array $choices The selectable choices
|
||||
* @param string[] $values The string values of the choices
|
||||
*
|
||||
* @throws InvalidArgumentException If the keys of the choices don't match
|
||||
* the keys of the values
|
||||
*/
|
||||
public function __construct(array $choices, array $values)
|
||||
{
|
||||
$choiceKeys = array_keys($choices);
|
||||
$valueKeys = array_keys($values);
|
||||
|
||||
if ($choiceKeys !== $valueKeys) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The keys of the choices and the values must match. The choice '.
|
||||
'keys are: "%s". The value keys are: "%s".',
|
||||
implode('", "', $choiceKeys),
|
||||
implode('", "', $valueKeys)
|
||||
));
|
||||
}
|
||||
|
||||
$this->choices = $choices;
|
||||
$this->values = array_map('strval', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChoices()
|
||||
{
|
||||
return $this->choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChoicesForValues(array $values)
|
||||
{
|
||||
$choices = array();
|
||||
|
||||
foreach ($values as $i => $givenValue) {
|
||||
foreach ($this->values as $j => $value) {
|
||||
if ($value !== (string) $givenValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$choices[$i] = $this->choices[$j];
|
||||
unset($values[$i]);
|
||||
|
||||
if (0 === count($values)) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValuesForChoices(array $choices)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($choices as $i => $givenChoice) {
|
||||
foreach ($this->choices as $j => $choice) {
|
||||
if ($choice !== $givenChoice) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values[$i] = $this->values[$j];
|
||||
unset($choices[$i]);
|
||||
|
||||
if (0 === count($choices)) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
173
src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php
Normal file
173
src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?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\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A list of choices that can be stored in the keys of a PHP array.
|
||||
*
|
||||
* PHP arrays accept only strings and integers as array keys. Other scalar types
|
||||
* are cast to integers and strings according to the description of
|
||||
* {@link toArrayKey()}. This implementation applies the same casting rules for
|
||||
* the choices passed to the constructor and to {@link getValuesForChoices()}.
|
||||
*
|
||||
* By default, the choices are cast to strings and used as values. Optionally,
|
||||
* you may pass custom values. The keys of the value array must match the keys
|
||||
* of the choice array.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* $choices = array('' => 'Don\'t know', 0 => 'No', 1 => 'Yes');
|
||||
* $choiceList = new ArrayKeyChoiceList(array_keys($choices));
|
||||
*
|
||||
* $values = $choiceList->getValues()
|
||||
* // => array('', '0', '1')
|
||||
*
|
||||
* $selectedValues = $choiceList->getValuesForChoices(array(true));
|
||||
* // => array('1')
|
||||
* ```
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Added for backwards compatibility in Symfony 2.7, to be removed
|
||||
* in Symfony 3.0.
|
||||
*/
|
||||
class ArrayKeyChoiceList implements ChoiceListInterface
|
||||
{
|
||||
/**
|
||||
* The selectable choices.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $choices = array();
|
||||
|
||||
/**
|
||||
* The values of the choices.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $values = array();
|
||||
|
||||
/**
|
||||
* Casts the given choice to an array key.
|
||||
*
|
||||
* PHP arrays accept only strings and integers as array keys. Integer
|
||||
* strings such as "42" are automatically cast to integers. The boolean
|
||||
* values "true" and "false" are cast to the integers 1 and 0. Every other
|
||||
* scalar value is cast to a string.
|
||||
*
|
||||
* @param mixed $choice The choice
|
||||
*
|
||||
* @return int|string The choice as PHP array key
|
||||
*
|
||||
* @throws InvalidArgumentException If the choice is not scalar
|
||||
*/
|
||||
public static function toArrayKey($choice)
|
||||
{
|
||||
if (!is_scalar($choice) && null !== $choice) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The value of type "%s" cannot be converted to a valid array key.',
|
||||
gettype($choice)
|
||||
));
|
||||
}
|
||||
|
||||
if (is_bool($choice) || (string) (int) $choice === (string) $choice) {
|
||||
return (int) $choice;
|
||||
}
|
||||
|
||||
return (string) $choice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list with the given choices and values.
|
||||
*
|
||||
* The given choice array must have the same array keys as the value array.
|
||||
* Each choice must be castable to an integer/string according to the
|
||||
* casting rules described in {@link toArrayKey()}.
|
||||
*
|
||||
* If no values are given, the choices are cast to strings and used as
|
||||
* values.
|
||||
*
|
||||
* @param array $choices The selectable choices
|
||||
* @param string[] $values Optional. The string values of the choices
|
||||
*
|
||||
* @throws InvalidArgumentException If the keys of the choices don't match
|
||||
* the keys of the values or if any of the
|
||||
* choices is not scalar
|
||||
*/
|
||||
public function __construct(array $choices, array $values = array())
|
||||
{
|
||||
if (empty($values)) {
|
||||
// The cast to strings happens later
|
||||
$values = $choices;
|
||||
} else {
|
||||
$choiceKeys = array_keys($choices);
|
||||
$valueKeys = array_keys($values);
|
||||
|
||||
if ($choiceKeys !== $valueKeys) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'The keys of the choices and the values must match. The choice '.
|
||||
'keys are: "%s". The value keys are: "%s".',
|
||||
implode('", "', $choiceKeys),
|
||||
implode('", "', $valueKeys)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
|
||||
$this->values = array_map('strval', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChoices()
|
||||
{
|
||||
return $this->choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChoicesForValues(array $values)
|
||||
{
|
||||
$values = array_map('strval', $values);
|
||||
|
||||
// The values are identical to the choices, so we can just return them
|
||||
// to improve performance a little bit
|
||||
return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, $this->values));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValuesForChoices(array $choices)
|
||||
{
|
||||
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
|
||||
|
||||
// The choices are identical to the values, so we can just return them
|
||||
// to improve performance a little bit
|
||||
return array_map('strval', array_intersect($choices, $this->choices));
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* A list of choices that can be selected in a choice field.
|
||||
*
|
||||
* A choice list assigns 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.
|
||||
*
|
||||
* The choices returned by {@link getChoices()} and the values returned by
|
||||
* {@link getValues()} must have the same array indices.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface ChoiceListInterface
|
||||
{
|
||||
/**
|
||||
* Returns all selectable choices.
|
||||
*
|
||||
* The keys of the choices correspond to the keys of the values returned by
|
||||
* {@link getValues()}.
|
||||
*
|
||||
* @return array The selectable choices
|
||||
*/
|
||||
public function getChoices();
|
||||
|
||||
/**
|
||||
* Returns the values for the choices.
|
||||
*
|
||||
* The keys of the values correspond to the keys of the choices returned by
|
||||
* {@link getChoices()}.
|
||||
*
|
||||
* @return string[] The choice values
|
||||
*/
|
||||
public function getValues();
|
||||
|
||||
/**
|
||||
* 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 An array of choices
|
||||
*/
|
||||
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[] An array of choice values
|
||||
*/
|
||||
public function getValuesForChoices(array $choices);
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
<?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\Form\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Caches the choice lists created by the decorated factory.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class CachingFactoryDecorator implements ChoiceListFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var ChoiceListFactoryInterface
|
||||
*/
|
||||
private $decoratedFactory;
|
||||
|
||||
/**
|
||||
* @var ChoiceListInterface[]
|
||||
*/
|
||||
private $lists = array();
|
||||
|
||||
/**
|
||||
* @var ChoiceListView[]
|
||||
*/
|
||||
private $views = array();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param string $namespace Optional. The namespace
|
||||
*
|
||||
* @return string The SHA-256 hash
|
||||
*
|
||||
* @internal Should not be used by user-land code.
|
||||
*/
|
||||
public static function generateHash($value, $namespace = '')
|
||||
{
|
||||
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.':'.json_encode($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates the given factory.
|
||||
*
|
||||
* @param ChoiceListFactoryInterface $decoratedFactory The decorated factory
|
||||
*/
|
||||
public function __construct(ChoiceListFactoryInterface $decoratedFactory)
|
||||
{
|
||||
$this->decoratedFactory = $decoratedFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decorated factory.
|
||||
*
|
||||
* @return ChoiceListFactoryInterface The decorated factory
|
||||
*/
|
||||
public function getDecoratedFactory()
|
||||
{
|
||||
return $this->decoratedFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createListFromChoices($choices, $value = null)
|
||||
{
|
||||
if (!is_array($choices) && !$choices instanceof \Traversable) {
|
||||
throw new UnexpectedTypeException($choices, 'array or \Traversable');
|
||||
}
|
||||
|
||||
if ($choices instanceof \Traversable) {
|
||||
$choices = iterator_to_array($choices);
|
||||
}
|
||||
|
||||
// The value is not validated on purpose. The decorated factory may
|
||||
// decide which values to accept and which not.
|
||||
|
||||
// We ignore the choice groups for caching. If two choice lists are
|
||||
// requested with the same choices, but a different grouping, the same
|
||||
// choice list is returned.
|
||||
DefaultChoiceListFactory::flatten($choices, $flatChoices);
|
||||
|
||||
$hash = self::generateHash(array($flatChoices, $value), 'fromChoices');
|
||||
|
||||
if (!isset($this->lists[$hash])) {
|
||||
$this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value);
|
||||
}
|
||||
|
||||
return $this->lists[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
|
||||
* removed in Symfony 3.0.
|
||||
*/
|
||||
public function createListFromFlippedChoices($choices, $value = null)
|
||||
{
|
||||
if (!is_array($choices) && !$choices instanceof \Traversable) {
|
||||
throw new UnexpectedTypeException($choices, 'array or \Traversable');
|
||||
}
|
||||
|
||||
if ($choices instanceof \Traversable) {
|
||||
$choices = iterator_to_array($choices);
|
||||
}
|
||||
|
||||
// The value is not validated on purpose. The decorated factory may
|
||||
// decide which values to accept and which not.
|
||||
|
||||
// We ignore the choice groups for caching. If two choice lists are
|
||||
// requested with the same choices, but a different grouping, the same
|
||||
// choice list is returned.
|
||||
DefaultChoiceListFactory::flattenFlipped($choices, $flatChoices);
|
||||
|
||||
$hash = self::generateHash(array($flatChoices, $value), 'fromFlippedChoices');
|
||||
|
||||
if (!isset($this->lists[$hash])) {
|
||||
$this->lists[$hash] = $this->decoratedFactory->createListFromFlippedChoices($choices, $value);
|
||||
}
|
||||
|
||||
return $this->lists[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
|
||||
{
|
||||
$hash = self::generateHash(array($loader, $value), 'fromLoader');
|
||||
|
||||
if (!isset($this->lists[$hash])) {
|
||||
$this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value);
|
||||
}
|
||||
|
||||
return $this->lists[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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.
|
||||
|
||||
$hash = self::generateHash(array($list, $preferredChoices, $label, $index, $groupBy, $attr));
|
||||
|
||||
if (!isset($this->views[$hash])) {
|
||||
$this->views[$hash] = $this->decoratedFactory->createView(
|
||||
$list,
|
||||
$preferredChoices,
|
||||
$label,
|
||||
$index,
|
||||
$groupBy,
|
||||
$attr
|
||||
);
|
||||
}
|
||||
|
||||
return $this->views[$hash];
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
<?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 first and the array key as the second
|
||||
* argument.
|
||||
*
|
||||
* @param array|\Traversable $choices The choices
|
||||
* @param null|callable $value The callable generating the choice
|
||||
* values
|
||||
*
|
||||
* @return ChoiceListInterface The choice list
|
||||
*/
|
||||
public function createListFromChoices($choices, $value = null);
|
||||
|
||||
/**
|
||||
* Creates a choice list for the given choices.
|
||||
*
|
||||
* The choices should be passed in the keys of the choices array. Since the
|
||||
* choices array will be flipped, the entries of the array must be strings
|
||||
* or integers.
|
||||
*
|
||||
* 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 array|\Traversable $choices The choices
|
||||
* @param null|callable $value The callable generating the choice
|
||||
* values
|
||||
*
|
||||
* @return ChoiceListInterface The choice list
|
||||
*
|
||||
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
|
||||
* removed in Symfony 3.0.
|
||||
*/
|
||||
public function createListFromFlippedChoices($choices, $value = 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 first and the array key as the second
|
||||
* argument.
|
||||
*
|
||||
* @param ChoiceLoaderInterface $loader The choice loader
|
||||
* @param null|callable $value The callable generating the choice
|
||||
* values
|
||||
*
|
||||
* @return ChoiceListInterface The choice list
|
||||
*/
|
||||
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = 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 groups can be passed as a multi-dimensional array. In that case, a
|
||||
* group will be created for each array entry containing a nested array.
|
||||
* For all other entries, the choice for the corresponding key will be
|
||||
* inserted at that position.
|
||||
*
|
||||
* 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 ChoiceListInterface $list The choice list
|
||||
* @param null|array|callable $preferredChoices The preferred choices
|
||||
* @param null|callable $label The callable generating
|
||||
* the choice labels
|
||||
* @param null|callable $index The callable generating
|
||||
* the view indices
|
||||
* @param null|array|\Traversable|callable $groupBy The callable generating
|
||||
* the group names
|
||||
* @param null|array|callable $attr The callable generating
|
||||
* the HTML attributes
|
||||
*
|
||||
* @return ChoiceListView The choice list view
|
||||
*/
|
||||
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null);
|
||||
}
|
@ -0,0 +1,414 @@
|
||||
<?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\ArrayKeyChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ChoiceListFactoryInterface}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class DefaultChoiceListFactory implements ChoiceListFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Flattens an array into the given output variable.
|
||||
*
|
||||
* @param array $array The array to flatten
|
||||
* @param array $output The flattened output
|
||||
*
|
||||
* @internal Should not be used by user-land code
|
||||
*/
|
||||
public static function flatten(array $array, &$output)
|
||||
{
|
||||
if (null === $output) {
|
||||
$output = array();
|
||||
}
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
self::flatten($value, $output);
|
||||
continue;
|
||||
}
|
||||
|
||||
$output[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens and flips an array into the given output variable.
|
||||
*
|
||||
* During the flattening, the keys and values of the input array are
|
||||
* flipped.
|
||||
*
|
||||
* @param array $array The array to flatten
|
||||
* @param array $output The flattened output
|
||||
*
|
||||
* @internal Should not be used by user-land code
|
||||
*/
|
||||
public static function flattenFlipped(array $array, &$output)
|
||||
{
|
||||
if (null === $output) {
|
||||
$output = array();
|
||||
}
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
self::flattenFlipped($value, $output);
|
||||
continue;
|
||||
}
|
||||
|
||||
$output[$value] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createListFromChoices($choices, $value = null)
|
||||
{
|
||||
if (!is_array($choices) && !$choices instanceof \Traversable) {
|
||||
throw new UnexpectedTypeException($choices, 'array or \Traversable');
|
||||
}
|
||||
|
||||
if (null !== $value && !is_callable($value)) {
|
||||
throw new UnexpectedTypeException($value, 'null or callable');
|
||||
}
|
||||
|
||||
if ($choices instanceof \Traversable) {
|
||||
$choices = iterator_to_array($choices);
|
||||
}
|
||||
|
||||
// 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.
|
||||
self::flatten($choices, $flatChoices);
|
||||
|
||||
// If no values are given, use incrementing integers as values
|
||||
// We can not use the choices themselves, because we don't know whether
|
||||
// choices can be converted to (duplicate-free) strings
|
||||
if (null === $value) {
|
||||
$values = $flatChoices;
|
||||
$i = 0;
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$values[$key] = (string) $i++;
|
||||
}
|
||||
|
||||
return new ArrayChoiceList($flatChoices, $values);
|
||||
}
|
||||
|
||||
// Can't use array_map(), because array_map() doesn't pass the key
|
||||
// Can't use array_walk(), which ignores the return value of the
|
||||
// closure
|
||||
$values = array();
|
||||
foreach ($flatChoices as $key => $choice) {
|
||||
$values[$key] = call_user_func($value, $choice, $key);
|
||||
}
|
||||
|
||||
return new ArrayChoiceList($flatChoices, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
|
||||
* removed in Symfony 3.0.
|
||||
*/
|
||||
public function createListFromFlippedChoices($choices, $value = null)
|
||||
{
|
||||
if (!is_array($choices) && !$choices instanceof \Traversable) {
|
||||
throw new UnexpectedTypeException($choices, 'array or \Traversable');
|
||||
}
|
||||
|
||||
if (null !== $value && !is_callable($value)) {
|
||||
throw new UnexpectedTypeException($value, 'null or callable');
|
||||
}
|
||||
|
||||
if ($choices instanceof \Traversable) {
|
||||
$choices = iterator_to_array($choices);
|
||||
}
|
||||
|
||||
// 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.
|
||||
self::flattenFlipped($choices, $flatChoices);
|
||||
|
||||
// If no values are given, use the choices as values
|
||||
// Since the choices are stored in the collection keys, i.e. they are
|
||||
// strings or integers, we are guaranteed to be able to convert them
|
||||
// to strings
|
||||
if (null === $value) {
|
||||
$values = array_map('strval', $flatChoices);
|
||||
|
||||
return new ArrayKeyChoiceList($flatChoices, $values);
|
||||
}
|
||||
|
||||
// Can't use array_map(), because array_map() doesn't pass the key
|
||||
// Can't use array_walk(), which ignores the return value of the
|
||||
// closure
|
||||
$values = array();
|
||||
foreach ($flatChoices as $key => $choice) {
|
||||
$values[$key] = call_user_func($value, $choice, $key);
|
||||
}
|
||||
|
||||
return new ArrayKeyChoiceList($flatChoices, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
|
||||
{
|
||||
if (null !== $value && !is_callable($value)) {
|
||||
throw new UnexpectedTypeException($value, 'null or callable');
|
||||
}
|
||||
|
||||
return new LazyChoiceList($loader, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
|
||||
{
|
||||
if (null !== $preferredChoices && !is_array($preferredChoices) && !is_callable($preferredChoices)) {
|
||||
throw new UnexpectedTypeException($preferredChoices, 'null, array or callable');
|
||||
}
|
||||
|
||||
if (null !== $label && !is_callable($label)) {
|
||||
throw new UnexpectedTypeException($label, 'null or callable');
|
||||
}
|
||||
|
||||
if (null !== $index && !is_callable($index)) {
|
||||
throw new UnexpectedTypeException($index, 'null or callable');
|
||||
}
|
||||
|
||||
if (null !== $groupBy && !is_array($groupBy) && !$groupBy instanceof \Traversable && !is_callable($groupBy)) {
|
||||
throw new UnexpectedTypeException($groupBy, 'null, array, \Traversable or callable');
|
||||
}
|
||||
|
||||
if (null !== $attr && !is_array($attr) && !is_callable($attr)) {
|
||||
throw new UnexpectedTypeException($attr, 'null, array or callable');
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
if ($list instanceof LegacyChoiceListInterface && null === $preferredChoices
|
||||
&& null === $label && null === $index && null === $groupBy && null === $attr) {
|
||||
return new ChoiceListView($list->getRemainingViews(), $list->getPreferredViews());
|
||||
}
|
||||
|
||||
$preferredViews = array();
|
||||
$otherViews = array();
|
||||
$choices = $list->getChoices();
|
||||
$values = $list->getValues();
|
||||
|
||||
if (!is_callable($preferredChoices) && !empty($preferredChoices)) {
|
||||
$preferredChoices = function ($choice) use ($preferredChoices) {
|
||||
return false !== array_search($choice, $preferredChoices, true);
|
||||
};
|
||||
}
|
||||
|
||||
// The names are generated from an incrementing integer by default
|
||||
if (null === $index) {
|
||||
$i = 0;
|
||||
$index = function () use (&$i) {
|
||||
return $i++;
|
||||
};
|
||||
}
|
||||
|
||||
// If $groupBy is not given, no grouping is done
|
||||
if (empty($groupBy)) {
|
||||
foreach ($choices as $key => $choice) {
|
||||
self::addChoiceView(
|
||||
$choice,
|
||||
$key,
|
||||
$label,
|
||||
$values,
|
||||
$index,
|
||||
$attr,
|
||||
$preferredChoices,
|
||||
$preferredViews,
|
||||
$otherViews
|
||||
);
|
||||
}
|
||||
|
||||
return new ChoiceListView($otherViews, $preferredViews);
|
||||
}
|
||||
|
||||
// If $groupBy is a callable, choices are added to the group with the
|
||||
// name returned by the callable. If the callable returns null, the
|
||||
// choice is not added to any group
|
||||
if (is_callable($groupBy)) {
|
||||
foreach ($choices as $key => $choice) {
|
||||
self::addChoiceViewGroupedBy(
|
||||
$groupBy,
|
||||
$choice,
|
||||
$key,
|
||||
$label,
|
||||
$values,
|
||||
$index,
|
||||
$attr,
|
||||
$preferredChoices,
|
||||
$preferredViews,
|
||||
$otherViews
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// If $groupBy is passed as array, use that array as template for
|
||||
// constructing the groups
|
||||
self::addChoiceViewsGroupedBy(
|
||||
$groupBy,
|
||||
$label,
|
||||
$choices,
|
||||
$values,
|
||||
$index,
|
||||
$attr,
|
||||
$preferredChoices,
|
||||
$preferredViews,
|
||||
$otherViews
|
||||
);
|
||||
}
|
||||
|
||||
// Remove any empty group views that may have been created by
|
||||
// addChoiceViewGroupedBy()
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ChoiceListView($otherViews, $preferredViews);
|
||||
}
|
||||
|
||||
private static function addChoiceView($choice, $key, $label, $values, $index, $attr, $isPreferred, &$preferredViews, &$otherViews)
|
||||
{
|
||||
$view = new ChoiceView(
|
||||
// If the labels are null, use the choice key by default
|
||||
null === $label ? (string) $key : (string) call_user_func($label, $choice, $key),
|
||||
$values[$key],
|
||||
$choice,
|
||||
// The attributes may be a callable or a mapping from choice indices
|
||||
// to nested arrays
|
||||
is_callable($attr) ? call_user_func($attr, $choice, $key) : (isset($attr[$key]) ? $attr[$key] : array())
|
||||
);
|
||||
|
||||
// $isPreferred may be null if no choices are preferred
|
||||
if ($isPreferred && call_user_func($isPreferred, $choice, $key)) {
|
||||
$preferredViews[call_user_func($index, $choice, $key)] = $view;
|
||||
} else {
|
||||
$otherViews[call_user_func($index, $choice, $key)] = $view;
|
||||
}
|
||||
}
|
||||
|
||||
private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $values, $index, $attr, $isPreferred, &$preferredViews, &$otherViews)
|
||||
{
|
||||
foreach ($groupBy as $key => $content) {
|
||||
// Add the contents of groups to new ChoiceGroupView instances
|
||||
if (is_array($content)) {
|
||||
$preferredViewsForGroup = array();
|
||||
$otherViewsForGroup = array();
|
||||
|
||||
self::addChoiceViewsGroupedBy(
|
||||
$content,
|
||||
$label,
|
||||
$choices,
|
||||
$values,
|
||||
$index,
|
||||
$attr,
|
||||
$isPreferred,
|
||||
$preferredViewsForGroup,
|
||||
$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[$key],
|
||||
$key,
|
||||
$label,
|
||||
$values,
|
||||
$index,
|
||||
$attr,
|
||||
$isPreferred,
|
||||
$preferredViews,
|
||||
$otherViews
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static function addChoiceViewGroupedBy($groupBy, $choice, $key, $label, $values, $index, $attr, $isPreferred, &$preferredViews, &$otherViews)
|
||||
{
|
||||
$groupLabel = call_user_func($groupBy, $choice, $key);
|
||||
|
||||
if (null === $groupLabel) {
|
||||
// If the callable returns null, don't group the choice
|
||||
self::addChoiceView(
|
||||
$choice,
|
||||
$key,
|
||||
$label,
|
||||
$values,
|
||||
$index,
|
||||
$attr,
|
||||
$isPreferred,
|
||||
$preferredViews,
|
||||
$otherViews
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the group views if necessary. Unnnecessarily 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);
|
||||
}
|
||||
|
||||
self::addChoiceView(
|
||||
$choice,
|
||||
$key,
|
||||
$label,
|
||||
$values,
|
||||
$index,
|
||||
$attr,
|
||||
$isPreferred,
|
||||
$preferredViews[$groupLabel]->choices,
|
||||
$otherViews[$groupLabel]->choices
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,226 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Adds property path support to a choice list factory.
|
||||
*
|
||||
* Pass the decorated factory to the constructor:
|
||||
*
|
||||
* ```php
|
||||
* $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:
|
||||
*
|
||||
* ```php
|
||||
* // extract values from the $value property
|
||||
* $list = $createListFromChoices($objects, 'value');
|
||||
* ```
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyAccessDecorator implements ChoiceListFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var ChoiceListFactoryInterface
|
||||
*/
|
||||
private $decoratedFactory;
|
||||
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
/**
|
||||
* Decorates the given factory.
|
||||
*
|
||||
* @param ChoiceListFactoryInterface $decoratedFactory The decorated factory
|
||||
* @param null|PropertyAccessorInterface $propertyAccessor The used property accessor
|
||||
*/
|
||||
public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->decoratedFactory = $decoratedFactory;
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decorated factory.
|
||||
*
|
||||
* @return ChoiceListFactoryInterface The decorated factory
|
||||
*/
|
||||
public function getDecoratedFactory()
|
||||
{
|
||||
return $this->decoratedFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array|\Traversable $choices The choices
|
||||
* @param null|callable|string|PropertyPath $value The callable or path for
|
||||
* generating the choice values
|
||||
*
|
||||
* @return ChoiceListInterface The choice list
|
||||
*/
|
||||
public function createListFromChoices($choices, $value = null)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = new PropertyPath($value);
|
||||
}
|
||||
|
||||
if ($value instanceof PropertyPath) {
|
||||
$accessor = $this->propertyAccessor;
|
||||
$value = function ($choice) use ($accessor, $value) {
|
||||
return $accessor->getValue($choice, $value);
|
||||
};
|
||||
}
|
||||
|
||||
return $this->decoratedFactory->createListFromChoices($choices, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array|\Traversable $choices The choices
|
||||
* @param null|callable|string|PropertyPath $value The callable or path for
|
||||
* generating the choice values
|
||||
*
|
||||
* @return ChoiceListInterface The choice list
|
||||
*
|
||||
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
|
||||
* removed in Symfony 3.0.
|
||||
*/
|
||||
public function createListFromFlippedChoices($choices, $value = null)
|
||||
{
|
||||
// Property paths are not supported here, because array keys can never
|
||||
// be objects
|
||||
return $this->decoratedFactory->createListFromFlippedChoices($choices, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param ChoiceLoaderInterface $loader The choice loader
|
||||
* @param null|callable|string|PropertyPath $value The callable or path for
|
||||
* generating the choice values
|
||||
*
|
||||
* @return ChoiceListInterface The choice list
|
||||
*/
|
||||
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = new PropertyPath($value);
|
||||
}
|
||||
|
||||
if ($value instanceof PropertyPath) {
|
||||
$accessor = $this->propertyAccessor;
|
||||
$value = function ($choice) use ($accessor, $value) {
|
||||
return $accessor->getValue($choice, $value);
|
||||
};
|
||||
}
|
||||
|
||||
return $this->decoratedFactory->createListFromLoader($loader, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param ChoiceListInterface $list The choice list
|
||||
* @param null|array|callable|PropertyPath $preferredChoices The preferred choices
|
||||
* @param null|callable|PropertyPath $label The callable or path
|
||||
* generating the choice labels
|
||||
* @param null|callable|PropertyPath $index The callable or path
|
||||
* generating the view indices
|
||||
* @param null|array|\Traversable|callable|PropertyPath $groupBy The callable or path
|
||||
* generating the group names
|
||||
* @param null|array|callable|PropertyPath $attr The callable or path
|
||||
* generating the HTML attributes
|
||||
*
|
||||
* @return ChoiceListView The choice list view
|
||||
*/
|
||||
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
|
||||
{
|
||||
$accessor = $this->propertyAccessor;
|
||||
|
||||
if (is_string($label)) {
|
||||
$label = new PropertyPath($label);
|
||||
}
|
||||
|
||||
if ($label instanceof PropertyPath) {
|
||||
$label = function ($choice) use ($accessor, $label) {
|
||||
return $accessor->getValue($choice, $label);
|
||||
};
|
||||
}
|
||||
|
||||
if (is_string($preferredChoices)) {
|
||||
$preferredChoices = new PropertyPath($preferredChoices);
|
||||
}
|
||||
|
||||
if ($preferredChoices instanceof PropertyPath) {
|
||||
$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 PropertyPath) {
|
||||
$index = function ($choice) use ($accessor, $index) {
|
||||
return $accessor->getValue($choice, $index);
|
||||
};
|
||||
}
|
||||
|
||||
if (is_string($groupBy)) {
|
||||
$groupBy = new PropertyPath($groupBy);
|
||||
}
|
||||
|
||||
if ($groupBy instanceof PropertyPath) {
|
||||
$groupBy = function ($choice) use ($accessor, $groupBy) {
|
||||
try {
|
||||
return $accessor->getValue($choice, $groupBy);
|
||||
} catch (UnexpectedTypeException $e) {
|
||||
// Don't group if path is not readable
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (is_string($attr)) {
|
||||
$attr = new PropertyPath($attr);
|
||||
}
|
||||
|
||||
if ($attr instanceof PropertyPath) {
|
||||
$attr = function ($choice) use ($accessor, $attr) {
|
||||
return $accessor->getValue($choice, $attr);
|
||||
};
|
||||
}
|
||||
|
||||
return $this->decoratedFactory->createView($list, $preferredChoices, $label, $index, $groupBy, $attr);
|
||||
}
|
||||
}
|
115
src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php
Normal file
115
src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* The choice loader.
|
||||
*
|
||||
* @var ChoiceLoaderInterface
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
/**
|
||||
* The callable creating string values for each choice.
|
||||
*
|
||||
* If null, choices are simply cast to strings.
|
||||
*
|
||||
* @var null|callable
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @var ChoiceListInterface
|
||||
*/
|
||||
private $loadedList;
|
||||
|
||||
/**
|
||||
* 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 ChoiceLoaderInterface $loader The choice loader
|
||||
* @param null|callable $value The callable generating the choice
|
||||
* values
|
||||
*/
|
||||
public function __construct(ChoiceLoaderInterface $loader, $value = null)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChoices()
|
||||
{
|
||||
if (!$this->loadedList) {
|
||||
$this->loadedList = $this->loader->loadChoiceList($this->value);
|
||||
}
|
||||
|
||||
return $this->loadedList->getChoices();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
if (!$this->loadedList) {
|
||||
$this->loadedList = $this->loader->loadChoiceList($this->value);
|
||||
}
|
||||
|
||||
return $this->loadedList->getValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChoicesForValues(array $values)
|
||||
{
|
||||
if (!$this->loadedList) {
|
||||
return $this->loader->loadChoicesForValues($values, $this->value);
|
||||
}
|
||||
|
||||
return $this->loadedList->getChoicesForValues($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValuesForChoices(array $choices)
|
||||
{
|
||||
if (!$this->loadedList) {
|
||||
return $this->loader->loadValuesForChoices($choices, $this->value);
|
||||
}
|
||||
|
||||
return $this->loadedList->getValuesForChoices($choices);
|
||||
}
|
||||
}
|
@ -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 first and the array key as the second
|
||||
* argument.
|
||||
*
|
||||
* @param null|callable $value The callable which generates the values
|
||||
* from choices
|
||||
*
|
||||
* @return ChoiceListInterface The loaded choice list
|
||||
*/
|
||||
public function loadChoiceList($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 first and the array key as the second
|
||||
* argument.
|
||||
*
|
||||
* @param string[] $values An array of choice values. Non-existing
|
||||
* values in this array are ignored
|
||||
* @param null|callable $value The callable generating the choice values
|
||||
*
|
||||
* @return array An array of choices
|
||||
*/
|
||||
public function loadChoicesForValues(array $values, $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 first and the array key as the second
|
||||
* argument.
|
||||
*
|
||||
* @param array $choices An array of choices. Non-existing choices in
|
||||
* this array are ignored
|
||||
* @param null|callable $value The callable generating the choice values
|
||||
*
|
||||
* @return string[] An array of choice values
|
||||
*/
|
||||
public function loadValuesForChoices(array $choices, $value = null);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?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>
|
||||
*/
|
||||
class ChoiceGroupView implements \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* The label of the group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The choice views in the group
|
||||
*
|
||||
* @var ChoiceGroupView[]|ChoiceView[]
|
||||
*/
|
||||
public $choices;
|
||||
|
||||
/**
|
||||
* Creates a new choice group view.
|
||||
*
|
||||
* @param string $label The label of the group.
|
||||
* @param ChoiceGroupView[]|ChoiceView[] $choices The choice views in the
|
||||
* group.
|
||||
*/
|
||||
public function __construct($label, array $choices = array())
|
||||
{
|
||||
$this->label = $label;
|
||||
$this->choices = $choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->choices);
|
||||
}
|
||||
}
|
@ -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\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
|
||||
{
|
||||
/**
|
||||
* The choices.
|
||||
*
|
||||
* @var ChoiceGroupView[]|ChoiceView[]
|
||||
*/
|
||||
public $choices;
|
||||
|
||||
/**
|
||||
* The preferred choices.
|
||||
*
|
||||
* @var ChoiceGroupView[]|ChoiceView[]
|
||||
*/
|
||||
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(), array $preferredChoices = array())
|
||||
{
|
||||
$this->choices = $choices;
|
||||
$this->preferredChoices = $preferredChoices;
|
||||
}
|
||||
}
|
64
src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php
Normal file
64
src/Symfony/Component/Form/ChoiceList/View/ChoiceView.php
Normal 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\View;
|
||||
|
||||
/**
|
||||
* Represents a choice in templates.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ChoiceView
|
||||
{
|
||||
/**
|
||||
* The label displayed to humans.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The view representation of the choice.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* The original choice value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* Additional attributes for the HTML tag.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $attr;
|
||||
|
||||
/**
|
||||
* Creates a new choice view.
|
||||
*
|
||||
* @param string $label The label displayed to humans
|
||||
* @param string $value The view representation of the choice
|
||||
* @param mixed $data The original choice
|
||||
* @param array $attr Additional attributes for the HTML tag
|
||||
*/
|
||||
public function __construct($label, $value, $data, array $attr = array())
|
||||
{
|
||||
$this->label = $label;
|
||||
$this->value = $value;
|
||||
$this->data = $data;
|
||||
$this->attr = $attr;
|
||||
}
|
||||
}
|
@ -29,10 +29,13 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
* <code>
|
||||
* $choices = array(true, false);
|
||||
* $labels = array('Agree', 'Disagree');
|
||||
* $choiceList = new ChoiceList($choices, $labels);
|
||||
* $choiceList = new ArrayChoiceList($choices, $labels);
|
||||
* </code>
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link \Symfony\Component\Form\ArrayChoiceList\ArrayChoiceList} instead.
|
||||
*/
|
||||
class ChoiceList implements ChoiceListInterface
|
||||
{
|
||||
|
@ -25,23 +25,13 @@ namespace Symfony\Component\Form\Extension\Core\ChoiceList;
|
||||
* in the HTML "value" attribute.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link \Symfony\Component\Form\ArrayChoiceList\ChoiceListInterface}
|
||||
* instead.
|
||||
*/
|
||||
interface ChoiceListInterface
|
||||
interface ChoiceListInterface extends \Symfony\Component\Form\ChoiceList\ChoiceListInterface
|
||||
{
|
||||
/**
|
||||
* Returns the list of choices.
|
||||
*
|
||||
* @return array The choices with their indices as keys
|
||||
*/
|
||||
public function getChoices();
|
||||
|
||||
/**
|
||||
* Returns the values for the choices.
|
||||
*
|
||||
* @return array The values with the corresponding choice indices as keys
|
||||
*/
|
||||
public function getValues();
|
||||
|
||||
/**
|
||||
* Returns the choice views of the preferred choices as nested array with
|
||||
* the choice groups as top-level keys.
|
||||
@ -92,37 +82,6 @@ interface ChoiceListInterface
|
||||
*/
|
||||
public function getRemainingViews();
|
||||
|
||||
/**
|
||||
* Returns the choices corresponding to the given values.
|
||||
*
|
||||
* The choices can have any data type.
|
||||
*
|
||||
* The choices must be returned with the same keys and in the same order
|
||||
* as the corresponding values in the given array.
|
||||
*
|
||||
* @param array $values An array of choice values. Not existing values in
|
||||
* this array are ignored
|
||||
*
|
||||
* @return array An array of choices with ascending, 0-based numeric keys
|
||||
*/
|
||||
public function getChoicesForValues(array $values);
|
||||
|
||||
/**
|
||||
* Returns the values corresponding to the given choices.
|
||||
*
|
||||
* The values must be strings.
|
||||
*
|
||||
* The values must be 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. Not existing choices in this
|
||||
* array are ignored
|
||||
*
|
||||
* @return array An array of choice values with ascending, 0-based numeric
|
||||
* keys
|
||||
*/
|
||||
public function getValuesForChoices(array $choices);
|
||||
|
||||
/**
|
||||
* Returns the indices corresponding to the given choices.
|
||||
*
|
||||
|
@ -21,6 +21,10 @@ use Symfony\Component\Form\Exception\InvalidArgumentException;
|
||||
* which should return a ChoiceListInterface instance.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link \Symfony\Component\Form\ArrayChoiceList\LazyChoiceList}
|
||||
* instead.
|
||||
*/
|
||||
abstract class LazyChoiceList implements ChoiceListInterface
|
||||
{
|
||||
|
@ -32,6 +32,10 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
* </code>
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link \Symfony\Component\Form\ArrayChoiceList\ArrayChoiceList}
|
||||
* instead.
|
||||
*/
|
||||
class ObjectChoiceList extends ChoiceList
|
||||
{
|
||||
|
@ -28,6 +28,10 @@ namespace Symfony\Component\Form\Extension\Core\ChoiceList;
|
||||
* </code>
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link \Symfony\Component\Form\ArrayChoiceList\ArrayKeyChoiceList}
|
||||
* instead.
|
||||
*/
|
||||
class SimpleChoiceList extends ChoiceList
|
||||
{
|
||||
|
@ -12,7 +12,12 @@
|
||||
namespace Symfony\Component\Form\Extension\Core;
|
||||
|
||||
use Symfony\Component\Form\AbstractExtension;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
/**
|
||||
* Represents the main form extension, which loads the core functionality.
|
||||
@ -21,13 +26,29 @@ use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
*/
|
||||
class CoreExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
/**
|
||||
* @var ChoiceListFactoryInterface
|
||||
*/
|
||||
private $choiceListFactory;
|
||||
|
||||
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
|
||||
}
|
||||
|
||||
protected function loadTypes()
|
||||
{
|
||||
return array(
|
||||
new Type\FormType(PropertyAccess::createPropertyAccessor()),
|
||||
new Type\FormType($this->propertyAccessor),
|
||||
new Type\BirthdayType(),
|
||||
new Type\CheckboxType(),
|
||||
new Type\ChoiceType(),
|
||||
new Type\ChoiceType($this->choiceListFactory),
|
||||
new Type\CollectionType(),
|
||||
new Type\CountryType(),
|
||||
new Type\DateType(),
|
||||
|
@ -0,0 +1,93 @@
|
||||
<?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\Extension\Core\DataMapper;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Exception;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
/**
|
||||
* Maps choices to/from checkbox forms.
|
||||
*
|
||||
* A {@link ChoiceListInterface} implementation is used to find the
|
||||
* corresponding string values for the choices. Each checkbox form whose "value"
|
||||
* option corresponds to any of the selected values is marked as selected.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class CheckboxListMapper implements DataMapperInterface
|
||||
{
|
||||
/**
|
||||
* @var ChoiceListInterface
|
||||
*/
|
||||
private $choiceList;
|
||||
|
||||
public function __construct(ChoiceListInterface $choiceList)
|
||||
{
|
||||
$this->choiceList = $choiceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mapDataToForms($choices, $checkboxes)
|
||||
{
|
||||
if (null === $choices) {
|
||||
$choices = array();
|
||||
}
|
||||
|
||||
if (!is_array($choices)) {
|
||||
throw new TransformationFailedException('Expected an array.');
|
||||
}
|
||||
|
||||
try {
|
||||
$valueMap = array_flip($this->choiceList->getValuesForChoices($choices));
|
||||
} catch (\Exception $e) {
|
||||
throw new TransformationFailedException(
|
||||
'Can not read the choices from the choice list.',
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($checkboxes as $checkbox) {
|
||||
$value = $checkbox->getConfig()->getOption('value');
|
||||
$checkbox->setData(isset($valueMap[$value]) ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mapFormsToData($checkboxes, &$choices)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($checkboxes as $checkbox) {
|
||||
if ($checkbox->getData()) {
|
||||
// construct an array of choice values
|
||||
$values[] = $checkbox->getConfig()->getOption('value');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$choices = $this->choiceList->getChoicesForValues($values);
|
||||
} catch (\Exception $e) {
|
||||
throw new TransformationFailedException(
|
||||
'Can not read the values from the choice list.',
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
/**
|
||||
* A data mapper using property paths to read/write data.
|
||||
* Maps arrays/objects to/from forms using property paths.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
@ -31,7 +31,7 @@ class PropertyPathMapper implements DataMapperInterface
|
||||
/**
|
||||
* Creates a new property path mapper.
|
||||
*
|
||||
* @param PropertyAccessorInterface $propertyAccessor
|
||||
* @param PropertyAccessorInterface $propertyAccessor The property accessor
|
||||
*/
|
||||
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
|
@ -0,0 +1,73 @@
|
||||
<?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\Extension\Core\DataMapper;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
|
||||
/**
|
||||
* Maps choices to/from radio forms.
|
||||
*
|
||||
* A {@link ChoiceListInterface} implementation is used to find the
|
||||
* corresponding string values for the choices. The radio form whose "value"
|
||||
* option corresponds to the selected value is marked as selected.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class RadioListMapper implements DataMapperInterface
|
||||
{
|
||||
/**
|
||||
* @var ChoiceListInterface
|
||||
*/
|
||||
private $choiceList;
|
||||
|
||||
public function __construct(ChoiceListInterface $choiceList)
|
||||
{
|
||||
$this->choiceList = $choiceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mapDataToForms($choice, $radios)
|
||||
{
|
||||
$valueMap = array_flip($this->choiceList->getValuesForChoices(array($choice)));
|
||||
|
||||
foreach ($radios as $radio) {
|
||||
$value = $radio->getConfig()->getOption('value');
|
||||
$radio->setData(isset($valueMap[$value]) ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mapFormsToData($radios, &$choice)
|
||||
{
|
||||
$choice = null;
|
||||
|
||||
foreach ($radios as $radio) {
|
||||
if ($radio->getData()) {
|
||||
if ('placeholder' === $radio->getName()) {
|
||||
$choice = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $radio->getConfig()->getOption('value');
|
||||
$choice = current($this->choiceList->getChoicesForValues(array($value)));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,12 +11,16 @@
|
||||
|
||||
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link \Symfony\Component\Form\ArrayChoiceList\LazyChoiceList}
|
||||
* instead.
|
||||
*/
|
||||
class ChoiceToBooleanArrayTransformer implements DataTransformerInterface
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\DataTransformer;
|
||||
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
@ -43,7 +43,7 @@ class ChoiceToValueTransformer implements DataTransformerInterface
|
||||
throw new TransformationFailedException('Expected a scalar.');
|
||||
}
|
||||
|
||||
// These are now valid ChoiceList values, so we can return null
|
||||
// These are now valid ArrayChoiceList values, so we can return null
|
||||
// right away
|
||||
if ('' === $value || null === $value) {
|
||||
return;
|
||||
|
@ -11,12 +11,16 @@
|
||||
|
||||
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
|
||||
* Use {@link \Symfony\Component\Form\ArrayChoiceList\LazyChoiceList}
|
||||
* instead.
|
||||
*/
|
||||
class ChoicesToBooleanArrayTransformer implements DataTransformerInterface
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\DataTransformer;
|
||||
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
|
@ -11,11 +11,11 @@
|
||||
|
||||
namespace Symfony\Component\Form\Extension\Core\EventListener;
|
||||
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
|
||||
/**
|
||||
* Takes care of converting the input from a list of checkboxes to a correctly
|
||||
|
@ -11,10 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\Form\Extension\Core\EventListener;
|
||||
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
|
||||
/**
|
||||
* Takes care of converting the input from a single radio button
|
||||
|
@ -12,19 +12,25 @@
|
||||
namespace Symfony\Component\Form\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper;
|
||||
use Symfony\Component\Form\Extension\Core\DataMapper\CheckboxListMapper;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\Form\Exception\LogicException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
|
||||
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener;
|
||||
use Symfony\Component\Form\Extension\Core\EventListener\FixCheckboxInputListener;
|
||||
use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToBooleanArrayTransformer;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
@ -33,55 +39,112 @@ class ChoiceType extends AbstractType
|
||||
/**
|
||||
* Caches created choice lists.
|
||||
*
|
||||
* @var array
|
||||
* @var ChoiceListFactoryInterface
|
||||
*/
|
||||
private $choiceListCache = array();
|
||||
private $choiceListFactory;
|
||||
|
||||
public function __construct(ChoiceListFactoryInterface $choiceListFactory = null)
|
||||
{
|
||||
$this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(new DefaultChoiceListFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
if (!$options['choice_list'] && !is_array($options['choices']) && !$options['choices'] instanceof \Traversable) {
|
||||
throw new LogicException('Either the option "choices" or "choice_list" must be set.');
|
||||
}
|
||||
|
||||
if ($options['expanded']) {
|
||||
$builder->setDataMapper($options['multiple']
|
||||
? new CheckboxListMapper($options['choice_list'])
|
||||
: new RadioListMapper($options['choice_list']));
|
||||
|
||||
// Initialize all choices before doing the index check below.
|
||||
// This helps in cases where index checks are optimized for non
|
||||
// initialized choice lists. For example, when using an SQL driver,
|
||||
// the index check would read in one SQL query and the initialization
|
||||
// requires another SQL query. When the initialization is done first,
|
||||
// one SQL query is sufficient.
|
||||
$preferredViews = $options['choice_list']->getPreferredViews();
|
||||
$remainingViews = $options['choice_list']->getRemainingViews();
|
||||
|
||||
$choiceListView = $this->createChoiceListView($options['choice_list'], $options);
|
||||
$builder->setAttribute('choice_list_view', $choiceListView);
|
||||
|
||||
// Check if the choices already contain the empty value
|
||||
// Only add the empty value option if this is not the case
|
||||
// Only add the placeholder option if this is not the case
|
||||
if (null !== $options['placeholder'] && 0 === count($options['choice_list']->getChoicesForValues(array('')))) {
|
||||
$placeholderView = new ChoiceView(null, '', $options['placeholder']);
|
||||
$placeholderView = new ChoiceView($options['placeholder'], '', null);
|
||||
|
||||
// "placeholder" is a reserved index
|
||||
$this->addSubForms($builder, array('placeholder' => $placeholderView), $options);
|
||||
// "placeholder" is a reserved name
|
||||
$this->addSubForm($builder, 'placeholder', $placeholderView, $options);
|
||||
}
|
||||
|
||||
$this->addSubForms($builder, $preferredViews, $options);
|
||||
$this->addSubForms($builder, $remainingViews, $options);
|
||||
$this->addSubForms($builder, $choiceListView->preferredChoices, $options);
|
||||
$this->addSubForms($builder, $choiceListView->choices, $options);
|
||||
|
||||
if ($options['multiple']) {
|
||||
$builder->addViewTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list']));
|
||||
$builder->addEventSubscriber(new FixCheckboxInputListener($options['choice_list']), 10);
|
||||
} else {
|
||||
$builder->addViewTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list'], $builder->has('placeholder')));
|
||||
$builder->addEventSubscriber(new FixRadioInputListener($options['choice_list'], $builder->has('placeholder')), 10);
|
||||
// Make sure that scalar, submitted values are converted to arrays
|
||||
// which can be submitted to the checkboxes/radio buttons
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
|
||||
$form = $event->getForm();
|
||||
$data = $event->getData();
|
||||
|
||||
// Convert the submitted data to a string, if scalar, before
|
||||
// casting it to an array
|
||||
if (!is_array($data)) {
|
||||
$data = (array) (string) $data;
|
||||
}
|
||||
} else {
|
||||
if ($options['multiple']) {
|
||||
|
||||
// A map from submitted values to integers
|
||||
$valueMap = array_flip($data);
|
||||
|
||||
// Make a copy of the value map to determine whether any unknown
|
||||
// values were submitted
|
||||
$unknownValues = $valueMap;
|
||||
|
||||
// Reconstruct the data as mapping from child names to values
|
||||
$data = array();
|
||||
|
||||
foreach ($form as $child) {
|
||||
$value = $child->getConfig()->getOption('value');
|
||||
|
||||
// Add the value to $data with the child's name as key
|
||||
if (isset($valueMap[$value])) {
|
||||
$data[$child->getName()] = $value;
|
||||
unset($unknownValues[$value]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The empty value is always known, independent of whether a
|
||||
// field exists for it or not
|
||||
unset($unknownValues['']);
|
||||
|
||||
// Throw exception if unknown values were submitted
|
||||
if (count($unknownValues) > 0) {
|
||||
throw new TransformationFailedException(sprintf(
|
||||
'The choices "%s" do not exist in the choice list.',
|
||||
implode('", "', array_keys($unknownValues))
|
||||
));
|
||||
}
|
||||
|
||||
$event->setData($data);
|
||||
});
|
||||
|
||||
if (!$options['multiple']) {
|
||||
// For radio lists, transform empty arrays to null
|
||||
// This is kind of a hack necessary because the RadioListMapper
|
||||
// is not invoked for forms without choices
|
||||
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
|
||||
if (array() === $event->getData()) {
|
||||
$event->setData(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
} elseif ($options['multiple']) {
|
||||
// <select> tag with "multiple" option
|
||||
$builder->addViewTransformer(new ChoicesToValuesTransformer($options['choice_list']));
|
||||
} else {
|
||||
// <select> tag without "multiple" option
|
||||
$builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list']));
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['multiple'] && $options['by_reference']) {
|
||||
// Make sure the collection created during the client->norm
|
||||
@ -95,11 +158,16 @@ class ChoiceType extends AbstractType
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
/** @var ChoiceListView $choiceListView */
|
||||
$choiceListView = $form->getConfig()->hasAttribute('choice_list_view')
|
||||
? $form->getConfig()->getAttribute('choice_list_view')
|
||||
: $this->createChoiceListView($options['choice_list'], $options);
|
||||
|
||||
$view->vars = array_replace($view->vars, array(
|
||||
'multiple' => $options['multiple'],
|
||||
'expanded' => $options['expanded'],
|
||||
'preferred_choices' => $options['choice_list']->getPreferredViews(),
|
||||
'choices' => $options['choice_list']->getRemainingViews(),
|
||||
'preferred_choices' => $choiceListView->preferredChoices,
|
||||
'choices' => $choiceListView->choices,
|
||||
'separator' => '-------------------',
|
||||
'placeholder' => null,
|
||||
));
|
||||
@ -163,20 +231,39 @@ class ChoiceType extends AbstractType
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$choiceListCache = &$this->choiceListCache;
|
||||
$choiceListFactory = $this->choiceListFactory;
|
||||
|
||||
$choiceList = function (Options $options) use ($choiceListFactory) {
|
||||
if (null !== $options['choice_loader']) {
|
||||
// Due to a bug in OptionsResolver, the choices haven't been
|
||||
// validated yet at this point. Remove the if statement once that
|
||||
// bug is resolved
|
||||
if (!$options['choice_loader'] instanceof ChoiceLoaderInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $choiceListFactory->createListFromLoader(
|
||||
$options['choice_loader'],
|
||||
$options['choice_value']
|
||||
);
|
||||
}
|
||||
|
||||
$choiceList = function (Options $options) use (&$choiceListCache) {
|
||||
// Harden against NULL values (like in EntityType and ModelType)
|
||||
$choices = null !== $options['choices'] ? $options['choices'] : array();
|
||||
|
||||
// Reuse existing choice lists in order to increase performance
|
||||
$hash = hash('sha256', serialize(array($choices, $options['preferred_choices'])));
|
||||
|
||||
if (!isset($choiceListCache[$hash])) {
|
||||
$choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices']);
|
||||
// Due to a bug in OptionsResolver, the choices haven't been
|
||||
// validated yet at this point. Remove the if statement once that
|
||||
// bug is resolved
|
||||
if (!is_array($choices) && !$choices instanceof \Traversable) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $choiceListCache[$hash];
|
||||
// BC when choices are in the keys, not in the values
|
||||
if (!$options['choices_as_values']) {
|
||||
return $choiceListFactory->createListFromFlippedChoices($choices, $options['choice_value']);
|
||||
}
|
||||
|
||||
return $choiceListFactory->createListFromChoices($choices, $options['choice_value']);
|
||||
};
|
||||
|
||||
$emptyData = function (Options $options) {
|
||||
@ -219,9 +306,16 @@ class ChoiceType extends AbstractType
|
||||
$resolver->setDefaults(array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choice_list' => $choiceList,
|
||||
'choice_list' => $choiceList, // deprecated
|
||||
'choices' => array(),
|
||||
'choices_as_values' => false,
|
||||
'choice_loader' => null,
|
||||
'choice_label' => null,
|
||||
'choice_name' => null,
|
||||
'choice_value' => null,
|
||||
'choice_attr' => null,
|
||||
'preferred_choices' => array(),
|
||||
'group_by' => null,
|
||||
'empty_data' => $emptyData,
|
||||
'empty_value' => $emptyValue, // deprecated
|
||||
'placeholder' => $placeholder,
|
||||
@ -236,7 +330,16 @@ class ChoiceType extends AbstractType
|
||||
$resolver->setNormalizer('empty_value', $placeholderNormalizer);
|
||||
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
||||
|
||||
$resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'));
|
||||
$resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface'));
|
||||
$resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable'));
|
||||
$resolver->setAllowedTypes('choices_as_values', 'bool');
|
||||
$resolver->setAllowedTypes('choice_loader', array('null', 'Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface'));
|
||||
$resolver->setAllowedTypes('choice_label', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
|
||||
$resolver->setAllowedTypes('choice_name', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
|
||||
$resolver->setAllowedTypes('choice_value', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
|
||||
$resolver->setAllowedTypes('choice_attr', array('null', 'array', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
|
||||
$resolver->setAllowedTypes('preferred_choices', array('array', '\Traversable', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
|
||||
$resolver->setAllowedTypes('group_by', array('null', 'array', '\Traversable', 'string', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,6 +350,21 @@ class ChoiceType extends AbstractType
|
||||
return 'choice';
|
||||
}
|
||||
|
||||
private static function flipRecursive($choices, &$output = array())
|
||||
{
|
||||
foreach ($choices as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$output[$key] = array();
|
||||
self::flipRecursive($value, $output[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$output[$value] = $key;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the sub fields for an expanded choice field.
|
||||
*
|
||||
@ -256,14 +374,36 @@ class ChoiceType extends AbstractType
|
||||
*/
|
||||
private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options)
|
||||
{
|
||||
foreach ($choiceViews as $i => $choiceView) {
|
||||
if (is_array($choiceView)) {
|
||||
foreach ($choiceViews as $name => $choiceView) {
|
||||
// Flatten groups
|
||||
if (is_array($choiceView)) {
|
||||
$this->addSubForms($builder, $choiceView, $options);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($choiceView instanceof ChoiceGroupView) {
|
||||
$this->addSubForms($builder, $choiceView->choices, $options);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addSubForm($builder, $name, $choiceView, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
* @param $name
|
||||
* @param $choiceView
|
||||
* @param array $options
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function addSubForm(FormBuilderInterface $builder, $name, ChoiceView $choiceView, array $options)
|
||||
{
|
||||
$choiceOpts = array(
|
||||
'value' => $choiceView->value,
|
||||
'label' => $choiceView->label,
|
||||
'attr' => $choiceView->attr,
|
||||
'translation_domain' => $options['translation_domain'],
|
||||
'block_name' => 'entry',
|
||||
);
|
||||
@ -277,8 +417,26 @@ class ChoiceType extends AbstractType
|
||||
$choiceType = 'radio';
|
||||
}
|
||||
|
||||
$builder->add($i, $choiceType, $choiceOpts);
|
||||
}
|
||||
}
|
||||
$builder->add($name, $choiceType, $choiceOpts);
|
||||
}
|
||||
|
||||
private function createChoiceListView(ChoiceListInterface $choiceList, array $options)
|
||||
{
|
||||
// If no explicit grouping information is given, use the structural
|
||||
// information from the "choices" option for creating groups
|
||||
if (!$options['group_by'] && $options['choices']) {
|
||||
$options['group_by'] = !$options['choices_as_values']
|
||||
? ChoiceType::flipRecursive($options['choices'])
|
||||
: $options['choices'];
|
||||
}
|
||||
|
||||
return $this->choiceListFactory->createView(
|
||||
$choiceList,
|
||||
$options['preferred_choices'],
|
||||
$options['choice_label'],
|
||||
$options['choice_name'],
|
||||
$options['group_by'],
|
||||
$options['choice_attr']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,29 +16,8 @@ namespace Symfony\Component\Form\Extension\Core\View;
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ChoiceView
|
||||
class ChoiceView extends \Symfony\Component\Form\ChoiceList\View\ChoiceView
|
||||
{
|
||||
/**
|
||||
* The original choice value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* The view representation of the choice.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* The label displayed to humans.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* Creates a new ChoiceView.
|
||||
*
|
||||
@ -48,8 +27,6 @@ class ChoiceView
|
||||
*/
|
||||
public function __construct($data, $value, $label)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->value = $value;
|
||||
$this->label = $label;
|
||||
parent::__construct($label, $value, $data);
|
||||
}
|
||||
}
|
||||
|
@ -516,6 +516,28 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleChoiceAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
|
||||
'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')),
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||
'/select
|
||||
[@name="name"]
|
||||
[not(@required)]
|
||||
[
|
||||
./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
|
||||
/following-sibling::option[@value="&b"][@class="foo&bar"][not(@selected)][.="[trans]Choice&B[/trans]"]
|
||||
]
|
||||
[count(./option)=2]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleChoiceWithPreferred()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
@ -776,6 +798,30 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
|
||||
);
|
||||
}
|
||||
|
||||
public function testMultipleChoiceAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
|
||||
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
|
||||
'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')),
|
||||
'required' => true,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||
'/select
|
||||
[@name="name[]"]
|
||||
[@required="required"]
|
||||
[@multiple="multiple"]
|
||||
[
|
||||
./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
|
||||
/following-sibling::option[@value="&b"][@class="foo&bar"][not(@selected)][.="[trans]Choice&B[/trans]"]
|
||||
]
|
||||
[count(./option)=2]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testMultipleChoiceSkipsPlaceholder()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', array('&a'), array(
|
||||
@ -842,6 +888,29 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleChoiceExpandedAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
|
||||
'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')),
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||
'/div
|
||||
[
|
||||
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
|
||||
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
|
||||
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][@class="foo&bar"][not(@checked)]
|
||||
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
||||
/following-sibling::input[@type="hidden"][@id="name__token"]
|
||||
]
|
||||
[count(./input)=3]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSingleChoiceExpandedWithPlaceholder()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', '&a', array(
|
||||
@ -914,6 +983,32 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
|
||||
);
|
||||
}
|
||||
|
||||
public function testMultipleChoiceExpandedAttributes()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(
|
||||
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'),
|
||||
'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')),
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'required' => true,
|
||||
));
|
||||
|
||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||
'/div
|
||||
[
|
||||
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
|
||||
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
|
||||
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][@class="foo&bar"][not(@checked)][not(@required)]
|
||||
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
||||
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
|
||||
/following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"]
|
||||
/following-sibling::input[@type="hidden"][@id="name__token"]
|
||||
]
|
||||
[count(./input)=4]
|
||||
'
|
||||
);
|
||||
}
|
||||
|
||||
public function testCountry()
|
||||
{
|
||||
$form = $this->factory->createNamed('name', 'country', 'AT');
|
||||
|
@ -0,0 +1,173 @@
|
||||
<?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\Tests\ChoiceList;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||
*/
|
||||
protected $list;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $choices;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $choice1;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $choice2;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $choice3;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $choice4;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value1;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value2;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value3;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value4;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->list = $this->createChoiceList();
|
||||
|
||||
$this->choices = $this->getChoices();
|
||||
$this->values = $this->getValues();
|
||||
|
||||
// allow access to the individual entries without relying on their indices
|
||||
reset($this->choices);
|
||||
reset($this->values);
|
||||
|
||||
for ($i = 1; $i <= 4; ++$i) {
|
||||
$this->{'choice'.$i} = current($this->choices);
|
||||
$this->{'value'.$i} = current($this->values);
|
||||
|
||||
next($this->choices);
|
||||
next($this->values);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetChoices()
|
||||
{
|
||||
$this->assertSame($this->choices, $this->list->getChoices());
|
||||
}
|
||||
|
||||
public function testGetValues()
|
||||
{
|
||||
$this->assertSame($this->values, $this->list->getValues());
|
||||
}
|
||||
|
||||
public function testGetChoicesForValues()
|
||||
{
|
||||
$values = array($this->value1, $this->value2);
|
||||
$this->assertSame(array($this->choice1, $this->choice2), $this->list->getChoicesForValues($values));
|
||||
}
|
||||
|
||||
public function testGetChoicesForValuesPreservesKeys()
|
||||
{
|
||||
$values = array(5 => $this->value1, 8 => $this->value2);
|
||||
$this->assertSame(array(5 => $this->choice1, 8 => $this->choice2), $this->list->getChoicesForValues($values));
|
||||
}
|
||||
|
||||
public function testGetChoicesForValuesPreservesOrder()
|
||||
{
|
||||
$values = array($this->value2, $this->value1);
|
||||
$this->assertSame(array($this->choice2, $this->choice1), $this->list->getChoicesForValues($values));
|
||||
}
|
||||
|
||||
public function testGetChoicesForValuesIgnoresNonExistingValues()
|
||||
{
|
||||
$values = array($this->value1, $this->value2, 'foobar');
|
||||
$this->assertSame(array($this->choice1, $this->choice2), $this->list->getChoicesForValues($values));
|
||||
}
|
||||
|
||||
// https://github.com/symfony/symfony/issues/3446
|
||||
public function testGetChoicesForValuesEmpty()
|
||||
{
|
||||
$this->assertSame(array(), $this->list->getChoicesForValues(array()));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoices()
|
||||
{
|
||||
$choices = array($this->choice1, $this->choice2);
|
||||
$this->assertSame(array($this->value1, $this->value2), $this->list->getValuesForChoices($choices));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoicesPreservesKeys()
|
||||
{
|
||||
$choices = array(5 => $this->choice1, 8 => $this->choice2);
|
||||
$this->assertSame(array(5 => $this->value1, 8 => $this->value2), $this->list->getValuesForChoices($choices));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoicesPreservesOrder()
|
||||
{
|
||||
$choices = array($this->choice2, $this->choice1);
|
||||
$this->assertSame(array($this->value2, $this->value1), $this->list->getValuesForChoices($choices));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoicesIgnoresNonExistingChoices()
|
||||
{
|
||||
$choices = array($this->choice1, $this->choice2, 'foobar');
|
||||
$this->assertSame(array($this->value1, $this->value2), $this->list->getValuesForChoices($choices));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoicesEmpty()
|
||||
{
|
||||
$this->assertSame(array(), $this->list->getValuesForChoices(array()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||
*/
|
||||
abstract protected function createChoiceList();
|
||||
|
||||
abstract protected function getChoices();
|
||||
|
||||
abstract protected function getValues();
|
||||
}
|
@ -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\Tests\ChoiceList;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ArrayChoiceListTest extends AbstractChoiceListTest
|
||||
{
|
||||
private $object;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->object = new \stdClass();
|
||||
}
|
||||
|
||||
protected function createChoiceList()
|
||||
{
|
||||
return new ArrayChoiceList($this->getChoices(), $this->getValues());
|
||||
}
|
||||
|
||||
protected function getChoices()
|
||||
{
|
||||
return array(0, 1, '1', 'a', false, true, $this->object);
|
||||
}
|
||||
|
||||
protected function getValues()
|
||||
{
|
||||
return array('0', '1', '2', '3', '4', '5', '6');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testFailIfKeyMismatch()
|
||||
{
|
||||
new ArrayChoiceList(array(0 => 'a', 1 => 'b'), array(1 => 'a', 2 => 'b'));
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
<?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\Tests\ChoiceList;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ArrayKeyChoiceListTest extends AbstractChoiceListTest
|
||||
{
|
||||
private $object;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->object = new \stdClass();
|
||||
}
|
||||
|
||||
protected function createChoiceList()
|
||||
{
|
||||
return new ArrayKeyChoiceList($this->getChoices(), $this->getValues());
|
||||
}
|
||||
|
||||
protected function getChoices()
|
||||
{
|
||||
return array(0, 1, 'a', 'b', '');
|
||||
}
|
||||
|
||||
protected function getValues()
|
||||
{
|
||||
return array('0', '1', 'a', 'b', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testFailIfKeyMismatch()
|
||||
{
|
||||
new ArrayKeyChoiceList(array(0 => 'a', 1 => 'b'), array(1 => 'a', 2 => 'b'));
|
||||
}
|
||||
|
||||
public function testUseChoicesAsValuesByDefault()
|
||||
{
|
||||
$list = new ArrayKeyChoiceList(array(1 => '', 3 => 0, 7 => '1', 10 => 1.23));
|
||||
|
||||
$this->assertSame(array(1 => '', 3 => '0', 7 => '1', 10 => '1.23'), $list->getValues());
|
||||
}
|
||||
|
||||
public function testNoChoices()
|
||||
{
|
||||
$list = new ArrayKeyChoiceList(array());
|
||||
|
||||
$this->assertSame(array(), $list->getValues());
|
||||
}
|
||||
|
||||
public function testGetChoicesForValuesConvertsValuesToStrings()
|
||||
{
|
||||
$this->assertSame(array(0), $this->list->getChoicesForValues(array(0)));
|
||||
$this->assertSame(array(0), $this->list->getChoicesForValues(array('0')));
|
||||
$this->assertSame(array(1), $this->list->getChoicesForValues(array(1)));
|
||||
$this->assertSame(array(1), $this->list->getChoicesForValues(array('1')));
|
||||
$this->assertSame(array('a'), $this->list->getChoicesForValues(array('a')));
|
||||
$this->assertSame(array('b'), $this->list->getChoicesForValues(array('b')));
|
||||
$this->assertSame(array(''), $this->list->getChoicesForValues(array('')));
|
||||
// "1" === (string) true
|
||||
$this->assertSame(array(1), $this->list->getChoicesForValues(array(true)));
|
||||
// "" === (string) false
|
||||
$this->assertSame(array(''), $this->list->getChoicesForValues(array(false)));
|
||||
// "" === (string) null
|
||||
$this->assertSame(array(''), $this->list->getChoicesForValues(array(null)));
|
||||
$this->assertSame(array(), $this->list->getChoicesForValues(array(1.23)));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoicesConvertsChoicesToArrayKeys()
|
||||
{
|
||||
$this->assertSame(array('0'), $this->list->getValuesForChoices(array(0)));
|
||||
$this->assertSame(array('0'), $this->list->getValuesForChoices(array('0')));
|
||||
$this->assertSame(array('1'), $this->list->getValuesForChoices(array(1)));
|
||||
$this->assertSame(array('1'), $this->list->getValuesForChoices(array('1')));
|
||||
$this->assertSame(array('a'), $this->list->getValuesForChoices(array('a')));
|
||||
$this->assertSame(array('b'), $this->list->getValuesForChoices(array('b')));
|
||||
// Always cast booleans to 0 and 1, because:
|
||||
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
|
||||
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
|
||||
$this->assertSame(array('0'), $this->list->getValuesForChoices(array(false)));
|
||||
$this->assertSame(array('1'), $this->list->getValuesForChoices(array(true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConvertibleChoices
|
||||
*/
|
||||
public function testConvertChoicesIfNecessary(array $choices, array $converted)
|
||||
{
|
||||
$list = new ArrayKeyChoiceList($choices, range(0, count($choices) - 1));
|
||||
|
||||
$this->assertSame($converted, $list->getChoices());
|
||||
}
|
||||
|
||||
public function provideConvertibleChoices()
|
||||
{
|
||||
return array(
|
||||
array(array(0), array(0)),
|
||||
array(array(1), array(1)),
|
||||
array(array('0'), array(0)),
|
||||
array(array('1'), array(1)),
|
||||
array(array('1.23'), array('1.23')),
|
||||
array(array('foobar'), array('foobar')),
|
||||
// The default value of choice fields is NULL. It should be treated
|
||||
// like the empty value for this choice list type
|
||||
array(array(null), array('')),
|
||||
array(array(1.23), array('1.23')),
|
||||
// Always cast booleans to 0 and 1, because:
|
||||
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
|
||||
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
|
||||
array(array(true), array(1)),
|
||||
array(array(false), array(0)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideInvalidChoices
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testFailIfInvalidChoices(array $choices)
|
||||
{
|
||||
new ArrayKeyChoiceList($choices, range(0, count($choices) - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideInvalidChoices
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testGetValuesForChoicesFailsIfInvalidChoices(array $choices)
|
||||
{
|
||||
$this->list->getValuesForChoices($choices);
|
||||
}
|
||||
|
||||
public function provideInvalidChoices()
|
||||
{
|
||||
return array(
|
||||
array(array(new \stdClass())),
|
||||
array(array(array(1, 2))),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConvertibleValues
|
||||
*/
|
||||
public function testConvertValuesToStrings(array $values, array $converted)
|
||||
{
|
||||
$list = new ArrayKeyChoiceList(range(0, count($values) - 1), $values);
|
||||
|
||||
$this->assertSame($converted, $list->getValues());
|
||||
}
|
||||
|
||||
public function provideConvertibleValues()
|
||||
{
|
||||
return array(
|
||||
array(array(0), array('0')),
|
||||
array(array(1), array('1')),
|
||||
array(array('0'), array('0')),
|
||||
array(array('1'), array('1')),
|
||||
array(array('1.23'), array('1.23')),
|
||||
array(array('foobar'), array('foobar')),
|
||||
// The default value of choice fields is NULL. It should be treated
|
||||
// like the empty value for this choice list type
|
||||
array(array(null), array('')),
|
||||
array(array(1.23), array('1.23')),
|
||||
// Always cast booleans to 0 and 1, because:
|
||||
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
|
||||
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
|
||||
array(array(true), array('1')),
|
||||
array(array(false), array('')),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,668 @@
|
||||
<?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\Tests\ChoiceList\Factory;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $decoratedFactory;
|
||||
|
||||
/**
|
||||
* @var CachingFactoryDecorator
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->decoratedFactory = $this->getMock('Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface');
|
||||
$this->factory = new CachingFactoryDecorator($this->decoratedFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateFromChoicesFailsIfChoicesNotArrayOrTraversable()
|
||||
{
|
||||
$this->factory->createListFromChoices('foobar');
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesEmpty()
|
||||
{
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with(array())
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromChoices(array()));
|
||||
$this->assertSame($list, $this->factory->createListFromChoices(array()));
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesComparesTraversableChoicesAsArray()
|
||||
{
|
||||
// The top-most traversable is converted to an array
|
||||
$choices1 = new \ArrayIterator(array('A' => 'a'));
|
||||
$choices2 = array('A' => 'a');
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with($choices2)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices1));
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices2));
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesFlattensChoices()
|
||||
{
|
||||
$choices1 = array('key' => array('A' => 'a'));
|
||||
$choices2 = array('A' => 'a');
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with($choices1)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices1));
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideSameChoices
|
||||
*/
|
||||
public function testCreateFromChoicesSameChoices($choice1, $choice2)
|
||||
{
|
||||
$choices1 = array($choice1);
|
||||
$choices2 = array($choice2);
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with($choices1)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices1));
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDistinguishedChoices
|
||||
*/
|
||||
public function testCreateFromChoicesDifferentChoices($choice1, $choice2)
|
||||
{
|
||||
$choices1 = array($choice1);
|
||||
$choices2 = array($choice2);
|
||||
$list1 = new \stdClass();
|
||||
$list2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromChoices')
|
||||
->with($choices1)
|
||||
->will($this->returnValue($list1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromChoices')
|
||||
->with($choices2)
|
||||
->will($this->returnValue($list2));
|
||||
|
||||
$this->assertSame($list1, $this->factory->createListFromChoices($choices1));
|
||||
$this->assertSame($list2, $this->factory->createListFromChoices($choices2));
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesSameValueClosure()
|
||||
{
|
||||
$choices = array(1);
|
||||
$list = new \stdClass();
|
||||
$closure = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with($choices, $closure)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices, $closure));
|
||||
$this->assertSame($list, $this->factory->createListFromChoices($choices, $closure));
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesDifferentValueClosure()
|
||||
{
|
||||
$choices = array(1);
|
||||
$list1 = new \stdClass();
|
||||
$list2 = new \stdClass();
|
||||
$closure1 = function () {};
|
||||
$closure2 = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromChoices')
|
||||
->with($choices, $closure1)
|
||||
->will($this->returnValue($list1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromChoices')
|
||||
->with($choices, $closure2)
|
||||
->will($this->returnValue($list2));
|
||||
|
||||
$this->assertSame($list1, $this->factory->createListFromChoices($choices, $closure1));
|
||||
$this->assertSame($list2, $this->factory->createListFromChoices($choices, $closure2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateFromFlippedChoicesFailsIfChoicesNotArrayOrTraversable()
|
||||
{
|
||||
$this->factory->createListFromFlippedChoices('foobar');
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesEmpty()
|
||||
{
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromFlippedChoices')
|
||||
->with(array())
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices(array()));
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices(array()));
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesComparesTraversableChoicesAsArray()
|
||||
{
|
||||
// The top-most traversable is converted to an array
|
||||
$choices1 = new \ArrayIterator(array('a' => 'A'));
|
||||
$choices2 = array('a' => 'A');
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices2)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices1));
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices2));
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesFlattensChoices()
|
||||
{
|
||||
$choices1 = array('key' => array('a' => 'A'));
|
||||
$choices2 = array('a' => 'A');
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices1)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices1));
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideSameKeyChoices
|
||||
*/
|
||||
public function testCreateFromFlippedChoicesSameChoices($choice1, $choice2)
|
||||
{
|
||||
$choices1 = array($choice1);
|
||||
$choices2 = array($choice2);
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices1)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices1));
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDistinguishedKeyChoices
|
||||
*/
|
||||
public function testCreateFromFlippedChoicesDifferentChoices($choice1, $choice2)
|
||||
{
|
||||
$choices1 = array($choice1);
|
||||
$choices2 = array($choice2);
|
||||
$list1 = new \stdClass();
|
||||
$list2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices1)
|
||||
->will($this->returnValue($list1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices2)
|
||||
->will($this->returnValue($list2));
|
||||
|
||||
$this->assertSame($list1, $this->factory->createListFromFlippedChoices($choices1));
|
||||
$this->assertSame($list2, $this->factory->createListFromFlippedChoices($choices2));
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesSameValueClosure()
|
||||
{
|
||||
$choices = array(1);
|
||||
$list = new \stdClass();
|
||||
$closure = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices, $closure)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices, $closure));
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices, $closure));
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesDifferentValueClosure()
|
||||
{
|
||||
$choices = array(1);
|
||||
$list1 = new \stdClass();
|
||||
$list2 = new \stdClass();
|
||||
$closure1 = function () {};
|
||||
$closure2 = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices, $closure1)
|
||||
->will($this->returnValue($list1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices, $closure2)
|
||||
->will($this->returnValue($list2));
|
||||
|
||||
$this->assertSame($list1, $this->factory->createListFromFlippedChoices($choices, $closure1));
|
||||
$this->assertSame($list2, $this->factory->createListFromFlippedChoices($choices, $closure2));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderSameLoader()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromLoader')
|
||||
->with($loader)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderDifferentLoader()
|
||||
{
|
||||
$loader1 = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
$loader2 = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
$list1 = new \stdClass();
|
||||
$list2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromLoader')
|
||||
->with($loader1)
|
||||
->will($this->returnValue($list1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromLoader')
|
||||
->with($loader2)
|
||||
->will($this->returnValue($list2));
|
||||
|
||||
$this->assertSame($list1, $this->factory->createListFromLoader($loader1));
|
||||
$this->assertSame($list2, $this->factory->createListFromLoader($loader2));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderSameValueClosure()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
$list = new \stdClass();
|
||||
$closure = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $closure)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader, $closure));
|
||||
$this->assertSame($list, $this->factory->createListFromLoader($loader, $closure));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderDifferentValueClosure()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
$list1 = new \stdClass();
|
||||
$list2 = new \stdClass();
|
||||
$closure1 = function () {};
|
||||
$closure2 = function () {};
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $closure1)
|
||||
->will($this->returnValue($list1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $closure2)
|
||||
->will($this->returnValue($list2));
|
||||
|
||||
$this->assertSame($list1, $this->factory->createListFromLoader($loader, $closure1));
|
||||
$this->assertSame($list2, $this->factory->createListFromLoader($loader, $closure2));
|
||||
}
|
||||
|
||||
public function testCreateViewSamePreferredChoices()
|
||||
{
|
||||
$preferred = array('a');
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->will($this->returnValue($view));
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentPreferredChoices()
|
||||
{
|
||||
$preferred1 = array('a');
|
||||
$preferred2 = array('b');
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view1 = new \stdClass();
|
||||
$view2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, $preferred1)
|
||||
->will($this->returnValue($view1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, $preferred2)
|
||||
->will($this->returnValue($view2));
|
||||
|
||||
$this->assertSame($view1, $this->factory->createView($list, $preferred1));
|
||||
$this->assertSame($view2, $this->factory->createView($list, $preferred2));
|
||||
}
|
||||
|
||||
public function testCreateViewSamePreferredChoicesClosure()
|
||||
{
|
||||
$preferred = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, $preferred)
|
||||
->will($this->returnValue($view));
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
$this->assertSame($view, $this->factory->createView($list, $preferred));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentPreferredChoicesClosure()
|
||||
{
|
||||
$preferred1 = function () {};
|
||||
$preferred2 = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view1 = new \stdClass();
|
||||
$view2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, $preferred1)
|
||||
->will($this->returnValue($view1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, $preferred2)
|
||||
->will($this->returnValue($view2));
|
||||
|
||||
$this->assertSame($view1, $this->factory->createView($list, $preferred1));
|
||||
$this->assertSame($view2, $this->factory->createView($list, $preferred2));
|
||||
}
|
||||
|
||||
public function testCreateViewSameLabelClosure()
|
||||
{
|
||||
$labels = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, $labels)
|
||||
->will($this->returnValue($view));
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, $labels));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, $labels));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentLabelClosure()
|
||||
{
|
||||
$labels1 = function () {};
|
||||
$labels2 = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view1 = new \stdClass();
|
||||
$view2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, $labels1)
|
||||
->will($this->returnValue($view1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, $labels2)
|
||||
->will($this->returnValue($view2));
|
||||
|
||||
$this->assertSame($view1, $this->factory->createView($list, null, $labels1));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, $labels2));
|
||||
}
|
||||
|
||||
public function testCreateViewSameIndexClosure()
|
||||
{
|
||||
$index = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, $index)
|
||||
->will($this->returnValue($view));
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, $index));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, $index));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentIndexClosure()
|
||||
{
|
||||
$index1 = function () {};
|
||||
$index2 = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view1 = new \stdClass();
|
||||
$view2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, $index1)
|
||||
->will($this->returnValue($view1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, $index2)
|
||||
->will($this->returnValue($view2));
|
||||
|
||||
$this->assertSame($view1, $this->factory->createView($list, null, null, $index1));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, $index2));
|
||||
}
|
||||
|
||||
public function testCreateViewSameGroupByClosure()
|
||||
{
|
||||
$groupBy = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $groupBy)
|
||||
->will($this->returnValue($view));
|
||||
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, $groupBy));
|
||||
$this->assertSame($view, $this->factory->createView($list, null, null, null, $groupBy));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentGroupByClosure()
|
||||
{
|
||||
$groupBy1 = function () {};
|
||||
$groupBy2 = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view1 = new \stdClass();
|
||||
$view2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $groupBy1)
|
||||
->will($this->returnValue($view1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $groupBy2)
|
||||
->will($this->returnValue($view2));
|
||||
|
||||
$this->assertSame($view1, $this->factory->createView($list, null, null, null, $groupBy1));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, null, $groupBy2));
|
||||
}
|
||||
|
||||
public function testCreateViewSameAttributes()
|
||||
{
|
||||
$attr = array('class' => 'foobar');
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->will($this->returnValue($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));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentAttributes()
|
||||
{
|
||||
$attr1 = array('class' => 'foobar1');
|
||||
$attr2 = array('class' => 'foobar2');
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view1 = new \stdClass();
|
||||
$view2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr1)
|
||||
->will($this->returnValue($view1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr2)
|
||||
->will($this->returnValue($view2));
|
||||
|
||||
$this->assertSame($view1, $this->factory->createView($list, null, null, null, null, $attr1));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, null, null, $attr2));
|
||||
}
|
||||
|
||||
public function testCreateViewSameAttributesClosure()
|
||||
{
|
||||
$attr = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr)
|
||||
->will($this->returnValue($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));
|
||||
}
|
||||
|
||||
public function testCreateViewDifferentAttributesClosure()
|
||||
{
|
||||
$attr1 = function () {};
|
||||
$attr2 = function () {};
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$view1 = new \stdClass();
|
||||
$view2 = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->at(0))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr1)
|
||||
->will($this->returnValue($view1));
|
||||
$this->decoratedFactory->expects($this->at(1))
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $attr2)
|
||||
->will($this->returnValue($view2));
|
||||
|
||||
$this->assertSame($view1, $this->factory->createView($list, null, null, null, null, $attr1));
|
||||
$this->assertSame($view2, $this->factory->createView($list, null, null, null, null, $attr2));
|
||||
}
|
||||
|
||||
public function provideSameChoices()
|
||||
{
|
||||
$object = (object) array('foo' => 'bar');
|
||||
|
||||
return array(
|
||||
array(0, 0),
|
||||
array('a', 'a'),
|
||||
// https://github.com/symfony/symfony/issues/10409
|
||||
array(chr(181).'meter', chr(181).'meter'), // UTF-8
|
||||
array($object, $object),
|
||||
);
|
||||
}
|
||||
|
||||
public function provideDistinguishedChoices()
|
||||
{
|
||||
return array(
|
||||
array(0, false),
|
||||
array(0, null),
|
||||
array(0, '0'),
|
||||
array(0, ''),
|
||||
array(1, true),
|
||||
array(1, '1'),
|
||||
array(1, 'a'),
|
||||
array('', false),
|
||||
array('', null),
|
||||
array(false, null),
|
||||
// Same properties, but not identical
|
||||
array((object) array('foo' => 'bar'), (object) array('foo' => 'bar')),
|
||||
);
|
||||
}
|
||||
|
||||
public function provideSameKeyChoices()
|
||||
{
|
||||
// Only test types here that can be used as array keys
|
||||
return array(
|
||||
array(0, 0),
|
||||
array(0, '0'),
|
||||
array('a', 'a'),
|
||||
array(chr(181).'meter', chr(181).'meter'),
|
||||
);
|
||||
}
|
||||
|
||||
public function provideDistinguishedKeyChoices()
|
||||
{
|
||||
// Only test types here that can be used as array keys
|
||||
return array(
|
||||
array(0, ''),
|
||||
array(1, 'a'),
|
||||
array('', 'a'),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,970 @@
|
||||
<?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\Tests\ChoiceList\Factory;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
|
||||
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
|
||||
class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $obj1;
|
||||
|
||||
private $obj2;
|
||||
|
||||
private $obj3;
|
||||
|
||||
private $obj4;
|
||||
|
||||
private $list;
|
||||
|
||||
/**
|
||||
* @var DefaultChoiceListFactory
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
public function getValue($object)
|
||||
{
|
||||
return $object->value;
|
||||
}
|
||||
|
||||
public function getScalarValue($choice)
|
||||
{
|
||||
switch ($choice) {
|
||||
case 'a': return 'a';
|
||||
case 'b': return 'b';
|
||||
case 'c': return '1';
|
||||
case 'd': return '2';
|
||||
}
|
||||
}
|
||||
|
||||
public function getLabel($object)
|
||||
{
|
||||
return $object->label;
|
||||
}
|
||||
|
||||
public function getFormIndex($object)
|
||||
{
|
||||
return $object->index;
|
||||
}
|
||||
|
||||
public function isPreferred($object)
|
||||
{
|
||||
return $this->obj2 === $object || $this->obj3 === $object;
|
||||
}
|
||||
|
||||
public function getAttr($object)
|
||||
{
|
||||
return $object->attr;
|
||||
}
|
||||
|
||||
public function getGroup($object)
|
||||
{
|
||||
return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2';
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->obj1 = (object) array('label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => array());
|
||||
$this->obj2 = (object) array('label' => 'B', 'index' => 'x', 'value' => 'b', 'preferred' => true, 'group' => 'Group 1', 'attr' => array('attr1' => 'value1'));
|
||||
$this->obj3 = (object) array('label' => 'C', 'index' => 'y', 'value' => 1, 'preferred' => true, 'group' => 'Group 2', 'attr' => array('attr2' => 'value2'));
|
||||
$this->obj4 = (object) array('label' => 'D', 'index' => 'z', 'value' => 2, 'preferred' => false, 'group' => 'Group 2', 'attr' => array());
|
||||
$this->list = new ArrayChoiceList(
|
||||
array('A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4),
|
||||
array('A' => '0', 'B' => '1', 'C' => '2', 'D' => '3')
|
||||
);
|
||||
$this->factory = new DefaultChoiceListFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateFromChoicesFailsIfChoicesNotArrayOrTraversable()
|
||||
{
|
||||
$this->factory->createListFromChoices('foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateFromChoicesFailsIfValuesNotCallableOrString()
|
||||
{
|
||||
$this->factory->createListFromChoices(array(), new \stdClass());
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesEmpty()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(array());
|
||||
|
||||
$this->assertSame(array(), $list->getChoices());
|
||||
$this->assertSame(array(), $list->getValues());
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesFlat()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array('A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4)
|
||||
);
|
||||
|
||||
$this->assertObjectListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesFlatTraversable()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
new \ArrayIterator(array('A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4))
|
||||
);
|
||||
|
||||
$this->assertObjectListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesFlatValuesAsCallable()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array('A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4),
|
||||
array($this, 'getValue')
|
||||
);
|
||||
|
||||
$this->assertObjectListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesFlatValuesAsClosure()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array('A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4),
|
||||
function ($object) { return $object->value; }
|
||||
);
|
||||
|
||||
$this->assertObjectListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesFlatValuesClosureReceivesKey()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array('A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4),
|
||||
function ($object, $key) {
|
||||
switch ($key) {
|
||||
case 'A': return 'a';
|
||||
case 'B': return 'b';
|
||||
case 'C': return '1';
|
||||
case 'D': return '2';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertObjectListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesGrouped()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array(
|
||||
'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
|
||||
'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertObjectListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesGroupedTraversable()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
new \ArrayIterator(array(
|
||||
'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
|
||||
'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
|
||||
))
|
||||
);
|
||||
|
||||
$this->assertObjectListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesGroupedValuesAsCallable()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array(
|
||||
'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
|
||||
'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
|
||||
),
|
||||
array($this, 'getValue')
|
||||
);
|
||||
|
||||
$this->assertObjectListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesGroupedValuesAsClosure()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array(
|
||||
'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
|
||||
'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
|
||||
),
|
||||
function ($object) { return $object->value; }
|
||||
);
|
||||
|
||||
$this->assertObjectListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesGroupedValuesAsClosureReceivesKey()
|
||||
{
|
||||
$list = $this->factory->createListFromChoices(
|
||||
array(
|
||||
'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
|
||||
'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
|
||||
),
|
||||
function ($object, $key) {
|
||||
switch ($key) {
|
||||
case 'A': return 'a';
|
||||
case 'B': return 'b';
|
||||
case 'C': return '1';
|
||||
case 'D': return '2';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertObjectListWithCustomValues($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateFromFlippedChoicesFailsIfChoicesNotArrayOrTraversable()
|
||||
{
|
||||
$this->factory->createListFromFlippedChoices('foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateFromFlippedChoicesFailsIfValuesNotCallableOrString()
|
||||
{
|
||||
$this->factory->createListFromFlippedChoices(array(), new \stdClass());
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesEmpty()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(array());
|
||||
|
||||
$this->assertSame(array(), $list->getChoices());
|
||||
$this->assertSame(array(), $list->getValues());
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesFlat()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D')
|
||||
);
|
||||
|
||||
$this->assertScalarListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesFlatTraversable()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
new \ArrayIterator(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'))
|
||||
);
|
||||
|
||||
$this->assertScalarListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesFlatValuesAsCallable()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'),
|
||||
array($this, 'getScalarValue')
|
||||
);
|
||||
|
||||
$this->assertScalarListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesFlatValuesAsClosure()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'),
|
||||
function ($choice) {
|
||||
switch ($choice) {
|
||||
case 'a': return 'a';
|
||||
case 'b': return 'b';
|
||||
case 'c': return '1';
|
||||
case 'd': return '2';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertScalarListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesFlatValuesClosureReceivesKey()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'),
|
||||
function ($choice, $key) {
|
||||
switch ($key) {
|
||||
case 'A': return 'a';
|
||||
case 'B': return 'b';
|
||||
case 'C': return '1';
|
||||
case 'D': return '2';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertScalarListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesGrouped()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array(
|
||||
'Group 1' => array('a' => 'A', 'b' => 'B'),
|
||||
'Group 2' => array('c' => 'C', 'd' => 'D'),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertScalarListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesGroupedTraversable()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
new \ArrayIterator(array(
|
||||
'Group 1' => array('a' => 'A', 'b' => 'B'),
|
||||
'Group 2' => array('c' => 'C', 'd' => 'D'),
|
||||
))
|
||||
);
|
||||
|
||||
$this->assertScalarListWithGeneratedValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesGroupedValuesAsCallable()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array(
|
||||
'Group 1' => array('a' => 'A', 'b' => 'B'),
|
||||
'Group 2' => array('c' => 'C', 'd' => 'D'),
|
||||
),
|
||||
array($this, 'getScalarValue')
|
||||
);
|
||||
|
||||
$this->assertScalarListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesGroupedValuesAsClosure()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array(
|
||||
'Group 1' => array('a' => 'A', 'b' => 'B'),
|
||||
'Group 2' => array('c' => 'C', 'd' => 'D'),
|
||||
),
|
||||
function ($choice) {
|
||||
switch ($choice) {
|
||||
case 'a': return 'a';
|
||||
case 'b': return 'b';
|
||||
case 'c': return '1';
|
||||
case 'd': return '2';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertScalarListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoicesGroupedValuesAsClosureReceivesKey()
|
||||
{
|
||||
$list = $this->factory->createListFromFlippedChoices(
|
||||
array(
|
||||
'Group 1' => array('a' => 'A', 'b' => 'B'),
|
||||
'Group 2' => array('c' => 'C', 'd' => 'D'),
|
||||
),
|
||||
function ($choice, $key) {
|
||||
switch ($key) {
|
||||
case 'A': return 'a';
|
||||
case 'B': return 'b';
|
||||
case 'C': return '1';
|
||||
case 'D': return '2';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertScalarListWithCustomValues($list);
|
||||
}
|
||||
|
||||
public function testCreateFromLoader()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
|
||||
$list = $this->factory->createListFromLoader($loader);
|
||||
|
||||
$this->assertEquals(new LazyChoiceList($loader), $list);
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderWithValues()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
|
||||
$value = function () {};
|
||||
$list = $this->factory->createListFromLoader($loader, $value);
|
||||
|
||||
$this->assertEquals(new LazyChoiceList($loader, $value), $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateFromLoaderFailsIfValuesNotCallableOrString()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
|
||||
$this->factory->createListFromLoader($loader, new \stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateViewFailsIfPreferredChoicesInvalid()
|
||||
{
|
||||
$this->factory->createView($this->list, new \stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateViewFailsIfLabelInvalid()
|
||||
{
|
||||
$this->factory->createView($this->list, null, new \stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateViewFailsIfIndexInvalid()
|
||||
{
|
||||
$this->factory->createView($this->list, null, null, new \stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateViewFailsIfGroupByInvalid()
|
||||
{
|
||||
$this->factory->createView($this->list, null, null, null, new \stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testCreateViewFailsIfAttrInvalid()
|
||||
{
|
||||
$this->factory->createView($this->list, null, null, null, null, new \stdClass());
|
||||
}
|
||||
|
||||
public function testCreateViewFlat()
|
||||
{
|
||||
$view = $this->factory->createView($this->list);
|
||||
|
||||
$this->assertEquals(new ChoiceListView(
|
||||
array(
|
||||
0 => new ChoiceView('A', '0', $this->obj1),
|
||||
1 => new ChoiceView('B', '1', $this->obj2),
|
||||
2 => new ChoiceView('C', '2', $this->obj3),
|
||||
3 => new ChoiceView('D', '3', $this->obj4),
|
||||
), array()
|
||||
), $view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatPreferredChoices()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3)
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatPreferredChoicesEmptyArray()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array()
|
||||
);
|
||||
|
||||
$this->assertEquals(new ChoiceListView(
|
||||
array(
|
||||
0 => new ChoiceView('A', '0', $this->obj1),
|
||||
1 => new ChoiceView('B', '1', $this->obj2),
|
||||
2 => new ChoiceView('C', '2', $this->obj3),
|
||||
3 => new ChoiceView('D', '3', $this->obj4),
|
||||
), array()
|
||||
), $view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatPreferredChoicesAsCallable()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this, 'isPreferred')
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatPreferredChoicesAsClosure()
|
||||
{
|
||||
$obj2 = $this->obj2;
|
||||
$obj3 = $this->obj3;
|
||||
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
function ($object) use ($obj2, $obj3) {
|
||||
return $obj2 === $object || $obj3 === $object;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatPreferredChoicesClosureReceivesKey()
|
||||
{
|
||||
$obj2 = $this->obj2;
|
||||
$obj3 = $this->obj3;
|
||||
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
function ($object, $key) use ($obj2, $obj3) {
|
||||
return 'B' === $key || 'C' === $key;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatLabelAsCallable()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
array($this, 'getLabel')
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatLabelAsClosure()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
function ($object) {
|
||||
return $object->label;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatLabelClosureReceivesKey()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
function ($object, $key) {
|
||||
return $key;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatIndexAsCallable()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
array($this, 'getFormIndex')
|
||||
);
|
||||
|
||||
$this->assertFlatViewWithCustomIndices($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatIndexAsClosure()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
function ($object) {
|
||||
return $object->index;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatViewWithCustomIndices($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatIndexClosureReceivesKey()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
function ($object, $key) {
|
||||
switch ($key) {
|
||||
case 'A': return 'w';
|
||||
case 'B': return 'x';
|
||||
case 'C': return 'y';
|
||||
case 'D': return 'z';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatViewWithCustomIndices($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatGroupByAsArray()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
array(
|
||||
'Group 1' => array('A' => true, 'B' => true),
|
||||
'Group 2' => array('C' => true, 'D' => true),
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertGroupedView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatGroupByAsTraversable()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
new \ArrayIterator(array(
|
||||
'Group 1' => array('A' => true, 'B' => true),
|
||||
'Group 2' => array('C' => true, 'D' => true),
|
||||
))
|
||||
);
|
||||
|
||||
$this->assertGroupedView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatGroupByEmpty()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
array() // ignored
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatGroupByAsCallable()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
array($this, 'getGroup')
|
||||
);
|
||||
|
||||
$this->assertGroupedView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatGroupByAsClosure()
|
||||
{
|
||||
$obj1 = $this->obj1;
|
||||
$obj2 = $this->obj2;
|
||||
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
function ($object) use ($obj1, $obj2) {
|
||||
return $obj1 === $object || $obj2 === $object ? 'Group 1'
|
||||
: 'Group 2';
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertGroupedView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatGroupByClosureReceivesKey()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
function ($object, $key) {
|
||||
return 'A' === $key || 'B' === $key ? 'Group 1' : 'Group 2';
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertGroupedView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatAttrAsArray()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
null, // group
|
||||
array(
|
||||
'B' => array('attr1' => 'value1'),
|
||||
'C' => array('attr2' => 'value2')
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertFlatViewWithAttr($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatAttrEmpty()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
null, // group
|
||||
array()
|
||||
);
|
||||
|
||||
$this->assertFlatView($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatAttrAsCallable()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
null, // group
|
||||
array($this, 'getAttr')
|
||||
);
|
||||
|
||||
$this->assertFlatViewWithAttr($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatAttrAsClosure()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
null, // group
|
||||
function ($object) {
|
||||
return $object->attr;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatViewWithAttr($view);
|
||||
}
|
||||
|
||||
public function testCreateViewFlatAttrClosureReceivesKey()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
array($this->obj2, $this->obj3),
|
||||
null, // label
|
||||
null, // index
|
||||
null, // group
|
||||
function ($object, $key) {
|
||||
switch ($key) {
|
||||
case 'B': return array('attr1' => 'value1');
|
||||
case 'C': return array('attr2' => 'value2');
|
||||
default: return array();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertFlatViewWithAttr($view);
|
||||
}
|
||||
|
||||
public function testCreateViewForLegacyChoiceList()
|
||||
{
|
||||
$preferred = array(new ChoiceView('Preferred', 'x', 'x'));
|
||||
$other = array(new ChoiceView('Other', 'y', 'y'));
|
||||
|
||||
$list = $this->getMock('Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$list->expects($this->once())
|
||||
->method('getPreferredViews')
|
||||
->will($this->returnValue($preferred));
|
||||
$list->expects($this->once())
|
||||
->method('getRemainingViews')
|
||||
->will($this->returnValue($other));
|
||||
|
||||
$view = $this->factory->createView($list);
|
||||
|
||||
$this->assertSame($other, $view->choices);
|
||||
$this->assertSame($preferred, $view->preferredChoices);
|
||||
}
|
||||
|
||||
private function assertScalarListWithGeneratedValues(ChoiceListInterface $list)
|
||||
{
|
||||
$this->assertSame(array(
|
||||
'A' => 'a',
|
||||
'B' => 'b',
|
||||
'C' => 'c',
|
||||
'D' => 'd',
|
||||
), $list->getChoices());
|
||||
|
||||
$this->assertSame(array(
|
||||
'A' => 'a',
|
||||
'B' => 'b',
|
||||
'C' => 'c',
|
||||
'D' => 'd',
|
||||
), $list->getValues());
|
||||
}
|
||||
|
||||
private function assertObjectListWithGeneratedValues(ChoiceListInterface $list)
|
||||
{
|
||||
$this->assertSame(array(
|
||||
'A' => $this->obj1,
|
||||
'B' => $this->obj2,
|
||||
'C' => $this->obj3,
|
||||
'D' => $this->obj4,
|
||||
), $list->getChoices());
|
||||
|
||||
$this->assertSame(array(
|
||||
'A' => '0',
|
||||
'B' => '1',
|
||||
'C' => '2',
|
||||
'D' => '3',
|
||||
), $list->getValues());
|
||||
}
|
||||
|
||||
private function assertScalarListWithCustomValues(ChoiceListInterface $list)
|
||||
{
|
||||
$this->assertSame(array(
|
||||
'A' => 'a',
|
||||
'B' => 'b',
|
||||
'C' => 'c',
|
||||
'D' => 'd',
|
||||
), $list->getChoices());
|
||||
|
||||
$this->assertSame(array(
|
||||
'A' => 'a',
|
||||
'B' => 'b',
|
||||
'C' => '1',
|
||||
'D' => '2',
|
||||
), $list->getValues());
|
||||
}
|
||||
|
||||
private function assertObjectListWithCustomValues(ChoiceListInterface $list)
|
||||
{
|
||||
$this->assertSame(array(
|
||||
'A' => $this->obj1,
|
||||
'B' => $this->obj2,
|
||||
'C' => $this->obj3,
|
||||
'D' => $this->obj4,
|
||||
), $list->getChoices());
|
||||
|
||||
$this->assertSame(array(
|
||||
'A' => 'a',
|
||||
'B' => 'b',
|
||||
'C' => '1',
|
||||
'D' => '2',
|
||||
), $list->getValues());
|
||||
}
|
||||
|
||||
private function assertFlatView($view)
|
||||
{
|
||||
$this->assertEquals(new ChoiceListView(
|
||||
array(
|
||||
0 => new ChoiceView('A', '0', $this->obj1),
|
||||
3 => new ChoiceView('D', '3', $this->obj4),
|
||||
), array(
|
||||
1 => new ChoiceView('B', '1', $this->obj2),
|
||||
2 => new ChoiceView('C', '2', $this->obj3),
|
||||
)
|
||||
), $view);
|
||||
}
|
||||
|
||||
private function assertFlatViewWithCustomIndices($view)
|
||||
{
|
||||
$this->assertEquals(new ChoiceListView(
|
||||
array(
|
||||
'w' => new ChoiceView('A', '0', $this->obj1),
|
||||
'z' => new ChoiceView('D', '3', $this->obj4),
|
||||
), array(
|
||||
'x' => new ChoiceView('B', '1', $this->obj2),
|
||||
'y' => new ChoiceView('C', '2', $this->obj3),
|
||||
)
|
||||
), $view);
|
||||
}
|
||||
|
||||
private function assertFlatViewWithAttr($view)
|
||||
{
|
||||
$this->assertEquals(new ChoiceListView(
|
||||
array(
|
||||
0 => new ChoiceView('A', '0', $this->obj1),
|
||||
3 => new ChoiceView('D', '3', $this->obj4),
|
||||
), array(
|
||||
1 => new ChoiceView(
|
||||
'B',
|
||||
'1',
|
||||
$this->obj2,
|
||||
array('attr1' => 'value1')
|
||||
),
|
||||
2 => new ChoiceView(
|
||||
'C',
|
||||
'2',
|
||||
$this->obj3,
|
||||
array('attr2' => 'value2')
|
||||
),
|
||||
)
|
||||
), $view);
|
||||
}
|
||||
|
||||
private function assertGroupedView($view)
|
||||
{
|
||||
$this->assertEquals(new ChoiceListView(
|
||||
array(
|
||||
'Group 1' => new ChoiceGroupView(
|
||||
'Group 1',
|
||||
array(0 => new ChoiceView('A', '0', $this->obj1))
|
||||
),
|
||||
'Group 2' => new ChoiceGroupView(
|
||||
'Group 2',
|
||||
array(3 => new ChoiceView('D', '3', $this->obj4))
|
||||
),
|
||||
), array(
|
||||
'Group 1' => new ChoiceGroupView(
|
||||
'Group 1',
|
||||
array(1 => new ChoiceView('B', '1', $this->obj2))
|
||||
),
|
||||
'Group 2' => new ChoiceGroupView(
|
||||
'Group 2',
|
||||
array(2 => new ChoiceView('C', '2', $this->obj3))
|
||||
),
|
||||
)
|
||||
), $view);
|
||||
}
|
||||
}
|
@ -0,0 +1,338 @@
|
||||
<?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\Tests\ChoiceList\Factory;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyAccessDecoratorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $decoratedFactory;
|
||||
|
||||
/**
|
||||
* @var PropertyAccessDecorator
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->decoratedFactory = $this->getMock('Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface');
|
||||
$this->factory = new PropertyAccessDecorator($this->decoratedFactory);
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesPropertyPath()
|
||||
{
|
||||
$choices = array((object) array('property' => 'value'));
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with($choices, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($choices, $callback) {
|
||||
return array_map($callback, $choices);
|
||||
}));
|
||||
|
||||
$this->assertSame(array('value'), $this->factory->createListFromChoices($choices, 'property'));
|
||||
}
|
||||
|
||||
public function testCreateFromChoicesPropertyPathInstance()
|
||||
{
|
||||
$choices = array((object) array('property' => 'value'));
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromChoices')
|
||||
->with($choices, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($choices, $callback) {
|
||||
return array_map($callback, $choices);
|
||||
}));
|
||||
|
||||
$this->assertSame(array('value'), $this->factory->createListFromChoices($choices, new PropertyPath('property')));
|
||||
}
|
||||
|
||||
public function testCreateFromFlippedChoices()
|
||||
{
|
||||
// Property paths are not supported here, because array keys can never
|
||||
// be objects anyway
|
||||
$choices = array('a' => 'A');
|
||||
$value = 'foobar';
|
||||
$list = new \stdClass();
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromFlippedChoices')
|
||||
->with($choices, $value)
|
||||
->will($this->returnValue($list));
|
||||
|
||||
$this->assertSame($list, $this->factory->createListFromFlippedChoices($choices, $value));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderPropertyPath()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($loader, $callback) {
|
||||
return $callback((object) array('property' => 'value'));
|
||||
}));
|
||||
|
||||
$this->assertSame('value', $this->factory->createListFromLoader($loader, 'property'));
|
||||
}
|
||||
|
||||
public function testCreateFromLoaderPropertyPathInstance()
|
||||
{
|
||||
$loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createListFromLoader')
|
||||
->with($loader, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($loader, $callback) {
|
||||
return $callback((object) array('property' => 'value'));
|
||||
}));
|
||||
|
||||
$this->assertSame('value', $this->factory->createListFromLoader($loader, new PropertyPath('property')));
|
||||
}
|
||||
|
||||
public function testCreateViewPreferredChoicesAsPropertyPath()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred) {
|
||||
return $preferred((object) array('property' => true));
|
||||
}));
|
||||
|
||||
$this->assertTrue($this->factory->createView(
|
||||
$list,
|
||||
'property'
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewPreferredChoicesAsPropertyPathInstance()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred) {
|
||||
return $preferred((object) array('property' => true));
|
||||
}));
|
||||
|
||||
$this->assertTrue($this->factory->createView(
|
||||
$list,
|
||||
new PropertyPath('property')
|
||||
));
|
||||
}
|
||||
|
||||
// https://github.com/symfony/symfony/issues/5494
|
||||
public function testCreateViewAssumeNullIfPreferredChoicesPropertyPathUnreadable()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred) {
|
||||
return $preferred((object) array('category' => null));
|
||||
}));
|
||||
|
||||
$this->assertFalse($this->factory->createView(
|
||||
$list,
|
||||
'category.preferred'
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewLabelsAsPropertyPath()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label) {
|
||||
return $label((object) array('property' => 'label'));
|
||||
}));
|
||||
|
||||
$this->assertSame('label', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
'property'
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewLabelsAsPropertyPathInstance()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label) {
|
||||
return $label((object) array('property' => 'label'));
|
||||
}));
|
||||
|
||||
$this->assertSame('label', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
new PropertyPath('property')
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewIndicesAsPropertyPath()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label, $index) {
|
||||
return $index((object) array('property' => 'index'));
|
||||
}));
|
||||
|
||||
$this->assertSame('index', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
null, // label
|
||||
'property'
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewIndicesAsPropertyPathInstance()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label, $index) {
|
||||
return $index((object) array('property' => 'index'));
|
||||
}));
|
||||
|
||||
$this->assertSame('index', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
null, // label
|
||||
new PropertyPath('property')
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewGroupsAsPropertyPath()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label, $index, $groupBy) {
|
||||
return $groupBy((object) array('property' => 'group'));
|
||||
}));
|
||||
|
||||
$this->assertSame('group', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
null, // label
|
||||
null, // index
|
||||
'property'
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewGroupsAsPropertyPathInstance()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label, $index, $groupBy) {
|
||||
return $groupBy((object) array('property' => 'group'));
|
||||
}));
|
||||
|
||||
$this->assertSame('group', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
null, // label
|
||||
null, // index
|
||||
new PropertyPath('property')
|
||||
));
|
||||
}
|
||||
|
||||
// https://github.com/symfony/symfony/issues/5494
|
||||
public function testCreateViewAssumeNullIfGroupsPropertyPathUnreadable()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label, $index, $groupBy) {
|
||||
return $groupBy((object) array('group' => null));
|
||||
}));
|
||||
|
||||
$this->assertNull($this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
null, // label
|
||||
null, // index
|
||||
'group.name'
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewAttrAsPropertyPath()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label, $index, $groupBy, $attr) {
|
||||
return $attr((object) array('property' => 'attr'));
|
||||
}));
|
||||
|
||||
$this->assertSame('attr', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
null, // label
|
||||
null, // index
|
||||
null, // groups
|
||||
'property'
|
||||
));
|
||||
}
|
||||
|
||||
public function testCreateViewAttrAsPropertyPathInstance()
|
||||
{
|
||||
$list = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
|
||||
$this->decoratedFactory->expects($this->once())
|
||||
->method('createView')
|
||||
->with($list, null, null, null, null, $this->isInstanceOf('\Closure'))
|
||||
->will($this->returnCallback(function ($list, $preferred, $label, $index, $groupBy, $attr) {
|
||||
return $attr((object) array('property' => 'attr'));
|
||||
}));
|
||||
|
||||
$this->assertSame('attr', $this->factory->createView(
|
||||
$list,
|
||||
null, // preferred choices
|
||||
null, // label
|
||||
null, // index
|
||||
null, // groups
|
||||
new PropertyPath('property')
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
<?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\Tests\ChoiceList;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var LazyChoiceList
|
||||
*/
|
||||
private $list;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $innerList;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $loader;
|
||||
|
||||
private $value;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->innerList = $this->getMock('Symfony\Component\Form\ChoiceList\ChoiceListInterface');
|
||||
$this->loader = $this->getMock('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface');
|
||||
$this->value = function () {};
|
||||
$this->list = new LazyChoiceList($this->loader, $this->value);
|
||||
}
|
||||
|
||||
public function testGetChoicesLoadsInnerListOnFirstCall()
|
||||
{
|
||||
$this->loader->expects($this->once())
|
||||
->method('loadChoiceList')
|
||||
->with($this->value)
|
||||
->will($this->returnValue($this->innerList));
|
||||
|
||||
$this->innerList->expects($this->exactly(2))
|
||||
->method('getChoices')
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
$this->assertSame('RESULT', $this->list->getChoices());
|
||||
$this->assertSame('RESULT', $this->list->getChoices());
|
||||
}
|
||||
|
||||
public function testGetValuesLoadsInnerListOnFirstCall()
|
||||
{
|
||||
$this->loader->expects($this->once())
|
||||
->method('loadChoiceList')
|
||||
->with($this->value)
|
||||
->will($this->returnValue($this->innerList));
|
||||
|
||||
$this->innerList->expects($this->exactly(2))
|
||||
->method('getValues')
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
$this->assertSame('RESULT', $this->list->getValues());
|
||||
$this->assertSame('RESULT', $this->list->getValues());
|
||||
}
|
||||
|
||||
public function testGetChoicesForValuesForwardsCallIfListNotLoaded()
|
||||
{
|
||||
$this->loader->expects($this->exactly(2))
|
||||
->method('loadChoicesForValues')
|
||||
->with(array('a', 'b'))
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
$this->assertSame('RESULT', $this->list->getChoicesForValues(array('a', 'b')));
|
||||
$this->assertSame('RESULT', $this->list->getChoicesForValues(array('a', 'b')));
|
||||
}
|
||||
|
||||
public function testGetChoicesForValuesUsesLoadedList()
|
||||
{
|
||||
$this->loader->expects($this->once())
|
||||
->method('loadChoiceList')
|
||||
->with($this->value)
|
||||
->will($this->returnValue($this->innerList));
|
||||
|
||||
$this->loader->expects($this->never())
|
||||
->method('loadChoicesForValues');
|
||||
|
||||
$this->innerList->expects($this->exactly(2))
|
||||
->method('getChoicesForValues')
|
||||
->with(array('a', 'b'))
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
// load choice list
|
||||
$this->list->getChoices();
|
||||
|
||||
$this->assertSame('RESULT', $this->list->getChoicesForValues(array('a', 'b')));
|
||||
$this->assertSame('RESULT', $this->list->getChoicesForValues(array('a', 'b')));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoicesForwardsCallIfListNotLoaded()
|
||||
{
|
||||
$this->loader->expects($this->exactly(2))
|
||||
->method('loadValuesForChoices')
|
||||
->with(array('a', 'b'))
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
$this->assertSame('RESULT', $this->list->getValuesForChoices(array('a', 'b')));
|
||||
$this->assertSame('RESULT', $this->list->getValuesForChoices(array('a', 'b')));
|
||||
}
|
||||
|
||||
public function testGetValuesForChoicesUsesLoadedList()
|
||||
{
|
||||
$this->loader->expects($this->once())
|
||||
->method('loadChoiceList')
|
||||
->with($this->value)
|
||||
->will($this->returnValue($this->innerList));
|
||||
|
||||
$this->loader->expects($this->never())
|
||||
->method('loadValuesForChoices');
|
||||
|
||||
$this->innerList->expects($this->exactly(2))
|
||||
->method('getValuesForChoices')
|
||||
->with(array('a', 'b'))
|
||||
->will($this->returnValue('RESULT'));
|
||||
|
||||
// load choice list
|
||||
$this->list->getChoices();
|
||||
|
||||
$this->assertSame('RESULT', $this->list->getValuesForChoices(array('a', 'b')));
|
||||
$this->assertSame('RESULT', $this->list->getValuesForChoices(array('a', 'b')));
|
||||
}
|
||||
}
|
@ -11,8 +11,9 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
|
||||
class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
{
|
||||
@ -66,6 +67,16 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->objectChoices = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||
*/
|
||||
public function testChoicesOptionExpectsArrayOrTraversable()
|
||||
{
|
||||
$this->factory->create('choice', null, array(
|
||||
'choices' => new \stdClass(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||
*/
|
||||
@ -76,6 +87,16 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
|
||||
*/
|
||||
public function testChoiceLoaderOptionExpectsChoiceLoaderInterface()
|
||||
{
|
||||
$this->factory->create('choice', null, array(
|
||||
'choice_loader' => new \stdClass(),
|
||||
));
|
||||
}
|
||||
|
||||
public function testChoiceListAndChoicesCanBeEmpty()
|
||||
{
|
||||
$this->factory->create('choice');
|
||||
@ -236,7 +257,118 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertFalse($form->isSynchronized());
|
||||
}
|
||||
|
||||
public function testSubmitSingleNonExpandedNull()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choices' => $this->choices,
|
||||
));
|
||||
|
||||
$form->submit(null);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame('', $form->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleNonExpandedNullNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(null);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame('', $form->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitSingleNonExpandedEmpty()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choices' => $this->choices,
|
||||
));
|
||||
|
||||
$form->submit('');
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame('', $form->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleNonExpandedEmptyNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit('');
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame('', $form->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitSingleNonExpandedFalse()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choices' => $this->choices,
|
||||
));
|
||||
|
||||
$form->submit(false);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame('', $form->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleNonExpandedFalseNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(false);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame('', $form->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitSingleNonExpandedObjectChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'choices' => $this->objectChoices,
|
||||
'choices_as_values' => true,
|
||||
'choice_label' => 'name',
|
||||
'choice_value' => 'id',
|
||||
));
|
||||
|
||||
// "id" value of the second entry
|
||||
$form->submit('2');
|
||||
|
||||
$this->assertEquals($this->objectChoices[1], $form->getData());
|
||||
$this->assertEquals('2', $form->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitSingleNonExpandedObjectChoicesBc()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
@ -273,6 +405,37 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertEquals(array('a', 'b'), $form->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitMultipleNonExpandedEmpty()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'choices' => $this->choices,
|
||||
));
|
||||
|
||||
$form->submit(array());
|
||||
|
||||
$this->assertSame(array(), $form->getData());
|
||||
$this->assertSame(array(), $form->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitMultipleNonExpandedEmptyNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(array());
|
||||
|
||||
$this->assertSame(array(), $form->getData());
|
||||
$this->assertSame(array(), $form->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitMultipleNonExpandedInvalidScalarChoice()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -304,6 +467,23 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
}
|
||||
|
||||
public function testSubmitMultipleNonExpandedObjectChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'choices' => $this->objectChoices,
|
||||
'choices_as_values' => true,
|
||||
'choice_label' => 'name',
|
||||
'choice_value' => 'id',
|
||||
));
|
||||
|
||||
$form->submit(array('2', '3'));
|
||||
|
||||
$this->assertEquals(array($this->objectChoices[1], $this->objectChoices[2]), $form->getData());
|
||||
$this->assertEquals(array('2', '3'), $form->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitMultipleNonExpandedObjectChoicesBc()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => true,
|
||||
@ -337,13 +517,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit('b');
|
||||
|
||||
$this->assertSame('b', $form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => true,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
), $form->getViewData());
|
||||
$this->assertSame('b', $form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -399,14 +573,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit('b');
|
||||
|
||||
$this->assertSame('b', $form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => true,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
'placeholder' => false,
|
||||
), $form->getViewData());
|
||||
$this->assertSame('b', $form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -464,13 +631,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit(null);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => false,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
), $form->getViewData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -486,6 +647,26 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleExpandedRequiredNullNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'required' => true,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(null);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedRequiredEmpty()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -498,13 +679,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit('');
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => false,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
), $form->getViewData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -520,6 +695,26 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleExpandedRequiredEmptyNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'required' => true,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit('');
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedRequiredFalse()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -532,13 +727,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit(false);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => false,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
), $form->getViewData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -554,6 +743,26 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleExpandedRequiredFalseNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'required' => true,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(false);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedNonRequiredNull()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -566,14 +775,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit(null);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => false,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
'placeholder' => true,
|
||||
), $form->getViewData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -591,6 +793,26 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleExpandedNonRequiredNullNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'required' => false,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(null);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedNonRequiredEmpty()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -603,14 +825,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit('');
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => false,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
'placeholder' => true,
|
||||
), $form->getViewData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -628,6 +843,26 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleExpandedNonRequiredEmptyNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'required' => false,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit('');
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedNonRequiredFalse()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -640,14 +875,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit(false);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => false,
|
||||
1 => false,
|
||||
2 => false,
|
||||
3 => false,
|
||||
4 => false,
|
||||
'placeholder' => true,
|
||||
), $form->getViewData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -665,6 +893,26 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitSingleExpandedNonRequiredFalseNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'required' => false,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(false);
|
||||
|
||||
$this->assertNull($form->getData());
|
||||
$this->assertNull($form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedWithEmptyChild()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -686,6 +934,32 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedObjectChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'choices' => $this->objectChoices,
|
||||
'choices_as_values' => true,
|
||||
'choice_label' => 'name',
|
||||
'choice_value' => 'id',
|
||||
));
|
||||
|
||||
$form->submit('2');
|
||||
|
||||
$this->assertSame($this->objectChoices[1], $form->getData());
|
||||
$this->assertFalse($form[0]->getData());
|
||||
$this->assertTrue($form[1]->getData());
|
||||
$this->assertFalse($form[2]->getData());
|
||||
$this->assertFalse($form[3]->getData());
|
||||
$this->assertFalse($form[4]->getData());
|
||||
$this->assertNull($form[0]->getViewData());
|
||||
$this->assertSame('2', $form[1]->getViewData());
|
||||
$this->assertNull($form[2]->getViewData());
|
||||
$this->assertNull($form[3]->getViewData());
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitSingleExpandedObjectChoicesBc()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => false,
|
||||
@ -750,13 +1024,7 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$form->submit(array('a', 'c'));
|
||||
|
||||
$this->assertSame(array('a', 'c'), $form->getData());
|
||||
$this->assertSame(array(
|
||||
0 => true,
|
||||
1 => false,
|
||||
2 => true,
|
||||
3 => false,
|
||||
4 => false,
|
||||
), $form->getViewData());
|
||||
$this->assertSame(array('a', 'c'), $form->getViewData());
|
||||
$this->assertEmpty($form->getExtraData());
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
@ -849,6 +1117,22 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
// In edge cases (for example, when choices are loaded dynamically by a
|
||||
// loader), the choices may be empty. Make sure to behave the same as when
|
||||
// choices are available.
|
||||
public function testSubmitMultipleExpandedEmptyNoChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'choices' => array(),
|
||||
));
|
||||
|
||||
$form->submit(array());
|
||||
|
||||
$this->assertSame(array(), $form->getData());
|
||||
}
|
||||
|
||||
public function testSubmitMultipleExpandedWithEmptyChild()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
@ -873,6 +1157,32 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
}
|
||||
|
||||
public function testSubmitMultipleExpandedObjectChoices()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'choices' => $this->objectChoices,
|
||||
'choices_as_values' => true,
|
||||
'choice_label' => 'name',
|
||||
'choice_value' => 'id',
|
||||
));
|
||||
|
||||
$form->submit(array('1', '2'));
|
||||
|
||||
$this->assertSame(array($this->objectChoices[0], $this->objectChoices[1]), $form->getData());
|
||||
$this->assertTrue($form[0]->getData());
|
||||
$this->assertTrue($form[1]->getData());
|
||||
$this->assertFalse($form[2]->getData());
|
||||
$this->assertFalse($form[3]->getData());
|
||||
$this->assertFalse($form[4]->getData());
|
||||
$this->assertSame('1', $form[0]->getViewData());
|
||||
$this->assertSame('2', $form[1]->getViewData());
|
||||
$this->assertNull($form[2]->getViewData());
|
||||
$this->assertNull($form[3]->getViewData());
|
||||
$this->assertNull($form[4]->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitMultipleExpandedObjectChoicesBc()
|
||||
{
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'multiple' => true,
|
||||
@ -1134,10 +1444,10 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('a', 'a', 'A'),
|
||||
new ChoiceView('b', 'b', 'B'),
|
||||
new ChoiceView('c', 'c', 'C'),
|
||||
new ChoiceView('d', 'd', 'D'),
|
||||
new ChoiceView('A', 'a', 'a'),
|
||||
new ChoiceView('B', 'b', 'b'),
|
||||
new ChoiceView('C', 'c', 'c'),
|
||||
new ChoiceView('D', 'd', 'd'),
|
||||
), $view->vars['choices']);
|
||||
}
|
||||
|
||||
@ -1151,12 +1461,12 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
0 => new ChoiceView('a', 'a', 'A'),
|
||||
2 => new ChoiceView('c', 'c', 'C'),
|
||||
0 => new ChoiceView('A', 'a', 'a'),
|
||||
2 => new ChoiceView('C', 'c', 'c'),
|
||||
), $view->vars['choices']);
|
||||
$this->assertEquals(array(
|
||||
1 => new ChoiceView('b', 'b', 'B'),
|
||||
3 => new ChoiceView('d', 'd', 'D'),
|
||||
1 => new ChoiceView('B', 'b', 'b'),
|
||||
3 => new ChoiceView('D', 'd', 'd'),
|
||||
), $view->vars['preferred_choices']);
|
||||
}
|
||||
|
||||
@ -1169,21 +1479,21 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
'Symfony' => array(
|
||||
0 => new ChoiceView('a', 'a', 'Bernhard'),
|
||||
2 => new ChoiceView('c', 'c', 'Kris'),
|
||||
),
|
||||
'Doctrine' => array(
|
||||
4 => new ChoiceView('e', 'e', 'Roman'),
|
||||
),
|
||||
'Symfony' => new ChoiceGroupView('Symfony', array(
|
||||
0 => new ChoiceView('Bernhard', 'a', 'a'),
|
||||
2 => new ChoiceView('Kris', 'c', 'c'),
|
||||
)),
|
||||
'Doctrine' => new ChoiceGroupView('Doctrine', array(
|
||||
4 => new ChoiceView('Roman', 'e', 'e'),
|
||||
)),
|
||||
), $view->vars['choices']);
|
||||
$this->assertEquals(array(
|
||||
'Symfony' => array(
|
||||
1 => new ChoiceView('b', 'b', 'Fabien'),
|
||||
),
|
||||
'Doctrine' => array(
|
||||
3 => new ChoiceView('d', 'd', 'Jon'),
|
||||
),
|
||||
'Symfony' => new ChoiceGroupView('Symfony', array(
|
||||
1 => new ChoiceView('Fabien', 'b', 'b'),
|
||||
)),
|
||||
'Doctrine' => new ChoiceGroupView('Doctrine', array(
|
||||
3 => new ChoiceView('Jon', 'd', 'd'),
|
||||
)),
|
||||
), $view->vars['preferred_choices']);
|
||||
}
|
||||
|
||||
@ -1194,15 +1504,18 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$obj3 = (object) array('value' => 'c', 'label' => 'C');
|
||||
$obj4 = (object) array('value' => 'd', 'label' => 'D');
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'choice_list' => new ObjectChoiceList(array($obj1, $obj2, $obj3, $obj4), 'label', array(), null, 'value'),
|
||||
'choices' => array($obj1, $obj2, $obj3, $obj4),
|
||||
'choices_as_values' => true,
|
||||
'choice_label' => 'label',
|
||||
'choice_value' => 'value',
|
||||
));
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView($obj1, 'a', 'A'),
|
||||
new ChoiceView($obj2, 'b', 'B'),
|
||||
new ChoiceView($obj3, 'c', 'C'),
|
||||
new ChoiceView($obj4, 'd', 'D'),
|
||||
new ChoiceView('A', 'a', $obj1),
|
||||
new ChoiceView('B', 'b', $obj2),
|
||||
new ChoiceView('C', 'c', $obj3),
|
||||
new ChoiceView('D', 'd', $obj4),
|
||||
), $view->vars['choices']);
|
||||
}
|
||||
|
||||
@ -1226,47 +1539,6 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
));
|
||||
}
|
||||
|
||||
// https://github.com/symfony/symfony/issues/10409
|
||||
public function testReuseNonUtf8ChoiceLists()
|
||||
{
|
||||
$form1 = $this->factory->createNamed('name', 'choice', null, array(
|
||||
'choices' => array(
|
||||
'meter' => 'm',
|
||||
'millimeter' => 'mm',
|
||||
'micrometer' => chr(181).'meter',
|
||||
),
|
||||
));
|
||||
|
||||
$form2 = $this->factory->createNamed('name', 'choice', null, array(
|
||||
'choices' => array(
|
||||
'meter' => 'm',
|
||||
'millimeter' => 'mm',
|
||||
'micrometer' => chr(181).'meter',
|
||||
),
|
||||
));
|
||||
|
||||
$form3 = $this->factory->createNamed('name', 'choice', null, array(
|
||||
'choices' => array(
|
||||
'meter' => 'm',
|
||||
'millimeter' => 'mm',
|
||||
'micrometer' => null,
|
||||
),
|
||||
));
|
||||
|
||||
// $form1 and $form2 use the same ChoiceList
|
||||
$this->assertSame(
|
||||
$form1->getConfig()->getOption('choice_list'),
|
||||
$form2->getConfig()->getOption('choice_list')
|
||||
);
|
||||
|
||||
// $form3 doesn't, but used to use the same when using json_encode()
|
||||
// instead of serialize for the hashing algorithm
|
||||
$this->assertNotSame(
|
||||
$form1->getConfig()->getOption('choice_list'),
|
||||
$form3->getConfig()->getOption('choice_list')
|
||||
);
|
||||
}
|
||||
|
||||
public function testInitializeWithDefaultObjectChoice()
|
||||
{
|
||||
$obj1 = (object) array('value' => 'a', 'label' => 'A');
|
||||
@ -1275,7 +1547,10 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$obj4 = (object) array('value' => 'd', 'label' => 'D');
|
||||
|
||||
$form = $this->factory->create('choice', null, array(
|
||||
'choice_list' => new ObjectChoiceList(array($obj1, $obj2, $obj3, $obj4), 'label', array(), null, 'value'),
|
||||
'choices' => array($obj1, $obj2, $obj3, $obj4),
|
||||
'choices_as_values' => true,
|
||||
'choice_label' => 'label',
|
||||
'choice_value' => 'value',
|
||||
// Used to break because "data_class" was inferred, which needs to
|
||||
// remain null in every case (because it refers to the view format)
|
||||
'data' => $obj3,
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\Test\TypeTestCase as TestCase;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||
|
||||
class CountryTypeTest extends TestCase
|
||||
@ -31,11 +31,11 @@ class CountryTypeTest extends TestCase
|
||||
$choices = $view->vars['choices'];
|
||||
|
||||
// Don't check objects for identity
|
||||
$this->assertContains(new ChoiceView('DE', 'DE', 'Germany'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('GB', 'GB', 'United Kingdom'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('US', 'US', 'United States'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('FR', 'FR', 'France'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('MY', 'MY', 'Malaysia'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('Germany', 'DE', 'DE'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('United Kingdom', 'GB', 'GB'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('United States', 'US', 'US'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('France', 'FR', 'FR'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('Malaysia', 'MY', 'MY'), $choices, '', false, false);
|
||||
}
|
||||
|
||||
public function testUnknownCountryIsNotIncluded()
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\Test\TypeTestCase as TestCase;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||
|
||||
class CurrencyTypeTest extends TestCase
|
||||
@ -30,8 +30,8 @@ class CurrencyTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
$choices = $view->vars['choices'];
|
||||
|
||||
$this->assertContains(new ChoiceView('EUR', 'EUR', 'Euro'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('USD', 'USD', 'US Dollar'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('SIT', 'SIT', 'Slovenian Tolar'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('Euro', 'EUR', 'EUR'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('US Dollar', 'USD', 'USD'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('Slovenian Tolar', 'SIT', 'SIT'), $choices, '', false, false);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\Test\TypeTestCase as TestCase;
|
||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||
@ -490,8 +490,8 @@ class DateTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('6', '6', '06'),
|
||||
new ChoiceView('7', '7', '07'),
|
||||
new ChoiceView('06', '6', '6'),
|
||||
new ChoiceView('07', '7', '7'),
|
||||
), $view['month']->vars['choices']);
|
||||
}
|
||||
|
||||
@ -505,8 +505,8 @@ class DateTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('1', '1', 'Jän'),
|
||||
new ChoiceView('4', '4', 'Apr.'),
|
||||
new ChoiceView('Jän', '1', '1'),
|
||||
new ChoiceView('Apr.', '4', '4'),
|
||||
), $view['month']->vars['choices']);
|
||||
}
|
||||
|
||||
@ -520,8 +520,8 @@ class DateTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('1', '1', 'Jänner'),
|
||||
new ChoiceView('4', '4', 'April'),
|
||||
new ChoiceView('Jänner', '1', '1'),
|
||||
new ChoiceView('April', '4', '4'),
|
||||
), $view['month']->vars['choices']);
|
||||
}
|
||||
|
||||
@ -535,8 +535,8 @@ class DateTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('1', '1', 'Jänner'),
|
||||
new ChoiceView('4', '4', 'April'),
|
||||
new ChoiceView('Jänner', '1', '1'),
|
||||
new ChoiceView('April', '4', '4'),
|
||||
), $view['month']->vars['choices']);
|
||||
}
|
||||
|
||||
@ -549,8 +549,8 @@ class DateTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('6', '6', '06'),
|
||||
new ChoiceView('7', '7', '07'),
|
||||
new ChoiceView('06', '6', '6'),
|
||||
new ChoiceView('07', '7', '7'),
|
||||
), $view['day']->vars['choices']);
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\Test\TypeTestCase as TestCase;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||
|
||||
class LanguageTypeTest extends TestCase
|
||||
@ -30,11 +30,11 @@ class LanguageTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
$choices = $view->vars['choices'];
|
||||
|
||||
$this->assertContains(new ChoiceView('en', 'en', 'English'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('en_GB', 'en_GB', 'British English'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('en_US', 'en_US', 'American English'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('fr', 'fr', 'French'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('my', 'my', 'Burmese'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('English', 'en', 'en'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('British English', 'en_GB', 'en_GB'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('American English', 'en_US', 'en_US'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('French', 'fr', 'fr'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('Burmese', 'my', 'my'), $choices, '', false, false);
|
||||
}
|
||||
|
||||
public function testMultipleLanguagesIsNotIncluded()
|
||||
@ -43,6 +43,6 @@ class LanguageTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
$choices = $view->vars['choices'];
|
||||
|
||||
$this->assertNotContains(new ChoiceView('mul', 'mul', 'Mehrsprachig'), $choices, '', false, false);
|
||||
$this->assertNotContains(new ChoiceView('Mehrsprachig', 'mul', 'mul'), $choices, '', false, false);
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\Test\TypeTestCase as TestCase;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||
|
||||
class LocaleTypeTest extends TestCase
|
||||
@ -30,8 +30,8 @@ class LocaleTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
$choices = $view->vars['choices'];
|
||||
|
||||
$this->assertContains(new ChoiceView('en', 'en', 'English'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('en_GB', 'en_GB', 'English (United Kingdom)'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('zh_Hant_MO', 'zh_Hant_MO', 'Chinese (Traditional, Macau SAR China)'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('English', 'en', 'en'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('English (United Kingdom)', 'en_GB', 'en_GB'), $choices, '', false, false);
|
||||
$this->assertContains(new ChoiceView('Chinese (Traditional, Macau SAR China)', 'zh_Hant_MO', 'zh_Hant_MO'), $choices, '', false, false);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\Test\TypeTestCase as TestCase;
|
||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||
@ -319,8 +319,8 @@ class TimeTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('6', '6', '06'),
|
||||
new ChoiceView('7', '7', '07'),
|
||||
new ChoiceView('06', '6', '6'),
|
||||
new ChoiceView('07', '7', '7'),
|
||||
), $view['hour']->vars['choices']);
|
||||
}
|
||||
|
||||
@ -333,8 +333,8 @@ class TimeTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('6', '6', '06'),
|
||||
new ChoiceView('7', '7', '07'),
|
||||
new ChoiceView('06', '6', '6'),
|
||||
new ChoiceView('07', '7', '7'),
|
||||
), $view['minute']->vars['choices']);
|
||||
}
|
||||
|
||||
@ -348,8 +348,8 @@ class TimeTypeTest extends TestCase
|
||||
$view = $form->createView();
|
||||
|
||||
$this->assertEquals(array(
|
||||
new ChoiceView('6', '6', '06'),
|
||||
new ChoiceView('7', '7', '07'),
|
||||
new ChoiceView('06', '6', '6'),
|
||||
new ChoiceView('07', '7', '7'),
|
||||
), $view['second']->vars['choices']);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
|
||||
class TimezoneTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
{
|
||||
@ -22,9 +22,9 @@ class TimezoneTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
|
||||
$choices = $view->vars['choices'];
|
||||
|
||||
$this->assertArrayHasKey('Africa', $choices);
|
||||
$this->assertContains(new ChoiceView('Africa/Kinshasa', 'Africa/Kinshasa', 'Kinshasa'), $choices['Africa'], '', false, false);
|
||||
$this->assertContains(new ChoiceView('Kinshasa', 'Africa/Kinshasa', 'Africa/Kinshasa'), $choices['Africa'], '', false, false);
|
||||
|
||||
$this->assertArrayHasKey('America', $choices);
|
||||
$this->assertContains(new ChoiceView('America/New_York', 'America/New_York', 'New York'), $choices['America'], '', false, false);
|
||||
$this->assertContains(new ChoiceView('New York', 'America/New_York', 'America/New_York'), $choices['America'], '', false, false);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user