-
-
Save nnmer/d08d5a8db7346db7df5e to your computer and use it in GitHub Desktop.
<?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'], | |
) | |
)); | |
} | |
} | |
} |
<?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> |
services: | |
form.type.translatable: | |
class: Acme\DemoBundle\Form\TranslatedFieldType | |
arguments: [ @service_container ] | |
tags: | |
- { name: form.type, alias: translatable_field } |
<?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'; | |
} | |
} |
@WildChildForLife to write and read default values added, see updated code
depends on the requirements, now you can set 'required_locale'=> []
at the form field parameters to allow field translations to be skiped as sometimes field can be empty without any data
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 :)
@fliespl added, see updated code