Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.13% covered (success)
98.13%
105 / 107
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
DomainToStorageConverter
98.13% covered (success)
98.13%
105 / 107
71.43% covered (warning)
71.43%
5 / 7
15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createTypeConverter
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
1
 injectExistingDomainObject
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
5
 createDomainObject
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 createStorageObject
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 injectExistingStorageObject
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
5
 create
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\StorageMetadata;
3
4use Apie\Core\FileStorage\ChainedFileStorage;
5use Apie\Core\Indexing\Indexer;
6use Apie\Core\TypeConverters\ArrayToDoctrineCollection;
7use Apie\Core\TypeConverters\DoctrineCollectionToArray;
8use Apie\StorageMetadata\ClassInstantiators\ChainedClassInstantiator;
9use Apie\StorageMetadata\ClassInstantiators\FromReflection;
10use Apie\StorageMetadata\ClassInstantiators\FromStorage;
11use Apie\StorageMetadata\ClassInstantiators\FromStoredFile;
12use Apie\StorageMetadata\Converters\ApieListToArray;
13use Apie\StorageMetadata\Converters\ArrayToItemHashmap;
14use Apie\StorageMetadata\Converters\ArrayToItemList;
15use Apie\StorageMetadata\Converters\ArrayToItemSet;
16use Apie\StorageMetadata\Converters\AutoIncrementTableToInt;
17use Apie\StorageMetadata\Converters\AutoIncrementTableToValueObject;
18use Apie\StorageMetadata\Converters\DateTimeToString;
19use Apie\StorageMetadata\Converters\EnumToString;
20use Apie\StorageMetadata\Converters\IntToAutoIncrementTable;
21use Apie\StorageMetadata\Converters\IntToValueObject;
22use Apie\StorageMetadata\Converters\MixedStorageToObject;
23use Apie\StorageMetadata\Converters\MixedToMixedStorage;
24use Apie\StorageMetadata\Converters\StringToDateTime;
25use Apie\StorageMetadata\Converters\StringToEnum;
26use Apie\StorageMetadata\Converters\StringToSearchIndex;
27use Apie\StorageMetadata\Converters\StringToUploadedFileInterface;
28use Apie\StorageMetadata\Converters\StringToValueObject;
29use Apie\StorageMetadata\Converters\UploadedFileInterfaceToString;
30use Apie\StorageMetadata\Converters\ValueObjectToAutoIncrementTable;
31use Apie\StorageMetadata\Converters\ValueObjectToFloat;
32use Apie\StorageMetadata\Converters\ValueObjectToInt;
33use Apie\StorageMetadata\Converters\ValueObjectToString;
34use Apie\StorageMetadata\Interfaces\ClassInstantiatorInterface;
35use Apie\StorageMetadata\Interfaces\PropertyConverterInterface;
36use Apie\StorageMetadata\Interfaces\StorageDtoInterface;
37use Apie\StorageMetadata\Mediators\DomainToStorageContext;
38use Apie\StorageMetadata\PropertyConverters\AccessControlListAttributeConverter;
39use Apie\StorageMetadata\PropertyConverters\DefaultValueAttributeConverter;
40use Apie\StorageMetadata\PropertyConverters\DiscriminatorMappingAttributeConverter;
41use Apie\StorageMetadata\PropertyConverters\GetSearchIndexAttributeConverter;
42use Apie\StorageMetadata\PropertyConverters\ManyToOneAttributeConverter;
43use Apie\StorageMetadata\PropertyConverters\MethodAttributeConverter;
44use Apie\StorageMetadata\PropertyConverters\OneToManyAttributeConverter;
45use Apie\StorageMetadata\PropertyConverters\OneToOneAttributeConverter;
46use Apie\StorageMetadata\PropertyConverters\OrderAttributeConverter;
47use Apie\StorageMetadata\PropertyConverters\ParentAttributeConverter;
48use Apie\StorageMetadata\PropertyConverters\PropertyAttributeConverter;
49use Apie\StorageMetadata\PropertyConverters\StorageMappingAttributeConverter;
50use Apie\TypeConverter\Converters\ObjectToObjectConverter;
51use Apie\TypeConverter\DefaultConvertersFactory;
52use Apie\TypeConverter\TypeConverter;
53use ReflectionClass;
54use ReflectionProperty;
55
56class DomainToStorageConverter
57{
58    /** @var array<int, PropertyConverterInterface> */
59    private array $propertyConverters;
60
61    public function __construct(
62        private readonly ClassInstantiatorInterface $classInstantiator,
63        private readonly ChainedFileStorage $fileStorage,
64        PropertyConverterInterface... $propertyConverters
65    ) {
66        $this->propertyConverters = $propertyConverters;
67    }
68
69    private function createTypeConverter(): TypeConverter
70    {
71        return new TypeConverter(
72            new ObjectToObjectConverter(),
73            ...DefaultConvertersFactory::create(
74                new StringToUploadedFileInterface($this->fileStorage),
75                new UploadedFileInterfaceToString($this->fileStorage),
76                new ArrayToDoctrineCollection(),
77                new StringToSearchIndex(),
78                new DoctrineCollectionToArray(),
79                new ApieListToArray(),
80                new AutoIncrementTableToInt(),
81                new AutoIncrementTableToValueObject(),
82                new IntToAutoIncrementTable(),
83                new ValueObjectToAutoIncrementTable(),
84                new ValueObjectToInt(),
85                new IntToValueObject(),
86                new ValueObjectToFloat(),
87                new MixedStorageToObject(),
88                new MixedToMixedStorage(),
89                new ValueObjectToString(),
90                new EnumToString(),
91                new StringToDateTime(),
92                new DateTimeToString(),
93                new StringToValueObject(),
94                new StringToEnum(),
95                new ArrayToItemHashmap(),
96                new ArrayToItemList(),
97                new ArrayToItemSet(),
98            )
99        );
100    }
101
102    /**
103     * @template T of object
104     * @param T $domainObject
105     * @return T
106     */
107    public function injectExistingDomainObject(
108        object $domainObject,
109        StorageDtoInterface $storageObject,
110        ?DomainToStorageContext $context = null
111    ): object {
112        $domainClass = $storageObject::getClassReference();
113        $typeConverter = $this->createTypeConverter();
114        $context = DomainToStorageContext::createFromContext(
115            $this,
116            $typeConverter,
117            $storageObject,
118            $domainObject,
119            $this->fileStorage,
120            $domainClass,
121            $context
122        );
123        $ptr = new ReflectionClass($storageObject);
124        $filters = null;
125        while ($ptr) {
126            foreach ($ptr->getProperties($filters) as $storageProperty) {
127                if ($storageProperty->isStatic()) {
128                    continue;
129                }
130                $propertyContext = $context->withStorageProperty($storageProperty);
131                foreach ($this->propertyConverters as $propertyConverter) {
132                    $propertyConverter->applyToDomain($propertyContext);
133                }
134            }
135            $ptr = $ptr->getParentClass();
136            // parent classes only add private properties
137            $filters = ReflectionProperty::IS_PRIVATE;
138        }
139
140        return $domainObject;
141    }
142
143    public function createDomainObject(StorageDtoInterface $storageObject, ?DomainToStorageContext $context = null): object
144    {
145        $domainClass = $storageObject::getClassReference();
146        
147        return $this->injectExistingDomainObject(
148            $this->classInstantiator->create($domainClass, $storageObject),
149            $storageObject,
150            $context
151        );
152    }
153
154    /**
155     * @template T of StorageDtoInterface
156     * @param ReflectionClass<T> $targetClass
157     * @return T
158     */
159    public function createStorageObject(
160        object $input,
161        ReflectionClass $targetClass,
162        ?DomainToStorageContext $context = null
163    ): StorageDtoInterface {
164        return $this->injectExistingStorageObject(
165            $input,
166            $this->classInstantiator->create($targetClass),
167            $context
168        );
169    }
170
171    public function injectExistingStorageObject(
172        object $domainObject,
173        StorageDtoInterface $storageObject,
174        ?DomainToStorageContext $context = null
175    ): StorageDtoInterface {
176        $domainClass = $storageObject::getClassReference();
177        $filters = null;
178        $ptr = new ReflectionClass($storageObject);
179        $typeConverter = $this->createTypeConverter();
180        $context = DomainToStorageContext::createFromContext(
181            $this,
182            $typeConverter,
183            $storageObject,
184            $domainObject,
185            $this->fileStorage,
186            $domainClass,
187            $context
188        );
189        while ($ptr) {
190            foreach ($ptr->getProperties($filters) as $storageProperty) {
191                if ($storageProperty->isStatic()) {
192                    continue;
193                }
194                $propertyContext = $context->withStorageProperty($storageProperty);
195                foreach ($this->propertyConverters as $propertyConverter) {
196                    $propertyConverter->applyToStorage($propertyContext);
197                }
198            }
199            $ptr = $ptr->getParentClass();
200            // parent classes only add private properties
201            $filters = ReflectionProperty::IS_PRIVATE;
202        }
203        return $storageObject;
204    }
205
206    public static function create(ChainedFileStorage $fileStorage, ?Indexer $indexer = null): self
207    {
208        return new self(
209            new ChainedClassInstantiator(
210                new FromStoredFile(),
211                new FromStorage(),
212                new FromReflection(),
213            ),
214            $fileStorage,
215            new DiscriminatorMappingAttributeConverter(),
216            new StorageMappingAttributeConverter(),
217            new ManyToOneAttributeConverter(),
218            new OneToOneAttributeConverter(),
219            new AccessControlListAttributeConverter(),
220            new OneToManyAttributeConverter(),
221            new PropertyAttributeConverter(),
222            new GetSearchIndexAttributeConverter($indexer ?? Indexer::create()),
223            new MethodAttributeConverter(),
224            new OrderAttributeConverter(),
225            new ParentAttributeConverter(),
226            new DefaultValueAttributeConverter(),
227        );
228    }
229}