docs: Doctrine section with Doctrine Behaviors and Doctrine Helper (uk + en)
uk/docs/doctrine/doctrine-behaviors/translatable.md
новий файл +150
−0
1
+
# Translatable
2
+
3
+
Переклади полів сутності в окремій сутності перекладу — по одному рядку на локаль.
4
+
5
+
## Дві сутності
6
+
7
+
Для сутності `<X>` поведінка очікує сутність `<X>Translation` у тому самому просторі імен: `App\Entity\Category` і `App\Entity\CategoryTranslation`.
8
+
9
+
Сутність перекладу містить поля, що перекладаються, і власний ідентифікатор:
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
+
Сутність, що перекладається, містить лише поля без перекладу:
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
+
## Мапінг
57
+
58
+
| Сутність | Що додає бандл |
59
+
| --- | --- |
60
+
| `Category` | `OneToMany` `translations` → `CategoryTranslation`, індекс за `locale`, `cascade: persist, remove`, `orphanRemoval` |
61
+
| `CategoryTranslation` | `ManyToOne` `translatable` → `Category` (колонка `translatable_id`, `ON DELETE CASCADE`), рядок `locale` довжиною 5, унікальний індекс (`translatable_id`, `locale`) |
62
+
63
+
Унікальний індекс називається `<таблиця>_unique_translation`; якщо назва довша за 63 символи, з 2.0.0 замість неї використовується `t_<хеш>_unique_translation`. `#[ORM\MappedSuperclass]` пропускається; в ієрархії наслідування індекс отримує лише коренева сутність перекладу.
64
+
65
+
## Використання
66
+
67
+
```php
68
+
$category = new Category();
69
+
$category->translate('uk')->setName('Взуття');
70
+
$category->translate('en')->setName('Shoes');
71
+
72
+
$entityManager->persist($category);
73
+
$category->mergeNewTranslations(); // перед flush()
74
+
$entityManager->flush();
75
+
76
+
$category->translate('en')->getName(); // Shoes
77
+
$category->translate()->getName(); // поточна локаль
78
+
```
79
+
80
+
`translate($locale = null, $fallbackToDefault = true)`:
81
+
82
+
1. шукає непорожній переклад для `$locale` (без аргументу — поточна локаль);
83
+
2. якщо його немає і `$fallbackToDefault` — пробує локаль без регіону (`uk_UA` → `uk`), потім локаль за замовчуванням;
84
+
3. якщо нічого не знайдено — створює новий переклад і кладе його в `getNewTranslations()`.
85
+
86
+
Нові переклади потрапляють у `translations` (і в базу) лише після `mergeNewTranslations()`. Той самий метод прибирає порожні переклади — `isEmpty()` вважає порожнім переклад, у якого всі властивості, крім `id`, `translatable` і `locale`, порожні.
87
+
88
+
Інші методи: `getTranslations()`, `setTranslations(iterable)`, `addTranslation()`, `removeTranslation()`, `getCurrentLocale()` / `setCurrentLocale()`, `getDefaultLocale()` / `setDefaultLocale()`.
89
+
90
+
## Локалі
91
+
92
+
Під час `postLoad` і `prePersist` бандл ставить сутності поточну локаль і локаль за замовчуванням з `LocaleProviderInterface` (див. [Налаштування](/docs/doctrine/doctrine-behaviors/config)). Поза HTTP-запитом поточна локаль не задається і дорівнює локалі за замовчуванням; початкове значення локалі за замовчуванням у трейті — `en`.
93
+
94
+
:::warning
95
+
Колонка `locale` має довжину 5 — локалі на кшталт `sr_Latn` чи `zh_Hant` у неї не вміщаються. Див. [Відомі помилки](/docs/doctrine/doctrine-behaviors/known_issues/open).
96
+
:::
97
+
98
+
## Проксі до перекладу
99
+
100
+
`proxyCurrentLocaleTranslation()` викликає метод перекладу поточної локалі; зручно в `__call()`, щоб `$category->getName()` (або `category.name` у Twig) повертав перекладене значення:
101
+
102
+
```php
103
+
public function __call(string $method, array $arguments): mixed
104
+
{
105
+
return $this->proxyCurrentLocaleTranslation($method, $arguments);
106
+
}
107
+
```
108
+
109
+
## Інші назви класів
110
+
111
+
Щоб назвати сутність перекладу інакше або винести в інший простір імен, перевизначте обидва статичні методи — кожен має повертати клас іншої сторони:
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
+
## Стратегія завантаження
136
+
137
+
```yaml
138
+
# config/services.yaml
139
+
parameters:
140
+
doctrine_behaviors_translatable_fetch_mode: EXTRA_LAZY # колекція translations
141
+
doctrine_behaviors_translation_fetch_mode: LAZY # зв'язок translatable
142
+
```
143
+
144
+
| Значення | Поведінка Doctrine |
145
+
| --- | --- |
146
+
| `LAZY` | Завантажує переклади при першому зверненні (за замовчуванням). |
147
+
| `EAGER` | Завантажує разом із сутністю. |
148
+
| `EXTRA_LAZY` | `count()`, `contains()` і подібні виклики не завантажують усю колекцію. |
149
+
150
+
JOIN бандл не додає — змінюється лише стратегія завантаження.