Skip to content

Commit d7e52a8

Browse files
Florian Bogeyfranmomu
authored andcommitted
[Translatable] fix Type error for non-nullable getter upon a missing translation
1 parent 529fdb3 commit d7e52a8

File tree

5 files changed

+223
-2
lines changed

5 files changed

+223
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ a release.
1919
---
2020

2121
## [Unreleased]
22+
### Added
23+
- Translatable: Add defaultTranslationValue option to allow null or string value (#2167). TranslatableListener can hydrate object properties with null value, but it may cause a Type error for non-nullable getter upon a missing translation.
24+
2225
### Fixed
2326
- Uploadable: `FileInfoInterface::getSize()` return type declaration (#2413).
2427
- Tree: Setting a new Tree Root when Tree Parent is `null`.

doc/translatable.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,15 @@ $translatableListener->setPersistDefaultLocaleTranslation(true); // default is f
609609
This would always store translations in all locales, also keeping original record
610610
translated field values in default locale set.
611611

612+
To set a default translation value upon a missing translation:
613+
614+
``` php
615+
<?php
616+
$translatableListener->setDefaultTranslationValue(''); // default is null
617+
```
618+
619+
**Note**: By default the value is null, but it may cause a Type error for non-nullable getter upon a missing translation.
620+
612621
### Translation Entity
613622

614623
In some cases if there are thousands of records or even more.. we would like to

src/Translatable/TranslatableListener.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ class TranslatableListener extends MappedEventSubscriber
119119
*/
120120
private $translationInDefaultLocale = [];
121121

122+
/**
123+
* Default translation value upon missing translation
124+
*
125+
* @var string|null
126+
*/
127+
private $defaultTranslationValue;
128+
122129
/**
123130
* Specifies the list of events to listen
124131
*
@@ -257,6 +264,17 @@ public function setTranslatableLocale($locale)
257264
return $this;
258265
}
259266

267+
/**
268+
* Set the default translation value on missing translation
269+
*
270+
* @deprecated usage of a non nullable value for defaultTranslationValue is deprecated
271+
* and will be removed on the next major release which will rely on the expected types
272+
*/
273+
public function setDefaultTranslationValue(?string $defaultTranslationValue): void
274+
{
275+
$this->defaultTranslationValue = $defaultTranslationValue;
276+
}
277+
260278
/**
261279
* Sets the default locale, this changes behavior
262280
* to not update the original record field if locale
@@ -483,16 +501,18 @@ public function postLoad(EventArgs $args)
483501
);
484502
// translate object's translatable properties
485503
foreach ($config['fields'] as $field) {
486-
$translated = null;
504+
$translated = $this->defaultTranslationValue;
505+
487506
foreach ($result as $entry) {
488507
if ($entry['field'] == $field) {
489508
$translated = $entry['content'] ?? null;
490509

491510
break;
492511
}
493512
}
513+
494514
// update translation
495-
if (null !== $translated
515+
if ($this->defaultTranslationValue !== $translated
496516
|| (!$this->translationFallback && (!isset($config['fallback'][$field]) || !$config['fallback'][$field]))
497517
|| ($this->translationFallback && isset($config['fallback'][$field]) && !$config['fallback'][$field])
498518
) {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Doctrine Behavioral Extensions package.
7+
* (c) Gediminas Morkevicius <[email protected]> http://www.gediminasm.org
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Gedmo\Tests\Translatable\Fixture\Issue2167;
13+
14+
use Doctrine\DBAL\Types\Types;
15+
use Doctrine\ORM\Mapping as ORM;
16+
use Gedmo\Mapping\Annotation as Gedmo;
17+
18+
/**
19+
* @ORM\Entity
20+
*/
21+
#[ORM\Entity]
22+
class Article
23+
{
24+
/**
25+
* @var int
26+
*
27+
* @ORM\Id
28+
* @ORM\GeneratedValue
29+
* @ORM\Column(type="integer")
30+
*/
31+
#[ORM\Id]
32+
#[ORM\GeneratedValue]
33+
#[ORM\Column(type: Types::INTEGER)]
34+
private $id;
35+
36+
/**
37+
* @var string
38+
*
39+
* @Gedmo\Translatable
40+
* @ORM\Column(name="title", type="string", length=128)
41+
*/
42+
#[Gedmo\Translatable]
43+
#[ORM\Column(name: 'title', type: Types::STRING, length: 128)]
44+
private $title;
45+
46+
/**
47+
* @var string
48+
*
49+
* @Gedmo\Locale()
50+
*/
51+
#[Gedmo\Locale]
52+
private $locale;
53+
54+
public function getId(): int
55+
{
56+
return $this->id;
57+
}
58+
59+
public function getTitle(): ?string
60+
{
61+
return $this->title;
62+
}
63+
64+
public function setTitle(string $title): void
65+
{
66+
$this->title = $title;
67+
}
68+
69+
public function getLocale(): string
70+
{
71+
return $this->locale;
72+
}
73+
74+
public function setLocale(string $locale): void
75+
{
76+
$this->locale = $locale;
77+
}
78+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Doctrine Behavioral Extensions package.
7+
* (c) Gediminas Morkevicius <[email protected]> http://www.gediminasm.org
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Gedmo\Translatable\Issue;
13+
14+
use Doctrine\Common\EventManager;
15+
use Gedmo\Tests\Tool\BaseTestCaseORM;
16+
use Gedmo\Tests\Translatable\Fixture\Issue2167\Article;
17+
use Gedmo\Translatable\Entity\Translation;
18+
use Gedmo\Translatable\TranslatableListener;
19+
20+
class Issue2167Test extends BaseTestCaseORM
21+
{
22+
private const TRANSLATION = Translation::class;
23+
private const ENTITY = Article::class;
24+
25+
/**
26+
* @var TranslatableListener
27+
*/
28+
private $translatableListener;
29+
30+
protected function setUp(): void
31+
{
32+
parent::setUp();
33+
34+
$evm = new EventManager();
35+
36+
$this->translatableListener = new TranslatableListener();
37+
$this->translatableListener->setTranslatableLocale('en');
38+
$this->translatableListener->setDefaultLocale('en');
39+
$this->translatableListener->setTranslationFallback(false);
40+
$evm->addEventSubscriber($this->translatableListener);
41+
42+
$this->getDefaultMockSqliteEntityManager($evm);
43+
}
44+
45+
public function testShouldFindInheritedClassTranslations(): void
46+
{
47+
$enTitle = 'My english title';
48+
$deTitle = 'My german title';
49+
50+
// English
51+
$entity = new Article();
52+
$entity->setTitle($enTitle);
53+
$entity->setLocale('en');
54+
$this->em->persist($entity);
55+
$this->em->flush();
56+
57+
// German
58+
$entity->setLocale('de');
59+
$entity->setTitle($deTitle);
60+
$this->em->flush();
61+
62+
// Find with default translation value as null value (default setting)
63+
$entityInEn = $this->findUsingQueryBuilder('en');
64+
$entityInDe = $this->findUsingQueryBuilder('de');
65+
$entityInFr = $this->findUsingQueryBuilder('fr');
66+
67+
static::assertSame($enTitle, $entityInEn->getTitle());
68+
static::assertSame($deTitle, $entityInDe->getTitle());
69+
static::assertNull($entityInFr->getTitle());
70+
71+
// Find with default translation value as empty string
72+
$this->translatableListener->setDefaultTranslationValue('');
73+
74+
$entityInEn = $this->findUsingQueryBuilder('en');
75+
$entityInDe = $this->findUsingQueryBuilder('de');
76+
$entityInFr = $this->findUsingQueryBuilder('fr');
77+
78+
static::assertSame($enTitle, $entityInEn->getTitle());
79+
static::assertSame($deTitle, $entityInDe->getTitle());
80+
static::assertSame('', $entityInFr->getTitle());
81+
82+
// Find with default translation value as not empty string
83+
$this->translatableListener->setDefaultTranslationValue('no_translated');
84+
85+
$entityInEn = $this->findUsingQueryBuilder('en');
86+
$entityInDe = $this->findUsingQueryBuilder('de');
87+
$entityInFr = $this->findUsingQueryBuilder('fr');
88+
89+
static::assertSame($enTitle, $entityInEn->getTitle());
90+
static::assertSame($deTitle, $entityInDe->getTitle());
91+
static::assertSame('no_translated', $entityInFr->getTitle());
92+
}
93+
94+
protected function getUsedEntityFixtures(): array
95+
{
96+
return [
97+
self::TRANSLATION,
98+
self::ENTITY,
99+
];
100+
}
101+
102+
private function findUsingQueryBuilder(string $locale): ?Article
103+
{
104+
$this->em->clear();
105+
$this->translatableListener->setTranslatableLocale($locale);
106+
107+
$qb = $this->em->createQueryBuilder()->select('e')->from(self::ENTITY, 'e');
108+
109+
return $qb->getQuery()->getSingleResult();
110+
}
111+
}

0 commit comments

Comments
 (0)