Added missing form features

This commit is contained in:
Kévin Gomez 2013-12-12 15:01:41 +00:00
parent d3ee81c3c5
commit b6285562b4
8 changed files with 776 additions and 0 deletions

View file

@ -0,0 +1,102 @@
<?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\PropelBundle\Form\EventListener;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
/**
* listener class for propel_translatable_collection
*
* @author Patrick Kaufmann
*/
class TranslationCollectionFormListener implements EventSubscriberInterface
{
private $i18nClass;
private $languages;
public function __construct($languages, $i18nClass)
{
$this->i18nClass = $i18nClass;
$this->languages = $languages;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => array('preSetData', 1),
);
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
if (null === $data) {
return;
}
if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) {
throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)');
}
//get the class name of the i18nClass
$temp = explode('\\', $this->i18nClass);
$dataClass = end($temp);
$rootData = $form->getRoot()->getData();
$foundData = false;
$addFunction = 'add'.$dataClass;
//add a database row for every needed language
foreach ($this->languages as $lang) {
$found = false;
foreach ($data as $i18n) {
if (!method_exists($i18n, 'getLocale')) {
throw new UnexpectedTypeException($i18n, 'Propel i18n object');
}
if ($i18n->getLocale() == $lang) {
$found = true;
break;
}
}
if (!$found) {
$currentForm = $form;
while (!$foundData) {
if (method_exists($rootData, $addFunction)) {
$foundData = true;
break;
} elseif ($currentForm->hasParent()) {
$currentForm = $currentForm->getParent();
$rootData = $currentForm->getData();
} else {
break;
}
}
if (!$foundData) {
throw new UnexpectedTypeException($rootData, 'Propel i18n object');
}
$newTranslation = new $this->i18nClass();
$newTranslation->setLocale($lang);
$rootData->$addFunction($newTranslation);
}
}
}
}

View file

@ -0,0 +1,81 @@
<?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\PropelBundle\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* Event Listener class for propel_translation
*
* @author Patrick Kaufmann
*/
class TranslationFormListener implements EventSubscriberInterface
{
private $columns;
private $dataClass;
public function __construct($columns, $dataClass)
{
$this->columns = $columns;
$this->dataClass = $dataClass;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => array('preSetData', 1),
);
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
if (!$data instanceof $this->dataClass) {
return;
}
//loop over all columns and add the input
foreach ($this->columns as $column => $options) {
if (is_string($options)) {
$column = $options;
$options = array();
}
if (null === $options) {
$options = array();
}
$type = 'text';
if (array_key_exists('type', $options)) {
$type = $options['type'];
}
$label = $column;
if (array_key_exists('label', $options)) {
$label = $options['label'];
}
$customOptions = array();
if (array_key_exists('options', $options)) {
$customOptions = $options['options'];
}
$options = array(
'label' => $label.' '.strtoupper($data->getLocale())
);
$options = array_merge($options, $customOptions);
$form->add($column, $type, $options);
}
}
}

37
Form/PropelExtension.php Normal file
View file

@ -0,0 +1,37 @@
<?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\PropelBundle\Form;
use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\PropertyAccess\PropertyAccess;
/**
* Represents the Propel form extension, which loads the Propel functionality.
*
* @author Joseph Rouff <rouffj@gmail.com>
*/
class PropelExtension extends AbstractExtension
{
protected function loadTypes()
{
return array(
new Type\ModelType(PropertyAccess::getPropertyAccessor()),
new Type\TranslationCollectionType(),
new Type\TranslationType()
);
}
protected function loadTypeGuesser()
{
return new TypeGuesser();
}
}

View file

@ -0,0 +1,75 @@
<?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\PropelBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Propel\PropelBundle\Form\EventListener\TranslationCollectionFormListener;
/**
* form type for i18n-columns in propel
*
* @author Patrick Kaufmann
*/
class TranslationCollectionType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (!isset($options['options']['data_class']) || null === $options['options']['data_class']) {
throw new MissingOptionsException('data_class must be set');
}
if (!isset($options['options']['columns']) || null === $options['options']['columns']) {
throw new MissingOptionsException('columns must be set');
}
$listener = new TranslationCollectionFormListener($options['languages'], $options['options']['data_class']);
$builder->addEventSubscriber($listener);
}
public function getParent()
{
return 'collection';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'propel_translation_collection';
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'languages'
));
$resolver->setDefaults(array(
'type' => 'propel_translation',
'allow_add' => false,
'allow_delete' => false,
'options' => array(
'data_class' => null,
'columns' => null
)
));
}
}

View file

@ -0,0 +1,54 @@
<?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\PropelBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Propel\PropelBundle\Form\EventListener\TranslationFormListener;
/**
* Translation type class
*
* @author Patrick Kaufmann
*/
class TranslationType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber(
new TranslationFormListener($options['columns'], $options['data_class'])
);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'propel_translation';
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'data_class',
'columns'
));
}
}

View file

@ -0,0 +1,131 @@
<?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\PropelBundle\Tests\Fixtures;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Connection\ConnectionInterface;
class TranslatableItem implements ActiveRecordInterface
{
private $id;
private $currentTranslations;
private $groupName;
private $price;
public function __construct($id = null, $translations = array())
{
$this->id = $id;
$this->currentTranslations = $translations;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getGroupName()
{
return $this->groupName;
}
public function getPrice()
{
return $this->price;
}
public function getPrimaryKey()
{
return $this->getId();
}
public function setPrimaryKey($primaryKey)
{
$this->setId($primaryKey);
}
public function isModified()
{
return false;
}
public function isColumnModified($col)
{
return false;
}
public function isNew()
{
return false;
}
public function setNew($b)
{
}
public function resetModified()
{
}
public function isDeleted()
{
return false;
}
public function setDeleted($b)
{
}
public function delete(ConnectionInterface $con = null)
{
}
public function save(ConnectionInterface $con = null)
{
}
public function getTranslation($locale = 'de', ConnectionInterface $con = null)
{
if (!isset($this->currentTranslations[$locale])) {
$translation = new TranslatableItemI18n();
$translation->setLocale($locale);
$this->currentTranslations[$locale] = $translation;
}
return $this->currentTranslations[$locale];
}
public function addTranslatableItemI18n(TranslatableItemI18n $i)
{
if (!in_array($i, $this->currentTranslations)) {
$this->currentTranslations[$i->getLocale()] = $i;
$i->setItem($this);
}
}
public function removeTranslatableItemI18n(TranslatableItemI18n $i)
{
unset($this->currentTranslations[$i->getLocale()]);
}
public function getTranslatableItemI18ns()
{
return $this->currentTranslations;
}
}

View file

@ -0,0 +1,138 @@
<?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\PropelBundle\Tests\Fixtures;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Connection\ConnectionInterface;
class TranslatableItemI18n implements ActiveRecordInterface
{
private $id;
private $locale;
private $value;
private $value2;
private $item;
public function __construct($id = null, $locale = null, $value = null)
{
$this->id = $id;
$this->locale = $locale;
$this->value = $value;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getPrimaryKey()
{
return $this->getId();
}
public function setPrimaryKey($primaryKey)
{
$this->setId($primaryKey);
}
public function isModified()
{
return false;
}
public function isColumnModified($col)
{
return false;
}
public function isNew()
{
return false;
}
public function setNew($b)
{
}
public function resetModified()
{
}
public function isDeleted()
{
return false;
}
public function setDeleted($b)
{
}
public function delete(ConnectionInterface $con = null)
{
}
public function save(ConnectionInterface $con = null)
{
}
public function setLocale($locale)
{
$this->locale = $locale;
}
public function getLocale()
{
return $this->locale;
}
public function getItem()
{
return $this->item;
}
public function setItem($item)
{
$this->item = $item;
}
public function setValue($value)
{
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
public function setValue2($value2)
{
$this->value2 = $value2;
}
public function getValue2()
{
return $this->value2;
}
}

View file

@ -0,0 +1,158 @@
<?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\PropelBundle\Tests\Form\Form\Type;
use Propel\PropelBundle\Tests\Fixtures\Item;
use Propel\PropelBundle\Form\PropelExtension;
use Propel\PropelBundle\Tests\Fixtures\TranslatableItemI18n;
use Propel\PropelBundle\Tests\Fixtures\TranslatableItem;
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
class TranslationCollectionTypeTest extends TypeTestCase
{
const TRANSLATION_CLASS = 'Propel\PropelBundle\Tests\Fixtures\TranslatableItem';
const TRANSLATABLE_I18N_CLASS = 'Propel\PropelBundle\Tests\Fixtures\TranslatableItemI18n';
const NON_TRANSLATION_CLASS = 'Propel\PropelBundle\Tests\Fixtures\Item';
protected function setUp()
{
parent::setUp();
}
protected function getExtensions()
{
return array_merge(parent::getExtensions(), array(
new PropelExtension(),
));
}
public function testTranslationsAdded()
{
$item = new TranslatableItem();
$item->addTranslatableItemI18n(new TranslatableItemI18n(1, 'fr', 'val1'));
$item->addTranslatableItemI18n(new TranslatableItemI18n(2, 'en', 'val2'));
$builder = $this->factory->createBuilder('form', null, array(
'data_class' => self::TRANSLATION_CLASS
));
$builder->add('translatableItemI18ns', 'propel_translation_collection', array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2' => array('label' => 'Label', 'type' => 'textarea'))
)
));
$form = $builder->getForm();
$form->setData($item);
$translations = $form->get('translatableItemI18ns');
$this->assertCount(2, $translations);
$this->assertInstanceOf('Symfony\Component\Form\Form', $translations['en']);
$this->assertInstanceOf('Symfony\Component\Form\Form', $translations['fr']);
$this->assertInstanceOf(self::TRANSLATABLE_I18N_CLASS, $translations['en']->getData());
$this->assertInstanceOf(self::TRANSLATABLE_I18N_CLASS, $translations['fr']->getData());
$this->assertEquals($item->getTranslation('en'), $translations['en']->getData());
$this->assertEquals($item->getTranslation('fr'), $translations['fr']->getData());
$columnOptions = $translations['fr']->getConfig()->getOption('columns');
$this->assertEquals('value', $columnOptions[0]);
$this->assertEquals('textarea', $columnOptions['value2']['type']);
$this->assertEquals('Label', $columnOptions['value2']['label']);
}
public function testNotPresentTranslationsAdded()
{
$item = new TranslatableItem();
$this->assertCount(0, $item->getTranslatableItemI18ns());
$builder = $this->factory->createBuilder('form', null, array(
'data_class' => self::TRANSLATION_CLASS
));
$builder->add('translatableItemI18ns', 'propel_translation_collection', array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2' => array('label' => 'Label', 'type' => 'textarea'))
)
));
$form = $builder->getForm();
$form->setData($item);
$this->assertCount(2, $item->getTranslatableItemI18ns());
}
/**
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testNoArrayGiven()
{
$item = new Item(null, 'val');
$builder = $this->factory->createBuilder('form', null, array(
'data_class' => self::NON_TRANSLATION_CLASS
));
$builder->add('value', 'propel_translation_collection', array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2' => array('label' => 'Label', 'type' => 'textarea'))
)
));
$form = $builder->getForm();
$form->setData($item);
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
*/
public function testNoDataClassAdded()
{
$this->factory->createNamed('itemI18ns', 'propel_translation_collection', null, array(
'languages' => array('en', 'fr'),
'options' => array(
'columns' => array('value', 'value2')
)
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
*/
public function testNoLanguagesAdded()
{
$this->factory->createNamed('itemI18ns', 'propel_translation_collection', null, array(
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS,
'columns' => array('value', 'value2')
)
));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException
*/
public function testNoColumnsAdded()
{
$this->factory->createNamed('itemI18ns', 'propel_translation_collection', null, array(
'languages' => array('en', 'fr'),
'options' => array(
'data_class' => self::TRANSLATABLE_I18N_CLASS
)
));
}
}