Skip to content

Commit ce5ea58

Browse files
fix(dependency-injection):: map Doctrine type ‘tuuid’ to itself instead of ‘guid’ and fix PHPStan issues
1 parent 66efbc6 commit ce5ea58

13 files changed

Lines changed: 98 additions & 61 deletions

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@ tmi_translation:
6868
# disabled_firewalls: ['main'] # Optional: disable filter for specific firewalls
6969
```
7070

71+
### Doctrine DBAL Custom Type - TuuidType
72+
73+
To use the `TuuidType` in your Symfony project, you must register it in your Doctrine configuration:
74+
75+
```yaml
76+
# config/packages/doctrine.yaml
77+
doctrine:
78+
dbal:
79+
types:
80+
tuuid: Tmi\TranslationBundle\Doctrine\Type\TuuidType
81+
```
82+
This ensures that Doctrine recognizes the tuuid type and avoids errors like:
83+
```pgsql
84+
Unknown column type "tuuid" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType().
85+
```
7186
## 🚀 Quick Start
7287

7388
### Make your entity translatable

src/DependencyInjection/TmiTranslationExtension.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,19 @@ public function load(array $configs, ContainerBuilder $container): void
3030
$container->setParameter($rootName, $config);
3131
$this->setConfigAsParameters($container, $config, $rootName);
3232

33-
// Register Doctrine Type for Tuuid if not already registered
33+
// Register Doctrine Type for Tuuid
3434
if (!Type::hasType(TuuidType::NAME)) {
35+
// In DBAL >3 you can't easily unregister, so we skip
3536
// @codeCoverageIgnoreStart
3637
Type::addType(TuuidType::NAME, TuuidType::class);
3738
// @codeCoverageIgnoreEnd
3839
}
3940

40-
// Safely map 'tuuid' to 'guid' for all platforms
41+
// Safely map 'tuuid' to 'tuuid' for all platforms
4142
if ($container->has('doctrine.dbal.default_connection')) {
4243
$connection = $container->get('doctrine.dbal.default_connection');
43-
$platform = $connection->getDatabasePlatform();
44-
$platform->registerDoctrineTypeMapping('tuuid', 'guid');
44+
$platform = $connection->getDatabasePlatform();
45+
$platform->registerDoctrineTypeMapping('tuuid', 'tuuid');
4546
}
4647

4748
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));

src/Translation/EntityTranslator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public function processTranslation(TranslationArgs $args): mixed
8888
$cacheKey = null;
8989

9090
$tuuid = $entity->getTuuid();
91-
if ($tuuid instanceof Tuuid && !empty($tuuid->getValue())) {
91+
if ($tuuid instanceof Tuuid && '' !== $tuuid->getValue()) {
9292
$cacheKey = $tuuid->getValue().':'.$locale;
9393
}
9494

@@ -246,7 +246,7 @@ private function warmupTranslations(array $entities, string $locale): void
246246

247247
/** @var class-string<TranslatableInterface> $class */
248248
foreach ($byClass as $class => $tuuids) {
249-
if (empty($tuuids)) {
249+
if (!is_array($tuuids) || 0 === count($tuuids)) {
250250
// @codeCoverageIgnoreStart
251251
continue;
252252
// @codeCoverageIgnoreEnd

src/Translation/Handlers/BidirectionalManyToManyHandler.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ public function handleEmptyOnTranslate(TranslationArgs $args): Collection
105105

106106
$newOwner = $args->getTranslatedParent();
107107

108-
$prop = $args->getProperty() ?? ($newOwner ? $this->discoverProperty($newOwner, $collection) : null);
108+
$prop = $args->getProperty() ?? (null !== $newOwner ? $this->discoverProperty($newOwner, $collection) : null);
109109

110-
if ($newOwner && $prop) {
110+
if (null !== $newOwner && $prop) {
111111
try {
112112
$prop->setValue($newOwner, new ArrayCollection());
113113
} catch (\Throwable) {
@@ -130,7 +130,7 @@ public function translate(TranslationArgs $args): Collection
130130
}
131131

132132
$newOwner = $args->getTranslatedParent();
133-
$prop = $args->getProperty() ?? ($newOwner ? $this->discoverProperty($newOwner, $collection) : null);
133+
$prop = $args->getProperty() ?? (null !== $newOwner ? $this->discoverProperty($newOwner, $collection) : null);
134134

135135
if (null === $newOwner || null === $prop) {
136136
return new ArrayCollection($collection->toArray());

src/Translation/Handlers/BidirectionalManyToOneHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function supports(TranslationArgs $args): bool
4545
}
4646

4747
$attributes = $property->getAttributes(ManyToOne::class);
48-
if (empty($attributes)) {
48+
if (0 === count($attributes)) {
4949
return false;
5050
}
5151

src/Translation/Handlers/BidirectionalOneToManyHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function supports(TranslationArgs $args): bool
4949
}
5050

5151
$attributes = $property->getAttributes(OneToMany::class);
52-
if (empty($attributes)) {
52+
if (0 === count($attributes)) {
5353
return false;
5454
}
5555

@@ -88,7 +88,7 @@ public function translate(TranslationArgs $args): Collection
8888
$property = $args->getProperty();
8989

9090
// Guard: must have both property and translated parent
91-
if (!$translatedParent || !$property) {
91+
if (null === $translatedParent || null === $property) {
9292
return $children; // nothing to translate → return original
9393
}
9494

src/Translation/Handlers/BidirectionalOneToOneHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function supports(TranslationArgs $args): bool
3939
}
4040

4141
$attributes = $property->getAttributes(OneToOne::class);
42-
if (empty($attributes)) {
42+
if (0 === count($attributes)) {
4343
return false;
4444
}
4545

src/Translation/Handlers/EmbeddedHandler.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,15 @@ public function handleEmptyOnTranslate(TranslationArgs $args): mixed
8989
// Only clear properties marked as #[EmptyOnTranslate]
9090
if ($this->attributeHelper->isEmptyOnTranslate($prop)) {
9191
$setter = 'set'.ucfirst($prop->getName());
92+
9293
if (method_exists($clone, $setter)) {
93-
$clone->$setter(null);
94+
$callable = \Closure::fromCallable([$clone, $setter]);
95+
$callable(null);
9496
} else {
95-
// fallback in case no setter exists
97+
// Fallback: set value via ReflectionProperty
9698
$prop->setValue($clone, null);
9799
}
100+
98101
$changed = true;
99102
}
100103
}

src/Translation/Handlers/UnidirectionalManyToManyHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public function handleSharedAmongstTranslations(TranslationArgs $args): Collecti
6666

6767
// Check for SharedAmongstTranslations attribute
6868
$sharedAttrs = $prop->getAttributes(SharedAmongstTranslations::class);
69-
if (!empty($sharedAttrs)) {
69+
if (count($sharedAttrs) > 0) {
7070
throw new \RuntimeException(sprintf('SharedAmongstTranslations is not allowed on unidirectional ManyToMany associations. Property "%s" of class "%s" is invalid.', $prop->getName(), $args->getDataToBeTranslated()::class));
7171
}
7272

tests/DependencyInjection/TmiTranslationExtensionTest.php

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,17 @@
55
namespace Tmi\TranslationBundle\Test\DependencyInjection;
66

77
use Doctrine\DBAL\Exception;
8+
use Doctrine\DBAL\Platforms\AbstractPlatform;
89
use Doctrine\DBAL\Types\Exception\TypesException;
910
use Doctrine\DBAL\Types\Type;
11+
use Symfony\Component\DependencyInjection\ContainerBuilder;
1012
use Tmi\TranslationBundle\DependencyInjection\TmiTranslationExtension;
1113
use Tmi\TranslationBundle\Doctrine\Type\TuuidType;
1214
use Tmi\TranslationBundle\EventSubscriber\LocaleFilterConfigurator;
1315
use Tmi\TranslationBundle\Test\IntegrationTestCase;
14-
use Symfony\Component\DependencyInjection\ContainerBuilder;
15-
use Doctrine\DBAL\Platforms\AbstractPlatform;
1616

1717
final class TmiTranslationExtensionTest extends IntegrationTestCase
1818
{
19-
private function createContainerBuilderFromKernel(): ContainerBuilder
20-
{
21-
$containerBuilder = new ContainerBuilder();
22-
23-
// Pull parameters from the booted kernel container if available
24-
if (self::$container === null) {
25-
self::bootKernel();
26-
self::$container = method_exists(self::class, 'getContainer')
27-
? self::getContainer()
28-
: self::$kernel->getContainer();
29-
}
30-
31-
if (self::$container !== null) {
32-
foreach (self::$container->getParameterBag()->all() as $key => $value) {
33-
$containerBuilder->setParameter($key, $value);
34-
}
35-
}
36-
37-
return $containerBuilder;
38-
}
39-
40-
4119
/**
4220
* @throws Exception
4321
* @throws TypesException
@@ -47,10 +25,10 @@ public function testLoadRegistersServices(): void
4725
$containerBuilder = $this->createContainerBuilderFromKernel();
4826

4927
$extension = new TmiTranslationExtension();
50-
$config = [
28+
$config = [
5129
[
52-
'locales' => ['en_US', 'de_DE', 'it_IT'],
53-
'default_locale' => 'en_US',
30+
'locales' => ['en_US', 'de_DE', 'it_IT'],
31+
'default_locale' => 'en_US',
5432
'disabled_firewalls' => ['main'],
5533
],
5634
];
@@ -65,7 +43,7 @@ public function testLoadRegistersServices(): void
6543
public function testPrependDoesNothing(): void
6644
{
6745
$containerBuilder = $this->createContainerBuilderFromKernel();
68-
$extension = new TmiTranslationExtension();
46+
$extension = new TmiTranslationExtension();
6947
$extension->prepend($containerBuilder);
7048

7149
$this->assertInstanceOf(ContainerBuilder::class, $containerBuilder);
@@ -84,7 +62,7 @@ public function testTuuidTypeIsRegistered(): void
8462
}
8563

8664
$containerBuilder = $this->createContainerBuilderFromKernel();
87-
$extension = new TmiTranslationExtension();
65+
$extension = new TmiTranslationExtension();
8866
$extension->load([['locales' => ['en_US', 'de_DE'], 'default_locale' => 'en_US']], $containerBuilder);
8967

9068
$this->assertTrue(Type::hasType(TuuidType::NAME));
@@ -102,12 +80,18 @@ public function testTuuidTypeMapping(): void
10280
// Create a fake DBAL platform stub
10381
$platformStub = $this->createMock(AbstractPlatform::class);
10482
$platformStub->method('registerDoctrineTypeMapping')
105-
->with('tuuid', 'guid');
83+
->with('tuuid', 'tuuid');
10684

10785
// Create a fake connection stub
10886
$connectionStub = new readonly class($platformStub) {
109-
public function __construct(private AbstractPlatform $platform) {}
110-
public function getDatabasePlatform(): AbstractPlatform { return $this->platform; }
87+
public function __construct(private AbstractPlatform $platform)
88+
{
89+
}
90+
91+
public function getDatabasePlatform(): AbstractPlatform
92+
{
93+
return $this->platform;
94+
}
11195
};
11296

11397
// Register the fake connection in the container
@@ -120,4 +104,25 @@ public function getDatabasePlatform(): AbstractPlatform { return $this->platform
120104
$this->assertTrue(Type::hasType(TuuidType::NAME), 'TuuidType should be registered');
121105
$this->assertInstanceOf(TuuidType::class, Type::getType(TuuidType::NAME));
122106
}
107+
108+
private function createContainerBuilderFromKernel(): ContainerBuilder
109+
{
110+
$containerBuilder = new ContainerBuilder();
111+
112+
// Pull parameters from the booted kernel container if available
113+
if (null === self::$container) {
114+
self::bootKernel();
115+
self::$container = method_exists(self::class, 'getContainer')
116+
? self::getContainer()
117+
: self::$kernel->getContainer();
118+
}
119+
120+
if (null !== self::$container) {
121+
foreach (self::$container->getParameterBag()->all() as $key => $value) {
122+
$containerBuilder->setParameter($key, $value);
123+
}
124+
}
125+
126+
return $containerBuilder;
127+
}
123128
}

0 commit comments

Comments
 (0)