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