Symfony 2.3 / Doctrine2个人翻译无法在Sonata Admin中使用TranslatedFieldType.php [英] Symfony 2.3 / Doctrine2 personal translations not working in Sonata Admin with TranslatedFieldType.php

查看:83
本文介绍了Symfony 2.3 / Doctrine2个人翻译无法在Sonata Admin中使用TranslatedFieldType.php的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在从事一个带有doctrine2的symfony2.3项目,试图在Sonata后端中实现个人翻译管理。

I am currently working on a symfony2.3 project with doctrine2 trying to implement personal translations management in the Sonata backend.

翻译基于doctrine2可翻译行为模型: https://github.com/l3pp4rd/DoctrineExtensions/ blob / master / doc / translatable.md
,更准确地说是Personal Translations。

Translations are based on the doctrine2 translatable behaviour model: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/translatable.md and more precisely Personal Translations.

管理表单使用的是symfony2.3版本的< a href = https://gist.github.com/taavit/5725754 rel = nofollow> TranslateFieldType.php 。

The admin form is using the symfony2.3 version of TranslatedFieldType.php.

我的实体类如下:

<?php
namespace Hr\OnlineBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @Gedmo\TranslationEntity(class="Hr\OnlineBundle\Entity\CategoryTranslation")
 */
class Category
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue
     */
    private $id;

    /**
     * @Gedmo\Translatable
     * @ORM\Column(length=64)
     */
    private $title;

    /**
     * @Gedmo\Translatable
     * @ORM\Column(type="text", nullable=true)
     */
    private $description;

    /**
     * @ORM\OneToMany(
     *   targetEntity="CategoryTranslation",
     *   mappedBy="object",
     *   cascade={"persist", "remove"}
     * )
     */
    private $translations;

    public function __construct()
    {
        $this->translations = new ArrayCollection();
    }

    public function getTranslations()
    {
        return $this->translations;
    }

    public function addTranslation(CategoryTranslation $t)
    {
        if (!$this->translations->contains($t)) {
            $this->translations[] = $t;
            $t->setObject($this);
        }
    }

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setDescription($description)
    {
        $this->description = $description;
    }

    public function getDescription()
    {
        return $this->description;
    }

    public function __toString()
    {
        return $this->getTitle();
    }
}

相关的个人翻译班是这样:

The related Personal Translation class is this:

<?php

namespace Hr\OnlineBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation;

/**
 * @ORM\Entity
 * @ORM\Table(name="category_translations",
 *     uniqueConstraints={@ORM\UniqueConstraint(name="lookup_unique_idx", columns={
 *         "locale", "object_id", "field"
 *     })}
 * )
 */
class CategoryTranslation extends AbstractPersonalTranslation
{
    /**
     * Convinient constructor
     *
     * @param string $locale
     * @param string $field
     * @param string $value
     */
    public function __construct($locale, $field, $value)
    {
        $this->setLocale($locale);
        $this->setField($field);
        $this->setContent($value);
    }

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="translations")
     * @ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
     */
    protected $object;
}

管理表单类为:

<?php
namespace Hr\OnlineBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Hr\OnlineBundle\Form\Type\TranslatedFieldType;

class CategoryAdmin extends Admin
{
    // Fields to be shown on create/edit forms
    protected function configureFormFields(FormMapper $formMapper)
    {

        $formMapper
        ->with('General')
            ->add('title', 'translatable_field', array(
                'field'                => 'title',
                'personal_translation' => 'Hr\OnlineBundle\Entity\CategoryTranslation',
                'property_path'        => 'translations',
            ))
        ->end();

    }

    // Fields to be shown on filter forms
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('title')
        ;
    }

    // Fields to be shown on lists
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('title')
        ;
    }
}

管理表单使用以下表单类型类:

The admin form is using the following form type class:

<?php
namespace Hr\OnlineBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

use Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber;

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 setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'remove_empty' => true,
            'csrf_protection'=> false,
            'field' => false,
            'personal_translation' => false,
            'locales'=>array('en', 'fr', 'de'),
            'required_locale'=>array('en'),
            'widget'=>'text',
            'entity_manager_removal'=>true,
        ));
    }

    public function getName()
    {
        return 'translatable_field';
    }
}

和相应的事件监听器:

<?php

namespace Hr\OnlineBundle\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\Form\FormEvent;
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 => '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'];

        return new $className($locale, $field, $content);
    }

    public function bindNormData(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 postBind(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.entity_manager')->remove($Translation);
                   }
               }
           }
           elseif(NULL !== $content)
           {
               //add it to entity
               $entity->addTranslation($Translation);

               if(! $data->contains($Translation))
               {
                   $data->add($Translation);
               }
           }
       }
    }

    public function preSetData(FormEvent $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(
                $binded['fieldName'],
                $this->options['widget'],
                $binded['translation']->getContent(),
                array(
                    'label' => $binded['locale'],
                    'required' => in_array($binded['locale'], $this->options['required_locale']),
                    'auto_initialize' => false,
                )
            ));
        }
    }
}

因此,该表格显示了三个指定语言环境的三个字段,这很好,但是在提交表单时会发生以下错误:

So, the form shows the three fields for the three specified locales, which is fine, but when submitting the form the following error occurs:

FatalErrorException: Error: Call to a member function getField() on a non-object in C:\wamp\www\hronline\src\Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber.php line 47
in C:\wamp\www\hronline\src\Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber.php line 47
at ErrorHandler->handleFatal() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Debug\ErrorHandler.php line 0
at AddTranslatedFieldSubscriber->bindTranslations() in C:\wamp\www\hronline\src\Hr\OnlineBundle\Form\EventListener\addTranslatedFieldSubscriber.php line 136
at AddTranslatedFieldSubscriber->postBind() in C:\wamp\www\hronline\app\cache\dev\classes.php line 1667
at ??call_user_func() in C:\wamp\www\hronline\app\cache\dev\classes.php line 1667
at EventDispatcher->doDispatch() in C:\wamp\www\hronline\app\cache\dev\classes.php line 1600
at EventDispatcher->dispatch() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\EventDispatcher\ImmutableEventDispatcher.php line 42
at ImmutableEventDispatcher->dispatch() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 631
at Form->submit() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 552
at Form->submit() in C:\wamp\www\hronline\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 645
at Form->bind() in C:\wamp\www\hronline\vendor\sonata-project\admin-bundle\Sonata\AdminBundle\Controller\CRUDController.php line 498
at CRUDController->createAction() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2844
at ??call_user_func_array() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2844
at HttpKernel->handleRaw() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2818
at HttpKernel->handle() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2947
at ContainerAwareHttpKernel->handle() in C:\wamp\www\hronline\app\bootstrap.php.cache line 2249
at Kernel->handle() in C:\wamp\www\hronline\web\app_dev.php line 28
at ??{main}() in C:\wamp\www\hronline\web\app_dev.php line 0

事件侦听器的这一行AddTranslatedFieldSubscriber:

It appears that on this line of the event listener AddTranslatedFieldSubscriber:

if(strtolower($Translation->getField()) == strtolower($this->options['field']))

$ Translation变量为字符串(例如字符串 Lorem(长度= 5))而不是对象。
由于某种原因,表单数据被转换为字符串数组,而不是类别转换类型的对象数组。

the $Translation variable comes as a string (e.g. string 'Lorem' (length=5)) instead of an object. For some reason the form data is converted to an array of strings instead of an array of objects of type CategoryTranslation.

这可能是什么原因?谢谢!

What could be the reason for this? Thanks!

推荐答案

帮个忙,不要使用Gedmo可翻译的行为。这是一辆越野车,速度很慢,并且有很多奇怪的情况。我建议使用 KNP可翻译行为,它相当好(需要PHP 5.4)或 Prezent Translatable 相似,但可以在PHP 5.3上使用。免责声明:我写了最后一个。它是Beta版,但效果很好。我只是为此添加了文档。

Do yourself a favour and don't use the Gedmo Translatable behaviour. It is very buggy, slow and has a lot of weird edge cases. I recommend using the KNP translatable behaviour which is quite good (requires PHP 5.4) or the Prezent Translatable which is similar but can work on PHP 5.3. Disclaimer: I wrote that last one. It's beta but works fine. I just added the documentation for it.

您可以使用 a2lix捆绑包,将其全部集成到Sonata Admin中。

You can use the a2lix bundle to integrate it all in Sonata Admin.

这篇关于Symfony 2.3 / Doctrine2个人翻译无法在Sonata Admin中使用TranslatedFieldType.php的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆