import ModelType from Propel1 Bridge
This commit is contained in:
parent
816e68266e
commit
ffbf2de5f5
435
Form/ChoiceList/ModelChoiceList.php
Normal file
435
Form/ChoiceList/ModelChoiceList.php
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
<?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 Propel\PropelBundle\Form\ChoiceList;
|
||||||
|
|
||||||
|
use Propel\Runtime\Map\ColumnMap;
|
||||||
|
use Propel\Runtime\ActiveQuery\ModelCriteria;
|
||||||
|
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\Exception\StringCastException;
|
||||||
|
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Widely inspired by the EntityChoiceList.
|
||||||
|
*
|
||||||
|
* @author William Durand <william.durand1@gmail.com>
|
||||||
|
* @author Toni Uebernickel <tuebernickel@gmail.com>
|
||||||
|
*/
|
||||||
|
class ModelChoiceList extends ObjectChoiceList
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The fields of which the identifier of the underlying class consists
|
||||||
|
*
|
||||||
|
* This property should only be accessed through identifier.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $identifier = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query to retrieve the choices of this list.
|
||||||
|
*
|
||||||
|
* @var ModelCriteria
|
||||||
|
*/
|
||||||
|
protected $query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query to retrieve the preferred choices for this list.
|
||||||
|
*
|
||||||
|
* @var ModelCriteria
|
||||||
|
*/
|
||||||
|
protected $preferredQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the model objects have already been loaded.
|
||||||
|
*
|
||||||
|
* @var Boolean
|
||||||
|
*/
|
||||||
|
protected $loaded = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use the identifier for index generation
|
||||||
|
*
|
||||||
|
* @var Boolean
|
||||||
|
*/
|
||||||
|
private $identifierAsIndex = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @see Symfony\Bridge\Propel1\Form\Type\ModelType How to use the preferred choices.
|
||||||
|
*
|
||||||
|
* @param string $class The FQCN of the model class to be loaded.
|
||||||
|
* @param string $labelPath A property path pointing to the property used for the choice labels.
|
||||||
|
* @param array $choices An optional array to use, rather than fetching the models.
|
||||||
|
* @param ModelCriteria $queryObject The query to use retrieving model data from database.
|
||||||
|
* @param string $groupPath A property path pointing to the property used to group the choices.
|
||||||
|
* @param array|ModelCriteria $preferred The preferred items of this choice.
|
||||||
|
* Either an array if $choices is given,
|
||||||
|
* or a ModelCriteria to be merged with the $queryObject.
|
||||||
|
* @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths.
|
||||||
|
*/
|
||||||
|
public function __construct($class, $labelPath = null, $choices = null, $queryObject = null, $groupPath = null, $preferred = array(), PropertyAccessorInterface $propertyAccessor = null)
|
||||||
|
{
|
||||||
|
$this->class = $class;
|
||||||
|
|
||||||
|
$queryClass = $this->class.'Query';
|
||||||
|
$query = new $queryClass();
|
||||||
|
|
||||||
|
$this->identifier = $query->getTableMap()->getPrimaryKeys();
|
||||||
|
$this->query = $queryObject ?: $query;
|
||||||
|
$this->loaded = is_array($choices) || $choices instanceof \Traversable;
|
||||||
|
|
||||||
|
if ($preferred instanceof ModelCriteria) {
|
||||||
|
$this->preferredQuery = $preferred->mergeWith($this->query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->loaded) {
|
||||||
|
// Make sure the constraints of the parent constructor are
|
||||||
|
// fulfilled
|
||||||
|
$choices = array();
|
||||||
|
$preferred = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 === count($this->identifier) && $this->isInteger(current($this->identifier))) {
|
||||||
|
$this->identifierAsIndex = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($choices, $labelPath, $preferred, $groupPath, null, $propertyAccessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the class name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getClass()
|
||||||
|
{
|
||||||
|
return $this->class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of model objects
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getChoices()
|
||||||
|
{
|
||||||
|
if (!$this->loaded) {
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getChoices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values for the model objects
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getValues()
|
||||||
|
{
|
||||||
|
if (!$this->loaded) {
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the choice views of the preferred choices as nested array with
|
||||||
|
* the choice groups as top-level keys.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getPreferredViews()
|
||||||
|
{
|
||||||
|
if (!$this->loaded) {
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getPreferredViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the choice views of the choices that are not preferred as nested
|
||||||
|
* array with the choice groups as top-level keys.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getRemainingViews()
|
||||||
|
{
|
||||||
|
if (!$this->loaded) {
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getRemainingViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the model objects corresponding to the given values.
|
||||||
|
*
|
||||||
|
* @param array $values
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getChoicesForValues(array $values)
|
||||||
|
{
|
||||||
|
if (!$this->loaded) {
|
||||||
|
if (1 === count($this->identifier)) {
|
||||||
|
$filterBy = 'filterBy'.current($this->identifier)->getPhpName();
|
||||||
|
|
||||||
|
return $this->query->create()
|
||||||
|
->$filterBy($values)
|
||||||
|
->find()
|
||||||
|
->getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getChoicesForValues($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values corresponding to the given model objects.
|
||||||
|
*
|
||||||
|
* @param array $models
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getValuesForChoices(array $models)
|
||||||
|
{
|
||||||
|
if (!$this->loaded) {
|
||||||
|
// 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 (1 === count($this->identifier)) {
|
||||||
|
$values = array();
|
||||||
|
foreach ($models as $model) {
|
||||||
|
if ($model instanceof $this->class) {
|
||||||
|
// Make sure to convert to the right format
|
||||||
|
$values[] = $this->fixValue(current($this->getIdentifierValues($model)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getValuesForChoices($models);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the indices corresponding to the given models.
|
||||||
|
*
|
||||||
|
* @param array $models
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getIndicesForChoices(array $models)
|
||||||
|
{
|
||||||
|
$indices = array();
|
||||||
|
|
||||||
|
if (!$this->loaded) {
|
||||||
|
// Optimize performance for single-field identifiers. We already
|
||||||
|
// know that the IDs are used as indices
|
||||||
|
|
||||||
|
// Attention: This optimization does not check choices for existence
|
||||||
|
if ($this->identifierAsIndex) {
|
||||||
|
foreach ($models as $model) {
|
||||||
|
if ($model instanceof $this->class) {
|
||||||
|
// Make sure to convert to the right format
|
||||||
|
$indices[] = $this->fixIndex(current($this->getIdentifierValues($model)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Overwriting default implementation.
|
||||||
|
*
|
||||||
|
* The two objects may represent the same entry in the database,
|
||||||
|
* but if they originated from different queries, there are not the same object within the code.
|
||||||
|
*
|
||||||
|
* This happens when using m:n relations with either sides model as data_class of the form.
|
||||||
|
* The choicelist will retrieve the list of available related models with a different query, resulting in different objects.
|
||||||
|
*/
|
||||||
|
$choices = $this->fixChoices($models);
|
||||||
|
foreach ($this->getChoices() as $i => $choice) {
|
||||||
|
foreach ($choices as $j => $givenChoice) {
|
||||||
|
if (null !== $givenChoice && $this->getIdentifierValues($choice) === $this->getIdentifierValues($givenChoice)) {
|
||||||
|
$indices[] = $i;
|
||||||
|
unset($choices[$j]);
|
||||||
|
|
||||||
|
if (0 === count($choices)) {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the models corresponding to the given values.
|
||||||
|
*
|
||||||
|
* @param array $values
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
|
*/
|
||||||
|
public function getIndicesForValues(array $values)
|
||||||
|
{
|
||||||
|
if (!$this->loaded) {
|
||||||
|
// Optimize performance for single-field identifiers. We already
|
||||||
|
// know that the IDs are used as indices and values
|
||||||
|
|
||||||
|
// Attention: This optimization does not check values for existence
|
||||||
|
if ($this->identifierAsIndex) {
|
||||||
|
return $this->fixIndices($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getIndicesForValues($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new unique index for this model.
|
||||||
|
*
|
||||||
|
* If the model has a single-field identifier, this identifier is used.
|
||||||
|
*
|
||||||
|
* Otherwise a new integer is generated.
|
||||||
|
*
|
||||||
|
* @param mixed $model The choice to create an index for
|
||||||
|
*
|
||||||
|
* @return integer|string A unique index containing only ASCII letters,
|
||||||
|
* digits and underscores.
|
||||||
|
*/
|
||||||
|
protected function createIndex($model)
|
||||||
|
{
|
||||||
|
if ($this->identifierAsIndex) {
|
||||||
|
return current($this->getIdentifierValues($model));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::createIndex($model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new unique value for this model.
|
||||||
|
*
|
||||||
|
* If the model has a single-field identifier, this identifier is used.
|
||||||
|
*
|
||||||
|
* Otherwise a new integer is generated.
|
||||||
|
*
|
||||||
|
* @param mixed $model The choice to create a value for
|
||||||
|
*
|
||||||
|
* @return integer|string A unique value without character limitations.
|
||||||
|
*/
|
||||||
|
protected function createValue($model)
|
||||||
|
{
|
||||||
|
if (1 === count($this->identifier)) {
|
||||||
|
return (string) current($this->getIdentifierValues($model));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::createValue($model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list with model objects.
|
||||||
|
*/
|
||||||
|
private function load()
|
||||||
|
{
|
||||||
|
$models = $this->query->find()->getData();
|
||||||
|
|
||||||
|
$preferred = array();
|
||||||
|
if ($this->preferredQuery instanceof ModelCriteria) {
|
||||||
|
$preferred = $this->preferredQuery->find()->getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// The second parameter $labels is ignored by ObjectChoiceList
|
||||||
|
parent::initialize($models, array(), $preferred);
|
||||||
|
} catch (StringCastException $e) {
|
||||||
|
throw new StringCastException(str_replace('argument $labelPath', 'option "property"', $e->getMessage()), null, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values of the identifier fields of an model
|
||||||
|
*
|
||||||
|
* Propel must know about this model, that is, the model must already
|
||||||
|
* be persisted or added to the idmodel map before. Otherwise an
|
||||||
|
* exception is thrown.
|
||||||
|
*
|
||||||
|
* @param object $model The model for which to get the identifier
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getIdentifierValues($model)
|
||||||
|
{
|
||||||
|
if ($model instanceof ActiveRecordInterface) {
|
||||||
|
return array($model->getPrimaryKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// readonly="true" models do not implement ActiveRecordInterface.
|
||||||
|
if ($model instanceof ActiveRecordInterface && method_exists($model, 'getPrimaryKey')) {
|
||||||
|
return array($model->getPrimaryKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $model) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $model->getPrimaryKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this column in an integer
|
||||||
|
*
|
||||||
|
* @param ColumnMap $column
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
private function isInteger(ColumnMap $column)
|
||||||
|
{
|
||||||
|
return $column->getPdoType() === \PDO::PARAM_INT;
|
||||||
|
}
|
||||||
|
}
|
55
Form/DataTransformer/CollectionToArrayTransformer.php
Normal file
55
Form/DataTransformer/CollectionToArrayTransformer.php
Normal 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 Propel\PropelBundle\Form\DataTransformer;
|
||||||
|
|
||||||
|
use Propel\Runtime\Collection\ObjectCollection;
|
||||||
|
use Symfony\Component\Form\DataTransformerInterface;
|
||||||
|
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CollectionToArrayTransformer class.
|
||||||
|
*
|
||||||
|
* @author William Durand <william.durand1@gmail.com>
|
||||||
|
* @author Pierre-Yves Lebecq <py.lebecq@gmail.com>
|
||||||
|
*/
|
||||||
|
class CollectionToArrayTransformer implements DataTransformerInterface
|
||||||
|
{
|
||||||
|
public function transform($collection)
|
||||||
|
{
|
||||||
|
if (null === $collection) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$collection instanceof ObjectCollection) {
|
||||||
|
throw new TransformationFailedException('Expected a \ObjectCollection.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $collection->getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseTransform($array)
|
||||||
|
{
|
||||||
|
$collection = new ObjectCollection();
|
||||||
|
|
||||||
|
if ('' === $array || null === $array) {
|
||||||
|
return $collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($array)) {
|
||||||
|
throw new TransformationFailedException('Expected an array.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$collection->setData($array);
|
||||||
|
|
||||||
|
return $collection;
|
||||||
|
}
|
||||||
|
}
|
109
Form/Type/ModelType.php
Normal file
109
Form/Type/ModelType.php
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<?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 Propel\PropelBundle\Form\Type;
|
||||||
|
|
||||||
|
use Propel\PropelBundle\Form\ChoiceList\ModelChoiceList;
|
||||||
|
use Propel\PropelBundle\Form\DataTransformer\CollectionToArrayTransformer;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ModelType class.
|
||||||
|
*
|
||||||
|
* @author William Durand <william.durand1@gmail.com>
|
||||||
|
* @author Toni Uebernickel <tuebernickel@gmail.com>
|
||||||
|
*
|
||||||
|
* Example using the preferred_choices option.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
* {
|
||||||
|
* $builder
|
||||||
|
* ->add('product', 'model', array(
|
||||||
|
* 'class' => 'Model\Product',
|
||||||
|
* 'query' => ProductQuery::create()
|
||||||
|
* ->filterIsActive(true)
|
||||||
|
* ->useI18nQuery($options['locale'])
|
||||||
|
* ->orderByName()
|
||||||
|
* ->endUse()
|
||||||
|
* ,
|
||||||
|
* 'preferred_choices' => ProductQuery::create()
|
||||||
|
* ->filterByIsTopProduct(true)
|
||||||
|
* ,
|
||||||
|
* ))
|
||||||
|
* ;
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
class ModelType extends AbstractType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PropertyAccessorInterface
|
||||||
|
*/
|
||||||
|
private $propertyAccessor;
|
||||||
|
|
||||||
|
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
|
||||||
|
{
|
||||||
|
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
if ($options['multiple']) {
|
||||||
|
$builder->addViewTransformer(new CollectionToArrayTransformer(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||||
|
{
|
||||||
|
$propertyAccessor = $this->propertyAccessor;
|
||||||
|
|
||||||
|
$choiceList = function (Options $options) use ($propertyAccessor) {
|
||||||
|
return new ModelChoiceList(
|
||||||
|
$options['class'],
|
||||||
|
$options['property'],
|
||||||
|
$options['choices'],
|
||||||
|
$options['query'],
|
||||||
|
$options['group_by'],
|
||||||
|
$options['preferred_choices'],
|
||||||
|
$propertyAccessor
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$resolver->setDefaults(array(
|
||||||
|
'template' => 'choice',
|
||||||
|
'multiple' => false,
|
||||||
|
'expanded' => false,
|
||||||
|
'class' => null,
|
||||||
|
'property' => null,
|
||||||
|
'query' => null,
|
||||||
|
'choices' => null,
|
||||||
|
'choice_list' => $choiceList,
|
||||||
|
'group_by' => null,
|
||||||
|
'by_reference' => false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent()
|
||||||
|
{
|
||||||
|
return 'choice';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'model';
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
<parameter key="propel.logger.class">Propel\PropelBundle\Logger\PropelLogger</parameter>
|
<parameter key="propel.logger.class">Propel\PropelBundle\Logger\PropelLogger</parameter>
|
||||||
<parameter key="propel.twig.extension.syntax.class">Propel\PropelBundle\Twig\Extension\SyntaxExtension</parameter>
|
<parameter key="propel.twig.extension.syntax.class">Propel\PropelBundle\Twig\Extension\SyntaxExtension</parameter>
|
||||||
<parameter key="form.type_guesser.propel.class">Propel\PropelBundle\Form\TypeGuesser</parameter>
|
<parameter key="form.type_guesser.propel.class">Propel\PropelBundle\Form\TypeGuesser</parameter>
|
||||||
|
<parameter key="propel.form.type.model.class">Propel\PropelBundle\Form\Type\ModelType</parameter>
|
||||||
<parameter key="propel.dumper.yaml.class">Propel\PropelBundle\DataFixtures\Dumper\YamlDataDumper</parameter>
|
<parameter key="propel.dumper.yaml.class">Propel\PropelBundle\DataFixtures\Dumper\YamlDataDumper</parameter>
|
||||||
<parameter key="propel.loader.yaml.class">Propel\PropelBundle\DataFixtures\Loader\YamlDataLoader</parameter>
|
<parameter key="propel.loader.yaml.class">Propel\PropelBundle\DataFixtures\Loader\YamlDataLoader</parameter>
|
||||||
<parameter key="propel.loader.xml.class">Propel\PropelBundle\DataFixtures\Loader\XmlDataLoader</parameter>
|
<parameter key="propel.loader.xml.class">Propel\PropelBundle\DataFixtures\Loader\XmlDataLoader</parameter>
|
||||||
|
@ -44,6 +45,10 @@
|
||||||
<tag name="form.type_guesser" />
|
<tag name="form.type_guesser" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="propel.form.type.model" class="%propel.form.type.model.class%">
|
||||||
|
<tag name="form.type" alias="model" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="propel.dumper.yaml" class="%propel.dumper.yaml.class%">
|
<service id="propel.dumper.yaml" class="%propel.dumper.yaml.class%">
|
||||||
<argument>%kernel.root_dir%</argument>
|
<argument>%kernel.root_dir%</argument>
|
||||||
<argument type="service" id="propel" />
|
<argument type="service" id="propel" />
|
||||||
|
|
27
Tests/Fixtures/ReadOnlyItem.php
Normal file
27
Tests/Fixtures/ReadOnlyItem.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Propel\PropelBundle\Tests\Fixtures;
|
||||||
|
|
||||||
|
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
|
||||||
|
|
||||||
|
class ReadOnlyItem implements ActiveRecordInterface
|
||||||
|
{
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'Marvin';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrimaryKey()
|
||||||
|
{
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
33
Tests/Fixtures/ReadOnlyItemQuery.php
Normal file
33
Tests/Fixtures/ReadOnlyItemQuery.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?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 Propel\PropelBundle\Tests\Fixtures;
|
||||||
|
|
||||||
|
use Propel\Runtime\Map\ColumnMap;
|
||||||
|
use Propel\Runtime\Map\TableMap;
|
||||||
|
|
||||||
|
class ReadOnlyItemQuery
|
||||||
|
{
|
||||||
|
public function getTableMap()
|
||||||
|
{
|
||||||
|
// Allows to define methods in this class
|
||||||
|
// to avoid a lot of mock classes
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrimaryKeys()
|
||||||
|
{
|
||||||
|
$cm = new ColumnMap('id', new TableMap());
|
||||||
|
$cm->setType('INTEGER');
|
||||||
|
|
||||||
|
return array('id' => $cm);
|
||||||
|
}
|
||||||
|
}
|
215
Tests/Form/ChoiceList/ModelChoiceListTest.php
Normal file
215
Tests/Form/ChoiceList/ModelChoiceListTest.php
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
<?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 Propel\PropelBundle\Tests\Form\ChoiceList;
|
||||||
|
|
||||||
|
use Propel\PropelBundle\Form\ChoiceList\ModelChoiceList;
|
||||||
|
use Propel\PropelBundle\Tests\Fixtures\Item;
|
||||||
|
use Propel\PropelBundle\Tests\Fixtures\ReadOnlyItem;
|
||||||
|
use Propel\PropelBundle\Tests\TestCase;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||||
|
|
||||||
|
class ModelChoiceListTest extends TestCase
|
||||||
|
{
|
||||||
|
const ITEM_CLASS = '\Propel\PropelBundle\Tests\Fixtures\Item';
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
if (!class_exists('Symfony\Component\Form\Form')) {
|
||||||
|
$this->markTestSkipped('The "Form" component is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccessor')) {
|
||||||
|
$this->markTestSkipped('The "PropertyAccessor" component is not available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyChoicesReturnsEmpty()
|
||||||
|
{
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(array(), $choiceList->getChoices());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadOnlyIsValidChoice()
|
||||||
|
{
|
||||||
|
$item = new ReadOnlyItem();
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
'\Propel\PropelBundle\Tests\Fixtures\ReadOnlyItem',
|
||||||
|
'name',
|
||||||
|
array(
|
||||||
|
$item,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(array(42 => $item), $choiceList->getChoices());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFlattenedChoices()
|
||||||
|
{
|
||||||
|
$item1 = new Item(1, 'Foo');
|
||||||
|
$item2 = new Item(2, 'Bar');
|
||||||
|
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array(
|
||||||
|
$item1,
|
||||||
|
$item2,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(array(1 => $item1, 2 => $item2), $choiceList->getChoices());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFlattenedPreferredChoices()
|
||||||
|
{
|
||||||
|
$item1 = new Item(1, 'Foo');
|
||||||
|
$item2 = new Item(2, 'Bar');
|
||||||
|
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array(
|
||||||
|
$item1,
|
||||||
|
$item2,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
array(
|
||||||
|
$item1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(array(1 => $item1, 2 => $item2), $choiceList->getChoices());
|
||||||
|
$this->assertEquals(array(1 => new ChoiceView($item1, '1', 'Foo')), $choiceList->getPreferredViews());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNestedChoices()
|
||||||
|
{
|
||||||
|
$item1 = new Item(1, 'Foo');
|
||||||
|
$item2 = new Item(2, 'Bar');
|
||||||
|
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array(
|
||||||
|
'group1' => array($item1),
|
||||||
|
'group2' => array($item2),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(array(1 => $item1, 2 => $item2), $choiceList->getChoices());
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'group1' => array(1 => new ChoiceView($item1, '1', 'Foo')),
|
||||||
|
'group2' => array(2 => new ChoiceView($item2, '2', 'Bar'))
|
||||||
|
), $choiceList->getRemainingViews());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupBySupportsString()
|
||||||
|
{
|
||||||
|
$item1 = new Item(1, 'Foo', 'Group1');
|
||||||
|
$item2 = new Item(2, 'Bar', 'Group1');
|
||||||
|
$item3 = new Item(3, 'Baz', 'Group2');
|
||||||
|
$item4 = new Item(4, 'Boo!', null);
|
||||||
|
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array(
|
||||||
|
$item1,
|
||||||
|
$item2,
|
||||||
|
$item3,
|
||||||
|
$item4,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
'groupName'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(array(1 => $item1, 2 => $item2, 3 => $item3, 4 => $item4), $choiceList->getChoices());
|
||||||
|
$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!')
|
||||||
|
), $choiceList->getRemainingViews());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupByInvalidPropertyPathReturnsFlatChoices()
|
||||||
|
{
|
||||||
|
$item1 = new Item(1, 'Foo', 'Group1');
|
||||||
|
$item2 = new Item(2, 'Bar', 'Group1');
|
||||||
|
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array(
|
||||||
|
$item1,
|
||||||
|
$item2,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
'child.that.does.not.exist'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(array(
|
||||||
|
1 => $item1,
|
||||||
|
2 => $item2
|
||||||
|
), $choiceList->getChoices());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetValuesForChoices()
|
||||||
|
{
|
||||||
|
$item1 = new Item(1, 'Foo');
|
||||||
|
$item2 = new Item(2, 'Bar');
|
||||||
|
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(array(1, 2), $choiceList->getValuesForChoices(array($item1, $item2)));
|
||||||
|
$this->assertEquals(array(1, 2), $choiceList->getIndicesForChoices(array($item1, $item2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDifferentEqualObjectsAreChoosen()
|
||||||
|
{
|
||||||
|
$item = new Item(1, 'Foo');
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array($item)
|
||||||
|
);
|
||||||
|
|
||||||
|
$choosenItem = new Item(1, 'Foo');
|
||||||
|
|
||||||
|
$this->assertEquals(array(1), $choiceList->getIndicesForChoices(array($choosenItem)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetIndicesForNullChoices()
|
||||||
|
{
|
||||||
|
$item = new Item(1, 'Foo');
|
||||||
|
$choiceList = new ModelChoiceList(
|
||||||
|
self::ITEM_CLASS,
|
||||||
|
'value',
|
||||||
|
array($item)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(array(), $choiceList->getIndicesForChoices(array(null)));
|
||||||
|
}
|
||||||
|
}
|
111
Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php
Normal file
111
Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?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 Propel\PropelBundle\Tests\Form\Form\DataTransformer;
|
||||||
|
|
||||||
|
use Propel\Runtime\Collection\ObjectCollection;
|
||||||
|
use Propel\PropelBundle\Tests\TestCase;
|
||||||
|
use Propel\PropelBundle\Form\DataTransformer\CollectionToArrayTransformer;
|
||||||
|
|
||||||
|
class CollectionToArrayTransformerTest extends TestCase
|
||||||
|
{
|
||||||
|
private $transformer;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
if (!class_exists('Symfony\Component\Form\Form')) {
|
||||||
|
$this->markTestSkipped('The "Form" component is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->transformer = new CollectionToArrayTransformer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransform()
|
||||||
|
{
|
||||||
|
$result = $this->transformer->transform(new ObjectCollection());
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($result));
|
||||||
|
$this->assertEquals(0, count($result));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransformWithNull()
|
||||||
|
{
|
||||||
|
$result = $this->transformer->transform(null);
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($result));
|
||||||
|
$this->assertEquals(0, count($result));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
|
||||||
|
*/
|
||||||
|
public function testTransformThrowsExceptionIfNotObjectCollection()
|
||||||
|
{
|
||||||
|
$this->transformer->transform(new DummyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransformWithData()
|
||||||
|
{
|
||||||
|
$coll = new ObjectCollection();
|
||||||
|
$coll->setData(array('foo', 'bar'));
|
||||||
|
|
||||||
|
$result = $this->transformer->transform($coll);
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($result));
|
||||||
|
$this->assertEquals(2, count($result));
|
||||||
|
$this->assertEquals('foo', $result[0]);
|
||||||
|
$this->assertEquals('bar', $result[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReverseTransformWithNull()
|
||||||
|
{
|
||||||
|
$result = $this->transformer->reverseTransform(null);
|
||||||
|
|
||||||
|
$this->assertInstanceOf('\Propel\Runtime\Collection\ObjectCollection', $result);
|
||||||
|
$this->assertEquals(0, count($result->getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReverseTransformWithEmptyString()
|
||||||
|
{
|
||||||
|
$result = $this->transformer->reverseTransform('');
|
||||||
|
|
||||||
|
$this->assertInstanceOf('\Propel\Runtime\Collection\ObjectCollection', $result);
|
||||||
|
$this->assertEquals(0, count($result->getData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
|
||||||
|
*/
|
||||||
|
public function testReverseTransformThrowsExceptionIfNotArray()
|
||||||
|
{
|
||||||
|
$this->transformer->reverseTransform(new DummyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReverseTransformWithData()
|
||||||
|
{
|
||||||
|
$inputData = array('foo', 'bar');
|
||||||
|
|
||||||
|
$result = $this->transformer->reverseTransform($inputData);
|
||||||
|
$data = $result->getData();
|
||||||
|
|
||||||
|
$this->assertInstanceOf('\Propel\Runtime\Collection\ObjectCollection', $result);
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($data));
|
||||||
|
$this->assertEquals(2, count($data));
|
||||||
|
$this->assertEquals('foo', $data[0]);
|
||||||
|
$this->assertEquals('bar', $data[1]);
|
||||||
|
$this->assertsame($inputData, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyObject {}
|
Loading…
Reference in a new issue