Merge branch 'feature/sf3FormGeneration' of https://github.com/IceShack/PropelBundle into IceShack-feature/sf3FormGeneration
This commit is contained in:
commit
4cef2b6701
65
Command/BundleTrait.php
Normal file
65
Command/BundleTrait.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?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\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\Question;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Moritz Schroeder <moritz.schroeder@molabs.de>
|
||||||
|
*/
|
||||||
|
trait BundleTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return ContainerInterface
|
||||||
|
*/
|
||||||
|
protected abstract function getContainer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected bundle.
|
||||||
|
* If no bundle argument is set, the user will get ask for it.
|
||||||
|
*
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
*
|
||||||
|
* @return BundleInterface
|
||||||
|
*/
|
||||||
|
protected function getBundle(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$kernel = $this
|
||||||
|
->getContainer()
|
||||||
|
->get('kernel');
|
||||||
|
|
||||||
|
if ($input->hasArgument('bundle') && '@' === substr($input->getArgument('bundle'), 0, 1)) {
|
||||||
|
return $kernel->getBundle(substr($input->getArgument('bundle'), 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
$bundleNames = array_keys($kernel->getBundles());
|
||||||
|
|
||||||
|
do {
|
||||||
|
$question = '<info>Select the bundle</info>: ';
|
||||||
|
$question = new Question($question);
|
||||||
|
$question->setAutocompleterValues($bundleNames);
|
||||||
|
|
||||||
|
$bundleName = $this->getHelperSet()->get('question')->ask($input, $output, $question);
|
||||||
|
|
||||||
|
if (in_array($bundleName, $bundleNames)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$output->writeln(sprintf('<bg=red>Bundle "%s" does not exist.</bg>', $bundleName));
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
return $kernel->getBundle($bundleName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,12 +10,11 @@
|
||||||
|
|
||||||
namespace Propel\Bundle\PropelBundle\Command;
|
namespace Propel\Bundle\PropelBundle\Command;
|
||||||
|
|
||||||
|
use Propel\Bundle\PropelBundle\Form\FormBuilder;
|
||||||
use Propel\Generator\Config\GeneratorConfig;
|
use Propel\Generator\Config\GeneratorConfig;
|
||||||
use Propel\Generator\Command\ModelBuildCommand as BaseModelBuildCommand;
|
|
||||||
use Propel\Generator\Model\Database;
|
use Propel\Generator\Model\Database;
|
||||||
use Propel\Generator\Model\Table;
|
use Propel\Generator\Model\Table;
|
||||||
use Propel\Generator\Manager\ModelManager;
|
use Propel\Generator\Manager\ModelManager;
|
||||||
use Propel\Runtime\Propel;
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
@ -30,6 +29,8 @@ use Symfony\Component\HttpKernel\Bundle\BundleInterface;
|
||||||
class FormGenerateCommand extends AbstractCommand
|
class FormGenerateCommand extends AbstractCommand
|
||||||
{
|
{
|
||||||
const DEFAULT_FORM_TYPE_DIRECTORY = '/Form/Type';
|
const DEFAULT_FORM_TYPE_DIRECTORY = '/Form/Type';
|
||||||
|
|
||||||
|
use BundleTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
@ -42,7 +43,7 @@ class FormGenerateCommand extends AbstractCommand
|
||||||
|
|
||||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Overwrite existing Form types')
|
->addOption('force', 'f', InputOption::VALUE_NONE, 'Overwrite existing Form types')
|
||||||
->addOption('platform', null, InputOption::VALUE_REQUIRED, 'The platform')
|
->addOption('platform', null, InputOption::VALUE_REQUIRED, 'The platform')
|
||||||
->addArgument('bundle', InputArgument::REQUIRED, 'The bundle to use to generate Form types (Ex: @AcmeDemoBundle)')
|
->addArgument('bundle', InputArgument::OPTIONAL, 'The bundle to use to generate Form types (Ex: @AcmeDemoBundle)')
|
||||||
->addArgument('models', InputArgument::IS_ARRAY, 'Model classes to generate Form Types from')
|
->addArgument('models', InputArgument::IS_ARRAY, 'Model classes to generate Form Types from')
|
||||||
|
|
||||||
->setHelp(<<<EOT
|
->setHelp(<<<EOT
|
||||||
|
@ -64,15 +65,12 @@ EOT
|
||||||
$models = $input->getArgument('models');
|
$models = $input->getArgument('models');
|
||||||
$force = $input->getOption('force');
|
$force = $input->getOption('force');
|
||||||
|
|
||||||
if (!$this->bundle) {
|
$bundle = $this->getBundle($input, $output);
|
||||||
throw new \InvalidArgumentException('No valid bundle given');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setupBuildTimeFiles();
|
$this->setupBuildTimeFiles();
|
||||||
|
$schemas = $this->getFinalSchemas($kernel, $bundle);
|
||||||
if (!($schemas = $this->getFinalSchemas($kernel, $this->bundle))) {
|
if (!$schemas) {
|
||||||
$output->writeln(sprintf('No <comment>*schemas.xml</comment> files found in bundle <comment>%s</comment>.', $this->bundle->getName()));
|
$output->writeln(sprintf('No <comment>*schemas.xml</comment> files found in bundle <comment>%s</comment>.', $bundle->getName()));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +78,7 @@ EOT
|
||||||
|
|
||||||
foreach ($manager->getDataModels() as $dataModel) {
|
foreach ($manager->getDataModels() as $dataModel) {
|
||||||
foreach ($dataModel->getDatabases() as $database) {
|
foreach ($dataModel->getDatabases() as $database) {
|
||||||
$this->createFormTypeFromDatabase($this->bundle, $database, $models, $output, $force);
|
$this->createFormTypeFromDatabase($bundle, $database, $models, $output, $force);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,45 +136,19 @@ EOT
|
||||||
*
|
*
|
||||||
* @param BundleInterface $bundle The bundle in which the FormType will be created.
|
* @param BundleInterface $bundle The bundle in which the FormType will be created.
|
||||||
* @param Table $table The table for which the FormType will be created.
|
* @param Table $table The table for which the FormType will be created.
|
||||||
* @param SplFileInfo $file File representing the FormType.
|
* @param \SplFileInfo $file File representing the FormType.
|
||||||
* @param boolean $force Is the write forced?
|
* @param boolean $force Is the write forced?
|
||||||
* @param OutputInterface $output An OutputInterface instance.
|
* @param OutputInterface $output An OutputInterface instance.
|
||||||
*/
|
*/
|
||||||
protected function writeFormType(BundleInterface $bundle, Table $table, \SplFileInfo $file, $force, OutputInterface $output)
|
protected function writeFormType(BundleInterface $bundle, Table $table, \SplFileInfo $file, $force, OutputInterface $output)
|
||||||
{
|
{
|
||||||
$modelName = $table->getPhpName();
|
$formBuilder = new FormBuilder();
|
||||||
$formTypeContent = file_get_contents(__DIR__ . '/../Resources/skeleton/FormType.php');
|
$formTypeContent = $formBuilder->buildFormType($bundle, $table, self::DEFAULT_FORM_TYPE_DIRECTORY);
|
||||||
|
|
||||||
$formTypeContent = str_replace('##NAMESPACE##', $bundle->getNamespace() . str_replace('/', '\\', self::DEFAULT_FORM_TYPE_DIRECTORY), $formTypeContent);
|
|
||||||
$formTypeContent = str_replace('##CLASS##', $modelName . 'Type', $formTypeContent);
|
|
||||||
$formTypeContent = str_replace('##FQCN##', sprintf('%s\%s', $table->getNamespace(), $modelName), $formTypeContent);
|
|
||||||
$formTypeContent = str_replace('##TYPE_NAME##', strtolower($modelName), $formTypeContent);
|
|
||||||
$formTypeContent = $this->addFields($table, $formTypeContent);
|
|
||||||
|
|
||||||
file_put_contents($file->getPathName(), $formTypeContent);
|
file_put_contents($file->getPathName(), $formTypeContent);
|
||||||
$this->writeNewFile($output, $this->getRelativeFileName($file) . ($force ? ' (forced)' : ''));
|
$this->writeNewFile($output, $this->getRelativeFileName($file) . ($force ? ' (forced)' : ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the fields in the FormType.
|
|
||||||
*
|
|
||||||
* @param Table $table Table from which the fields will be extracted.
|
|
||||||
* @param string $formTypeContent FormType skeleton.
|
|
||||||
*
|
|
||||||
* @return string The FormType code.
|
|
||||||
*/
|
|
||||||
protected function addFields(Table $table, $formTypeContent)
|
|
||||||
{
|
|
||||||
$buildCode = '';
|
|
||||||
foreach ($table->getColumns() as $column) {
|
|
||||||
if (!$column->isPrimaryKey()) {
|
|
||||||
$buildCode .= sprintf("\n \$builder->add('%s');", lcfirst($column->getPhpName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str_replace('##BUILD_CODE##', $buildCode, $formTypeContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \SplFileInfo $file
|
* @param \SplFileInfo $file
|
||||||
* @return string
|
* @return string
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author William DURAND <william.durand1@gmail.com>
|
* @author William DURAND <william.durand1@gmail.com>
|
||||||
|
* @deprecated use AbstractType directly
|
||||||
*/
|
*/
|
||||||
abstract class BaseAbstractType extends AbstractType
|
abstract class BaseAbstractType extends AbstractType
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
Form/FormBuilder.php
Normal file
72
Form/FormBuilder.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use Propel\Generator\Model\ForeignKey;
|
||||||
|
use Propel\Generator\Model\Table;
|
||||||
|
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Moritz Schroeder <moritz.schroeder@molabs.de>
|
||||||
|
*/
|
||||||
|
class FormBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Build a form based on the given table.
|
||||||
|
*
|
||||||
|
* @param BundleInterface $bundle
|
||||||
|
* @param Table $table
|
||||||
|
* @param string $formTypeNamespace
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function buildFormType(BundleInterface $bundle, Table $table, $formTypeNamespace)
|
||||||
|
{
|
||||||
|
$modelName = $table->getPhpName();
|
||||||
|
$formTypeContent = file_get_contents(__DIR__ . '/../Resources/skeleton/FormType.php');
|
||||||
|
|
||||||
|
$formTypeContent = str_replace('##NAMESPACE##', $bundle->getNamespace() . str_replace('/', '\\', $formTypeNamespace), $formTypeContent);
|
||||||
|
$formTypeContent = str_replace('##CLASS##', $modelName . 'Type', $formTypeContent);
|
||||||
|
$formTypeContent = str_replace('##FQCN##', sprintf('%s\%s', $table->getNamespace(), $modelName), $formTypeContent);
|
||||||
|
$formTypeContent = str_replace('##TYPE_NAME##', strtolower($modelName), $formTypeContent);
|
||||||
|
$formTypeContent = str_replace('##BUILD_CODE##', $this->buildFormFields($table), $formTypeContent);
|
||||||
|
|
||||||
|
return $formTypeContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the fields in the FormType.
|
||||||
|
*
|
||||||
|
* @param Table $table Table from which the fields will be extracted.
|
||||||
|
*
|
||||||
|
* @return string The FormType code.
|
||||||
|
*/
|
||||||
|
protected function buildFormFields(Table $table)
|
||||||
|
{
|
||||||
|
$buildCode = '';
|
||||||
|
foreach ($table->getColumns() as $column) {
|
||||||
|
if ($column->isPrimaryKey()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $column->getPhpName();
|
||||||
|
|
||||||
|
// Use foreignKey table name, so the TypeGuesser gets it right
|
||||||
|
if ($column->isForeignKey()) {
|
||||||
|
/** @var ForeignKey $foreignKey */
|
||||||
|
$foreignKey = current($column->getForeignKeys());
|
||||||
|
$name = $foreignKey->getForeignTable()->getPhpName();
|
||||||
|
}
|
||||||
|
$buildCode .= sprintf("\n \$builder->add('%s');", lcfirst($name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buildCode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,188 @@ 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
$choiceLabelNormalizer = function (Options $options, $choiceLabel) {
|
||||||
|
if ($choiceLabel === null) {
|
||||||
|
if ($options['property'] == null) {
|
||||||
|
$choiceLabel = array(__CLASS__, 'createChoiceLabel');
|
||||||
|
} else {
|
||||||
|
$valueProperty = $options['property'];
|
||||||
|
/** @var ModelCriteria $query */
|
||||||
|
$query = $options['query'];
|
||||||
|
$getter = 'get' . ucfirst($query->getTableMap()->getColumn($valueProperty)->getPhpName());
|
||||||
|
|
||||||
|
$choiceLabel = function($choice) use ($getter) {
|
||||||
|
return call_user_func([$choice, $getter]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $choiceLabel;
|
||||||
|
};
|
||||||
|
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'query' => null,
|
||||||
|
'index_property' => null,
|
||||||
|
'property' => null,
|
||||||
|
'choices' => null,
|
||||||
|
'choice_loader' => $choiceLoader,
|
||||||
|
'choice_label' => null,
|
||||||
|
'choice_name' => $choiceName,
|
||||||
|
'choice_value' => $choiceValue,
|
||||||
|
'choice_translation_domain' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resolver->setRequired(array('class'));
|
||||||
|
$resolver->setNormalizer('query', $queryNormalizer);
|
||||||
|
$resolver->setNormalizer('choice_label', $choiceLabelNormalizer);
|
||||||
|
$resolver->setAllowedTypes('query', ['null', 'Propel\Runtime\ActiveQuery\ModelCriteria']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,8 +249,8 @@ class ModelType extends AbstractType
|
||||||
return 'model';
|
return 'model';
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,27 @@
|
||||||
|
|
||||||
namespace ##NAMESPACE##;
|
namespace ##NAMESPACE##;
|
||||||
|
|
||||||
use Propel\Bundle\PropelBundle\Form\BaseAbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
class ##CLASS## extends BaseAbstractType
|
class ##CLASS## extends AbstractType
|
||||||
{
|
{
|
||||||
protected $options = array(
|
|
||||||
'data_class' => '##FQCN##',
|
|
||||||
'name' => '##TYPE_NAME##',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{##BUILD_CODE##
|
{##BUILD_CODE##
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => '##FQCN##',
|
||||||
|
'name' => '##TYPE_NAME##',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue