Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.78% covered (success)
97.78%
88 / 90
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
DomainToStorageConverter
97.78% covered (success)
97.78%
88 / 90
75.00% covered (warning)
75.00%
6 / 8
21
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%
1 / 1
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
 fixFileUploads
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 injectExistingStorageObject
95.83% covered (success)
95.83%
23 / 24
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\FileStorage\StoredFile;
6use Apie\Core\Indexing\Indexer;
7use Apie\StorageMetadata\ClassInstantiators\ChainedClassInstantiator;
8use Apie\StorageMetadata\ClassInstantiators\FromReflection;
9use Apie\StorageMetadata\ClassInstantiators\FromStorage;
10use Apie\StorageMetadata\ClassInstantiators\FromStoredFile;
11use Apie\StorageMetadata\Interfaces\ClassInstantiatorInterface;
12use Apie\StorageMetadata\Interfaces\PropertyConverterInterface;
13use Apie\StorageMetadata\Interfaces\StorageDtoInterface;
14use Apie\StorageMetadata\Mediators\DomainToStorageContext;
15use Apie\StorageMetadata\PropertyConverters\AccessControlListAttributeConverter;
16use Apie\StorageMetadata\PropertyConverters\DefaultValueAttributeConverter;
17use Apie\StorageMetadata\PropertyConverters\DiscriminatorMappingAttributeConverter;
18use Apie\StorageMetadata\PropertyConverters\GetSearchIndexAttributeConverter;
19use Apie\StorageMetadata\PropertyConverters\ManyToOneAttributeConverter;
20use Apie\StorageMetadata\PropertyConverters\MethodAttributeConverter;
21use Apie\StorageMetadata\PropertyConverters\OneToManyAttributeConverter;
22use Apie\StorageMetadata\PropertyConverters\OneToOneAttributeConverter;
23use Apie\StorageMetadata\PropertyConverters\OrderAttributeConverter;
24use Apie\StorageMetadata\PropertyConverters\ParentAttributeConverter;
25use Apie\StorageMetadata\PropertyConverters\PropertyAttributeConverter;
26use Apie\StorageMetadata\PropertyConverters\StorageMappingAttributeConverter;
27use Apie\TypeConverter\TypeConverter;
28use Psr\Http\Message\UploadedFileInterface;
29use ReflectionClass;
30use ReflectionProperty;
31
32class DomainToStorageConverter
33{
34    /** @var array<int, PropertyConverterInterface> */
35    private array $propertyConverters;
36
37    public function __construct(
38        private readonly ClassInstantiatorInterface $classInstantiator,
39        private readonly ChainedFileStorage $fileStorage,
40        PropertyConverterInterface... $propertyConverters
41    ) {
42        $this->propertyConverters = $propertyConverters;
43    }
44
45    private function createTypeConverter(): TypeConverter
46    {
47        return TypeConverterFactory::create($this->fileStorage);
48    }
49
50    /**
51     * @template T of object
52     * @param T $domainObject
53     * @return T
54     */
55    public function injectExistingDomainObject(
56        object $domainObject,
57        StorageDtoInterface $storageObject,
58        ?DomainToStorageContext $context = null
59    ): object {
60        $domainClass = $storageObject::getClassReference();
61        $typeConverter = $this->createTypeConverter();
62        $context = DomainToStorageContext::createFromContext(
63            $this,
64            $typeConverter,
65            $storageObject,
66            $domainObject,
67            $this->fileStorage,
68            $domainClass,
69            $context
70        );
71        $ptr = new ReflectionClass($storageObject);
72        $filters = null;
73        while ($ptr) {
74            foreach ($ptr->getProperties($filters) as $storageProperty) {
75                if ($storageProperty->isStatic()) {
76                    continue;
77                }
78                $propertyContext = $context->withStorageProperty($storageProperty);
79                foreach ($this->propertyConverters as $propertyConverter) {
80                    $propertyConverter->applyToDomain($propertyContext);
81                }
82            }
83            $ptr = $ptr->getParentClass();
84            // parent classes only add private properties
85            $filters = ReflectionProperty::IS_PRIVATE;
86        }
87
88        return $domainObject;
89    }
90
91    public function createDomainObject(StorageDtoInterface $storageObject, ?DomainToStorageContext $context = null): object
92    {
93        $domainClass = $storageObject::getClassReference();
94        
95        return $this->injectExistingDomainObject(
96            $this->classInstantiator->create($domainClass, $storageObject),
97            $storageObject,
98            $context
99        );
100    }
101
102    /**
103     * @template T of StorageDtoInterface
104     * @param ReflectionClass<T> $targetClass
105     * @return T
106     */
107    public function createStorageObject(
108        object $input,
109        ReflectionClass $targetClass,
110        ?DomainToStorageContext $context = null
111    ): StorageDtoInterface {
112        return $this->injectExistingStorageObject(
113            $input,
114            $this->classInstantiator->create($targetClass),
115            $context
116        );
117    }
118
119    /**
120     * @template T of object
121     * @param T $domainObject
122     * @param ReflectionClass<T> $reflectionClass
123     */
124
125    private function fixFileUploads(object $domainObject, ReflectionClass $reflectionClass): void
126    {
127        foreach ($reflectionClass->getProperties() as $property) {
128            if ($property->isStatic()) {
129                continue;
130            }
131            $value = $property->getValue($domainObject);
132            if ($value instanceof UploadedFileInterface && !($value instanceof StoredFile)) {
133                $storedFile = StoredFile::createFromUploadedFile($value);
134                $property->setValue($domainObject, $storedFile);
135            }
136        }
137        $parentClass = $reflectionClass->getParentClass();
138        if ($parentClass) {
139            $this->fixFileUploads($domainObject, $parentClass);
140        }
141    }
142
143    public function injectExistingStorageObject(
144        object $domainObject,
145        StorageDtoInterface $storageObject,
146        ?DomainToStorageContext $context = null
147    ): StorageDtoInterface {
148        $domainClass = $storageObject::getClassReference();
149        $filters = null;
150        $ptr = new ReflectionClass($storageObject);
151        $typeConverter = $this->createTypeConverter();
152        $context = DomainToStorageContext::createFromContext(
153            $this,
154            $typeConverter,
155            $storageObject,
156            $domainObject,
157            $this->fileStorage,
158            $domainClass,
159            $context
160        );
161        $this->fixFileUploads($domainObject, new ReflectionClass($domainObject));
162        while ($ptr) {
163            foreach ($ptr->getProperties($filters) as $storageProperty) {
164                if ($storageProperty->isStatic()) {
165                    continue;
166                }
167                $propertyContext = $context->withStorageProperty($storageProperty);
168                foreach ($this->propertyConverters as $propertyConverter) {
169                    $propertyConverter->applyToStorage($propertyContext);
170                }
171            }
172            $ptr = $ptr->getParentClass();
173            // parent classes only add private properties
174            $filters = ReflectionProperty::IS_PRIVATE;
175        }
176        return $storageObject;
177    }
178
179    public static function create(ChainedFileStorage $fileStorage, ?Indexer $indexer = null): self
180    {
181        return new self(
182            new ChainedClassInstantiator(
183                new FromStoredFile(),
184                new FromStorage(),
185                new FromReflection(),
186            ),
187            $fileStorage,
188            new DiscriminatorMappingAttributeConverter(),
189            new StorageMappingAttributeConverter(),
190            new ManyToOneAttributeConverter(),
191            new OneToOneAttributeConverter(),
192            new AccessControlListAttributeConverter(),
193            new OneToManyAttributeConverter(),
194            new PropertyAttributeConverter(),
195            new GetSearchIndexAttributeConverter($indexer ?? Indexer::create()),
196            new MethodAttributeConverter(),
197            new OrderAttributeConverter(),
198            new ParentAttributeConverter(),
199            new DefaultValueAttributeConverter(),
200        );
201    }
202}