Symfony 3

时间:2017-08-11 16:31:12

标签: translation admin entities sonata symfony-3.3

是否有人成功地在Symfony 3上翻译Sonata Admin实体(实际上我使用的是3.3)。

我尝试了不同的解决方案,但没有一个真正奏效。 使用gedmo转换时,主要问题是为数据库上的不同语言保存了翻译,但是在管理员(列表结束表单)中,Sonata包仅显示默认的语言环境转换,尽管单击了不同的标记/翻译/选用。

我也尝试过使用KNP tarnslation包,并使用A2lix翻译,但是这两个有同样的问题:当你设置(在管理类中)一个字段为" sortable"然后在记录列表中,当你尝试按字段排序时,Symfony会抛出一个错误,因为翻译系统试图创建一个与另一个不存在的字段的关联!

无论如何,坚持Gedmo的灵魂,主要的问题是(由于我已经提到过的问题而将A2lix解决方案拆开)我不知道如何在管理类中设置一个可翻译的字段(BlogPostAdmin。 php)因为只是使用配置文件和实体和翻译类,似乎没有用。如前所述,问题在于翻译保存在数据库中,但不会显示在管理列表/表单中。

以下是我的配置和实体文件:

AppKernel.php

<?php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            new Symfony\Bundle\MonologBundle\MonologBundle(),
            new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
            new AppBundle\AppBundle(),
            /// These are the other bundles the SonataAdminBundle relies on
            new Sonata\CoreBundle\SonataCoreBundle(),
            new Sonata\BlockBundle\SonataBlockBundle(),
            new Knp\Bundle\MenuBundle\KnpMenuBundle(),
            new Sonata\TranslationBundle\SonataTranslationBundle(),

            // And finally, the storage and SonataAdminBundle
            new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
            new Sonata\AdminBundle\SonataAdminBundle(),

            // stof [used in Sonata translations]
            new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

            // assetic
            new Symfony\Bundle\AsseticBundle\AsseticBundle(),
        ];

        if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
            $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
            $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();

            if ('dev' === $this->getEnvironment()) {
                $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
                $bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
            }
        }

        return $bundles;
    }

    public function getRootDir()
    {
        return __DIR__;
    }

    public function getCacheDir()
    {
        return dirname(__DIR__).'/var/cache/'.$this->getEnvironment();
    }

    public function getLogDir()
    {
        return dirname(__DIR__).'/var/logs';
    }

    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
    }
}

config.yml

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: services.yml }

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
    locale: it

framework:
    #esi: ~
    translator: { fallbacks: ['%locale%'] }
    secret: '%secret%'
    router:
        resource: '%kernel.project_dir%/app/config/routing.yml'
        strict_requirements: ~
    form: ~
    csrf_protection: ~
    validation: { enable_annotations: true }
    #serializer: { enable_annotations: true }
    templating:
        engines: ['twig']
    default_locale: '%locale%'
    trusted_hosts: ~
    session:
        # https://symfony.com/doc/current/reference/configuration/framework.html#handler-id
        handler_id: session.handler.native_file
        save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
    fragments: ~
    http_method_override: true
    assets: ~
    php_errors:
        log: true

# Twig Configuration
twig:
    debug: '%kernel.debug%'
    strict_variables: '%kernel.debug%'

# Doctrine Configuration
doctrine:
    dbal:
        driver: pdo_mysql
        host: '%database_host%'
        port: '%database_port%'
        dbname: '%database_name%'
        user: '%database_user%'
        password: '%database_password%'
        charset: UTF8
        # if using pdo_sqlite as your database driver:
        #   1. add the path in parameters.yml
        #     e.g. database_path: "%kernel.project_dir%/var/data/data.sqlite"
        #   2. Uncomment database_path in parameters.yml.dist
        #   3. Uncomment next line:
        #path: '%database_path%'

    orm:
        auto_generate_proxy_classes: '%kernel.debug%'
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
#        mappings:
#            # Doctrine extensions
#            translatable:
#                type: annotation
#                alias: Gedmo
#                prefix: Gedmo\Translatable\Entity
#                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity/MappedSuperclass"

# Swiftmailer Configuration
swiftmailer:
    transport: '%mailer_transport%'
    host: '%mailer_host%'
    username: '%mailer_user%'
    password: '%mailer_password%'
    spool: { type: memory }

sonata_block:
    default_contexts: [cms]
    blocks:
        # enable the SonataAdminBundle block
        sonata.admin.block.admin_list:
            contexts: [admin]

sonata_translation:
    locales: [it, en]
    default_locale: %locale%
    # here enable the types you need
    gedmo:
         enabled: true
#    knplabs:
#        enabled: true
    #phpcr:
    #    enabled: true

sonata_admin:
    templates:
        layout: admin/layout.html.twig

assetic:
    debug:          '%kernel.debug%'
    use_controller: '%kernel.debug%'
    filters:
        cssrewrite: ~

#stof_doctrine_extensions:
#    #default_locale: %locale%
#    orm:
#        default:
#            sluggable: true
#            timestampable: true

services.yml

# Learn more about services, parameters and containers at
# https://symfony.com/doc/current/service_container.html
parameters:
    locale: 'it'
    locales: ['it', 'en']

services:
    # default configuration for services in *this* file
    _defaults:
        # automatically injects dependencies in your services
        autowire: true
        # automatically registers your services as commands, event subscribers, etc.
        autoconfigure: true
        # this means you cannot fetch services directly from the container via $container->get()
        # if you need to do this, you can override this setting on individual services
        public: false

    # makes classes in src/AppBundle available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    AppBundle\:
        resource: '../../src/AppBundle/*'
        # you can exclude directories or files
        # but if a service is unused, it's removed anyway
        exclude: '../../src/AppBundle/{Entity,Repository,Tests}'

    # controllers are imported separately to make sure they're public
    # and have a tag that allows actions to type-hint services
    AppBundle\Controller\:
        resource: '../../src/AppBundle/Controller'
        public: true
        tags: ['controller.service_arguments']

    # add more services, or override services that need manual wiring
    # AppBundle\Service\ExampleService:
    #     arguments:
    #         $someArgument: 'some_value'

    admin.category:
            class: AppBundle\Admin\CategoryAdmin
            arguments: [~, AppBundle\Entity\Category, ~]
            tags:
                - { name: sonata.admin, manager_type: orm, label: Category }
            public: true

    admin.blog_post:
        class: AppBundle\Admin\BlogPostAdmin
        arguments: [~, AppBundle\Entity\BlogPost, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: Blog post }
        public: true


    # Doctrine Extension listeners to handle behaviors
    gedmo.listener.translatable:
        class: Gedmo\Translatable\TranslatableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            #- [ setAnnotationReader, [ @annotation_reader ] ]
            - [ setDefaultLocale, [ it ] ]
            - [ setTranslationFallback, [ false ] ]
            - [ setPersistDefaultLocaleTranslation, [ false ] ]

BlogPost.php

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sonata\TranslationBundle\Model\Gedmo\AbstractPersonalTranslatable;
use Gedmo\Mapping\Annotation as Gedmo;
use Sonata\TranslationBundle\Model\Gedmo\TranslatableInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Sonata\TranslationBundle\Model\Gedmo\AbstractPersonalTranslation;
use Sonata\TranslationBundle\Traits\Gedmo\PersonalTranslatableTrait;

/**
 * BlogPost
 *
 * @ORM\Table(name="blog_post")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\BlogPostRepository")
 * @Gedmo\TranslationEntity(class="AppBundle\Entity\Translations\BlogPostTr")
 * @ORM\HasLifecycleCallbacks
 */

class BlogPost implements TranslatableInterface
{
    use PersonalTranslatableTrait;

    /**
     * Post locale
     * Used locale to override Translation listener's locale
     *
     * @Gedmo\Locale
     */
    protected $locale;

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="blogPosts")
     */
    private $category;

    public function setCategory(Category $category)
    {
        $this->category = $category;
    }

    public function getCategory()
    {
        return $this->category;
    }

    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     * @Gedmo\Translatable
     */
    private $title;

    /**
     * @var string
     *
     * @ORM\Column(name="body", type="text")
     * @Gedmo\Translatable
     */
    private $body;

    /**
     * @var bool
     *
     * @ORM\Column(name="draft", type="boolean")
     */
    private $draft = false;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     *
     * @return BlogPost
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set body
     *
     * @param string $body
     *
     * @return BlogPost
     */
    public function setBody($body)
    {
        $this->body = $body;

        return $this;
    }

    /**
     * Get body
     *
     * @return string
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * Set draft
     *
     * @param boolean $draft
     *
     * @return BlogPost
     */
    public function setDraft($draft)
    {
        $this->draft = $draft;

        return $this;
    }

    /**
     * Get draft
     *
     * @return bool
     */
    public function getDraft()
    {
        return $this->draft;
    }

    // TRANSLATION
    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\Translations\BlogPostTr", mappedBy="object", cascade={"persist", "remove"})
     */
    protected $translations;


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

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

    public function addTranslation(AbstractPersonalTranslation $t)
    {
        $this->translations->add($t);
        $t->setObject($this);
    }

    public function removeTranslation(AbstractPersonalTranslation $t)
    {
        $this->translations->removeElement($t);
    }

    public function setTranslations($translations)
    {
        $this->translations = $translations;
    }

    /**
     * Sets translatable locale
     *
     * @param string $locale
     */
    public function setTranslatableLocale($locale)
    {
        $this->locale = $locale;
    }
}

BlogPostTr.php

<?php
namespace AppBundle\Entity\Translations;

use Doctrine\ORM\Mapping as ORM;
use Sonata\TranslationBundle\Model\Gedmo\AbstractPersonalTranslation;

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

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

BlogPostAdmin.php

<?php

namespace AppBundle\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;

class BlogPostAdmin extends AbstractAdmin
{
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->tab('Post')
            ->with('Content', array('class' => 'col-md-9'))
            ->add('title', 'text')
//            ->add('title', 'translatable_field', array(
//                'allow_extra_fields' => true,
//                'field' => 'title',
//                'personal_translation' => 'AppBundle\Entity\Translations\BlogPostTr',
//                'property_path' => 'translations',
//            ))
            ->add('body', 'textarea')
            ->end()
            ->end()
            ->tab('Publishing options')
            ->with('Meta data', array('class' => 'col-md-3'))
            ->add('category', 'sonata_type_model', array(
                'class' => 'AppBundle\Entity\Category',
                'property' => 'name',
            ))
            ->end()
            ->end();
    }





//    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
//    {
//        $datagridMapper->add('title');
//    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper->addIdentifier('title');
    }

    public function toString($object)
    {
        return $object instanceof BlogPost
            ? $object->getTitle()
            : 'Blog Post'; // shown in the breadcrumb on the create view
    }
}

请帮忙!

1 个答案:

答案 0 :(得分:2)

有同样的问题。其实我有两个问题:

  1. 实体未实现Sonata \ TranslationBundle \ Model \ Gedmo \ TranslatableInterface。即使稍后添加,也需要清除symfony缓存。

  2. 我发现在Sonata Admin安装或奏鸣曲包之一中我添加了DoctrineExtensionListener,如shown here。在听众中,Gedmo启动了当前的语言环境,当然它对SonataTranslateBundle一无所知。

  3. 所以我建议从请求参数,sonata翻译语言环境和gedmo启动的语言环境中转储(调试)symfony语言环境,并检查这些是否已同步。

    最后我将语言环境设置为stored in user session并更新了DoctrineExtensionListener以使用会话中的语言环境。