Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
74.12% |
63 / 85 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
OneToManyAttributeConverter | |
74.12% |
63 / 85 |
|
33.33% |
1 / 3 |
45.60 | |
0.00% |
0 / 1 |
applyToDomain | |
92.86% |
26 / 28 |
|
0.00% |
0 / 1 |
11.04 | |||
toReflClass | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
applyToStorage | |
62.26% |
33 / 53 |
|
0.00% |
0 / 1 |
29.76 |
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 | $oneToManyInfo = $oneToManyAttribute->newInstance(); |
23 | $domainProperty = $oneToManyInfo->getReflectionProperty($context->domainClass, $context->domainObject); |
24 | if ($domainProperty) { |
25 | $storagePropertyValue = $context->getStoragePropertyValue(); |
26 | if ($oneToManyInfo->nullableField) { |
27 | $fieldName = $oneToManyInfo->nullableField; |
28 | if ($context->storageObject->{$fieldName}) { |
29 | $domainProperty->setValue($context->domainObject, null); |
30 | return; |
31 | } |
32 | } |
33 | $storagePropertyValue = Utils::toArray($storagePropertyValue); |
34 | $domainPropertyType = $domainProperty->getType(); |
35 | $domainProperties = $domainProperty->isInitialized($context->domainObject) |
36 | ? Utils::toArray($domainProperty->getValue($context->domainObject) ?? []) |
37 | : []; |
38 | foreach ($storagePropertyValue as $arrayKey => $arrayValue) { |
39 | if ($arrayValue instanceof MixedStorageInterface) { |
40 | $domainProperties[$arrayKey] = $arrayValue->toOriginalObject(); |
41 | } elseif ($arrayValue instanceof StorageDtoInterface && isset($domainProperties[$arrayKey])) { |
42 | $context->domainToStorageConverter->injectExistingDomainObject( |
43 | $domainProperties[$arrayKey], |
44 | $arrayValue, |
45 | $context |
46 | ); |
47 | } else { |
48 | $domainProperties[$arrayKey] = $arrayValue instanceof StorageDtoInterface |
49 | ? $context->domainToStorageConverter->createDomainObject($arrayValue, $context) |
50 | : $context->dynamicCast($arrayValue, ReflectionTypeFactory::createReflectionType($oneToManyAttribute->newInstance()->declaredClass)); |
51 | } |
52 | } |
53 | $domainProperty->setValue($context->domainObject, $context->dynamicCast($domainProperties, $domainPropertyType)); |
54 | } |
55 | } |
56 | } |
57 | |
58 | /** |
59 | * @template T of object |
60 | * @param class-string<T> $className |
61 | * @return ReflectionClass<T> |
62 | */ |
63 | private function toReflClass(string $className, mixed $contextStorageObject): ReflectionClass |
64 | { |
65 | if (str_starts_with($className, 'apie_') && is_object($contextStorageObject)) { |
66 | $refl = new ReflectionClass($contextStorageObject); |
67 | return new ReflectionClass($refl->getNamespaceName() . '\\' . $className); |
68 | } |
69 | return new ReflectionClass($className); |
70 | } |
71 | |
72 | public function applyToStorage( |
73 | DomainToStorageContext $context |
74 | ): void { |
75 | foreach ($context->storageProperty->getAttributes(OneToManyAttribute::class) as $oneToManyAttribute) { |
76 | $oneToManyInfo = $oneToManyAttribute->newInstance(); |
77 | $domainProperty = $oneToManyInfo->getReflectionProperty($context->domainClass, $context->domainObject); |
78 | if ($domainProperty) { |
79 | $domainPropertyValue = $domainProperty->getValue($context->domainObject); |
80 | if ($oneToManyInfo->nullableField) { |
81 | $fieldName = $oneToManyInfo->nullableField; |
82 | $context->storageObject->{$fieldName} = $domainPropertyValue === null; |
83 | } |
84 | $domainPropertyValue = Utils::toArray($domainPropertyValue ?? []); |
85 | $storageShouldBeReplaced = !$context->storageProperty->isInitialized($context->storageObject); |
86 | $storageProperties = $storageShouldBeReplaced |
87 | ? [] |
88 | : $context->storageProperty->getValue($context->storageObject); |
89 | $keysToRemove = array_diff( |
90 | array_keys(Utils::toArray($storageProperties)), |
91 | array_keys($domainPropertyValue), |
92 | ); |
93 | try { |
94 | foreach ($keysToRemove as $keyToRemove) { |
95 | unset($storageProperties[$keyToRemove]); |
96 | // this is an edge case where we have some immutable item list object. |
97 | if (isset($storageProperties[$keyToRemove])) { |
98 | $storageProperties = $context->dynamicCast([], $context->storageProperty->getType()); |
99 | $storageShouldBeReplaced = true; |
100 | break; |
101 | } |
102 | } |
103 | } catch (Throwable) { |
104 | // another edge case where an array object class throws an exception. |
105 | $storageProperties = $context->dynamicCast([], $context->storageProperty->getType()); |
106 | $storageShouldBeReplaced = true; |
107 | } |
108 | foreach ($domainPropertyValue as $arrayKey => $arrayValue) { |
109 | $arrayContext = $context->withArrayKey($arrayKey); |
110 | $storageClassRefl = $this->toReflClass($oneToManyAttribute->newInstance()->storageClass, $context->storageObject); |
111 | if (is_object($arrayValue) && in_array(StorageDtoInterface::class, $storageClassRefl->getInterfaceNames())) { |
112 | if (isset($storageProperties[$arrayKey]) && $storageProperties[$arrayKey] instanceof StorageDtoInterface && !$storageShouldBeReplaced) { |
113 | $arrayContext->domainToStorageConverter->injectExistingStorageObject( |
114 | $arrayValue, |
115 | $storageProperties[$arrayKey], |
116 | $arrayContext |
117 | ); |
118 | } else { |
119 | $storageProperties[$arrayKey] = $arrayContext->domainToStorageConverter->createStorageObject( |
120 | $arrayValue, |
121 | $storageClassRefl, |
122 | $arrayContext |
123 | ); |
124 | } |
125 | } else { |
126 | // if we do not do this hack we get cloned objects. |
127 | if ($storageProperties instanceof PersistentCollection) { |
128 | $storageProperties = new ArrayCollection( |
129 | $storageProperties |
130 | ->map(function ($t) { return clone $t; }) |
131 | ->toArray() |
132 | ); |
133 | $storageShouldBeReplaced = true; |
134 | } |
135 | $storageProperties[$arrayKey] = $storageClassRefl->newInstance(Utils::toString($arrayValue)); |
136 | // @phpstan-ignore-next-line |
137 | $storageProperties[$arrayKey]->listOrder = $context->dynamicCast($arrayKey, $storageClassRefl->getProperty('listOrder')->getType()); |
138 | // @phpstan-ignore-next-line |
139 | $storageProperties[$arrayKey]->parent = $context->storageObject; |
140 | } |
141 | } |
142 | if ($storageShouldBeReplaced) { |
143 | $context->storageProperty->setValue($context->storageObject, $context->dynamicCast($storageProperties, $context->storageProperty->getType())); |
144 | } |
145 | } |
146 | } |
147 | } |
148 | } |