Initial commit for new sf3 choiceList

This commit is contained in:
Moritz Schroeder 2016-02-17 18:54:32 +01:00
parent 3ba19e83d4
commit 6119fc9806
5 changed files with 420 additions and 569 deletions

View file

@ -1,514 +0,0 @@
<?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\Bundle\PropelBundle\Form\ChoiceList;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Map\ColumnMap;
use Symfony\Component\Form\Exception\StringCastException;
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* A choice list for object choices based on Propel model.
*
* @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 bool
*/
protected $loaded = false;
/**
* Whether to use the identifier for index generation.
*
* @var bool
*/
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.
* @param string $useAsIdentifier a custom unique column (eg slug) to use instead of primary
* key.
*
* @throws MissingOptionsException In case the class parameter is empty.
* @throws InvalidOptionsException In case the query class is not found.
*/
public function __construct(
$class,
$labelPath = null,
$choices = null,
$queryObject = null,
$groupPath = null,
$preferred = array(),
PropertyAccessorInterface $propertyAccessor = null,
$useAsIdentifier = null
) {
$this->class = $class;
$queryClass = $this->class . 'Query';
if (!class_exists($queryClass)) {
if (empty($this->class)) {
throw new MissingOptionsException('The "class" parameter is empty, you should provide the model class');
}
throw new InvalidOptionsException(
sprintf(
'The query class "%s" is not found, you should provide the FQCN of the model class',
$queryClass
)
);
}
$query = new $queryClass();
$this->query = $queryObject ?: $query;
if ($useAsIdentifier) {
$this->identifier = array($this->query->getTableMap()->getColumn($useAsIdentifier));
} else {
$this->identifier = $this->query->getTableMap()->getPrimaryKeys();
}
$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->isScalar(current($this->identifier))) {
$this->identifierAsIndex = true;
}
parent::__construct($choices, $labelPath, $preferred, $groupPath, null, $propertyAccessor);
}
/**
* Returns the class name of the model.
*
* @return string
*/
public function getClass()
{
return $this->class;
}
/**
* {@inheritdoc}
*/
public function getChoices()
{
$this->load();
return parent::getChoices();
}
/**
* {@inheritdoc}
*/
public function getValues()
{
$this->load();
return parent::getValues();
}
/**
* {@inheritdoc}
*/
public function getPreferredViews()
{
$this->load();
return parent::getPreferredViews();
}
/**
* {@inheritdoc}
*/
public function getRemainingViews()
{
$this->load();
return parent::getRemainingViews();
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
if (empty($values)) {
return array();
}
/**
* This performance optimization reflects a common scenario:
* * A simple select of a model entry.
* * The choice option "expanded" is set to false.
* * The current request is the submission of the selected value.
*
* @see \Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer::reverseTransform
* @see \Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer::reverseTransform
*/
if (!$this->loaded) {
if (1 === count($this->identifier)) {
$filterBy = 'filterBy' . current($this->identifier)->getPhpName();
// The initial query is cloned, so all additional filters are applied correctly.
$query = clone $this->query;
$query
->$filterBy($values)
;
$result = iterator_to_array($query->find());
// Preserve the keys as provided by the values.
$models = array();
foreach ($values as $index => $value) {
foreach ($result as $eachModel) {
if ($value === $this->createValue($eachModel)) {
// Make sure to convert to the right format
$models[$index] = $this->fixChoice($eachModel);
// If all values have been assigned, skip further loops.
unset($values[$index]);
if (0 === count($values)) {
break 2;
}
}
}
}
return $models;
}
}
$this->load();
return parent::getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $models)
{
if (empty($models)) {
return array();
}
if (!$this->loaded) {
/**
* This performance optimization assumes the validation of the respective values will be done by other means.
*
* It correlates with the performance optimization in {@link ModelChoiceList::getChoicesForValues()}
* as it won't load the actual entries from the database.
*
* @see \Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer::transform
* @see \Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer::transform
*/
if (1 === count($this->identifier)) {
$values = array();
foreach ($models as $index => $model) {
if ($model instanceof $this->class) {
// Make sure to convert to the right format
$values[$index] = $this->fixValue(current($this->getIdentifierValues($model)));
}
}
return $values;
}
}
$this->load();
$values = array();
$availableValues = $this->getValues();
/*
* 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 ($choices as $i => $givenChoice) {
if (null === $givenChoice) {
continue;
}
foreach ($this->getChoices() as $j => $choice) {
if ($this->isEqual($choice, $givenChoice)) {
$values[$i] = $availableValues[$j];
// If all choices have been assigned, skip further loops.
unset($choices[$i]);
if (0 === count($choices)) {
break 2;
}
}
}
}
return $values;
}
/**
* {@inheritdoc}
*
* @deprecated Deprecated since version 2.4, to be removed in 3.0.
*/
public function getIndicesForChoices(array $models)
{
if (empty($models)) {
return array();
}
$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 ($choices as $i => $givenChoice) {
if (null === $givenChoice) {
continue;
}
foreach ($this->getChoices() as $j => $choice) {
if ($this->isEqual($choice, $givenChoice)) {
$indices[$i] = $j;
// If all choices have been assigned, skip further loops.
unset($choices[$i]);
if (0 === count($choices)) {
break 2;
}
}
}
}
return $indices;
}
/**
* {@inheritdoc}
*
* @deprecated Deprecated since version 2.4, to be removed in 3.0.
*/
public function getIndicesForValues(array $values)
{
if (empty($values)) {
return array();
}
$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 int|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 int|string A unique value without character limitations.
*/
protected function createValue($model)
{
if ($this->identifierAsIndex) {
return (string)current($this->getIdentifierValues($model));
}
return parent::createValue($model);
}
/**
* Loads the complete choice list entries, once.
*
* If data has been loaded the choice list is initialized with the retrieved data.
*/
private function load()
{
if ($this->loaded) {
return;
}
$models = iterator_to_array($this->query->find());
$preferred = array();
if ($this->preferredQuery instanceof ModelCriteria) {
$preferred = iterator_to_array($this->preferredQuery->find());
}
try {
// The second parameter $labels is ignored by ObjectChoiceList
parent::initialize($models, array(), $preferred);
$this->loaded = true;
} catch(StringCastException $e) {
throw new StringCastException(
str_replace('argument $labelPath', 'option "property"', $e->getMessage()),
null,
$e
);
}
}
/**
* Returns the values of the identifier fields of a 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 $this->class) {
return array();
}
if (1 === count($this->identifier) && current($this->identifier) instanceof ColumnMap) {
$phpName = current($this->identifier)->getPhpName();
if (method_exists($model, 'get' . $phpName)) {
return array($model->{'get' . $phpName}());
}
}
if ($model instanceof ActiveRecordInterface) {
return array($model->getPrimaryKey());
}
return $model->getPrimaryKeys();
}
/**
* Whether this column contains scalar values (to be used as indices).
*
* @param ColumnMap $column
*
* @return bool
*/
private function isScalar(ColumnMap $column)
{
return in_array(
$column->getPdoType(),
array(
\PDO::PARAM_BOOL,
\PDO::PARAM_INT,
\PDO::PARAM_STR,
)
);
}
/**
* Check the given choices for equality.
*
* @param mixed $choice
* @param mixed $givenChoice
*
* @return bool
*/
private function isEqual($choice, $givenChoice)
{
if ($choice === $givenChoice) {
return true;
}
if ($this->getIdentifierValues($choice) === $this->getIdentifierValues($givenChoice)) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,211 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\Bundle\PropelBundle\Form\ChoiceList;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Map\ColumnMap;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
/**
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
* @author Moritz Schroeder <moritz.schroeder@molabs.de>
*/
class PropelChoiceLoader implements ChoiceLoaderInterface
{
/**
* @var ChoiceListFactoryInterface
*/
protected $factory;
/**
* @var string
*/
protected $class;
/**
* @var ModelCriteria
*/
protected $query;
/**
* The fields of which the identifier of the underlying class consists
*
* This property should only be accessed through identifier.
*
* @var array
*/
protected $identifier = array();
/**
* Whether to use the identifier for index generation.
*
* @var bool
*/
protected $identifierAsIndex = false;
/**
* @var ChoiceListInterface
*/
protected $choiceList;
/**
* PropelChoiceListLoader constructor.
*
* @param ChoiceListFactoryInterface $factory
* @param string $class
*/
public function __construct(ChoiceListFactoryInterface $factory, $class, ModelCriteria $queryObject, $useAsIdentifier = null)
{
$this->factory = $factory;
$this->class = $class;
$this->query = $queryObject;
if ($useAsIdentifier) {
$this->identifier = array($this->query->getTableMap()->getColumn($useAsIdentifier));
} else {
$this->identifier = $this->query->getTableMap()->getPrimaryKeys();
}
if (1 === count($this->identifier) && $this->isScalar(current($this->identifier))) {
$this->identifierAsIndex = true;
}
}
/**
* {@inheritdoc}
*/
public function loadChoiceList($value = null)
{
if ($this->choiceList) {
return $this->choiceList;
}
$models = iterator_to_array($this->query->find());
$this->choiceList = $this->factory->createListFromChoices($models, $value);
return $this->choiceList;
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, $value = null)
{
// Performance optimization
if (empty($values)) {
return array();
}
// Optimize performance in case we have a single-field identifier
if (!$this->choiceList && $this->identifierAsIndex && current($this->identifier) instanceof ColumnMap) {
$phpName = current($this->identifier)->getPhpName();
$unorderedObjects = $this->query->filterBy($phpName, $values);
$objectsById = array();
$objects = array();
// Maintain order and indices from the given $values
foreach ($unorderedObjects as $object) {
$objectsById[(string) current($this->getIdentifierValues($object))] = $object;
}
foreach ($values as $i => $id) {
if (isset($objectsById[$id])) {
$objects[$i] = $objectsById[$id];
}
}
return $objects;
}
return $this->loadChoiceList($value)->getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, $value = null)
{
// Performance optimization
if (empty($choices)) {
return array();
}
if (!$this->choiceList && $this->identifierAsIndex) {
$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) current($this->getIdentifierValues($object));
}
}
return $values;
}
return $this->loadChoiceList($value)->getValuesForChoices($choices);
}
/**
* Whether this column contains scalar values (to be used as indices).
*
* @param ColumnMap $column
*
* @return bool
*/
private function isScalar(ColumnMap $column)
{
return in_array(
$column->getPdoType(),
array(
\PDO::PARAM_BOOL,
\PDO::PARAM_INT,
\PDO::PARAM_STR,
)
);
}
/**
* Returns the values of the identifier fields of a 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 $this->class) {
return array();
}
if (1 === count($this->identifier) && current($this->identifier) instanceof ColumnMap) {
$phpName = current($this->identifier)->getPhpName();
if (method_exists($model, 'get' . $phpName)) {
return array($model->{'get' . $phpName}());
}
}
if ($model instanceof ActiveRecordInterface) {
return array($model->getPrimaryKey());
}
return $model->getPrimaryKeys();
}
}

View file

@ -12,7 +12,11 @@
namespace Propel\Bundle\PropelBundle\Form;
use Symfony\Component\Form\AbstractExtension;
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 Propel form extension, which loads the Propel functionality.
@ -21,10 +25,33 @@ use Symfony\Component\PropertyAccess\PropertyAccess;
*/
class PropelExtension extends AbstractExtension
{
/**
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* @var ChoiceListFactoryInterface
*/
protected $choiceListFactory;
/**
* PropelExtension constructor.
*
* @param PropertyAccessorInterface|null $propertyAccessor
* @param ChoiceListFactoryInterface|null $choiceListFactory
*/
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
{
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
$this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor);
}
protected function loadTypes()
{
return array(
new Type\ModelType(PropertyAccess::createPropertyAccessor()),
new Type\ModelType($this->propertyAccessor, $this->choiceListFactory),
new Type\TranslationCollectionType(),
new Type\TranslationType()
);

View file

@ -11,16 +11,20 @@
namespace Propel\Bundle\PropelBundle\Form\Type;
use Propel\Bundle\PropelBundle\Form\ChoiceList\ModelChoiceList;
use Propel\Bundle\PropelBundle\Form\ChoiceList\PropelChoiceLoader;
use Propel\Bundle\PropelBundle\Form\DataTransformer\CollectionToArrayTransformer;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Propel\Runtime\Map\ColumnMap;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
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\FormBuilderInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
/**
* ModelType class.
@ -53,61 +57,167 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class ModelType extends AbstractType
{
/**
* @var PropertyAccessorInterface
* @var ChoiceListFactoryInterface
*/
private $propertyAccessor;
private $choiceListFactory;
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
/**
* ModelType constructor.
*
* @param PropertyAccessorInterface|null $propertyAccessor
* @param ChoiceListFactoryInterface|null $choiceListFactory
*/
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
{
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['multiple']) {
$builder->addViewTransformer(new CollectionToArrayTransformer(), true);
}
}
public function configureOptions(OptionsResolver $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,
$options['index_property']
);
};
$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,
'index_property' => null,
'choice_translation_domain' => false,
));
$this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(
new DefaultChoiceListFactory(),
$propertyAccessor
);
}
/**
* {@inheritdoc}
* 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 function getParent()
public static function createChoiceLabel($choice)
{
return ChoiceType::class;
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 str_replace('-', '_', (string) $value);
}
/**
* {@inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['multiple']) {
$builder
# ->addEventSubscriber(new MergeDoctrineCollectionListener())
->addViewTransformer(new CollectionToArrayTransformer(), true)
;
}
}
/**
* {@inheritDoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$choiceLoader = function (Options $options) {
// Unless the choices are given explicitly, load them on demand
if (null === $options['choices']) {
$propelChoiceLoader = new PropelChoiceLoader(
$this->choiceListFactory,
$options['class'],
$options['query'],
$options['index_property']
);
return $propelChoiceLoader;
}
return null;
};
$choiceName = function (Options $options) {
/** @var ModelCriteria $query */
$query = $options['query'];
if ($options['index_property']) {
$identifier = array($query->getTableMap()->getColumn($options['index_property']));
} else {
$identifier = $query->getTableMap()->getPrimaryKeys();
}
/** @var ColumnMap $firstIdentifier */
$firstIdentifier = current($identifier);
if (count($identifier) === 1 && $firstIdentifier->getPdoType() === \PDO::PARAM_INT) {
return array(__CLASS__, 'createChoiceName');
}
return null;
};
$choiceValue = function (Options $options) {
/** @var ModelCriteria $query */
$query = $options['query'];
if ($options['index_property']) {
$identifier = array($query->getTableMap()->getColumn($options['index_property']));
} else {
$identifier = $query->getTableMap()->getPrimaryKeys();
}
/** @var ColumnMap $firstIdentifier */
$firstIdentifier = current($identifier);
if (count($identifier) === 1 && in_array($firstIdentifier->getPdoType(), [\PDO::PARAM_BOOL, \PDO::PARAM_INT, \PDO::PARAM_STR])) {
return function($object) use ($firstIdentifier) {
return call_user_func([$object, 'get' . ucfirst($firstIdentifier->getPhpName())]);
};
}
return null;
};
$queryNormalizer = function (Options $options, $query) {
if ($query === null) {
$queryClass = $options['class'] . 'Query';
if (!class_exists($queryClass)) {
if (empty($options['class'])) {
throw new MissingOptionsException('The "class" parameter is empty, you should provide the model class');
}
throw new InvalidOptionsException(
sprintf(
'The query class "%s" is not found, you should provide the FQCN of the model class',
$queryClass
)
);
}
$query = new $queryClass();
}
return $query;
};
$resolver->setDefaults([
'query' => null,
'index_property' => null,
'choices' => null,
'choice_loader' => $choiceLoader,
'choice_label' => array(__CLASS__, 'createChoiceLabel'),
'choice_name' => $choiceName,
'choice_value' => $choiceValue,
'choice_translation_domain' => false,
]);
$resolver->setRequired(array('class'));
$resolver->setNormalizer('query', $queryNormalizer);
$resolver->setAllowedTypes('query', ['null', 'Propel\Runtime\ActiveQuery\ModelCriteria']);
}
/**
@ -115,11 +225,11 @@ class ModelType extends AbstractType
*/
public function getBlockPrefix()
{
return 'model';
return 'entity';
}
public function getName()
public function getParent()
{
return $this->getBlockPrefix();
return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
}
}

View file

@ -12,8 +12,10 @@
namespace Propel\Bundle\PropelBundle\Form;
use Propel\Bundle\PropelBundle\Form\Type\ModelType;
use Propel\Runtime\Map\ColumnMap;
use Propel\Runtime\Map\RelationMap;
use Propel\Generator\Model\PropelTypes;
use Propel\Runtime\Map\TableMap;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
@ -164,6 +166,11 @@ class TypeGuesser implements FormTypeGuesserInterface
}
}
/**
* @param string $class
*
* @return TableMap|null
*/
protected function getTable($class)
{
if (isset($this->cache[$class])) {
@ -175,8 +182,16 @@ class TypeGuesser implements FormTypeGuesserInterface
return $this->cache[$class] = $query->getTableMap();
}
return null;
}
/**
* @param string $class
* @param string $property
*
* @return ColumnMap|null
*/
protected function getColumn($class, $property)
{
if (isset($this->cache[$class.'::'.$property])) {
@ -188,5 +203,7 @@ class TypeGuesser implements FormTypeGuesserInterface
if ($table && $table->hasColumn($property)) {
return $this->cache[$class.'::'.$property] = $table->getColumn($property);
}
return null;
}
}