Symfony 3 上的 Sonata Admin 实体翻译
Posted
技术标签:
【中文标题】Symfony 3 上的 Sonata Admin 实体翻译【英文标题】:Sonata Admin entities translation on Symfony 3 【发布时间】:2018-01-20 05:53:02 【问题描述】:有人在 Symfony 3 上成功翻译了 Sonata Admin 实体(实际上我使用的是 3.3)。
我尝试了不同的解决方案,但没有一个真正奏效。 使用 gedmo 翻译,主要问题是在数据库中保存了不同语言的翻译,但是在管理员中(也列出了结尾表单),尽管单击了不同的标志/翻译,但 Sonata 包仅显示默认的语言环境翻译/选择。
我也尝试使用 KNP tarnslation 捆绑包和 A2lix 翻译,但是这两个有相同的问题:当您(在管理类中)将一个字段设置为“可排序”时,然后在您尝试排序时在记录列表中通过该字段,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:
cs-s-rewrite: ~
#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】:有同样的问题。其实我有两个问题:
实体未实现 Sonata\TranslationBundle\Model\Gedmo\TranslatableInterface。并且即使稍后添加,symfony 缓存也需要清除。
我发现在 Sonata Admin 安装或奏鸣曲捆绑包之一期间,我添加了像 shown here 这样的 DoctrineExtensionListener。在该侦听器内部,Gedmo 启动当前语言环境,当然它对 SonataTranslateBundle 一无所知。
所以我建议从请求参数、奏鸣曲翻译语言环境和 gedmo 启动的语言环境中转储(调试)symfony 语言环境,并检查它们是否同步。
最后,我将语言环境设置为 stored in user session,并更新了 DoctrineExtensionListener 以使用会话中的语言环境。
【讨论】:
以上是关于Symfony 3 上的 Sonata Admin 实体翻译的主要内容,如果未能解决你的问题,请参考以下文章
Symfony 2.3.6 和 Sonata Admin Bundle:空仪表板,没有错误
在 Symfony 4 上的 Sonata 管理页面中创建一个新页面
Symfony 3 Sonata Admin 使用注释创建管理员
如何在 Sonata Admin(Symfony 3.3、PHP 7)中使用自定义 javascript 为模态窗口扩展模板?