[Propel] Refactored the whole ModelChoiceList
This commit is contained in:
parent
6e60967827
commit
8257efd38f
@ -11,8 +11,8 @@
|
|||||||
|
|
||||||
namespace Symfony\Bridge\Propel1\Form\ChoiceList;
|
namespace Symfony\Bridge\Propel1\Form\ChoiceList;
|
||||||
|
|
||||||
use Symfony\Component\Form\Util\PropertyPath;
|
|
||||||
use Symfony\Component\Form\Exception\FormException;
|
use Symfony\Component\Form\Exception\FormException;
|
||||||
|
use Symfony\Component\Form\Exception\StringCastException;
|
||||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,19 +22,6 @@ use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
|||||||
*/
|
*/
|
||||||
class ModelChoiceList extends ObjectChoiceList
|
class ModelChoiceList extends ObjectChoiceList
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The models from which the user can choose
|
|
||||||
*
|
|
||||||
* This array is either indexed by ID (if the ID is a single field)
|
|
||||||
* or by key in the choices array (if the ID consists of multiple fields)
|
|
||||||
*
|
|
||||||
* This property is initialized by initializeChoices(). It should only
|
|
||||||
* be accessed through getModel() and getModels().
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $models = array();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The fields of which the identifier of the underlying class consists
|
* The fields of which the identifier of the underlying class consists
|
||||||
*
|
*
|
||||||
@ -51,25 +38,26 @@ class ModelChoiceList extends ObjectChoiceList
|
|||||||
*/
|
*/
|
||||||
private $table = null;
|
private $table = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Property path
|
|
||||||
*
|
|
||||||
* @var \Symfony\Component\Form\Util\PropertyPath
|
|
||||||
*/
|
|
||||||
private $propertyPath = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query
|
* Query
|
||||||
*/
|
*/
|
||||||
private $query = null;
|
private $query = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the model objects have already been loaded.
|
||||||
|
*
|
||||||
|
* @var Boolean
|
||||||
|
*/
|
||||||
|
private $loaded = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $class
|
* @param string $class
|
||||||
* @param string $property
|
* @param string $labelPath
|
||||||
* @param array $choices
|
* @param array $choices
|
||||||
* @param \ModelCriteria $queryObject
|
* @param \ModelCriteria $queryObject
|
||||||
|
* @param string $groupPath
|
||||||
*/
|
*/
|
||||||
public function __construct($class, $property = null, $choices = array(), $queryObject = null)
|
public function __construct($class, $labelPath = null, $choices = null, $queryObject = null, $groupPath = null)
|
||||||
{
|
{
|
||||||
$this->class = $class;
|
$this->class = $class;
|
||||||
|
|
||||||
@ -79,78 +67,258 @@ class ModelChoiceList extends ObjectChoiceList
|
|||||||
$this->table = $query->getTableMap();
|
$this->table = $query->getTableMap();
|
||||||
$this->identifier = $this->table->getPrimaryKeys();
|
$this->identifier = $this->table->getPrimaryKeys();
|
||||||
$this->query = $queryObject ?: $query;
|
$this->query = $queryObject ?: $query;
|
||||||
|
$this->loaded = is_array($choices) || $choices instanceof \Traversable;
|
||||||
|
|
||||||
// The property option defines, which property (path) is used for
|
if (!$this->loaded) {
|
||||||
// displaying models as strings
|
// Make sure the constraints of the parent constructor are
|
||||||
if ($property) {
|
// fulfilled
|
||||||
$this->propertyPath = new PropertyPath($property);
|
$choices = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::__construct($choices);
|
parent::__construct($choices, $labelPath, array(), $groupPath);
|
||||||
}
|
|
||||||
|
|
||||||
public function getIdentifier()
|
|
||||||
{
|
|
||||||
return $this->identifier;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the according models for the choices
|
* Returns the list of model objects
|
||||||
*
|
*
|
||||||
* If the choices were not initialized, they are initialized now. This
|
* @return array
|
||||||
* is an expensive operation, except if the models were passed in the
|
|
||||||
* "choices" option.
|
|
||||||
*
|
*
|
||||||
* @return array An array of models
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
*/
|
*/
|
||||||
public function getModels()
|
public function getChoices()
|
||||||
{
|
{
|
||||||
if (!$this->loaded) {
|
if (!$this->loaded) {
|
||||||
$this->load();
|
$this->load();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->models;
|
return parent::getChoices();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the model for the given key
|
* Returns the values for the model objects
|
||||||
*
|
*
|
||||||
* If the underlying models have composite identifiers, the choices
|
* @return array
|
||||||
* are intialized. The key is expected to be the index in the choices
|
|
||||||
* array in this case.
|
|
||||||
*
|
*
|
||||||
* If they have single identifiers, they are either fetched from the
|
* @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
|
||||||
* internal model cache (if filled) or loaded from the database.
|
|
||||||
*
|
|
||||||
* @param string $key The choice key (for models with composite
|
|
||||||
* identifiers) or model ID (for models with single
|
|
||||||
* identifiers)
|
|
||||||
* @return object The matching model
|
|
||||||
*/
|
*/
|
||||||
public function getModel($key)
|
public function getValues()
|
||||||
{
|
{
|
||||||
if (!$this->loaded) {
|
if (!$this->loaded) {
|
||||||
$this->load();
|
$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 (count($this->identifier) === 1) {
|
||||||
|
return $this->query->create()->filterBy(current($this->identifier), $values)->findOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
$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 (count($this->identifier) === 1) {
|
||||||
|
$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)
|
||||||
|
{
|
||||||
|
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 (count($this->identifier) === 1) {
|
||||||
|
$indices = array();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::getIndicesForChoices($models);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (count($this->identifier) === 1) {
|
||||||
|
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 $choice 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 (count($this->identifier) === 1) {
|
||||||
|
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 $choice The choice to create a value for
|
||||||
|
*
|
||||||
|
* @return integer|string A unique value without character limitations.
|
||||||
|
*/
|
||||||
|
protected function createValue($model)
|
||||||
|
{
|
||||||
|
if (count($this->identifier) === 1) {
|
||||||
|
return current($this->getIdentifierValues($model));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::createValue($model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list with model objects.
|
||||||
|
*/
|
||||||
|
private function load()
|
||||||
|
{
|
||||||
|
$models = $this->query->find();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (count($this->identifier) > 1) {
|
// The second parameter $labels is ignored by ObjectChoiceList
|
||||||
// $key is a collection index
|
// The third parameter $preferredChoices is currently not supported
|
||||||
$models = $this->getModels();
|
parent::initialize($models, array(), array());
|
||||||
|
} catch (StringCastException $e) {
|
||||||
return isset($models[$key]) ? $models[$key] : null;
|
throw new StringCastException(str_replace('argument $labelPath', 'option "property"', $e->getMessage()), null, $e);
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->models) {
|
|
||||||
return isset($this->models[$key]) ? $this->models[$key] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$queryClass = $this->class . 'Query';
|
|
||||||
|
|
||||||
return $queryClass::create()->findPk($key);
|
|
||||||
} catch (NoResultException $e) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,67 +331,12 @@ class ModelChoiceList extends ObjectChoiceList
|
|||||||
* @param object $model The model for which to get the identifier
|
* @param object $model The model for which to get the identifier
|
||||||
* @throws FormException If the model does not exist
|
* @throws FormException If the model does not exist
|
||||||
*/
|
*/
|
||||||
public function getIdentifierValues($model)
|
private function getIdentifierValues($model)
|
||||||
{
|
{
|
||||||
if ($model instanceof \BaseObject) {
|
if ($model instanceof \Persistent) {
|
||||||
return array($model->getPrimaryKey());
|
return array($model->getPrimaryKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $model->getPrimaryKeys();
|
return $model->getPrimaryKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the choices and returns them
|
|
||||||
*
|
|
||||||
* The choices are generated from the models. If the models have a
|
|
||||||
* composite identifier, the choices are indexed using ascending integers.
|
|
||||||
* Otherwise the identifiers are used as indices.
|
|
||||||
*
|
|
||||||
* If the models were passed in the "choices" option, this method
|
|
||||||
* does not have any significant overhead. Otherwise, if a query object
|
|
||||||
* was passed in the "query" option, this query is now used and executed.
|
|
||||||
* In the last case, all models for the underlying class are fetched.
|
|
||||||
*
|
|
||||||
* If the option "property" was passed, the property path in that option
|
|
||||||
* is used as option values. Otherwise this method tries to convert
|
|
||||||
* objects to strings using __toString().
|
|
||||||
*
|
|
||||||
* @return array An array of choices
|
|
||||||
*/
|
|
||||||
protected function load()
|
|
||||||
{
|
|
||||||
parent::load();
|
|
||||||
|
|
||||||
if ($this->choices) {
|
|
||||||
$models = $this->choices;
|
|
||||||
} else {
|
|
||||||
$models = $this->query->find();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->choices = array();
|
|
||||||
$this->models = array();
|
|
||||||
|
|
||||||
foreach ($models as $key => $model) {
|
|
||||||
if ($this->propertyPath) {
|
|
||||||
// If the property option was given, use it
|
|
||||||
$value = $this->propertyPath->getValue($model);
|
|
||||||
} else {
|
|
||||||
// Otherwise expect a __toString() method in the model
|
|
||||||
$value = (string)$model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($this->identifier) > 1) {
|
|
||||||
// When the identifier consists of multiple field, use
|
|
||||||
// naturally ordered keys to refer to the choices
|
|
||||||
$this->choices[$key] = $value;
|
|
||||||
$this->models[$key] = $model;
|
|
||||||
} else {
|
|
||||||
// When the identifier is a single field, index choices by
|
|
||||||
// model ID for performance reasons
|
|
||||||
$id = current($this->getIdentifierValues($model));
|
|
||||||
$this->choices[$id] = $value;
|
|
||||||
$this->models[$id] = $model;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user