Skip to content

Commit 38b85f4

Browse files
committed
feat(DoctrineEagerPropertiesResolver): Automatically identifies ID columns of a Doctrine entity.
1 parent 48b32f2 commit 38b85f4

File tree

13 files changed

+404
-18
lines changed

13 files changed

+404
-18
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* feat(`Profiler`): Show mapping table.
1616
* refactor(`MappingFactory`): Separate cache warmer from
1717
`WarmableMappingFactory`.
18+
* feat(`DoctrineEagerPropertiesResolver`): Automatically identifies ID columns
19+
of a Doctrine entity.
1820

1921
## 0.7.2
2022

composer.json

+9-5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
},
4040
"require-dev": {
4141
"bnf/phpstan-psr-container": "^1.0",
42+
"brick/money": "^0.9.0",
43+
"dave-liddament/php-language-extensions": "^0.6.0",
44+
"dave-liddament/phpstan-php-language-extensions": "^0.5.0",
45+
"doctrine/doctrine-bundle": "^2.4",
46+
"doctrine/orm": "^2.14",
47+
"doctrine/persistence": "^2.0 || ^3.0",
4248
"ekino/phpstan-banned-code": "^1.0",
4349
"phpstan/phpstan": "^1.10.50",
4450
"phpstan/phpstan-deprecation-rules": "^1.1",
@@ -50,12 +56,10 @@
5056
"symfony/phpunit-bridge": "^6.4 || ^7.0",
5157
"symfony/uid": "^6.4 || ^7.0",
5258
"symfony/var-dumper": "^6.4 || ^7.0",
53-
"vimeo/psalm": "^5.18",
54-
"dave-liddament/php-language-extensions": "^0.6.0",
55-
"dave-liddament/phpstan-php-language-extensions": "^0.5.0",
56-
"brick/money": "^0.9.0",
59+
"symfony/yaml": "^6.4 || ^7.0",
5760
"tomasvotruba/unused-public": "^0.3.5",
58-
"twig/twig": "^2.12|^3.0"
61+
"twig/twig": "^2.12|^3.0",
62+
"vimeo/psalm": "^5.18"
5963
},
6064
"autoload": {
6165
"psr-4": {

config/services.php

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Rekalogika\Mapper\Transformer\CopyTransformer;
3636
use Rekalogika\Mapper\Transformer\DateTimeTransformer;
3737
use Rekalogika\Mapper\Transformer\EagerPropertiesResolver\Implementation\ChainEagerPropertiesResolver;
38+
use Rekalogika\Mapper\Transformer\EagerPropertiesResolver\Implementation\DoctrineEagerPropertiesResolver;
3839
use Rekalogika\Mapper\Transformer\EagerPropertiesResolver\Implementation\HeuristicsEagerPropertiesResolver;
3940
use Rekalogika\Mapper\Transformer\NullTransformer;
4041
use Rekalogika\Mapper\Transformer\ObjectMapperTransformer;
@@ -366,6 +367,11 @@
366367
->set('rekalogika.mapper.eager_properties_resolver.heuristics', HeuristicsEagerPropertiesResolver::class)
367368
->tag('rekalogika.mapper.eager_properties_resolver', ['priority' => -1000]);
368369

370+
$services
371+
->set('rekalogika.mapper.eager_properties_resolver.doctrine', DoctrineEagerPropertiesResolver::class)
372+
->args([service('doctrine')])
373+
->tag('rekalogika.mapper.eager_properties_resolver', ['priority' => -500]);
374+
369375
# proxy
370376

371377
$services

src/DependencyInjection/CompilerPass/RemoveOptionalDefinitionPass.php

+4
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@ public function process(ContainerBuilder $container)
2525
if (!class_exists(UuidFactory::class)) {
2626
$container->removeDefinition(SymfonyUidTransformer::class);
2727
}
28+
29+
if (!$container->hasDefinition('doctrine')) {
30+
$container->removeDefinition('rekalogika.mapper.eager_properties_resolver.doctrine');
31+
}
2832
}
2933
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Transformer\EagerPropertiesResolver\Implementation;
15+
16+
use Doctrine\Persistence\ManagerRegistry;
17+
use Doctrine\Persistence\Mapping\MappingException;
18+
use Rekalogika\Mapper\Transformer\EagerPropertiesResolver\EagerPropertiesResolverInterface;
19+
20+
class DoctrineEagerPropertiesResolver implements EagerPropertiesResolverInterface
21+
{
22+
public function __construct(private ManagerRegistry $managerRegistry)
23+
{
24+
}
25+
26+
public function getEagerProperties(string $sourceClass): array
27+
{
28+
$manager = $this->managerRegistry->getManagerForClass($sourceClass);
29+
30+
if (!$manager) {
31+
return [];
32+
}
33+
34+
try {
35+
$metadata = $manager->getClassMetadata($sourceClass);
36+
} catch (\ReflectionException | MappingException) {
37+
return [];
38+
}
39+
40+
$identifiers = $metadata->getIdentifierFieldNames();
41+
42+
return $identifiers;
43+
}
44+
}

tests/Common/AbstractFrameworkTest.php

+28
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
namespace Rekalogika\Mapper\Tests\Common;
1515

16+
use Doctrine\ORM\EntityManagerInterface;
17+
use Doctrine\ORM\Tools\SchemaTool;
18+
use Doctrine\Persistence\ManagerRegistry;
1619
use PHPUnit\Framework\TestCase;
1720
use Rekalogika\Mapper\Debug\TraceableTransformer;
1821
use Rekalogika\Mapper\MapperInterface;
@@ -77,4 +80,29 @@ protected function initialize(object $object): void
7780
$object->initializeLazyObject();
7881
}
7982
}
83+
84+
private ?EntityManagerInterface $entityManager = null;
85+
86+
private function doctrineInit(): EntityManagerInterface
87+
{
88+
$managerRegistry = $this->get('doctrine');
89+
$this->assertInstanceOf(ManagerRegistry::class, $managerRegistry);
90+
91+
$entityManager = $managerRegistry->getManager();
92+
$this->assertInstanceOf(EntityManagerInterface::class, $entityManager);
93+
94+
$schemaTool = new SchemaTool($entityManager);
95+
$schemaTool->createSchema($entityManager->getMetadataFactory()->getAllMetadata());
96+
97+
return $entityManager;
98+
}
99+
100+
public function getEntityManager(): EntityManagerInterface
101+
{
102+
if ($this->entityManager !== null) {
103+
return $this->entityManager;
104+
}
105+
106+
return $this->entityManager = $this->doctrineInit();
107+
}
80108
}

tests/Common/TestKernel.php

+9-13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace Rekalogika\Mapper\Tests\Common;
1515

16+
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
1617
use Rekalogika\Mapper\MapperInterface;
1718
use Rekalogika\Mapper\Mapping\MappingFactoryInterface;
1819
use Rekalogika\Mapper\RekalogikaMapperBundle;
@@ -48,25 +49,16 @@ public function __construct(private array $config = [])
4849
public function registerBundles(): iterable
4950
{
5051
yield new FrameworkBundle();
52+
yield new DoctrineBundle();
5153
yield new RekalogikaMapperBundle();
5254
}
5355

5456
public function registerContainerConfiguration(LoaderInterface $loader): void
5557
{
56-
$loader->load(function (ContainerBuilder $container) {
57-
$container->setParameter('kernel.secret', 'test');
58-
$container->loadFromExtension('framework', [
59-
'http_method_override' => false,
60-
'handle_all_throwables' => true,
61-
'php_errors' => [
62-
'log' => true,
63-
],
64-
'uid' => [
65-
'default_uuid_version' => 7,
66-
'time_based_uuid_version' => 7,
67-
]
68-
]);
58+
$confDir = $this->getProjectDir() . '/tests/Resources/';
59+
$loader->load($confDir . '*' . '.yaml', 'glob');
6960

61+
$loader->load(function (ContainerBuilder $container) {
7062
$container->loadFromExtension('rekalogika_mapper', $this->config);
7163
});
7264
}
@@ -132,5 +124,9 @@ public static function getServiceIds(): iterable
132124
yield 'rekalogika.mapper.command.try_property';
133125

134126
yield 'rekalogika.mapper.data_collector';
127+
128+
yield 'rekalogika.mapper.eager_properties_resolver';
129+
yield 'rekalogika.mapper.eager_properties_resolver.heuristics';
130+
yield 'rekalogika.mapper.eager_properties_resolver.doctrine';
135131
}
136132
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Tests\Fixtures\Doctrine;
15+
16+
use Doctrine\ORM\Mapping as ORM;
17+
18+
#[ORM\Entity]
19+
class EntityWithMultipleIdentifier
20+
{
21+
#[ORM\Id]
22+
#[ORM\Column(name: 'id1', type: 'string', length: 255)]
23+
private string $id1;
24+
25+
#[ORM\Id]
26+
#[ORM\Column(name: 'id2', type: 'string', length: 255)]
27+
private string $id2;
28+
29+
#[ORM\Column]
30+
private string $name;
31+
32+
public function __construct(string $id1, string $id2, string $name)
33+
{
34+
$this->id1 = $id1;
35+
$this->id2 = $id2;
36+
$this->name = $name;
37+
}
38+
39+
public function getId1(): string
40+
{
41+
return $this->id1;
42+
}
43+
44+
public function getId2(): string
45+
{
46+
return $this->id2;
47+
}
48+
49+
public function getName(): string
50+
{
51+
return $this->name;
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Tests\Fixtures\Doctrine;
15+
16+
use Doctrine\Common\Collections\ArrayCollection;
17+
use Doctrine\Common\Collections\Collection;
18+
use Doctrine\ORM\Mapping as ORM;
19+
20+
#[ORM\Entity]
21+
class EntityWithSingleIdentifier
22+
{
23+
#[ORM\Id]
24+
#[ORM\Column(name: 'my_identifier', type: 'string', length: 255)]
25+
private string $myIdentifier;
26+
27+
#[ORM\Column]
28+
private string $name;
29+
30+
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
31+
#[ORM\JoinColumn(name: 'parent_id', referencedColumnName: 'my_identifier')]
32+
private ?self $parent = null;
33+
34+
/**
35+
* @var Collection<array-key,self>
36+
*/
37+
#[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')]
38+
private Collection $children;
39+
40+
public function __construct(string $myIdentifier, string $name)
41+
{
42+
$this->myIdentifier = $myIdentifier;
43+
$this->name = $name;
44+
$this->children = new ArrayCollection();
45+
}
46+
47+
public function getMyIdentifier(): string
48+
{
49+
return $this->myIdentifier;
50+
}
51+
52+
public function getName(): string
53+
{
54+
return $this->name;
55+
}
56+
57+
public function getParent(): ?self
58+
{
59+
return $this->parent;
60+
}
61+
62+
public function setParent(?self $parent): void
63+
{
64+
$this->parent = $parent;
65+
}
66+
67+
/**
68+
* @return Collection<array-key,self>
69+
*/
70+
public function getChildren(): Collection
71+
{
72+
return $this->children;
73+
}
74+
75+
public function addChild(self $child): void
76+
{
77+
if (!$this->children->contains($child)) {
78+
$this->children->add($child);
79+
$child->setParent($this);
80+
}
81+
}
82+
83+
public function removeChild(self $child): void
84+
{
85+
if ($this->children->removeElement($child)) {
86+
$child->setParent(null);
87+
}
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Tests\Fixtures\Doctrine;
15+
16+
class EntityWithSingleIdentifierDto
17+
{
18+
public ?string $myIdentifier = null;
19+
public ?string $name = null;
20+
public ?self $parent = null;
21+
22+
/**
23+
* @var array<int,self>
24+
*/
25+
public array $children = [];
26+
}

0 commit comments

Comments
 (0)