Skip to content

Commit e095738

Browse files
committed
wip
1 parent fa376c9 commit e095738

14 files changed

+309
-239
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
namespace SoureCode\Bundle\DoctrineExtension\Attributes;
44

55
#[\Attribute(\Attribute::TARGET_PROPERTY)]
6-
final class SetOnCreation
6+
final class SetOnPersist
77
{
88
public function __construct(
9-
public ?string $type = null,
9+
public ?string $provider = null,
1010
) {
1111
}
1212
}

src/Attributes/SetOnUpdate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
final class SetOnUpdate
77
{
88
public function __construct(
9-
public ?string $type = null,
9+
public ?string $provider = null,
1010
) {
1111
}
1212
}

src/EventListener/CreationListener.php

Lines changed: 0 additions & 137 deletions
This file was deleted.
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<?php
2+
3+
namespace SoureCode\Bundle\DoctrineExtension\EventListener;
4+
5+
use Doctrine\Common\Collections\Expr\Value;
6+
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
7+
use Doctrine\ORM\Event\PrePersistEventArgs;
8+
use Doctrine\ORM\Event\PreUpdateEventArgs;
9+
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
10+
use SoureCode\Bundle\DoctrineExtension\Mapping\ClassMetadata;
11+
use SoureCode\Bundle\DoctrineExtension\Mapping\ClassMetadataFactory;
12+
use SoureCode\Bundle\DoctrineExtension\Provider\ValueProviderInterface;
13+
use Symfony\Component\DependencyInjection\ContainerInterface;
14+
use Symfony\Component\DependencyInjection\ServiceLocator;
15+
use Symfony\Component\Security\Core\User\UserInterface;
16+
use Symfony\Contracts\Service\ResetInterface;
17+
18+
/**
19+
* @phpstan-import-type PropertyCollectionMetadataType from ClassMetadata
20+
* @phpstan-import-type PropertyMetadataType from ClassMetadata
21+
*/
22+
final class PropertyListener implements ResetInterface
23+
{
24+
/**
25+
* @var array<class-string, ClassMetadata>
26+
*/
27+
private array $classMetadataCache = [];
28+
29+
/**
30+
* @var array<string, ValueProviderInterface>
31+
*/
32+
private array $valueProvidersCache = [];
33+
34+
public function __construct(
35+
/**
36+
* @var iterable<object>
37+
*/
38+
private readonly iterable $valueProviders,
39+
private readonly ClassMetadataFactory $classMetadataFactory,
40+
) {
41+
}
42+
43+
public function prePersist(PrePersistEventArgs $event): void
44+
{
45+
$object = $event->getObject();
46+
$objectManager = $event->getObjectManager();
47+
$unitOfWork = $objectManager->getUnitOfWork();
48+
$className = $object::class;
49+
$doctrineClassMetadata = $objectManager->getClassMetadata($className);
50+
$classMetadata = $this->classMetadataCache[$className] ??= $this->classMetadataFactory->create($className);
51+
52+
foreach ($classMetadata->persistProperties as $propertyName => $propertyMetadata) {
53+
$currentValue = $doctrineClassMetadata->getFieldValue($object, $propertyName);
54+
55+
if ($currentValue !== null) {
56+
continue;
57+
}
58+
59+
$valueProvider = $this->getValueProvider($propertyMetadata['provider']);
60+
$value = $valueProvider->provide($propertyMetadata['propertyType']);
61+
62+
$doctrineClassMetadata->setFieldValue($object, $propertyName, $value);
63+
$unitOfWork->propertyChanged($object, $propertyName, $currentValue, $value);
64+
}
65+
}
66+
67+
private function getValueProvider(string $type): ValueProviderInterface
68+
{
69+
return $this->valueProvidersCache[$type] ?? $this->doGetValueProvider($type);
70+
}
71+
72+
private function doGetValueProvider(string $type): ValueProviderInterface
73+
{
74+
foreach ($this->valueProviders as $valueProvider) {
75+
if (!($valueProvider instanceof ValueProviderInterface)) {
76+
throw new \RuntimeException(sprintf(
77+
'Value provider "%s" for type "%s" does not implement "%s".',
78+
$valueProvider::class,
79+
$type,
80+
ValueProviderInterface::class
81+
));
82+
}
83+
84+
if ($valueProvider->supports($type)) {
85+
return $valueProvider;
86+
}
87+
}
88+
89+
throw new \RuntimeException(sprintf(
90+
'No value provider found for type "%s".',
91+
$type
92+
));
93+
94+
}
95+
96+
public function preUpdate(PreUpdateEventArgs $event): void
97+
{
98+
// @todo deduplicate this code with the one above
99+
$object = $event->getObject();
100+
$objectManager = $event->getObjectManager();
101+
$className = $object::class;
102+
$doctrineClassMetadata = $objectManager->getClassMetadata($className);
103+
$classMetadata = $this->classMetadataCache[$className] ??= $this->classMetadataFactory->create($className);
104+
105+
foreach ($classMetadata->updateProperties as $propertyName => $propertyMetadata) {
106+
$value = $doctrineClassMetadata->getFieldValue($object, $propertyName);
107+
108+
if ($value !== null) {
109+
continue;
110+
}
111+
112+
$valueProvider = $this->getValueProvider($propertyMetadata['provider']);
113+
$value = $valueProvider->provide($propertyMetadata['propertyType']);
114+
115+
$doctrineClassMetadata->setFieldValue($object, $propertyName, $value);
116+
}
117+
}
118+
119+
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void
120+
{
121+
$doctrineClassMetadata = $eventArgs->getClassMetadata();
122+
$classMetadata = $this->classMetadataFactory->create($doctrineClassMetadata->getName());
123+
124+
foreach ($classMetadata->persistProperties as $propertyName => $propertyMetadata) {
125+
if ($doctrineClassMetadata->hasField($propertyName) || $doctrineClassMetadata->hasAssociation($propertyName)) {
126+
continue;
127+
}
128+
129+
if ($propertyMetadata['doctrineType'] === null) {
130+
throw new \RuntimeException(sprintf(
131+
'Property "%s" in class "%s" does not have a doctrine type.',
132+
$propertyName,
133+
$classMetadata->className
134+
));
135+
}
136+
137+
$classMetadataBuilder = new ClassMetadataBuilder($doctrineClassMetadata);
138+
$classMetadataBuilder->createField($propertyName, $propertyMetadata['doctrineType'])
139+
->nullable($propertyMetadata['nullable'])
140+
->build();
141+
}
142+
143+
foreach ($classMetadata->updateProperties as $propertyName => $propertyMetadata) {
144+
// @todo deduplicate this code with the one above
145+
if ($doctrineClassMetadata->hasField($propertyName) || $doctrineClassMetadata->hasAssociation($propertyName)) {
146+
continue;
147+
}
148+
149+
if ($propertyMetadata['doctrineType'] === null) {
150+
throw new \RuntimeException(sprintf(
151+
'Property "%s" in class "%s" does not have a doctrine type.',
152+
$propertyName,
153+
$classMetadata->className
154+
));
155+
}
156+
157+
$classMetadataBuilder = new ClassMetadataBuilder($doctrineClassMetadata);
158+
$classMetadataBuilder->createField($propertyName, $propertyMetadata['doctrineType'])
159+
->nullable($propertyMetadata['nullable'])
160+
->build();
161+
}
162+
}
163+
164+
public function reset()
165+
{
166+
$this->classMetadataCache = [];
167+
$this->valueProvidersCache = [];
168+
}
169+
}

src/Mapping/ClassMetadata.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use SoureCode\Bundle\DoctrineExtension\Contracts\TranslationInterface;
66

77
/**
8-
* @phpstan-type PropertyMetadataType array{type: string, propertyType: string, doctrineType: string, nullable: bool}
8+
* @phpstan-type PropertyMetadataType array{provider: string, propertyType: string, doctrineType: string|null, nullable: bool}
99
* @phpstan-type PropertyCollectionMetadataType = array<string, PropertyMetadataType>
1010
*/
1111
final class ClassMetadata
@@ -26,7 +26,7 @@ public function __construct(
2626
/**
2727
* @var PropertyCollectionMetadataType
2828
*/
29-
public readonly array $creationProperties,
29+
public readonly array $persistProperties,
3030
/**
3131
* @var PropertyCollectionMetadataType
3232
*/

src/Mapping/ClassMetadataFactory.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace SoureCode\Bundle\DoctrineExtension\Mapping;
44

5-
final class ClassMetadataFactory
5+
use Symfony\Contracts\Service\ResetInterface;
6+
7+
final class ClassMetadataFactory implements ResetInterface
68
{
79
/**
810
* @var array<class-string, ClassMetadata>
@@ -33,9 +35,14 @@ public function doCreate(string $className): ClassMetadata
3335
className: $classMetadata['className'],
3436
translationClassName: $classMetadata['translationClassName'],
3537
translatableClassName: $classMetadata['translatableClassName'],
36-
creationProperties: $classMetadata['creationProperties'],
38+
persistProperties: $classMetadata['persistProperties'],
3739
updateProperties: $classMetadata['updateProperties'],
3840
localeAware: $classMetadata['localeAware'],
3941
);
4042
}
43+
44+
public function reset(): void
45+
{
46+
$this->cache = [];
47+
}
4148
}

0 commit comments

Comments
 (0)