feature #14050 [Form] Refactored choice lists to support dynamic label, value, index and attribute generation (webmozart)

This PR was merged into the 2.7 branch.

Discussion
----------

[Form] Refactored choice lists to support dynamic label, value, index and attribute generation

This is a rebase of #12148 on the 2.7 branch.

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | yes
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #4067, #5494, #3836, #8658, #12148
| License       | MIT
| Doc PR        | TODO

I implemented the additional options "choice_label", "choice_name", "choice_value", "choice_attr", "group_by" and "choices_as_values" for ChoiceType. Additionally the "preferred_choices" option was updated to accept callables and property paths.

The "choices_as_values" option will be removed in Symfony 3.0, where the choices will be passed in the values of the "choices" option by default. The reason for that is that, right now, choices are limited to strings and integers (i.e. valid array keys). When we flip the array, we remove that limitation. Since choice labels are always strings, we can also always use them as array keys:

```php
// Not possible currently, but possible with "flip_choices"
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
));
```

All the features described here obviously also apply to subtypes of "choice", such as "entity".

**choice_label**

Returns the label for each choice. Can be a callable (which receives the choice as first and the key of the "choices" array as second argument) or a property path.

If `null`, the keys of the "choices" array are used as labels.

```php
// callable
$builder->add('attending', 'choice', array(
    'choices' => array(
        'yes' => true,
        'no' => false,
        'maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_label' => function ($choice, $key) {
        return 'form.choice.'.$key;
    },
));

// property path
$builder->add('attending', 'choice', array(
    'choices' => array(
        Status::getInstance(Status::YES),
        Status::getInstance(Status::NO),
        Status::getInstance(Status::MAYBE),
    ),
    'choices_as_values' => true,
    'choice_label' => 'displayName',
));
```

**choice_name**

Returns the form name for each choice. That name is used as name of the checkbox/radio form for this choice. It is also used as index of the choice views in the template. Can be a callable (like for "choice_label") or a property path.

The generated names must be valid form names, i.e. contain alpha-numeric symbols, underscores, hyphens and colons only. They must start with an alpha-numeric symbol or an underscore.

If `null`, an incrementing integer is used as name.

```php
// callable
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_name' => function ($choice, $key) {
        // use the labels as names
        return strtolower($key);
    },
));

// property path
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => Status::getInstance(Status::YES),
        'No' => Status::getInstance(Status::NO),
        'Maybe' => Status::getInstance(Status::MAYBE),
    ),
    'choices_as_values' => true,
    'choice_name' => 'value',
));
```

**choice_value**

Returns the string value for each choice. This value is displayed in the "value" attributes and submitted in the POST/PUT requests. Can be a callable (like for "choice_label") or a property path.

If `null`, an incrementing integer is used as value.

```php
// callable
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_value' => function ($choice, $key) {
        if (null === $choice) {
            return 'null';
        }

        if (true === $choice) {
            return 'true';
        }

        return 'false';
    },
));

// property path
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => Status::getInstance(Status::YES),
        'No' => Status::getInstance(Status::NO),
        'Maybe' => Status::getInstance(Status::MAYBE),
    ),
    'choices_as_values' => true,
    'choice_value' => 'value',
));
```

**choice_attr**

Returns the additional HTML attributes for choices. Can be an array, a callable (like for "choice_label") or a property path.
If an array, the key of the "choices" array must be used as keys.

```php
// array
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_attr' => array(
        'Maybe' => array('class' => 'greyed-out'),
    ),
));

// callable
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'choice_attr' => function ($choice, $key) {
        if (null === $choice) {
            return array('class' => 'greyed-out');
        }
    },
));

// property path
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => Status::getInstance(Status::YES),
        'No' => Status::getInstance(Status::NO),
        'Maybe' => Status::getInstance(Status::MAYBE),
    ),
    'choices_as_values' => true,
    'choice_value' => 'htmlAttributes',
));
```

**group_by**

Returns the grouping used for the choices. Can be an array/Traversable, a callable (like for "choice_label") or a property path.

The return values of the callable/property path are used as group labels. If `null` is returned, a choice is not grouped.

If `null`, the structure of the "choices" array is used to construct the groups.

```php
// default
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Decided' => array(
            'Yes' => true,
            'No' => false,
        ),
        'Undecided' => array(
            'Maybe' => null,
        ),
    ),
    'choices_as_values' => true,
));

// callable
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'group_by' => function ($choice, $key) {
        if (null === $choice) {
            return 'Undecided';
        }

        return 'Decided';
    },
));

// property path
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => Status::getInstance(Status::YES),
        'No' => Status::getInstance(Status::NO),
        'Maybe' => Status::getInstance(Status::MAYBE),
    ),
    'choices_as_values' => true,
    'group_by' => 'type',
));
```

**preferred_choices**

Returns the preferred choices. Can be an array/Traversable, a callable (like for "choice_label") or a property path.

```php
// array
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'preferred_choices' => array(true),
));

// callable
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => true,
        'No' => false,
        'Maybe' => null,
    ),
    'choices_as_values' => true,
    'preferred_choices' => function ($choice, $key) {
        return true === $choice;
    },
));

// property path
$builder->add('attending', 'choice', array(
    'choices' => array(
        'Yes' => Status::getInstance(Status::YES),
        'No' => Status::getInstance(Status::NO),
        'Maybe' => Status::getInstance(Status::MAYBE),
    ),
    'choices_as_values' => true,
    'preferred_choices' => 'preferred',
));
```

**Technical Changes**

To properly implement all this, the old `ChoiceListInterface` class was deprecated and replaced by a new, slimmer one. The creation of choice views is now separated from choice lists. Hence a lot of logic is not executed anymore when processing (but not displaying) a form.

Internally, a `ChoiceListFactoryInterface` implementation is used to construct choice lists and choice views. Two decorators exist for this class:

* `CachingFactoryDecorator`: caches choice lists/views so that multiple fields displaying the same choices (e.g. in collection fields) use the same choice list/view
* `PropertyAccessDecorator`: adds support for property paths to a factory

**BC Breaks**

The option "choice_list" of ChoiceType now contains a `Symfony\Component\Form\ChoiceList\ChoiceListInterface` instance, which is a super-type of the deprecated `ChoiceListInterface`.

**Todos**

- [ ] Adapt CHANGELOGs
- [ ] Adapt UPGRADE files
- [ ] symfony/symfony-docs issue/PR

Commits
-------

94d18e9 [Form] Fixed CS
7e0960d [Form] Fixed failing layout tests
1d89922 [Form] Fixed tests using legacy functionality
d6179c8 [Form] Fixed PR comments
26eba76 [Form] Fixed regression: Choices are compared by their values if a value callback is given
a289deb [Form] Fixed new ArrayChoiceList to compare choices by their values, if enabled
e6739bf [DoctrineBridge] DoctrineType now respects the "query_builder" option when caching the choice loader
3846b37 [DoctrineBridge] Fixed: don't cache choice lists if query builders are constructed dynamically
03efce1 [Form] Refactored choice lists to support dynamic label, value, index and attribute generation
This commit is contained in:
Bernhard Schussek 2015-04-01 12:04:55 +02:00
commit 776796435e
77 changed files with 6079 additions and 676 deletions

View File

@ -0,0 +1,171 @@
<?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\ObjectManager;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
/**
* Loads choices using a Doctrine object manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DoctrineChoiceLoader implements ChoiceLoaderInterface
{
/**
* @var ChoiceListFactoryInterface
*/
private $factory;
/**
* @var ObjectManager
*/
private $manager;
/**
* @var string
*/
private $class;
/**
* @var IdReader
*/
private $idReader;
/**
* @var null|EntityLoaderInterface
*/
private $objectLoader;
/**
* @var ChoiceListInterface
*/
private $choiceList;
/**
* Creates a new choice loader.
*
* Optionally, an implementation of {@link EntityLoaderInterface} can be
* passed which optimizes the object 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 class name of the
* loaded objects
* @param IdReader $idReader The reader for the object
* IDs.
* @param null|EntityLoaderInterface $objectLoader The objects loader
*/
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, IdReader $idReader, EntityLoaderInterface $objectLoader = null)
{
$this->factory = $factory;
$this->manager = $manager;
$this->class = $manager->getClassMetadata($class)->getName();
$this->idReader = $idReader;
$this->objectLoader = $objectLoader;
}
/**
* {@inheritdoc}
*/
public function loadChoiceList($value = null)
{
if ($this->choiceList) {
return $this->choiceList;
}
$objects = $this->objectLoader
? $this->objectLoader->getEntities()
: $this->manager->getRepository($this->class)->findAll();
$this->choiceList = $this->factory->createListFromChoices($objects, $value);
return $this->choiceList;
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, $value = null)
{
// Performance optimization
if (empty($choices)) {
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->idReader->isSingleId()) {
$values = array();
// Maintain order and indices of the given objects
foreach ($choices as $i => $object) {
if ($object instanceof $this->class) {
// Make sure to convert to the right format
$values[$i] = (string) $this->idReader->getIdValue($object);
}
}
return $values;
}
return $this->loadChoiceList($value)->getValuesForChoices($choices);
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, $value = null)
{
// Performance optimization
// Also prevents the generation of "WHERE id IN ()" queries through the
// object 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 object loader and
// a single-field identifier
if (!$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) {
$unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values);
$objectsById = array();
$objects = 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 ($unorderedObjects as $object) {
$objectsById[$this->idReader->getIdValue($object)] = $object;
}
foreach ($values as $i => $id) {
if (isset($objectsById[$id])) {
$objects[$i] = $objectsById[$id];
}
}
return $objects;
}
return $this->loadChoiceList($value)->getChoicesForValues($values);
}
}

View File

@ -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 DoctrineChoiceLoader} instead.
*/
class EntityChoiceList extends ObjectChoiceList
{
@ -126,6 +129,8 @@ class EntityChoiceList extends ObjectChoiceList
}
parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor);
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader instead.', E_USER_DEPRECATED);
}
/**

View File

@ -0,0 +1,125 @@
<?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\Exception\RuntimeException;
/**
* A utility for reading object IDs.
*
* @since 1.0
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal This class is meant for internal use only.
*/
class IdReader
{
/**
* @var ObjectManager
*/
private $om;
/**
* @var ClassMetadata
*/
private $classMetadata;
/**
* @var bool
*/
private $singleId;
/**
* @var bool
*/
private $intId;
/**
* @var string
*/
private $idField;
public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
{
$ids = $classMetadata->getIdentifierFieldNames();
$idType = $classMetadata->getTypeOfField(current($ids));
$this->om = $om;
$this->classMetadata = $classMetadata;
$this->singleId = 1 === count($ids);
$this->intId = $this->singleId && 1 === count($ids) && in_array($idType, array('integer', 'smallint', 'bigint'));
$this->idField = current($ids);
}
/**
* Returns whether the class has a single-column ID.
*
* @return bool Returns `true` if the class has a single-column ID and
* `false` otherwise.
*/
public function isSingleId()
{
return $this->singleId;
}
/**
* Returns whether the class has a single-column integer ID.
*
* @return bool Returns `true` if the class has a single-column integer ID
* and `false` otherwise.
*/
public function isIntId()
{
return $this->intId;
}
/**
* Returns the ID value for an object.
*
* This method assumes that the object has a single-column ID.
*
* @param object $object The object.
*
* @return mixed The ID value.
*/
public function getIdValue($object)
{
if (!$object) {
return;
}
if (!$this->om->contains($object)) {
throw new RuntimeException(
'Entities passed to the choice field must be managed. Maybe '.
'persist them in the entity manager?'
);
}
$this->om->initializeObject($object);
return current($this->classMetadata->getIdentifierValues($object));
}
/**
* Returns the name of the ID field.
*
* This method assumes that the object has a single-column ID.
*
* @return string The name of the ID field.
*/
public function getIdField()
{
return $this->idField;
}
}

View File

@ -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
{
@ -34,9 +37,14 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
/**
* Construct an ORM Query Builder Loader.
*
* @param QueryBuilder|\Closure $queryBuilder
* @param EntityManager $manager
* @param string $class
* @param QueryBuilder|\Closure $queryBuilder The query builder or a closure
* for creating the query builder.
* Passing a closure is
* deprecated and will not be
* supported anymore as of
* Symfony 3.0.
* @param EntityManager $manager Deprecated.
* @param string $class Deprecated.
*
* @throws UnexpectedTypeException
*/
@ -49,10 +57,15 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
}
if ($queryBuilder instanceof \Closure) {
trigger_error('Passing a QueryBuilder closure to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
if (!$manager instanceof EntityManager) {
throw new UnexpectedTypeException($manager, 'Doctrine\ORM\EntityManager');
}
trigger_error('Passing an EntityManager to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
trigger_error('Passing a class to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
$queryBuilder = $queryBuilder($manager->getRepository($class));
if (!$queryBuilder instanceof QueryBuilder) {

View File

@ -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),
);
}

View File

@ -12,17 +12,23 @@
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 Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader;
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\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\Exception\UnexpectedTypeException;
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 +39,63 @@ abstract class DoctrineType extends AbstractType
protected $registry;
/**
* @var array
* @var ChoiceListFactoryInterface
*/
private $choiceListCache = array();
private $choiceListFactory;
/**
* @var PropertyAccessorInterface
* @var IdReader[]
*/
private $propertyAccessor;
private $idReaders = array();
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null)
/**
* @var DoctrineChoiceLoader[]
*/
private $choiceLoaders = array();
/**
* Creates the label for a choice.
*
* For backwards compatibility, objects are cast to strings by default.
*
* @param object $choice The object.
*
* @return string The string representation of the object.
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public static function createChoiceLabel($choice)
{
return (string) $choice;
}
/**
* Creates the field name for a choice.
*
* This method is used to generate field names if the underlying object has
* a single-column integer ID. In that case, the value of the field is
* the ID of the object. That ID is also used as field name.
*
* @param object $choice The object.
* @param int|string $key The choice key.
* @param string $value The choice value. Corresponds to the object's
* ID here.
*
* @return string The field name.
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public static function createChoiceName($choice, $key, $value)
{
return (string) $value;
}
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 +110,97 @@ abstract class DoctrineType extends AbstractType
public function configureOptions(OptionsResolver $resolver)
{
$choiceListCache = &$this->choiceListCache;
$registry = $this->registry;
$propertyAccessor = $this->propertyAccessor;
$choiceListFactory = $this->choiceListFactory;
$idReaders = &$this->idReaders;
$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');
$choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders, $type) {
// This closure and the "query_builder" options should be pushed to
// EntityType in Symfony 3.0 as they are specific to the ORM
return $type->getLoader($options['em'], $queryBuilder, $options['class']);
};
// Unless the choices are given explicitly, load them on demand
if (null === $options['choices']) {
// We consider two query builders with an equal SQL string and
// equal parameters to be equal
$qbParts = $options['query_builder']
? array(
$options['query_builder']->getQuery()->getSQL(),
$options['query_builder']->getParameters()->toArray(),
)
: null;
$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(
$hash = CachingFactoryDecorator::generateHash(array(
$options['em'],
$options['class'],
$options['property'],
$qbParts,
$options['loader'],
$options['choices'],
$options['preferred_choices'],
$options['group_by'],
$propertyAccessor
));
if (isset($choiceLoaders[$hash])) {
return $choiceLoaders[$hash];
}
if ($options['loader']) {
$entityLoader = $options['loader'];
} elseif (null !== $options['query_builder']) {
$entityLoader = $type->getLoader($options['em'], $options['query_builder'], $options['class']);
} else {
$queryBuilder = $options['em']->getRepository($options['class'])->createQueryBuilder('e');
$entityLoader = $type->getLoader($options['em'], $queryBuilder, $options['class']);
}
$choiceLoaders[$hash] = new DoctrineChoiceLoader(
$choiceListFactory,
$options['em'],
$options['class'],
$options['id_reader'],
$entityLoader
);
return $choiceLoaders[$hash];
}
};
$choiceLabel = function (Options $options) {
// BC with the "property" option
if ($options['property']) {
return $options['property'];
}
return $choiceListCache[$hash];
// BC: use __toString() by default
return array(__CLASS__, 'createChoiceLabel');
};
$choiceName = function (Options $options) {
/** @var IdReader $idReader */
$idReader = $options['id_reader'];
// If the object has a single-column, numeric ID, use that ID as
// field name. We can only use numeric IDs as names, as we cannot
// guarantee that a non-numeric ID contains a valid form name
if ($idReader->isIntId()) {
return array(__CLASS__, 'createChoiceName');
}
// Otherwise, an incrementing integer is used as name automatically
};
// The choices are always indexed by ID (see "choices" normalizer
// and DoctrineChoiceLoader), unless the ID is composite. Then they
// are indexed by an incrementing integer.
// Use the ID/incrementing integer as choice value.
$choiceValue = function (Options $options) {
/** @var IdReader $idReader */
$idReader = $options['id_reader'];
// If the entity has a single-column ID, use that ID as value
if ($idReader->isSingleId()) {
return array($idReader, 'getIdValue');
}
// Otherwise, an incrementing integer is used as value automatically
};
$emNormalizer = function (Options $options, $em) use ($registry) {
@ -165,22 +226,85 @@ abstract class DoctrineType extends AbstractType
return $em;
};
// deprecation note
$propertyNormalizer = function (Options $options, $propertyName) {
if ($propertyName) {
trigger_error('The "property" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_label" instead.', E_USER_DEPRECATED);
}
return $propertyName;
};
// Invoke the query builder closure so that we can cache choice lists
// for equal query builders
$queryBuilderNormalizer = function (Options $options, $queryBuilder) {
if (is_callable($queryBuilder)) {
$queryBuilder = call_user_func($queryBuilder, $options['em']->getRepository($options['class']));
if (!$queryBuilder instanceof QueryBuilder) {
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
}
}
return $queryBuilder;
};
// deprecation note
$loaderNormalizer = function (Options $options, $loader) {
if ($loader) {
trigger_error('The "loader" option is deprecated since version 2.7 and will be removed in 3.0. Override getLoader() instead.', E_USER_DEPRECATED);
}
return $loader;
};
// Set the "id_reader" option via the normalizer. This option is not
// supposed to be set by the user.
$idReaderNormalizer = function (Options $options) use (&$idReaders) {
$hash = CachingFactoryDecorator::generateHash(array(
$options['em'],
$options['class'],
));
// The ID reader is a utility that is needed to read the object IDs
// when generating the field values. The callback generating the
// field values has no access to the object manager or the class
// of the field, so we store that information in the reader.
// The reader is cached so that two choice lists for the same class
// (and hence with the same reader) can successfully be cached.
if (!isset($idReaders[$hash])) {
$classMetadata = $options['em']->getClassMetadata($options['class']);
$idReaders[$hash] = new IdReader($options['em'], $classMetadata);
}
return $idReaders[$hash];
};
$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,
'id_reader' => null, // internal
));
$resolver->setRequired(array('class'));
$resolver->setNormalizer('em', $emNormalizer);
$resolver->setNormalizer('property', $propertyNormalizer);
$resolver->setNormalizer('query_builder', $queryBuilderNormalizer);
$resolver->setNormalizer('loader', $loaderNormalizer);
$resolver->setNormalizer('id_reader', $idReaderNormalizer);
$resolver->setAllowedTypes('em', array('null', 'string', 'Doctrine\Common\Persistence\ObjectManager'));
$resolver->setAllowedTypes('loader', array('null', 'Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface'));
$resolver->setAllowedTypes('query_builder', array('null', 'callable', 'Doctrine\ORM\QueryBuilder'));
}
/**

View File

@ -17,71 +17,18 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
class EntityType extends DoctrineType
{
/**
* @var ORMQueryBuilderLoader[]
*/
private $loaderCache = array();
/**
* Return the default loader object.
*
* @param ObjectManager $manager
* @param mixed $queryBuilder
* @param QueryBuilder $queryBuilder
* @param string $class
*
* @return ORMQueryBuilderLoader
*/
public function getLoader(ObjectManager $manager, $queryBuilder, $class)
{
if (!$queryBuilder instanceof QueryBuilder) {
return new ORMQueryBuilderLoader(
$queryBuilder,
$manager,
$class
);
}
$queryBuilderHash = $this->getQueryBuilderHash($queryBuilder);
$loaderHash = $this->getLoaderHash($manager, $queryBuilderHash, $class);
if (!isset($this->loaderCache[$loaderHash])) {
$this->loaderCache[$loaderHash] = new ORMQueryBuilderLoader(
$queryBuilder,
$manager,
$class
);
}
return $this->loaderCache[$loaderHash];
}
/**
* @param QueryBuilder $queryBuilder
*
* @return string
*/
private function getQueryBuilderHash(QueryBuilder $queryBuilder)
{
return hash('sha256', json_encode(array(
'sql' => $queryBuilder->getQuery()->getSQL(),
'parameters' => $queryBuilder->getParameters(),
)));
}
/**
* @param ObjectManager $manager
* @param string $queryBuilderHash
* @param string $class
*
* @return string
*/
private function getLoaderHash(ObjectManager $manager, $queryBuilderHash, $class)
{
return hash('sha256', json_encode(array(
'manager' => spl_object_hash($manager),
'queryBuilder' => $queryBuilderHash,
'class' => $class,
)));
return new ORMQueryBuilderLoader($queryBuilder, $manager, $class);
}
public function getName()

View File

@ -19,6 +19,9 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Doctrine\ORM\Tools\SchemaTool;
/**
* @group legacy
*/
class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
{
const SINGLE_INT_ID_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity';
@ -36,6 +39,8 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$this->em = DoctrineTestHelper::createTestEntityManager();
$schemaTool = new SchemaTool($this->em);
@ -70,7 +75,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
* @expectedException \Symfony\Component\Form\Exception\StringCastException
* @expectedMessage Entity "Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity" passed to the choice field must have a "__toString()" method defined (or you can also override the "property" option).
*/
public function testEntitiesMustHaveAToStringMethod()
public function testLegacyEntitiesMustHaveAToStringMethod()
{
$entity1 = new SingleIntIdNoToStringEntity(1, 'Foo');
$entity2 = new SingleIntIdNoToStringEntity(2, 'Bar');
@ -96,7 +101,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
/**
* @expectedException \Symfony\Component\Form\Exception\RuntimeException
*/
public function testChoicesMustBeManaged()
public function testLegacyChoicesMustBeManaged()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar');
@ -118,7 +123,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$choiceList->getChoices();
}
public function testInitExplicitChoices()
public function testLegacyInitExplicitChoices()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar');
@ -141,7 +146,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
}
public function testInitEmptyChoices()
public function testLegacyInitEmptyChoices()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar');
@ -161,7 +166,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(), $choiceList->getChoices());
}
public function testInitNestedChoices()
public function testLegacyInitNestedChoices()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar');
@ -189,7 +194,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
), $choiceList->getRemainingViews());
}
public function testGroupByPropertyPath()
public function testLegacyGroupByPropertyPath()
{
$item1 = new GroupableEntity(1, 'Foo', 'Group1');
$item2 = new GroupableEntity(2, 'Bar', 'Group1');
@ -224,7 +229,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
), $choiceList->getRemainingViews());
}
public function testGroupByInvalidPropertyPathReturnsFlatChoices()
public function testLegacyGroupByInvalidPropertyPathReturnsFlatChoices()
{
$item1 = new GroupableEntity(1, 'Foo', 'Group1');
$item2 = new GroupableEntity(2, 'Bar', 'Group1');
@ -251,7 +256,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
), $choiceList->getChoices());
}
public function testInitShorthandEntityName()
public function testLegacyInitShorthandEntityName()
{
$item1 = new SingleIntIdEntity(1, 'Foo');
$item2 = new SingleIntIdEntity(2, 'Bar');
@ -267,13 +272,8 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(1, 2), $choiceList->getValuesForChoices(array($item1, $item2)));
}
/**
* @group legacy
*/
public function testLegacyInitShorthandEntityName()
public function testLegacyInitShorthandEntityName2()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$item1 = new SingleIntIdEntity(1, 'Foo');
$item2 = new SingleIntIdEntity(2, 'Bar');

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class LoadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest
{

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class LoadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest
{

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class LoadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest
{

View File

@ -13,10 +13,11 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class UnloadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest
{
public function testGetIndicesForValuesIgnoresNonExistingValues()
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
}

View File

@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest extends UnloadedEntityChoiceListCompositeIdTest
{

View File

@ -13,17 +13,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class UnloadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest
{
public function testGetIndicesForValuesIgnoresNonExistingValues()
{
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');

View File

@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleIntIdTest
{

View File

@ -13,10 +13,11 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class UnloadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest
{
public function testGetIndicesForValuesIgnoresNonExistingValues()
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
}

View File

@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/
class UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleStringIdTest
{

View File

@ -11,21 +11,24 @@
namespace Symfony\Bridge\Doctrine\Tests\Form\Type;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
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 +40,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;
@ -128,10 +131,10 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'property' => 'name',
'choice_label' => '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 +150,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()
@ -162,15 +165,15 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'property' => 'name',
'choice_label' => 'name',
'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']);
}
/**
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
* @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/
public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure()
{
@ -249,7 +252,7 @@ class EntityTypeTest extends TypeTestCase
$field->submit(null);
$this->assertNull($field->getData());
$this->assertSame(array(), $field->getViewData());
$this->assertNull($field->getViewData());
}
public function testSubmitSingleNonExpandedNull()
@ -291,7 +294,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('2');
@ -313,7 +316,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
// the collection key is used here
@ -337,7 +340,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
$field->submit(array('1', '3'));
@ -362,7 +365,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
$existing = new ArrayCollection(array(0 => $entity2));
@ -393,7 +396,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
// because of the composite key collection keys are used
@ -419,7 +422,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
$existing = new ArrayCollection(array(0 => $entity2));
@ -449,7 +452,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => true,
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('2');
@ -475,7 +478,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => true,
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
$field->submit(array('1', '3'));
@ -505,12 +508,12 @@ class EntityTypeTest extends TypeTestCase
'class' => self::SINGLE_IDENT_CLASS,
// not all persisted entities should be displayed
'choices' => array($entity1, $entity2),
'property' => 'name',
'choice_label' => 'name',
));
$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());
@ -529,7 +532,7 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default',
'class' => self::ITEM_GROUP_CLASS,
'choices' => array($item1, $item2, $item3, $item4),
'property' => 'name',
'choice_label' => 'name',
'group_by' => 'groupName',
));
@ -537,9 +540,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']);
}
@ -555,11 +563,11 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'preferred_choices' => array($entity3, $entity2),
'property' => 'name',
'choice_label' => '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()
@ -575,11 +583,11 @@ class EntityTypeTest extends TypeTestCase
'class' => self::SINGLE_IDENT_CLASS,
'choices' => array($entity2, $entity3),
'preferred_choices' => array($entity3),
'property' => 'name',
'choice_label' => '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()
@ -594,7 +602,7 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'choices' => array($entity1, $entity2),
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('3');
@ -615,7 +623,7 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS,
'choices' => array($entity1, $entity2),
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('2');
@ -639,7 +647,7 @@ class EntityTypeTest extends TypeTestCase
'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $repository->createQueryBuilder('e')
->where('e.id IN (1, 2)'),
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('3');
@ -663,7 +671,7 @@ class EntityTypeTest extends TypeTestCase
return $repository->createQueryBuilder('e')
->where('e.id IN (1, 2)');
},
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('3');
@ -687,7 +695,7 @@ class EntityTypeTest extends TypeTestCase
return $repository->createQueryBuilder('e')
->where('e.id1 IN (10, 50)');
},
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('2');
@ -707,7 +715,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::SINGLE_STRING_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
$field->submit('foo');
@ -728,7 +736,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false,
'em' => 'default',
'class' => self::COMPOSITE_STRING_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
// the collection key is used here
@ -752,7 +760,7 @@ class EntityTypeTest extends TypeTestCase
$this->factory->createNamed('name', 'entity', null, array(
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'property' => 'name',
'choice_label' => 'name',
));
}
@ -767,7 +775,7 @@ class EntityTypeTest extends TypeTestCase
$this->factory->createNamed('name', 'entity', null, array(
'em' => $this->em,
'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name',
'choice_label' => 'name',
));
}
@ -779,8 +787,7 @@ class EntityTypeTest extends TypeTestCase
$this->persist(array($entity1, $entity2, $entity3));
$repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS);
$qb = $repository->createQueryBuilder('e')->where('e.id IN (1, 2)');
$repo = $this->em->getRepository(self::SINGLE_IDENT_CLASS);
$entityType = new EntityType(
$this->emRegistry,
@ -799,19 +806,23 @@ class EntityTypeTest extends TypeTestCase
$formBuilder->add('property1', 'entity', array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $qb,
'query_builder' => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'),
));
$formBuilder->add('property2', 'entity', array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $qb,
'query_builder' => function (EntityRepository $repo) {
return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)');
},
));
$formBuilder->add('property3', 'entity', array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $qb,
'query_builder' => function (EntityRepository $repo) {
return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)');
},
));
$form = $formBuilder->getForm();
@ -822,15 +833,59 @@ class EntityTypeTest extends TypeTestCase
'property3' => 2,
));
$reflectionClass = new \ReflectionObject($entityType);
$reflectionProperty = $reflectionClass->getProperty('loaderCache');
$reflectionProperty->setAccessible(true);
$choiceList1 = $form->get('property1')->getConfig()->getOption('choice_list');
$choiceList2 = $form->get('property2')->getConfig()->getOption('choice_list');
$choiceList3 = $form->get('property3')->getConfig()->getOption('choice_list');
$loaders = $reflectionProperty->getValue($entityType);
$this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $choiceList1);
$this->assertSame($choiceList1, $choiceList2);
$this->assertSame($choiceList1, $choiceList3);
}
$reflectionProperty->setAccessible(false);
public function testCacheChoiceLists()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');
$this->assertCount(1, $loaders);
$this->persist(array($entity1));
$field1 = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'choice_label' => 'name',
));
$field2 = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'choice_label' => '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'));
}
/**
* @group legacy
*/
public function testLegacyPropertyOption()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar');
$this->persist(array($entity1, $entity2));
$field = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'property' => 'name',
));
$this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
}
protected function createRegistryMock($name, $em)

View File

@ -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.

View File

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

View File

@ -17,8 +17,8 @@ use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\Tests\AbstractDivLayoutTest;
class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
@ -132,7 +132,7 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
*/
public function testIsChoiceSelected($expected, $choice, $value)
{
$choice = new ChoiceView($choice, $choice, $choice.' label');
$choice = new ChoiceView($choice.' label', $choice, $choice);
$this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value));
}

View File

@ -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 ?>

View File

@ -0,0 +1,158 @@
<?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\UnexpectedTypeException;
/**
* 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();
/**
* The callback for creating the value for a choice.
*
* @var callable
*/
protected $valueCallback;
/**
* Creates a list with the given choices and values.
*
* The given choice array must have the same array keys as the value array.
*
* @param array $choices The selectable choices
* @param callable|null $value The callable for creating the value for a
* choice. If `null` is passed, incrementing
* integers are used as values
*/
public function __construct(array $choices, $value = null)
{
if (null !== $value && !is_callable($value)) {
throw new UnexpectedTypeException($value, 'null or callable');
}
$this->choices = $choices;
$this->values = array();
$this->valueCallback = $value;
if (null === $value) {
$i = 0;
foreach ($this->choices as $key => $choice) {
$this->values[$key] = (string) $i++;
}
} else {
foreach ($choices as $key => $choice) {
$this->values[$key] = (string) call_user_func($value, $choice);
}
}
}
/**
* {@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();
// Use the value callback to compare choices by their values, if present
if ($this->valueCallback) {
$givenValues = array();
foreach ($choices as $i => $givenChoice) {
$givenValues[$i] = (string) call_user_func($this->valueCallback, $givenChoice);
}
return array_intersect($givenValues, $this->values);
}
// Otherwise compare choices by identity
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;
}
}

View File

@ -0,0 +1,146 @@
<?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>
*/
class ArrayKeyChoiceList extends ArrayChoiceList
{
/**
* Whether the choices are used as values.
*
* @var bool
*/
private $useChoicesAsValues = false;
/**
* 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 callable $value The callable for creating the value for a
* choice. If `null` is passed, the choices are
* cast to strings and used as values
*
* @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, $value = null)
{
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
if (null === $value) {
$value = function ($choice) {
return (string) $choice;
};
$this->useChoicesAsValues = true;
}
parent::__construct($choices, $value);
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
if ($this->useChoicesAsValues) {
$values = array_map('strval', $values);
// If 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));
}
return parent::getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
if ($this->useChoicesAsValues) {
// If the choices are identical to the values, we can just return
// them to improve performance a little bit
return array_map('strval', array_intersect($choices, $this->choices));
}
return parent::getValuesForChoices($choices);
}
}

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
/**
* 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);
}

View File

@ -0,0 +1,180 @@
<?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;
/**
* 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 ($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 ($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];
}
}

View File

@ -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);
}

View File

@ -0,0 +1,343 @@
<?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\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 ($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);
return new ArrayChoiceList($flatChoices, $value);
}
/**
* {@inheritdoc}
*
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
* removed in Symfony 3.0.
*/
public function createListFromFlippedChoices($choices, $value = null)
{
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) {
$value = function ($choice) {
return (string) $choice;
};
}
return new ArrayKeyChoiceList($flatChoices, $value);
}
/**
* {@inheritdoc}
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
{
return new LazyChoiceList($loader, $value);
}
/**
* {@inheritdoc}
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
{
// 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) {
$index = 0;
}
// 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 view 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)
{
$value = $values[$key];
$nextIndex = is_int($index) ? $index++ : call_user_func($index, $choice, $key, $value);
$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, $value),
$value,
$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, $value) : (isset($attr[$key]) ? $attr[$key] : array())
);
// $isPreferred may be null if no choices are preferred
if ($isPreferred && call_user_func($isPreferred, $choice, $key, $value)) {
$preferredViews[$nextIndex] = $view;
} else {
$otherViews[$nextIndex] = $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, $values[$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
);
}
}

View File

@ -0,0 +1,230 @@
<?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) {
// The callable may be invoked with a non-object/array value
// when such values are passed to
// ChoiceListInterface::getValuesForChoices(). Handle this case
// so that the call to getValue() doesn't break.
if (is_object($choice) || is_array($choice)) {
return $accessor->getValue($choice, $value);
}
return;
};
}
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|string|PropertyPath $preferredChoices The preferred choices
* @param null|callable|string|PropertyPath $label The callable or path generating the choice labels
* @param null|callable|string|PropertyPath $index The callable or path generating the view indices
* @param null|array|\Traversable|callable|string|PropertyPath $groupBy The callable or path generating the group names
* @param null|array|callable|string|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);
}
}

View File

@ -0,0 +1,123 @@
<?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;
/**
* Whether to use the value callback to compare choices.
*
* @var bool
*/
private $compareByValue;
/**
* @var ChoiceListInterface|null
*/
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, $compareByValue = false)
{
$this->loader = $loader;
$this->value = $value;
$this->compareByValue = $compareByValue;
}
/**
* {@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);
}
}

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\Loader;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
/**
* Loads a choice list.
*
* The methods {@link loadChoicesForValues()} and {@link loadValuesForChoices()}
* can be used to load the list only partially in cases where a fully-loaded
* list is not necessary.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceLoaderInterface
{
/**
* Loads a list of choices.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as 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);
}

View File

@ -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);
}
}

View File

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

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\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;
}
}

View File

@ -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\ChoiceList\ArrayChoiceList} instead.
*/
class ChoiceList implements ChoiceListInterface
{
@ -89,6 +92,8 @@ class ChoiceList implements ChoiceListInterface
}
$this->initialize($choices, $labels, $preferredChoices);
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\ChoiceList\ArrayChoiceList instead.', E_USER_DEPRECATED);
}
/**

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Form\Extension\Core\ChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface as BaseChoiceListInterface;
/**
* Contains choices that can be selected in a form field.
*
@ -25,23 +27,12 @@ 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 BaseChoiceListInterface} instead.
*/
interface ChoiceListInterface
interface ChoiceListInterface extends BaseChoiceListInterface
{
/**
* 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 +83,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.
*

View File

@ -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\ChoiceList\LazyChoiceList}
* instead.
*/
abstract class LazyChoiceList implements ChoiceListInterface
{
@ -31,6 +35,11 @@ abstract class LazyChoiceList implements ChoiceListInterface
*/
private $choiceList;
public function __construct()
{
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\ChoiceList\LazyChoiceList instead.', E_USER_DEPRECATED);
}
/**
* {@inheritdoc}
*/

View File

@ -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\ChoiceList\ArrayChoiceList}
* instead.
*/
class ObjectChoiceList extends ChoiceList
{
@ -93,6 +97,8 @@ class ObjectChoiceList extends ChoiceList
$this->valuePath = null !== $valuePath ? new PropertyPath($valuePath) : null;
parent::__construct($choices, array(), $preferredChoices);
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\ChoiceList\ArrayChoiceList instead.', E_USER_DEPRECATED);
}
/**

View File

@ -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\ChoiceList\ArrayChoiceList}
* instead.
*/
class SimpleChoiceList extends ChoiceList
{

View File

@ -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(),

View File

@ -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
);
}
}
}

View File

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

View File

@ -0,0 +1,71 @@
<?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()) {
return;
}
$value = $radio->getConfig()->getOption('value');
$choice = current($this->choiceList->getChoicesForValues(array($value)));
return;
}
}
}
}

View File

@ -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\ChoiceList\LazyChoiceList}
* instead.
*/
class ChoiceToBooleanArrayTransformer implements DataTransformerInterface
{
@ -34,6 +38,8 @@ class ChoiceToBooleanArrayTransformer implements DataTransformerInterface
{
$this->choiceList = $choiceList;
$this->placeholderPresent = $placeholderPresent;
trigger_error('The class '.__CLASS__.' is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\ChoiceList\LazyChoiceList instead.', E_USER_DEPRECATED);
}
/**

View File

@ -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,20 +43,12 @@ class ChoiceToValueTransformer implements DataTransformerInterface
throw new TransformationFailedException('Expected a scalar.');
}
// These are now valid ChoiceList values, so we can return null
// right away
if ('' === $value || null === $value) {
return;
}
$choices = $this->choiceList->getChoicesForValues(array($value));
$choices = $this->choiceList->getChoicesForValues(array((string) $value));
if (1 !== count($choices)) {
throw new TransformationFailedException(sprintf('The choice "%s" does not exist or is not unique', $value));
}
$choice = current($choices);
return '' === $choice ? null : $choice;
return current($choices);
}
}

View File

@ -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\ChoiceList\LazyChoiceList}
* instead.
*/
class ChoicesToBooleanArrayTransformer implements DataTransformerInterface
{
@ -25,6 +29,8 @@ class ChoicesToBooleanArrayTransformer implements DataTransformerInterface
public function __construct(ChoiceListInterface $choiceList)
{
$this->choiceList = $choiceList;
trigger_error('The class '.__CLASS__.' is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\ChoiceList\LazyChoiceList instead.', E_USER_DEPRECATED);
}
/**

View File

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

View File

@ -11,17 +11,21 @@
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
* indexed array.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
* Use {@link \Symfony\Component\Form\Extension\Core\DataMapper\CheckboxListMapper}
* instead.
*/
class FixCheckboxInputListener implements EventSubscriberInterface
{
@ -35,6 +39,8 @@ class FixCheckboxInputListener implements EventSubscriberInterface
public function __construct(ChoiceListInterface $choiceList)
{
$this->choiceList = $choiceList;
trigger_error('The class '.__CLASS__.' is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\Extension\Core\DataMapper\CheckboxListMapper instead.', E_USER_DEPRECATED);
}
public function preSubmit(FormEvent $event)

View File

@ -11,16 +11,20 @@
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
* to an array.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
* Use {@link \Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper}
* instead.
*/
class FixRadioInputListener implements EventSubscriberInterface
{
@ -38,6 +42,8 @@ class FixRadioInputListener implements EventSubscriberInterface
{
$this->choiceList = $choiceList;
$this->placeholderPresent = $placeholderPresent;
trigger_error('The class '.__CLASS__.' is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper instead.', E_USER_DEPRECATED);
}
public function preSubmit(FormEvent $event)

View File

@ -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,54 +39,111 @@ 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;
}
// 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 {
if ($options['multiple']) {
$builder->addViewTransformer(new ChoicesToValuesTransformer($options['choice_list']));
} else {
$builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list']));
}
// <select> tag without "multiple" option
$builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list']));
}
if ($options['multiple'] && $options['by_reference']) {
@ -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,21 +231,7 @@ class ChoiceType extends AbstractType
*/
public function configureOptions(OptionsResolver $resolver)
{
$choiceListCache = &$this->choiceListCache;
$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']);
}
return $choiceListCache[$hash];
};
$choiceListFactory = $this->choiceListFactory;
$emptyData = function (Options $options) {
if ($options['multiple'] || $options['expanded']) {
@ -196,6 +250,31 @@ class ChoiceType extends AbstractType
return $options['empty_value'];
};
$choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) {
if ($choiceList) {
trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED);
return $choiceList;
}
if (null !== $options['choice_loader']) {
return $choiceListFactory->createListFromLoader(
$options['choice_loader'],
$options['choice_value']
);
}
// Harden against NULL values (like in EntityType and ModelType)
$choices = null !== $options['choices'] ? $options['choices'] : array();
// 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']);
};
$placeholderNormalizer = function (Options $options, $placeholder) {
if ($options['multiple']) {
// never use an empty value for this case
@ -219,9 +298,16 @@ class ChoiceType extends AbstractType
$resolver->setDefaults(array(
'multiple' => false,
'expanded' => false,
'choice_list' => $choiceList,
'choice_list' => null, // 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,
@ -233,10 +319,20 @@ class ChoiceType extends AbstractType
'data_class' => null,
));
$resolver->setNormalizer('choice_list', $choiceListNormalizer);
$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 +343,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,29 +367,69 @@ class ChoiceType extends AbstractType
*/
private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options)
{
foreach ($choiceViews as $i => $choiceView) {
foreach ($choiceViews as $name => $choiceView) {
// Flatten groups
if (is_array($choiceView)) {
// Flatten groups
$this->addSubForms($builder, $choiceView, $options);
} else {
$choiceOpts = array(
'value' => $choiceView->value,
'label' => $choiceView->label,
'translation_domain' => $options['translation_domain'],
'block_name' => 'entry',
);
if ($options['multiple']) {
$choiceType = 'checkbox';
// The user can check 0 or more checkboxes. If required
// is true, he is required to check all of them.
$choiceOpts['required'] = false;
} else {
$choiceType = 'radio';
}
$builder->add($i, $choiceType, $choiceOpts);
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',
);
if ($options['multiple']) {
$choiceType = 'checkbox';
// The user can check 0 or more checkboxes. If required
// is true, he is required to check all of them.
$choiceOpts['required'] = false;
} else {
$choiceType = 'radio';
}
$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']
);
}
}

View File

@ -11,34 +11,18 @@
namespace Symfony\Component\Form\Extension\Core\View;
use Symfony\Component\Form\ChoiceList\View\ChoiceView as BaseChoiceView;
/**
* Represents a choice in templates.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
* Use {@link BaseChoiceView} instead.
*/
class ChoiceView
class ChoiceView extends BaseChoiceView
{
/**
* 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 +32,8 @@ class ChoiceView
*/
public function __construct($data, $value, $label)
{
$this->data = $data;
$this->value = $value;
$this->label = $label;
parent::__construct($label, $value, $data);
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\ChoiceList\View\ChoiceView instead.', E_USER_DEPRECATED);
}
}

View File

@ -231,6 +231,29 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
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('attr' => array('class' => 'my&class')),
'/select
[@name="name"]
[@class="my&class form-control"]
[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(
@ -496,6 +519,31 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
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('attr' => array('class' => 'my&class')),
'/select
[@name="name[]"]
[@class="my&class form-control"]
[@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(
@ -577,6 +625,42 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
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
[
./div
[@class="radio"]
[
./label
[.="[trans]Choice&A[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="radio"]
[
./label
[.="[trans]Choice&B[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)][@class="foo&bar"]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testSingleChoiceExpandedWithPlaceholder()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -702,6 +786,52 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
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
[
./div
[@class="checkbox"]
[
./label
[.="[trans]Choice&A[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[.="[trans]Choice&B[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)][@class="foo&bar"]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[.="[trans]Choice&C[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testCountry()
{
$form = $this->factory->createNamed('name', 'country', 'AT');

View File

@ -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');

View 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\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();
}

View File

@ -0,0 +1,81 @@
<?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()
{
$i = 0;
return new ArrayChoiceList($this->getChoices());
}
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'));
}
public function testCreateChoiceListWithValueCallback()
{
$callback = function ($choice) {
return ':'.$choice;
};
$choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback);
$this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues());
$this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz')));
$this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
}
public function testCompareChoicesByIdentityByDefault()
{
$callback = function ($choice) {
return $choice->value;
};
$obj1 = (object) array('value' => 'value1');
$obj2 = (object) array('value' => 'value2');
$choiceList = new ArrayChoiceList(array($obj1, $obj2), $callback);
$this->assertSame(array(2 => 'value2'), $choiceList->getValuesForChoices(array(2 => $obj2)));
$this->assertSame(array(2 => 'value2'), $choiceList->getValuesForChoices(array(2 => (object) array('value' => 'value2'))));
}
}

View File

@ -0,0 +1,196 @@
<?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());
}
protected function getChoices()
{
return array(0, 1, 'a', 'b', '');
}
protected function getValues()
{
return array('0', '1', 'a', '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);
$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);
}
/**
* @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($value, $converted)
{
$callback = function () use ($value) {
return $value;
};
$list = new ArrayKeyChoiceList(array('choice'), $callback);
$this->assertSame(array($converted), $list->getValues());
}
public function provideConvertibleValues()
{
return array(
array(0, '0'),
array(1, '1'),
array('0', '0'),
array('1', '1'),
array('1.23', '1.23'),
array('foobar', 'foobar'),
// The default value of choice fields is NULL. It should be treated
// like the empty value for this choice list type
array(null, ''),
array(1.23, '1.23'),
// Always cast booleans to 0 and 1, because:
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
array(true, '1'),
array(false, ''),
);
}
public function testCreateChoiceListWithValueCallback()
{
$callback = function ($choice) {
return ':'.$choice;
};
$choiceList = new ArrayKeyChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback);
$this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues());
$this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz')));
$this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
}
}

View File

@ -0,0 +1,652 @@
<?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);
}
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));
}
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'),
);
}
}

View File

@ -0,0 +1,894 @@
<?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)
);
$this->factory = new DefaultChoiceListFactory();
}
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 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 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 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 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);
}
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()
{
$view = $this->factory->createView(
$this->list,
function ($object, $key) {
return 'B' === $key || 'C' === $key;
}
);
$this->assertFlatView($view);
}
public function testCreateViewFlatPreferredChoicesClosureReceivesValue()
{
$view = $this->factory->createView(
$this->list,
function ($object, $key, $value) {
return '1' === $value || '2' === $value;
}
);
$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 testCreateViewFlatLabelClosureReceivesValue()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
function ($object, $key, $value) {
switch ($value) {
case '0': return 'A';
case '1': return 'B';
case '2': return 'C';
case '3': return 'D';
}
}
);
$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 testCreateViewFlatIndexClosureReceivesValue()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
null, // label
function ($object, $key, $value) {
switch ($value) {
case '0': return 'w';
case '1': return 'x';
case '2': return 'y';
case '3': 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 testCreateViewFlatGroupByClosureReceivesValue()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
null, // label
null, // index
function ($object, $key, $value) {
return '0' === $value || '1' === $value ? '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 testCreateViewFlatAttrClosureReceivesValue()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
null, // label
null, // index
null, // group
function ($object, $key, $value) {
switch ($value) {
case '1': return array('attr1' => 'value1');
case '2': 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);
}
}

View File

@ -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')
));
}
}

View File

@ -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')));
}
}

View File

@ -123,6 +123,8 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
parent::setUp();
$this->list = $this->createChoiceList();
@ -151,19 +153,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
}
}
public function testGetChoices()
public function testLegacyGetChoices()
{
$this->assertSame($this->choices, $this->list->getChoices());
}
public function testGetValues()
public function testLegacyGetValues()
{
$this->assertSame($this->values, $this->list->getValues());
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForChoices()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -172,9 +171,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForChoices($choices));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForChoicesPreservesKeys()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -183,9 +179,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(5 => $this->index1, 8 => $this->index2), $this->list->getIndicesForChoices($choices));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForChoicesPreservesOrder()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -194,9 +187,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array($this->index2, $this->index1), $this->list->getIndicesForChoices($choices));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForChoicesIgnoresNonExistingChoices()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -205,9 +195,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForChoices($choices));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForChoicesEmpty()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -215,9 +202,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(), $this->list->getIndicesForChoices(array()));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValues()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -227,9 +211,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForValues($values));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesPreservesKeys()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -239,9 +220,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(5 => $this->index1, 8 => $this->index2), $this->list->getIndicesForValues($values));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesPreservesOrder()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -250,9 +228,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array($this->index2, $this->index1), $this->list->getIndicesForValues($values));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -261,9 +236,6 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForValues($values));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesEmpty()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -271,61 +243,61 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(), $this->list->getIndicesForValues(array()));
}
public function testGetChoicesForValues()
public function testLegacyGetChoicesForValues()
{
$values = array($this->value1, $this->value2);
$this->assertSame(array($this->choice1, $this->choice2), $this->list->getChoicesForValues($values));
}
public function testGetChoicesForValuesPreservesKeys()
public function testLegacyGetChoicesForValuesPreservesKeys()
{
$values = array(5 => $this->value1, 8 => $this->value2);
$this->assertSame(array(5 => $this->choice1, 8 => $this->choice2), $this->list->getChoicesForValues($values));
}
public function testGetChoicesForValuesPreservesOrder()
public function testLegacyGetChoicesForValuesPreservesOrder()
{
$values = array($this->value2, $this->value1);
$this->assertSame(array($this->choice2, $this->choice1), $this->list->getChoicesForValues($values));
}
public function testGetChoicesForValuesIgnoresNonExistingValues()
public function testLegacyGetChoicesForValuesIgnoresNonExistingValues()
{
$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()
public function testLegacyGetChoicesForValuesEmpty()
{
$this->assertSame(array(), $this->list->getChoicesForValues(array()));
}
public function testGetValuesForChoices()
public function testLegacyGetValuesForChoices()
{
$choices = array($this->choice1, $this->choice2);
$this->assertSame(array($this->value1, $this->value2), $this->list->getValuesForChoices($choices));
}
public function testGetValuesForChoicesPreservesKeys()
public function testLegacyGetValuesForChoicesPreservesKeys()
{
$choices = array(5 => $this->choice1, 8 => $this->choice2);
$this->assertSame(array(5 => $this->value1, 8 => $this->value2), $this->list->getValuesForChoices($choices));
}
public function testGetValuesForChoicesPreservesOrder()
public function testLegacyGetValuesForChoicesPreservesOrder()
{
$choices = array($this->choice2, $this->choice1);
$this->assertSame(array($this->value2, $this->value1), $this->list->getValuesForChoices($choices));
}
public function testGetValuesForChoicesIgnoresNonExistingChoices()
public function testLegacyGetValuesForChoicesIgnoresNonExistingChoices()
{
$choices = array($this->choice1, $this->choice2, 'foobar');
$this->assertSame(array($this->value1, $this->value2), $this->list->getValuesForChoices($choices));
}
public function testGetValuesForChoicesEmpty()
public function testLegacyGetValuesForChoicesEmpty()
{
$this->assertSame(array(), $this->list->getValuesForChoices(array()));
}

View File

@ -14,6 +14,9 @@ namespace Symfony\Component\Form\Tests\Extension\Core\ChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
/**
* @group legacy
*/
class ChoiceListTest extends AbstractChoiceListTest
{
private $obj1;
@ -34,7 +37,7 @@ class ChoiceListTest extends AbstractChoiceListTest
parent::setUp();
}
public function testInitArray()
public function testLegacyInitArray()
{
$this->list = new ChoiceList(
array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
@ -53,7 +56,7 @@ class ChoiceListTest extends AbstractChoiceListTest
* choices parameter. A choice itself that is an object implementing \Traversable
* is not treated as hierarchical structure, but as-is.
*/
public function testInitNestedTraversable()
public function testLegacyInitNestedTraversable()
{
$traversableChoice = new \ArrayIterator(array($this->obj3, $this->obj4));
@ -80,7 +83,7 @@ class ChoiceListTest extends AbstractChoiceListTest
), $this->list->getRemainingViews());
}
public function testInitNestedArray()
public function testLegacyInitNestedArray()
{
$this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
$this->assertSame(array('0', '1', '2', '3'), $this->list->getValues());
@ -97,7 +100,7 @@ class ChoiceListTest extends AbstractChoiceListTest
/**
* @expectedException \InvalidArgumentException
*/
public function testInitWithInsufficientLabels()
public function testLegacyInitWithInsufficientLabels()
{
$this->list = new ChoiceList(
array($this->obj1, $this->obj2),
@ -105,7 +108,7 @@ class ChoiceListTest extends AbstractChoiceListTest
);
}
public function testInitWithLabelsContainingNull()
public function testLegacyInitWithLabelsContainingNull()
{
$this->list = new ChoiceList(
array($this->obj1, $this->obj2),

View File

@ -15,8 +15,14 @@ use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
/**
* @group legacy
*/
class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
{
/**
* @var LazyChoiceListTest_Impl
*/
private $list;
protected function setUp()
@ -37,22 +43,22 @@ class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
$this->list = null;
}
public function testGetChoices()
public function testLegacyGetChoices()
{
$this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c'), $this->list->getChoices());
}
public function testGetValues()
public function testLegacyGetValues()
{
$this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c'), $this->list->getValues());
}
public function testGetPreferredViews()
public function testLegacyGetPreferredViews()
{
$this->assertEquals(array(1 => new ChoiceView('b', 'b', 'B')), $this->list->getPreferredViews());
}
public function testGetRemainingViews()
public function testLegacyGetRemainingViews()
{
$this->assertEquals(array(0 => new ChoiceView('a', 'a', 'A'), 2 => new ChoiceView('c', 'c', 'C')), $this->list->getRemainingViews());
}
@ -79,13 +85,13 @@ class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(1, 2), $this->list->getIndicesForValues($values));
}
public function testGetChoicesForValues()
public function testLegacyGetChoicesForValues()
{
$values = array('b', 'c');
$this->assertSame(array('b', 'c'), $this->list->getChoicesForValues($values));
}
public function testGetValuesForChoices()
public function testLegacyGetValuesForChoices()
{
$choices = array('b', 'c');
$this->assertSame(array('b', 'c'), $this->list->getValuesForChoices($choices));
@ -94,7 +100,7 @@ class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
/**
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
*/
public function testLoadChoiceListShouldReturnChoiceList()
public function testLegacyLoadChoiceListShouldReturnChoiceList()
{
$list = new LazyChoiceListTest_InvalidImpl();

View File

@ -29,6 +29,9 @@ class ObjectChoiceListTest_EntityWithToString
}
}
/**
* @group legacy
*/
class ObjectChoiceListTest extends AbstractChoiceListTest
{
private $obj1;
@ -49,7 +52,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
parent::setUp();
}
public function testInitArray()
public function testLegacyInitArray()
{
$this->list = new ObjectChoiceList(
array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
@ -63,7 +66,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
$this->assertEquals(array(0 => new ChoiceView($this->obj1, '0', 'A'), 2 => new ChoiceView($this->obj3, '2', 'C'), 3 => new ChoiceView($this->obj4, '3', 'D')), $this->list->getRemainingViews());
}
public function testInitNestedArray()
public function testLegacyInitNestedArray()
{
$this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
$this->assertSame(array('0', '1', '2', '3'), $this->list->getValues());
@ -77,7 +80,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
), $this->list->getRemainingViews());
}
public function testInitArrayWithGroupPath()
public function testLegacyInitArrayWithGroupPath()
{
$this->obj1 = (object) array('name' => 'A', 'category' => 'Group 1');
$this->obj2 = (object) array('name' => 'B', 'category' => 'Group 1');
@ -115,7 +118,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
/**
* @expectedException \InvalidArgumentException
*/
public function testInitArrayWithGroupPathThrowsExceptionIfNestedArray()
public function testLegacyInitArrayWithGroupPathThrowsExceptionIfNestedArray()
{
$this->obj1 = (object) array('name' => 'A', 'category' => 'Group 1');
$this->obj2 = (object) array('name' => 'B', 'category' => 'Group 1');
@ -133,7 +136,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
);
}
public function testInitArrayWithValuePath()
public function testLegacyInitArrayWithValuePath()
{
$this->obj1 = (object) array('name' => 'A', 'id' => 10);
$this->obj2 = (object) array('name' => 'B', 'id' => 20);
@ -154,7 +157,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
$this->assertEquals(array(0 => new ChoiceView($this->obj1, '10', 'A'), 3 => new ChoiceView($this->obj4, '40', 'D')), $this->list->getRemainingViews());
}
public function testInitArrayUsesToString()
public function testLegacyInitArrayUsesToString()
{
$this->obj1 = new ObjectChoiceListTest_EntityWithToString('A');
$this->obj2 = new ObjectChoiceListTest_EntityWithToString('B');
@ -173,7 +176,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
/**
* @expectedException \Symfony\Component\Form\Exception\StringCastException
*/
public function testInitArrayThrowsExceptionIfToStringNotFound()
public function testLegacyInitArrayThrowsExceptionIfToStringNotFound()
{
$this->obj1 = new ObjectChoiceListTest_EntityWithToString('A');
$this->obj2 = new ObjectChoiceListTest_EntityWithToString('B');
@ -262,7 +265,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
$this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForChoices($choices));
}
public function testGetValuesForChoicesWithValuePath()
public function testLegacyGetValuesForChoicesWithValuePath()
{
$this->list = new ObjectChoiceList(
array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
@ -276,7 +279,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
$this->assertSame(array('A', 'B'), $this->list->getValuesForChoices($choices));
}
public function testGetValuesForChoicesWithValuePathPreservesKeys()
public function testLegacyGetValuesForChoicesWithValuePathPreservesKeys()
{
$this->list = new ObjectChoiceList(
array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
@ -290,7 +293,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
$this->assertSame(array(5 => 'A', 8 => 'B'), $this->list->getValuesForChoices($choices));
}
public function testGetValuesForChoicesWithValuePathPreservesOrder()
public function testLegacyGetValuesForChoicesWithValuePathPreservesOrder()
{
$this->list = new ObjectChoiceList(
array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
@ -304,7 +307,7 @@ class ObjectChoiceListTest extends AbstractChoiceListTest
$this->assertSame(array('B', 'A'), $this->list->getValuesForChoices($choices));
}
public function testGetValuesForChoicesWithValuePathIgnoresNonExistingChoices()
public function testLegacyGetValuesForChoicesWithValuePathIgnoresNonExistingChoices()
{
$this->list = new ObjectChoiceList(
array($this->obj1, $this->obj2, $this->obj3, $this->obj4),

View File

@ -14,9 +14,12 @@ namespace Symfony\Component\Form\Tests\Extension\Core\ChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
/**
* @group legacy
*/
class SimpleChoiceListTest extends AbstractChoiceListTest
{
public function testInitArray()
public function testLegacyInitArray()
{
$choices = array('a' => 'A', 'b' => 'B', 'c' => 'C');
$this->list = new SimpleChoiceList($choices, array('b'));
@ -27,7 +30,7 @@ class SimpleChoiceListTest extends AbstractChoiceListTest
$this->assertEquals(array(0 => new ChoiceView('a', 'a', 'A'), 2 => new ChoiceView('c', 'c', 'C')), $this->list->getRemainingViews());
}
public function testInitNestedArray()
public function testLegacyInitNestedArray()
{
$this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $this->list->getChoices());
$this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $this->list->getValues());
@ -44,7 +47,7 @@ class SimpleChoiceListTest extends AbstractChoiceListTest
/**
* @dataProvider dirtyValuesProvider
*/
public function testGetValuesForChoicesDealsWithDirtyValues($choice, $value)
public function testLegacyGetValuesForChoicesDealsWithDirtyValues($choice, $value)
{
$choices = array(
'0' => 'Zero',

View File

@ -13,11 +13,11 @@ namespace Symfony\Component\Form\Tests\Extension\Core\ChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
/**
* @group legacy
*/
class SimpleNumericChoiceListTest extends AbstractChoiceListTest
{
/**
* @group legacy
*/
public function testLegacyGetIndicesForChoicesDealsWithNumericChoices()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -27,9 +27,6 @@ class SimpleNumericChoiceListTest extends AbstractChoiceListTest
$this->assertSame(array(0, 1), $this->list->getIndicesForChoices($choices));
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesDealsWithNumericValues()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
@ -39,14 +36,14 @@ class SimpleNumericChoiceListTest extends AbstractChoiceListTest
$this->assertSame(array(0, 1), $this->list->getIndicesForValues($values));
}
public function testGetChoicesForValuesDealsWithNumericValues()
public function testLegacyGetChoicesForValuesDealsWithNumericValues()
{
// Pass values as strings although they are integers
$values = array('0', '1');
$this->assertSame(array(0, 1), $this->list->getChoicesForValues($values));
}
public function testGetValuesForChoicesDealsWithNumericValues()
public function testLegacyGetValuesForChoicesDealsWithNumericValues()
{
// Pass values as strings although they are integers
$values = array('0', '1');

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
class ChoiceToValueTransformerTest extends \PHPUnit_Framework_TestCase
@ -20,7 +20,8 @@ class ChoiceToValueTransformerTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$list = new SimpleChoiceList(array('' => 'A', 0 => 'B', 1 => 'C'));
$list = new ArrayChoiceList(array('', 0, 'X'));
$this->transformer = new ChoiceToValueTransformer($list);
}
@ -33,9 +34,8 @@ class ChoiceToValueTransformerTest extends \PHPUnit_Framework_TestCase
{
return array(
// more extensive test set can be found in FormUtilTest
array(0, '0'),
array(false, '0'),
array('', ''),
array('', '0'),
array(0, '1'),
);
}
@ -52,9 +52,9 @@ class ChoiceToValueTransformerTest extends \PHPUnit_Framework_TestCase
return array(
// values are expected to be valid choice keys already and stay
// the same
array('0', 0),
array('', null),
array(null, null),
array('0', ''),
array('1', 0),
array('2', 'X'),
);
}

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
class ChoicesToValuesTransformerTest extends \PHPUnit_Framework_TestCase
@ -20,7 +20,7 @@ class ChoicesToValuesTransformerTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$list = new SimpleChoiceList(array(0 => 'A', 1 => 'B', 2 => 'C'));
$list = new ArrayChoiceList(array('A', 'B', 'C'));
$this->transformer = new ChoicesToValuesTransformer($list);
}
@ -31,8 +31,7 @@ class ChoicesToValuesTransformerTest extends \PHPUnit_Framework_TestCase
public function testTransform()
{
// Value strategy in SimpleChoiceList is to copy and convert to string
$in = array(0, 1, 2);
$in = array('A', 'B', 'C');
$out = array('0', '1', '2');
$this->assertSame($out, $this->transformer->transform($in));
@ -55,7 +54,7 @@ class ChoicesToValuesTransformerTest extends \PHPUnit_Framework_TestCase
{
// values are expected to be valid choices and stay the same
$in = array('0', '1', '2');
$out = array(0, 1, 2);
$out = array('A', 'B', 'C');
$this->assertSame($out, $this->transformer->reverseTransform($in));
}

View File

@ -15,12 +15,17 @@ use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
/**
* @group legacy
*/
class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
{
private $choiceList;
protected function setUp()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
parent::setUp();
$this->choiceList = new SimpleChoiceList(array('' => 'Empty', 0 => 'A', 1 => 'B'));
@ -33,7 +38,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$listener = null;
}
public function testFixRadio()
public function testLegacyFixRadio()
{
$data = '1';
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
@ -46,7 +51,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(2 => '1'), $event->getData());
}
public function testFixZero()
public function testLegacyFixZero()
{
$data = '0';
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
@ -59,7 +64,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(1 => '0'), $event->getData());
}
public function testFixEmptyString()
public function testLegacyFixEmptyString()
{
$data = '';
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
@ -72,7 +77,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(0 => ''), $event->getData());
}
public function testConvertEmptyStringToPlaceholderIfNotFound()
public function testLegacyConvertEmptyStringToPlaceholderIfNotFound()
{
$list = new SimpleChoiceList(array(0 => 'A', 1 => 'B'));
@ -86,7 +91,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array('placeholder' => ''), $event->getData());
}
public function testDontConvertEmptyStringToPlaceholderIfNoPlaceholderUsed()
public function testLegacyDontConvertEmptyStringToPlaceholderIfNoPlaceholderUsed()
{
$list = new SimpleChoiceList(array(0 => 'A', 1 => 'B'));

View File

@ -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,8 +257,124 @@ 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());
}
/**
* @group legacy
*/
public function testLegacySubmitSingleNonExpandedObjectChoices()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$form = $this->factory->create('choice', null, array(
'multiple' => false,
'expanded' => false,
@ -273,6 +410,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(
@ -305,6 +473,28 @@ 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());
}
/**
* @group legacy
*/
public function testLegacySubmitMultipleNonExpandedObjectChoices()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$form = $this->factory->create('choice', null, array(
'multiple' => true,
'expanded' => false,
@ -337,13 +527,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 +583,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 +641,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 +657,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 +689,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 +705,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 +737,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 +753,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 +785,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 +803,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 +835,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 +853,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 +885,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 +903,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(
@ -687,6 +945,37 @@ 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());
}
/**
* @group legacy
*/
public function testLegacySubmitSingleExpandedObjectChoices()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$form = $this->factory->create('choice', null, array(
'multiple' => false,
'expanded' => true,
@ -750,13 +1039,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 +1132,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(
@ -874,6 +1173,37 @@ 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());
}
/**
* @group legacy
*/
public function testLegacySubmitMultipleExpandedObjectChoices()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$form = $this->factory->create('choice', null, array(
'multiple' => true,
'expanded' => true,
@ -1134,10 +1464,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 +1481,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 +1499,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 +1524,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 +1559,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 +1567,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,

View File

@ -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()

View File

@ -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);
}
}

View File

@ -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']);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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']);
}

View File

@ -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);
}
}