Created
April 21, 2012 13:29
-
-
Save qrizly/2437078 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace ExampleBundle\Form\EventListener; | |
use Symfony\Component\Form\Event\DataEvent; | |
use Symfony\Component\Form\FormFactoryInterface; | |
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
use Symfony\Component\Form\FormEvents; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use Symfony\Component\Form\FormError; | |
class AddTranslatedFieldSubscriber implements EventSubscriberInterface | |
{ | |
private $factory; | |
private $options; | |
private $container; | |
public function __construct(FormFactoryInterface $factory, ContainerInterface $container, Array $options) | |
{ | |
$this->factory = $factory; | |
$this->options = $options; | |
$this->container = $container; | |
} | |
public static function getSubscribedEvents() | |
{ | |
// Tells the dispatcher that we want to listen on the form.pre_set_data | |
// , form.post_data and form.bind_norm_data event | |
return array( | |
FormEvents::PRE_SET_DATA => 'preSetData', | |
FormEvents::POST_BIND => 'postBind', | |
FormEvents::BIND_NORM_DATA => 'bindNormData' | |
); | |
} | |
private function bindTranslations($data) | |
{ | |
//Small helper function to extract all Personal Translation | |
//from the Entity for the field we are interested in | |
//and combines it with the fields | |
$collection = array(); | |
$availableTranslations = array(); | |
foreach($data as $Translation) | |
{ | |
if(strtolower($Translation->getField()) == strtolower($this->options['field'])) | |
{ | |
$availableTranslations[ strtolower($Translation->getLocale()) ] = $Translation; | |
} | |
} | |
foreach($this->getFieldNames() as $locale => $fieldName) | |
{ | |
if(isset($availableTranslations[ strtolower($locale) ])) | |
{ | |
$Translation = $availableTranslations[ strtolower($locale) ]; | |
} | |
else | |
{ | |
$Translation = $this->createPersonalTranslation($locale, $this->options['field'], NULL); | |
} | |
$collection[] = array( | |
'locale' => $locale, | |
'fieldName' => $fieldName, | |
'translation' => $Translation, | |
); | |
} | |
return $collection; | |
} | |
private function getFieldNames() | |
{ | |
//helper function to generate all field names in format: | |
// '<locale>' => '<field>|<locale>' | |
$collection = array(); | |
foreach($this->options['locales'] as $locale) | |
{ | |
$collection[ $locale ] = $this->options['field'] ."|". $locale; | |
} | |
return $collection; | |
} | |
private function createPersonalTranslation($locale, $field, $content) | |
{ | |
//creates a new Personal Translation | |
$className = $this->options['personal_translation']; | |
$Translation = new $className(); | |
$Translation->setLocale($locale); | |
$Translation->setField($field); | |
$Translation->setContent($content); | |
return $Translation; | |
} | |
public function bindNormData(DataEvent $event) | |
{ | |
//Validates the submitted form | |
$data = $event->getData(); | |
$form = $event->getForm(); | |
$validator = $this->container->get('validator'); | |
foreach($this->getFieldNames() as $locale => $fieldName) | |
{ | |
$content = $form->get($fieldName)->getData(); | |
if( | |
NULL === $content && | |
in_array($locale, $this->options['required_locale'])) | |
{ | |
$form->addError(new FormError(sprintf("Field '%s' for locale '%s' cannot be blank", $this->options['field'], $locale))); | |
} | |
else | |
{ | |
$Translation = $this->createPersonalTranslation($locale, $fieldName, $content); | |
$errors = $validator->validate($Translation, array(sprintf("%s:%s", $this->options['field'], $locale))); | |
if(count($errors) > 0) | |
{ | |
foreach($errors as $error) | |
{ | |
$form->addError(new FormError($error->getMessage())); | |
} | |
} | |
} | |
} | |
} | |
public function postBind(DataEvent $event) | |
{ | |
//if the form passed the validattion then set the corresponding Personal Translations | |
$form = $event->getForm(); | |
$data = $form->getData(); | |
$entity = $form->getParent()->getData(); | |
foreach($this->bindTranslations($data) as $binded) | |
{ | |
$content = $form->get($binded['fieldName'])->getData(); | |
$Translation = $binded['translation']; | |
// set the submitted content | |
$Translation->setContent($content); | |
//test if its new | |
if($Translation->getId()) | |
{ | |
//Delete the Personal Translation if its empty | |
if( | |
NULL === $content && | |
$this->options['remove_empty'] | |
) | |
{ | |
$data->removeElement($Translation); | |
if($this->options['entity_manager_removal']) | |
{ | |
$this->container->get('doctrine.orm.entity_manager')->remove($Translation); | |
} | |
} | |
} | |
elseif(NULL !== $content) | |
{ | |
//add it to entity | |
$entity->addTranslation($Translation); | |
if(! $data->contains($Translation)) | |
{ | |
$data->add($Translation); | |
} | |
} | |
} | |
} | |
public function preSetData(DataEvent $event) | |
{ | |
//Builds the custom 'form' based on the provided locales | |
$data = $event->getData(); | |
$form = $event->getForm(); | |
// During form creation setData() is called with null as an argument | |
// by the FormBuilder constructor. We're only concerned with when | |
// setData is called with an actual Entity object in it (whether new, | |
// or fetched with Doctrine). This if statement let's us skip right | |
// over the null condition. | |
if (null === $data) | |
{ | |
return; | |
} | |
foreach($this->bindTranslations($data) as $binded) | |
{ | |
$form->add($this->factory->createNamed( | |
$this->options['widget'], | |
$binded['fieldName'], | |
$binded['translation']->getContent(), | |
array( | |
'label' => $binded['locale'], | |
'required' => in_array($binded['locale'], $this->options['required_locale']), | |
'property_path'=> false, | |
) | |
)); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services: | |
form.type.translatable: | |
class: ExampleBundle\Form\TranslatedFieldType | |
arguments: [ @service_container ] | |
tags: | |
- { name: form.type, alias: translatable_field } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace ExampleBundle\Form; | |
use Symfony\Component\Form\AbstractType; | |
use Symfony\Component\Form\FormBuilder; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use ExampleBundle\Form\EventListener\addTranslatedFieldSubscriber; | |
class TranslatedFieldType extends AbstractType | |
{ | |
protected $container; | |
public function __construct(ContainerInterface $container) | |
{ | |
$this->container = $container; | |
} | |
public function buildForm(FormBuilder $builder, array $options) | |
{ | |
if(! class_exists($options['personal_translation'])) | |
{ | |
Throw new \InvalidArgumentException(sprintf("Unable to find personal translation class: '%s'", $options['personal_translation'])); | |
} | |
if(! $options['field']) | |
{ | |
Throw new \InvalidArgumentException("You should provide a field to translate"); | |
} | |
$subscriber = new addTranslatedFieldSubscriber($builder->getFormFactory(), $this->container, $options); | |
$builder->addEventSubscriber($subscriber); | |
} | |
public function getDefaultOptions(array $options = array()) | |
{ | |
$options['remove_empty'] = true; //Personal Translations without content are removed | |
$options['csrf_protection'] = false; | |
$options['personal_translation'] = false; //Personal Translation class | |
$options['locales'] = array('en', 'nl'); //the locales you wish to edit | |
$options['required_locale'] = array('en'); //the required locales cannot be blank | |
$options['field'] = false; //the field that you wish to translate | |
$options['widget'] = "text"; //change this to another widget like 'texarea' if needed | |
$options['entity_manager_removal'] = true; //auto removes the Personal Translation thru entity manager | |
return $options; | |
} | |
public function getName() | |
{ | |
return 'translator'; | |
} | |
} |
adapted for Symfony 2.8
My fork
https://gist.github.com/peter-gribanov/06aeffbf10b94b998fc3
Changes:
- Add support Symfony 2.7 (Symfony 3 not tested, but should work)
- Correct code style to PSR
- Add annotations for autocomplete in IDE
- Add attr option (can change CSS class for field)
- Rename field translatable_field -> translatable
- Rename translatable classes
- ExampleBundle\Form\EventListener\AddTranslatedFieldSubscriber -> ExampleBundle\Form\Event\Subscriber\Translatable
- ExampleBundle\Form\TranslatedFieldType -> ExampleBundle\Form\TranslatableType
- Get supported locales from service parameters
- Not use DI container
- Add example usage
- Auto set the field option from the form field name (_field_ option is not a required)
To handle the Collection type, you can use the peter-gribanov fork (above)
and Add the script that i posted in its thread
First, thank you all for your awesome work.
My question : Is there a way to specify a global template renderer for the translatable fields ?
Thank you.
How to make it work with @Assert
from entities?
Hi! I need to set original field to null. I set required =>false but every time I do submit with empty translations the original filed didn't changed
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi lekeutch,
you have to replace the DataEvent with Symfony\Component\Form\FormEvent.
In the TranslatedFieldType class you have to replace the getDefaultOptions method with setDefaultOptions(OptionsResolverInterface $resolver) and return in the getName method "translatable_field". That's all.