3. Translate Doctrine ORM models

You can either use Gedmo Doctrine Extensions or KnpLabs Doctrine Behaviors.

3.1. Using Gedmo Doctrine Extensions

Doctrine ORM models translations are handled by Gedmo translatable extension.

Gedmo has two ways to handle translations.

Either everything is saved in a unique table, this is easier to set up but can lead to bad performance if your project grows or it can have one translation table for every model table. This second way is called personal translation.

3.1.1. Implement Translatable

First step, your entities have to implement GedmoTranslatableTranslatable.

3.1.2. Define translatable Fields

Please check the docs in the Gedmo translatable documentation.

3.1.2.1. Example using Personal Translation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// src/Entity/FAQCategory.php

namespace Presta\CMSFAQBundle\Entity;

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

/**
 * @ORM\Table(name="presta_cms_faq_category")
 * @ORM\Entity(repositoryClass="Presta\CMSFAQBundle\Entity\FAQCategory\Repository")
 * @Gedmo\TranslationEntity(class="Presta\CMSFAQBundle\Entity\FAQCategory\Translation")
 */
class FAQCategory implements Translatable
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

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

    /**
     * @var integer $position
     *
     * @ORM\Column(name="position", type="integer", length=2, nullable=true)
     */
    private $position;

    /**
     * @var ArrayCollection
     *
     * @ORM\OneToMany(
     *     targetEntity="Presta\CMSFAQBundle\Entity\FAQCategory\Translation",
     *     mappedBy="object",
     *     cascade={"persist", "remove"}
     * )
     */
    protected $translations;

    // ...
}

3.1.3. Define your translation Table

This step is optional, but if you choose Personal Translation, you have to make a translation class to handle it.

3.1.3.1. Example for translation class for Personal Translation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Entity/FAQCategory/Translation.php

namespace Presta\CMSFAQBundle\Entity\FAQCategory;

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

/**
 * @ORM\Entity
 * @ORM\Table(name="presta_cms_faq_category_translation",
 *     uniqueConstraints={@ORM\UniqueConstraint(name="lookup_unique_faq_category_translation_idx", columns={
 *         "locale", "object_id", "field"
 *     })}
 * )
 */
class Translation extends AbstractPersonalTranslation
{
    /**
     * @ORM\ManyToOne(targetEntity="Presta\CMSFAQBundle\Entity\FAQCategory", inversedBy="translations")
     * @ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
     */
    protected $object;
}

3.1.4. Configure search filter

This step is optional, but you can use the TranslationFieldFilter::class filter to search on fields and on their translations. Depending on whether you choose to use KnpLabs or Gedmo, you should configure the default_filter_mode in the configuration. You can also configure how the filtering logic should work on a per-field basis by specifying an option named filter_mode on your field. An enumeration exposes the two supported modes: TranslationFilterMode::GEDMO and TranslationFilterMode::KNPLABS

3.1.4.1. Example for configure search filter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
namespace App\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\TranslationBundle\Filter\TranslationFieldFilter;
use Sonata\TranslationBundle\Enum\TranslationFilterMode;

final class FAQCategoryAdmin extends AbstractAdmin
{
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('title', TranslationFieldFilter::class, [
                // if not specified, it will default to the value
                // you set in `default_filter_mode`
                'filter_mode' => TranslationFilterMode::KNPLABS
            ]);
    }

3.2. Using KnpLabs Doctrine Behaviors

Due to Sonata internals, the magic method of Doctrine Behavior does not work. For more background on that topic, see this post:

// src/Entity/TranslatableEntity.php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait;

/**
 * @ORM\Table(name="app_translatable_entity")
 * @ORM\Entity()
 */
class TranslatableEntity implements TranslatableInterface
{
    use TranslatableTrait;

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

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=255)
     */
    private $nonTranslatedField;

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

    /**
     * @return string
     */
    public function getNonTranslatableField()
    {
        return $this->nonTranslatedField;
    }

    /**
     * @param string $nonTranslatedField
     *
     * @return TranslatableEntity
     */
    public function setNonTranslatableField($nonTranslatedField)
    {
        $this->nonTranslatedField = $nonTranslatedField;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->translate(null, false)->getName();
    }

    /**
     * @param string $name
     */
    public function setName($name)
    {
        $this->translate(null, false)->setName($name);

        return $this;
    }
}

3.2.1. Define your translation table

Please refer to KnpLabs Doctrine2 Behaviors Documentation.

Here is an example:

// src/Entity/TranslatableEntityTranslation.php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\TranslationInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslationTrait;

/**
 * @ORM\Entity
 */
class TranslatableEntityTranslation implements TranslationInterface
{
    use TranslationTrait;

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

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

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param string $name
     *
     * @return TranslatableEntityTranslation
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }
}