203 lines
6.4 KiB
PHP
203 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace Propel\PropelBundle\Form\ChoiceList;
|
|
|
|
use Symfony\Component\Form\Util\PropertyPath;
|
|
use Symfony\Component\Form\Exception\FormException;
|
|
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
|
use Symfony\Component\Form\Extension\Core\ChoiceList\ArrayChoiceList;
|
|
|
|
class ModelChoiceList extends ArrayChoiceList
|
|
{
|
|
/**
|
|
* 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 Collection
|
|
*/
|
|
private $models = array();
|
|
|
|
/**
|
|
* The fields of which the identifier of the underlying class consists
|
|
*
|
|
* This property should only be accessed through identifier.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $identifier = array();
|
|
|
|
/**
|
|
* A cache for \ReflectionProperty instances for the underlying class
|
|
*
|
|
* This property should only be accessed through getReflProperty().
|
|
*
|
|
* @var array
|
|
*/
|
|
private $reflProperties = array();
|
|
|
|
private $propertyPath;
|
|
|
|
private $relationMap;
|
|
|
|
private $table;
|
|
|
|
public function __construct(\RelationMap $relationMap, $class, $property = null, $queryBuilder = null, $choices = array())
|
|
{
|
|
$this->relationMap = $relationMap;
|
|
$this->class = $class;
|
|
|
|
$queryClass = $this->class . 'Query';
|
|
$query = new $queryClass();
|
|
|
|
$this->table = $query->getTableMap();
|
|
$this->identifier = $this->table->getPrimaryKeys();
|
|
|
|
// The property option defines, which property (path) is used for
|
|
// displaying models as strings
|
|
if ($property) {
|
|
$this->propertyPath = new PropertyPath($property);
|
|
}
|
|
|
|
parent::__construct($choices);
|
|
}
|
|
|
|
/**
|
|
* 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 builder
|
|
* was passed in the "query_builder" option, this builder is now used
|
|
* to construct a query which is executed. In the last case, all models
|
|
* for the underlying class are fetched from the repository.
|
|
*
|
|
* 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 {
|
|
$queryClass = $this->class . 'Query';
|
|
$models = $queryClass::create()->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;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getIdentifier()
|
|
{
|
|
return $this->identifier;
|
|
}
|
|
|
|
/**
|
|
* Returns the according models for the choices
|
|
*
|
|
* If the choices were not initialized, they are initialized now. This
|
|
* is an expensive operation, except if the models were passed in the
|
|
* "choices" option.
|
|
*
|
|
* @return array An array of models
|
|
*/
|
|
public function getModels()
|
|
{
|
|
if (!$this->loaded) {
|
|
$this->load();
|
|
}
|
|
|
|
return $this->models;
|
|
}
|
|
|
|
/**
|
|
* Returns the model for the given key
|
|
*
|
|
* If the underlying models have composite identifiers, the choices
|
|
* 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
|
|
* 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)
|
|
{
|
|
if (!$this->loaded) {
|
|
$this->load();
|
|
}
|
|
|
|
try {
|
|
if (count($this->identifier) > 1) {
|
|
// $key is a collection index
|
|
$models = $this->getModels();
|
|
return isset($models[$key]) ? $models[$key] : null;
|
|
} else 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the values of the identifier fields of an model
|
|
*
|
|
* Doctrine 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
|
|
* @throws FormException If the model does not exist in Doctrine's
|
|
* idmodel map
|
|
*/
|
|
public function getIdentifierValues($model)
|
|
{
|
|
$pks = $model->getPrimaryKey();
|
|
|
|
return is_array($pks) ? $pks : array($pks);
|
|
}
|
|
}
|