1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Hgraca\MicroOrm\DataMapper; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use Hgraca\Common\Collection\Contract\CollectionInterface; |
7
|
|
|
use Hgraca\Common\Entity\Concept\EntityAbstract; |
8
|
|
|
use Hgraca\Common\Entity\Contract\EntityInterface; |
9
|
|
|
use Hgraca\Helper\ClassHelper; |
10
|
|
|
use Hgraca\MicroOrm\RepositoryInterface; |
11
|
|
|
use ReflectionProperty; |
12
|
|
|
|
13
|
|
|
class DataMapper implements DataMapperInterface |
14
|
|
|
{ |
15
|
|
|
/** @var EntityAbstract|string */ |
16
|
|
|
private $entityFqcn; |
17
|
|
|
|
18
|
|
|
/** @var CollectionInterface|string */ |
19
|
|
|
private $collectionFqcn; |
20
|
|
|
|
21
|
|
|
/** @var string */ |
22
|
|
|
private $tableName; |
23
|
|
|
|
24
|
|
|
/** @var RepositoryInterface|string */ |
25
|
|
|
private $repositoryFqcn; |
26
|
|
|
|
27
|
|
|
/** @var array */ |
28
|
|
|
private $attributes; |
29
|
|
|
|
30
|
|
|
/** @var array */ |
31
|
|
|
private $propertyToColumnMapper; |
32
|
|
|
|
33
|
|
|
/** @var array */ |
34
|
|
|
private $propertyTypeMapper; |
35
|
|
|
|
36
|
|
|
/** @var ReflectionProperty[] */ |
37
|
|
|
private $reflectionPropertyList; |
38
|
|
|
|
39
|
|
|
/** @var string */ |
40
|
|
|
private $dateTimeFormat; |
41
|
|
|
|
42
|
|
|
public function __construct( |
43
|
|
|
string $entityFqcn, |
44
|
|
|
array $config |
45
|
|
|
) { |
46
|
|
|
$this->entityFqcn = $entityFqcn; |
47
|
|
|
$this->repositoryFqcn = $config['repositoryFqcn']; |
48
|
|
|
$this->collectionFqcn = $config['collectionFqcn']; |
49
|
|
|
$this->dateTimeFormat = $config['dateTimeFormat']; |
50
|
|
|
$this->tableName = $config['tableName']; |
51
|
|
|
$this->attributes = $config['attributes']; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
private function getEntityFqcn(): string |
55
|
|
|
{ |
56
|
|
|
return $this->entityFqcn; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
private function getCollectionFqcn(): string |
60
|
|
|
{ |
61
|
|
|
return $this->collectionFqcn; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function getTableName(): string |
65
|
|
|
{ |
66
|
|
|
return $this->tableName; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @return RepositoryInterface|string |
71
|
|
|
*/ |
72
|
|
|
public function getRepositoryFqcn(): string |
73
|
|
|
{ |
74
|
|
|
return $this->repositoryFqcn; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
public function mapRecordListToEntityCollection(array $recordList): CollectionInterface |
78
|
|
|
{ |
79
|
|
|
$entityResultList = []; |
80
|
|
|
foreach ($recordList as $record) { |
81
|
|
|
$entityResultList[] = $this->mapRecordToEntity($record); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
$collectionFqcn = $this->getCollectionFqcn(); |
85
|
|
|
|
86
|
|
|
return new $collectionFqcn($entityResultList); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
public function mapEntityToRecord(EntityInterface $entity): array |
90
|
|
|
{ |
91
|
|
|
$entityFqcn = $this->getEntityFqcn(); |
92
|
|
|
$reflectionPropertyList = $this->getReflectionPropertyList($entityFqcn); |
93
|
|
|
|
94
|
|
|
$record = []; |
95
|
|
|
foreach ($reflectionPropertyList as $reflectionProperty) { |
96
|
|
|
$propertyName = $reflectionProperty->getName(); |
97
|
|
|
$propertyValue = $reflectionProperty->getValue($entity); |
98
|
|
|
|
99
|
|
|
$record += $this->mapPropertyToColumn($propertyName, $propertyValue); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
return $record; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
public function mapPropertiesToColumns(array $propertyList): array |
106
|
|
|
{ |
107
|
|
|
$columnList = []; |
108
|
|
|
foreach ($propertyList as $propertyName => $propertyValue) { |
109
|
|
|
$columnList += $this->mapPropertyToColumn($propertyName, $propertyValue); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
return $columnList; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
public function mapRecordToEntity(array $record): EntityInterface |
116
|
|
|
{ |
117
|
|
|
$entity = $this->createEntity(); |
118
|
|
|
|
119
|
|
|
$this->updateEntityFromRecord($entity, $record); |
120
|
|
|
|
121
|
|
|
return $entity; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
public function mapColumnsToProperties(array $columnList): array |
125
|
|
|
{ |
126
|
|
|
$propertyList = []; |
127
|
|
|
|
128
|
|
|
foreach ($columnList as $columnName => $columnValue) { |
129
|
|
|
$propertyList += $this->mapColumnToProperty($columnName, $columnValue); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return $propertyList; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
public function updateEntityFromRecord(EntityInterface $entity, array $record) |
136
|
|
|
{ |
137
|
|
|
$reflectionPropertyList = $this->getReflectionPropertyList($this->getEntityFqcn()); |
138
|
|
|
$newPropertyValueArray = $this->mapColumnsToProperties($record); |
139
|
|
|
|
140
|
|
|
foreach ($reflectionPropertyList as $reflectionProperty) { |
141
|
|
|
$propertyName = $reflectionProperty->getName(); |
142
|
|
|
|
143
|
|
|
if (!isset($newPropertyValueArray[$propertyName])) { |
144
|
|
|
continue; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
$reflectionProperty->setValue($entity, $newPropertyValueArray[$propertyName]); |
148
|
|
|
} |
149
|
|
|
} |
150
|
|
|
|
151
|
|
View Code Duplication |
public function getPropertyToColumnMapper(): array |
152
|
|
|
{ |
153
|
|
|
if (null !== $this->propertyToColumnMapper) { |
154
|
|
|
return $this->propertyToColumnMapper; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
$this->propertyToColumnMapper = []; |
158
|
|
|
foreach ($this->attributes as $attributeName => $attributeMetadata) { |
159
|
|
|
$this->propertyToColumnMapper[$attributeName] = $attributeMetadata['column'] ?? $attributeName; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return $this->propertyToColumnMapper; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @param string $entityFqcn |
167
|
|
|
* |
168
|
|
|
* @return ReflectionProperty[] |
169
|
|
|
*/ |
170
|
|
|
private function getReflectionPropertyList(string $entityFqcn): array |
171
|
|
|
{ |
172
|
|
|
if (null !== $this->reflectionPropertyList) { |
173
|
|
|
return $this->reflectionPropertyList; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
$this->reflectionPropertyList = ClassHelper::getReflectionProperties($entityFqcn); |
177
|
|
|
ClassHelper::setReflectionPropertiesAccessible($this->reflectionPropertyList); |
178
|
|
|
|
179
|
|
|
return $this->reflectionPropertyList; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
private function mapColumnValueToPropertyValue($value, string $type) |
183
|
|
|
{ |
184
|
|
|
// TODO use a serializer service for this, so we can have ValueObjects |
185
|
|
View Code Duplication |
switch ($type) { |
|
|
|
|
186
|
|
|
case 'datetime': |
|
|
|
|
187
|
|
|
return DateTime::createFromFormat($this->dateTimeFormat, $value); |
188
|
|
|
|
189
|
|
|
case 'text': |
190
|
|
|
settype($value, 'string'); |
191
|
|
|
|
192
|
|
|
return $value; |
193
|
|
|
|
194
|
|
|
default: |
195
|
|
|
settype($value, $type); |
196
|
|
|
|
197
|
|
|
return $value; |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
private function mapPropertyValueToColumnValue($value, string $type) |
202
|
|
|
{ |
203
|
|
|
if (null === $value) { |
204
|
|
|
return $value; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
// TODO use a serializer service for this, so we can have ValueObjects |
208
|
|
View Code Duplication |
switch ($type) { |
|
|
|
|
209
|
|
|
case 'datetime': |
210
|
|
|
return date_format($value, $this->dateTimeFormat); |
211
|
|
|
|
212
|
|
|
case 'text': |
213
|
|
|
settype($value, 'string'); |
214
|
|
|
|
215
|
|
|
return $value; |
216
|
|
|
|
217
|
|
|
default: |
218
|
|
|
settype($value, $type); |
219
|
|
|
|
220
|
|
|
return $value; |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
private function createEntity(): EntityInterface |
225
|
|
|
{ |
226
|
|
|
$entityFqcn = $this->getEntityFqcn(); |
227
|
|
|
|
228
|
|
|
return new $entityFqcn(); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
private function mapPropertyToColumn(string $propertyName, $propertyValue): array |
232
|
|
|
{ |
233
|
|
|
$propertyType = $this->getPropertyType($propertyName); |
234
|
|
|
$columnName = $this->getColumnName($propertyName); |
235
|
|
|
|
236
|
|
|
return [$columnName => $this->mapPropertyValueToColumnValue($propertyValue, $propertyType)]; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
private function mapColumnToProperty(string $columnName, $columnValue): array |
240
|
|
|
{ |
241
|
|
|
$propertyName = $this->getPropertyName($columnName); |
242
|
|
|
$propertyType = $this->getPropertyType($propertyName); |
243
|
|
|
|
244
|
|
|
return [$propertyName => $this->mapColumnValueToPropertyValue($columnValue, $propertyType)]; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
private function getColumnToPropertyMapper(): array |
248
|
|
|
{ |
249
|
|
|
return array_flip($this->getPropertyToColumnMapper()); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
View Code Duplication |
private function getPropertyTypeMapper(): array |
253
|
|
|
{ |
254
|
|
|
if (null !== $this->propertyTypeMapper) { |
255
|
|
|
return $this->propertyTypeMapper; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
$this->propertyTypeMapper = []; |
259
|
|
|
foreach ($this->attributes as $attributeName => $attributeMetadata) { |
260
|
|
|
$this->propertyTypeMapper[$attributeName] = $attributeMetadata['type'] ?? 'string'; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
return $this->propertyTypeMapper; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
private function getPropertyType($propertyName): string |
267
|
|
|
{ |
268
|
|
|
$propertyTypeMapper = $this->getPropertyTypeMapper(); |
269
|
|
|
|
270
|
|
|
return $propertyTypeMapper[$propertyName] ?? 'string'; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
private function getColumnName(string $propertyName): string |
274
|
|
|
{ |
275
|
|
|
$propertyToColumnMapper = $this->getPropertyToColumnMapper(); |
276
|
|
|
|
277
|
|
|
return $propertyToColumnMapper[$propertyName] ?? $propertyName; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
private function getPropertyName(string $columnName): string |
281
|
|
|
{ |
282
|
|
|
$columnToPropertyMapper = $this->getColumnToPropertyMapper(); |
283
|
|
|
|
284
|
|
|
return $columnToPropertyMapper[$columnName] ?? $columnName; |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.