Initial commit for new sf3 choiceList
This commit is contained in:
parent
3ba19e83d4
commit
6119fc9806
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
211
Form/ChoiceList/PropelChoiceLoader.php
Normal file
211
Form/ChoiceList/PropelChoiceLoader.php
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,7 +12,11 @@
|
||||||
namespace Propel\Bundle\PropelBundle\Form;
|
namespace Propel\Bundle\PropelBundle\Form;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractExtension;
|
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\PropertyAccess;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the Propel form extension, which loads the Propel functionality.
|
* Represents the Propel form extension, which loads the Propel functionality.
|
||||||
|
@ -21,10 +25,33 @@ use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||||
*/
|
*/
|
||||||
class PropelExtension extends AbstractExtension
|
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()
|
protected function loadTypes()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
new Type\ModelType(PropertyAccess::createPropertyAccessor()),
|
new Type\ModelType($this->propertyAccessor, $this->choiceListFactory),
|
||||||
new Type\TranslationCollectionType(),
|
new Type\TranslationCollectionType(),
|
||||||
new Type\TranslationType()
|
new Type\TranslationType()
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,16 +11,20 @@
|
||||||
|
|
||||||
namespace Propel\Bundle\PropelBundle\Form\Type;
|
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\Bundle\PropelBundle\Form\DataTransformer\CollectionToArrayTransformer;
|
||||||
|
use Propel\Runtime\ActiveQuery\ModelCriteria;
|
||||||
|
use Propel\Runtime\Map\ColumnMap;
|
||||||
use Symfony\Component\Form\AbstractType;
|
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\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\Options;
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||||
|
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ModelType class.
|
* ModelType class.
|
||||||
|
@ -53,61 +57,167 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||||
class ModelType extends AbstractType
|
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();
|
$this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(
|
||||||
}
|
new DefaultChoiceListFactory(),
|
||||||
|
$propertyAccessor
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@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()
|
public function getBlockPrefix()
|
||||||
{
|
{
|
||||||
return 'model';
|
return 'entity';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getParent()
|
||||||
{
|
{
|
||||||
return $this->getBlockPrefix();
|
return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
namespace Propel\Bundle\PropelBundle\Form;
|
namespace Propel\Bundle\PropelBundle\Form;
|
||||||
|
|
||||||
use Propel\Bundle\PropelBundle\Form\Type\ModelType;
|
use Propel\Bundle\PropelBundle\Form\Type\ModelType;
|
||||||
|
use Propel\Runtime\Map\ColumnMap;
|
||||||
use Propel\Runtime\Map\RelationMap;
|
use Propel\Runtime\Map\RelationMap;
|
||||||
use Propel\Generator\Model\PropelTypes;
|
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\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
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)
|
protected function getTable($class)
|
||||||
{
|
{
|
||||||
if (isset($this->cache[$class])) {
|
if (isset($this->cache[$class])) {
|
||||||
|
@ -175,8 +182,16 @@ class TypeGuesser implements FormTypeGuesserInterface
|
||||||
|
|
||||||
return $this->cache[$class] = $query->getTableMap();
|
return $this->cache[$class] = $query->getTableMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $class
|
||||||
|
* @param string $property
|
||||||
|
*
|
||||||
|
* @return ColumnMap|null
|
||||||
|
*/
|
||||||
protected function getColumn($class, $property)
|
protected function getColumn($class, $property)
|
||||||
{
|
{
|
||||||
if (isset($this->cache[$class.'::'.$property])) {
|
if (isset($this->cache[$class.'::'.$property])) {
|
||||||
|
@ -188,5 +203,7 @@ class TypeGuesser implements FormTypeGuesserInterface
|
||||||
if ($table && $table->hasColumn($property)) {
|
if ($table && $table->hasColumn($property)) {
|
||||||
return $this->cache[$class.'::'.$property] = $table->getColumn($property);
|
return $this->cache[$class.'::'.$property] = $table->getColumn($property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue