docs: Doctrine section with Doctrine Behaviors and Doctrine Helper (uk + en)
en/docs/doctrine/doctrine-behaviors/translatable.md
new file +150
ā0
1
+
# Translatable
2
+
3
+
Translations of entity fields in a separate translation entity ā one row per locale.
4
+
5
+
## Two entities
6
+
7
+
For an entity `<X>` the behavior expects an `<X>Translation` entity in the same namespace: `App\Entity\Category` and `App\Entity\CategoryTranslation`.
8
+
9
+
The translation entity holds the translated fields and its own identifier:
10
+
11
+
```php
12
+
use Doctrine\ORM\Mapping as ORM;
13
+
use Ufo\DoctrineBehaviors\Contract\Entity\TranslationInterface;
14
+
use Ufo\DoctrineBehaviors\Model\Translatable\TranslationTrait;
15
+
16
+
#[ORM\Entity]
17
+
class CategoryTranslation implements TranslationInterface
18
+
{
19
+
use TranslationTrait;
20
+
21
+
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
22
+
private ?int $id = null;
23
+
24
+
#[ORM\Column(length: 255, nullable: true)]
25
+
private ?string $name = null;
26
+
27
+
public function getName(): ?string
28
+
{
29
+
return $this->name;
30
+
}
31
+
32
+
public function setName(string $name): void
33
+
{
34
+
$this->name = $name;
35
+
}
36
+
}
37
+
```
38
+
39
+
The translatable entity holds only the fields that are not translated:
40
+
41
+
```php
42
+
use Doctrine\ORM\Mapping as ORM;
43
+
use Ufo\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
44
+
use Ufo\DoctrineBehaviors\Model\Translatable\TranslatableTrait;
45
+
46
+
#[ORM\Entity]
47
+
class Category implements TranslatableInterface
48
+
{
49
+
use TranslatableTrait;
50
+
51
+
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
52
+
private ?int $id = null;
53
+
}
54
+
```
55
+
56
+
## Mapping
57
+
58
+
| Entity | What the bundle adds |
59
+
| --- | --- |
60
+
| `Category` | `OneToMany` `translations` ā `CategoryTranslation`, indexed by `locale`, `cascade: persist, remove`, `orphanRemoval` |
61
+
| `CategoryTranslation` | `ManyToOne` `translatable` ā `Category` (column `translatable_id`, `ON DELETE CASCADE`), a `locale` string of length 5, a unique index (`translatable_id`, `locale`) |
62
+
63
+
The unique index is named `<table>_unique_translation`; if the name is longer than 63 characters, since 2.0.0 `t_<hash>_unique_translation` is used instead. `#[ORM\MappedSuperclass]` is skipped; in an inheritance hierarchy only the root translation entity gets the index.
64
+
65
+
## Usage
66
+
67
+
```php
68
+
$category = new Category();
69
+
$category->translate('fr')->setName('Chaussures');
70
+
$category->translate('en')->setName('Shoes');
71
+
72
+
$entityManager->persist($category);
73
+
$category->mergeNewTranslations(); // before flush()
74
+
$entityManager->flush();
75
+
76
+
$category->translate('en')->getName(); // Shoes
77
+
$category->translate()->getName(); // current locale
78
+
```
79
+
80
+
`translate($locale = null, $fallbackToDefault = true)`:
81
+
82
+
1. looks for a non-empty translation for `$locale` (without an argument ā the current locale);
83
+
2. if there is none and `$fallbackToDefault` is set ā tries the locale without the region (`uk_UA` ā `uk`), then the default locale;
84
+
3. if nothing is found ā creates a new translation and puts it into `getNewTranslations()`.
85
+
86
+
New translations get into `translations` (and into the database) only after `mergeNewTranslations()`. The same method removes empty translations ā `isEmpty()` treats a translation as empty when all its properties except `id`, `translatable` and `locale` are empty.
87
+
88
+
Other methods: `getTranslations()`, `setTranslations(iterable)`, `addTranslation()`, `removeTranslation()`, `getCurrentLocale()` / `setCurrentLocale()`, `getDefaultLocale()` / `setDefaultLocale()`.
89
+
90
+
## Locales
91
+
92
+
On `postLoad` and `prePersist` the bundle sets the entity's current and default locale from `LocaleProviderInterface` (see [Configuration](/docs/doctrine/doctrine-behaviors/config)). Outside an HTTP request the current locale is not set and equals the default locale; the trait's initial default locale is `en`.
93
+
94
+
:::warning
95
+
The `locale` column has length 5 ā locales like `sr_Latn` or `zh_Hant` do not fit. See [Known issues](/docs/doctrine/doctrine-behaviors/known_issues/open).
96
+
:::
97
+
98
+
## Proxy to the translation
99
+
100
+
`proxyCurrentLocaleTranslation()` calls a method of the current locale's translation; handy in `__call()` so that `$category->getName()` (or `category.name` in Twig) returns the translated value:
101
+
102
+
```php
103
+
public function __call(string $method, array $arguments): mixed
104
+
{
105
+
return $this->proxyCurrentLocaleTranslation($method, $arguments);
106
+
}
107
+
```
108
+
109
+
## Other class names
110
+
111
+
To name the translation entity differently or move it to another namespace, override both static methods ā each must return the class of the other side:
112
+
113
+
```php
114
+
class Category implements TranslatableInterface
115
+
{
116
+
use TranslatableTrait;
117
+
118
+
public static function getTranslationEntityClass(): string
119
+
{
120
+
return \App\Entity\Translation\CategoryTranslation::class;
121
+
}
122
+
}
123
+
124
+
class CategoryTranslation implements TranslationInterface
125
+
{
126
+
use TranslationTrait;
127
+
128
+
public static function getTranslatableEntityClass(): string
129
+
{
130
+
return \App\Entity\Category::class;
131
+
}
132
+
}
133
+
```
134
+
135
+
## Fetch strategy
136
+
137
+
```yaml
138
+
# config/services.yaml
139
+
parameters:
140
+
doctrine_behaviors_translatable_fetch_mode: EXTRA_LAZY # translations collection
141
+
doctrine_behaviors_translation_fetch_mode: LAZY # translatable relation
142
+
```
143
+
144
+
| Value | Doctrine behavior |
145
+
| --- | --- |
146
+
| `LAZY` | Loads translations on first access (default). |
147
+
| `EAGER` | Loads them together with the entity. |
148
+
| `EXTRA_LAZY` | `count()`, `contains()` and similar calls do not load the whole collection. |
149
+
150
+
The bundle adds no JOINs ā only the fetch strategy changes.