-
-
Save nnmer/d08d5a8db7346db7df5e to your computer and use it in GitHub Desktop.
Translated Field Type for Symfony 2.8
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 Acme\DemoBundle\Form\EventListener; | |
use Symfony\Component\Form\FormEvent; | |
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_SUBMIT => 'postSubmit', | |
FormEvents::SUBMIT => 'submit' | |
); | |
} | |
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(is_object($Translation) && 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']; | |
return new $className($locale, $field, $content); | |
} | |
public function submit(FormEvent $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 postSubmit(FormEvent $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.default_entity_manager')->remove($Translation); | |
} | |
} | |
} | |
elseif(NULL !== $content) | |
{ | |
//add it to entity | |
$entity->addTranslation($Translation); | |
if(! $data->contains($Translation)) | |
{ | |
$data->add($Translation); | |
} | |
} | |
} | |
foreach ($data as $rec) { // remove string elements from "translations", we need only objects | |
if (!is_object($rec)){ | |
$data->removeElement($rec); | |
continue; | |
} | |
if (is_array($this->options['required_locale']) && count($this->options['required_locale'])>0) { | |
if ($rec->getLocale() == $this->options['required_locale'][0]){ | |
$method = "set".ucfirst($rec->getField()); | |
$entity->$method($rec->getContent()); | |
} | |
} | |
} | |
} | |
public function preSetData(FormEvent $event) | |
{ | |
//Builds the custom 'form' based on the provided locales | |
$data = $event->getData(); | |
$form = $event->getForm(); | |
$entity = $form->getParent()->getData(); | |
// 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) | |
{ | |
$filedValue = $binded['translation']->getContent(); | |
if (is_array($this->options['required_locale']) && count($this->options['required_locale'])>0) { | |
if ($binded['translation']->getLocale() == $this->options['required_locale'][0] && null == $filedValue) { | |
$method = "get" . ucfirst($this->options['field']); | |
$filedValue = $entity->$method(); | |
} | |
} | |
$form->add($this->factory->createNamed( | |
$binded['fieldName'], | |
$this->options['widget'], | |
$filedValue, | |
array( | |
'auto_initialize'=> false, | |
'label' => $binded['locale'], | |
'required' => in_array($binded['locale'], $this->options['required_locale']), | |
'property_path'=> null, | |
'attr' => $this->options['attr'], | |
) | |
)); | |
} | |
} | |
} |
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
<?xml version="1.0" ?> | |
<container xmlns="http://symfony.com/schema/dic/services" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | |
<services> | |
<service id="form.type.translatable" class="Acme\DemoBundle\Form\TranslatedFieldType"> | |
<tag name="form.type" alias="translatable_field"/> | |
<argument type="service" id="service_container" /> | |
</service> | |
</services> | |
</container> |
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: Acme\DemoBundle\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 Acme\DemoBundle\Form; | |
use Symfony\Component\Form\AbstractType; | |
use Symfony\Component\Form\FormBuilderInterface; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use SMPlatform\CommonBundle\Form\EventListener\AddTranslatedFieldSubscriber; | |
use Symfony\Component\OptionsResolver\OptionsResolver; | |
class TranslatedFieldType extends AbstractType | |
{ | |
protected $container; | |
public function __construct(ContainerInterface $container) | |
{ | |
$this->container = $container; | |
} | |
public function buildForm(FormBuilderInterface $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 configureOptions(OptionsResolver $resolver) | |
{ | |
$resolver->setDefaults($this->getDefaultOptions()); | |
} | |
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', 'zh_CN', 'zh_HK'); //the locales you wish to edit | |
$options['required_locale'] = array($this->container->getParameter('locale')); //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 | |
$options['attr'] = []; | |
return $options; | |
} | |
public function getName() | |
{ | |
return 'translatable_field'; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello :)
I need you help, i am using this implemenation of form for backend for symfony 2.8 and i am facing a problem
My problem focus on the sluggable and translatable behavior of doctrine extensions bundle with the implementaion of this form.
MY CASE:
I have an entity (FAQ) with 2 fields translatable ( Implementation of doctrine entensions bunlde - Personal Translation) and 1 slugabble field.
The sluggable field (slug) is also a translatable field
So i have in my database my main table (faq) and my translation table (faq_translation - stores the translation of all fields that are translatable)
THE PROBLEM:
When using this form the sluggable field doesnt crreate a slug into every translation entry.
So i want the slug to be persisted into the translation entity and that is not happening.
Imagine that i have a field question in my table (faq) and my slug depends on the value of the question to generate the slug
Imagine also that i am working with 2 locales (EN and PT) and my EN is my default locale
if i fill the question form field with values in EN and PT locales and submit the form this field will be persisted in the translation entity (faq_translation) but my slug will only be generated for the my default locale (EN) and will not be generated for the other locales that i have
IN the end i will have this in my Database:
[FAQ]
question - example question ?
slug - example-question
[FAQ translation]
field - question
locale - PT
content - questao exemplo ?
What i want to happen:
[FAQ]
question - example question ?
slug - example-question
[FAQ translation]
field - question
locale - PT
content - questao exemplo ?
field - slug
locale - PT
content - questao-exemplo
Can you help me ?
Do You have any solution for my problem
Thanks for the attention :)