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