Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
69.86% |
51 / 73 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
OneToManyAttributeConverter | |
69.86% |
51 / 73 |
|
33.33% |
1 / 3 |
46.95 | |
0.00% |
0 / 1 |
applyToDomain | |
90.48% |
19 / 21 |
|
0.00% |
0 / 1 |
9.07 | |||
toReflClass | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
applyToStorage | |
58.33% |
28 / 48 |
|
0.00% |
0 / 1 |
31.28 |
1 | <?php |
2 | namespace Apie\StorageMetadata\PropertyConverters; |
3 | |
4 | use Apie\Core\ValueObjects\Utils; |
5 | use Apie\StorageMetadata\Attributes\OneToManyAttribute; |
6 | use Apie\StorageMetadata\Interfaces\PropertyConverterInterface; |
7 | use Apie\StorageMetadata\Interfaces\StorageDtoInterface; |
8 | use Apie\StorageMetadata\Mediators\DomainToStorageContext; |
9 | use Apie\StorageMetadataBuilder\Interfaces\MixedStorageInterface; |
10 | use Apie\TypeConverter\ReflectionTypeFactory; |
11 | use Doctrine\Common\Collections\ArrayCollection; |
12 | use Doctrine\ORM\PersistentCollection; |
13 | use ReflectionClass; |
14 | use Throwable; |
15 | |
16 | class OneToManyAttributeConverter implements PropertyConverterInterface |
17 | { |
18 | public function applyToDomain( |
19 | DomainToStorageContext $context |
20 | ): void { |
21 | foreach ($context->storageProperty->getAttributes(OneToManyAttribute::class) as $oneToManyAttribute) { |
22 | $domainProperty = $oneToManyAttribute->newInstance()->getReflectionProperty($context->domainClass, $context->domainObject); |
23 | if ($domainProperty) { |
24 | $storagePropertyValue = Utils::toArray($context->getStoragePropertyValue()); |
25 | $domainPropertyType = $domainProperty->getType(); |
26 | $domainProperties = $domainProperty->isInitialized($context->domainObject) |
27 | ? Utils::toArray($domainProperty->getValue($context->domainObject)) |
28 | : []; |
29 | foreach ($storagePropertyValue as $arrayKey => $arrayValue) { |
30 | if ($arrayValue instanceof MixedStorageInterface) { |
31 | $domainProperties[$arrayKey] = $arrayValue->toOriginalObject(); |
32 | } elseif ($arrayValue instanceof StorageDtoInterface && isset($domainProperties[$arrayKey])) { |
33 | $context->domainToStorageConverter->injectExistingDomainObject( |
34 | $domainProperties[$arrayKey], |
35 | $arrayValue, |
36 | $context |
37 | ); |
38 | } else { |
39 | $domainProperties[$arrayKey] = $arrayValue instanceof StorageDtoInterface |
40 | ? $context->domainToStorageConverter->createDomainObject($arrayValue, $context) |
41 | : $context->dynamicCast($arrayValue, ReflectionTypeFactory::createReflectionType($oneToManyAttribute->newInstance()->declaredClass)); |
42 | } |
43 | } |
44 | $domainProperty->setValue($context->domainObject, $context->dynamicCast($domainProperties, $domainPropertyType)); |
45 | } |
46 | } |
47 | } |
48 | |
49 | /** |
50 | * @template T of object |
51 | * @param class-string<T> $className |
52 | * @return ReflectionClass<T> |
53 | */ |
54 | private function toReflClass(string $className, mixed $contextStorageObject): ReflectionClass |
55 | { |
56 | if (str_starts_with($className, 'apie_') && is_object($contextStorageObject)) { |
57 | $refl = new ReflectionClass($contextStorageObject); |
58 | return new ReflectionClass($refl->getNamespaceName() . '\\' . $className); |
59 | } |
60 | return new ReflectionClass($className); |
61 | } |
62 | |
63 | public function applyToStorage( |
64 | DomainToStorageContext $context |
65 | ): void { |
66 | foreach ($context->storageProperty->getAttributes(OneToManyAttribute::class) as $oneToManyAttribute) { |
67 | $domainProperty = $oneToManyAttribute->newInstance()->getReflectionProperty($context->domainClass, $context->domainObject); |
68 | if ($domainProperty) { |
69 | $domainPropertyValue = Utils::toArray($domainProperty->getValue($context->domainObject)); |
70 | $storageShouldBeReplaced = !$context->storageProperty->isInitialized($context->storageObject); |
71 | $storageProperties = $storageShouldBeReplaced |
72 | ? [] |
73 | : $context->storageProperty->getValue($context->storageObject); |
74 | $keysToRemove = array_diff( |
75 | array_keys(Utils::toArray($storageProperties)), |
76 | array_keys($domainPropertyValue), |
77 | ); |
78 | try { |
79 | foreach ($keysToRemove as $keyToRemove) { |
80 | unset($storageProperties[$keyToRemove]); |
81 | // this is an edge case where we have some item list that can not unset values |
82 | if (isset($storageProperties[$keyToRemove])) { |
83 | $storageProperties = $context->dynamicCast([], $context->storageProperty->getType()); |
84 | $storageShouldBeReplaced = true; |
85 | break; |
86 | } |
87 | } |
88 | } catch (Throwable) { |
89 | // another edge case where an array object class throws an exception. |
90 | $storageProperties = $context->dynamicCast([], $context->storageProperty->getType()); |
91 | $storageShouldBeReplaced = true; |
92 | } |
93 | foreach ($domainPropertyValue as $arrayKey => $arrayValue) { |
94 | $arrayContext = $context->withArrayKey($arrayKey); |
95 | $storageClassRefl = $this->toReflClass($oneToManyAttribute->newInstance()->storageClass, $context->storageObject); |
96 | if (is_object($arrayValue) && in_array(StorageDtoInterface::class, $storageClassRefl->getInterfaceNames())) { |
97 | if (isset($storageProperties[$arrayKey]) && $storageProperties[$arrayKey] instanceof StorageDtoInterface && !$storageShouldBeReplaced) { |
98 | $arrayContext->domainToStorageConverter->injectExistingStorageObject( |
99 | $arrayValue, |
100 | $storageProperties[$arrayKey], |
101 | $arrayContext |
102 | ); |
103 | } else { |
104 | $storageProperties[$arrayKey] = $arrayContext->domainToStorageConverter->createStorageObject( |
105 | $arrayValue, |
106 | $storageClassRefl, |
107 | $arrayContext |
108 | ); |
109 | } |
110 | } else { |
111 | // if we do not do this hack we get cloned objects. |
112 | if ($storageProperties instanceof PersistentCollection) { |
113 | $storageProperties = new ArrayCollection( |
114 | $storageProperties |
115 | ->map(function ($t) { return clone $t; }) |
116 | ->toArray() |
117 | ); |
118 | $storageShouldBeReplaced = true; |
119 | } |
120 | $storageProperties[$arrayKey] = $storageClassRefl->newInstance(Utils::toString($arrayValue)); |
121 | // @phpstan-ignore-next-line |
122 | $storageProperties[$arrayKey]->listOrder = $arrayKey; |
123 | // @phpstan-ignore-next-line |
124 | $storageProperties[$arrayKey]->parent = $context->storageObject; |
125 | } |
126 | } |
127 | if ($storageShouldBeReplaced) { |
128 | $context->storageProperty->setValue($context->storageObject, $context->dynamicCast($storageProperties, $context->storageProperty->getType())); |
129 | } |
130 | } |
131 | } |
132 | } |
133 | } |