|
1
|
|
|
<?php |
|
2
|
|
|
/******************************************************************************** |
|
3
|
|
|
* Apache License, Version 2.0 * |
|
4
|
|
|
* * |
|
5
|
|
|
* Copyright [2020] [Nurlan Mukhanov <[email protected]>] * |
|
6
|
|
|
* * |
|
7
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); * |
|
8
|
|
|
* you may not use this file except in compliance with the License. * |
|
9
|
|
|
* You may obtain a copy of the License at * |
|
10
|
|
|
* * |
|
11
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 * |
|
12
|
|
|
* * |
|
13
|
|
|
* Unless required by applicable law or agreed to in writing, software * |
|
14
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, * |
|
15
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * |
|
16
|
|
|
* See the License for the specific language governing permissions and * |
|
17
|
|
|
* limitations under the License. * |
|
18
|
|
|
* * |
|
19
|
|
|
********************************************************************************/ |
|
20
|
|
|
|
|
21
|
|
|
declare(strict_types=1); |
|
22
|
|
|
|
|
23
|
|
|
namespace DBD\Entity; |
|
24
|
|
|
|
|
25
|
|
|
use DBD\Common\Singleton; |
|
26
|
|
|
use DBD\Entity\Common\Enforcer; |
|
27
|
|
|
use DBD\Entity\Common\EntityException; |
|
28
|
|
|
use DBD\Entity\Interfaces\FullEntity; |
|
29
|
|
|
use DBD\Entity\Interfaces\OnlyDeclaredPropertiesEntity; |
|
30
|
|
|
use DBD\Entity\Interfaces\StrictlyFilledEntity; |
|
31
|
|
|
use DBD\Entity\Interfaces\SyntheticEntity; |
|
32
|
|
|
use Exception; |
|
33
|
|
|
use ReflectionClass; |
|
34
|
|
|
use ReflectionObject; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* Class Entity |
|
38
|
|
|
* |
|
39
|
|
|
* @package DBD\Entity |
|
40
|
|
|
*/ |
|
41
|
|
|
abstract class Entity |
|
42
|
|
|
{ |
|
43
|
|
|
const SCHEME = "abstract"; |
|
44
|
|
|
const TABLE = "abstract"; |
|
45
|
|
|
|
|
46
|
|
|
/** @var array */ |
|
47
|
|
|
private $rawData; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* Конструктор модели |
|
51
|
|
|
* |
|
52
|
|
|
* @param array|null $data |
|
53
|
|
|
* @param int $maxLevels |
|
54
|
|
|
* @param int $currentLevel |
|
55
|
|
|
* |
|
56
|
|
|
* @throws EntityException |
|
57
|
|
|
*/ |
|
58
|
|
|
public function __construct(array $data = null, int $maxLevels = 2, int $currentLevel = 0) |
|
59
|
|
|
{ |
|
60
|
|
|
$this->rawData = $data; |
|
61
|
|
|
|
|
62
|
|
|
$calledClass = get_class($this); |
|
63
|
|
|
|
|
64
|
|
|
if (!$this instanceof SyntheticEntity) { |
|
65
|
|
|
Enforcer::__add(__CLASS__, $calledClass); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
try { |
|
69
|
|
|
/** @var Mapper $map */ |
|
70
|
|
|
$map = self::map(); |
|
71
|
|
|
} catch (Exception $e) { |
|
72
|
|
|
throw new EntityException(sprintf("Construction of %s failed, %s", $calledClass, $e->getMessage())); |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
if (!isset(EntityCache::$mapCache[$calledClass])) { |
|
76
|
|
|
/** @scrutinizer ignore-call */ |
|
77
|
|
|
$columnsDefinition = $map->getOriginFieldNames(); |
|
78
|
|
|
|
|
79
|
|
|
EntityCache::$mapCache[$calledClass][EntityCache::ARRAY_MAP] = $columnsDefinition; |
|
80
|
|
|
EntityCache::$mapCache[$calledClass][EntityCache::ARRAY_REVERSE_MAP] = array_flip($columnsDefinition); |
|
81
|
|
|
|
|
82
|
|
|
/* if ($this instanceof FullEntity or $this instanceof StrictlyFilledEntity) { |
|
83
|
|
|
foreach (get_object_vars($this) as $propertyName => $propertyDefaultValue) { |
|
84
|
|
|
if (!array_key_exists($propertyName, $columnsDefinition)) |
|
85
|
|
|
throw new EntityException(sprintf("FullEntity or StrictlyFilledEntity %s has unmapped property '%s'", $calledClass, $propertyName)); |
|
86
|
|
|
} |
|
87
|
|
|
}*/ |
|
88
|
|
|
|
|
89
|
|
|
// У нас может быть цепочка классов, где какой-то конечный уже не имеет интерфейса OnlyDeclaredPropertiesEntity |
|
90
|
|
|
// соответственно нам надо собрать все переменные всех дочерних классов, даже если они расширяют друг друга |
|
91
|
|
|
if ($this instanceof OnlyDeclaredPropertiesEntity) { |
|
92
|
|
|
$this->collectDeclarationsOnly(new ReflectionObject($this), $calledClass); |
|
93
|
|
|
} |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
if ($this instanceof OnlyDeclaredPropertiesEntity) { |
|
97
|
|
|
foreach (get_object_vars($this) as $varName => $varValue) { |
|
98
|
|
|
if (!isset(EntityCache::$mapCache[$calledClass][EntityCache::DECLARED_PROPERTIES][$varName]) && $varName != 'rawData') { |
|
99
|
|
|
unset($this->$varName); |
|
100
|
|
|
EntityCache::$mapCache[$calledClass][EntityCache::UNSET_PROPERTIES][$varName] = true; |
|
101
|
|
|
} |
|
102
|
|
|
} |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
if ($this instanceof FullEntity or $this instanceof StrictlyFilledEntity) { |
|
106
|
|
|
$checkAgainst = array_merge($map->getColumns(), $map->getComplex(), $map->getEmbedded(), $map->getConstraints()); |
|
107
|
|
|
foreach (get_object_vars($this) as $propertyName => $propertyDefaultValue) { |
|
108
|
|
|
if (!array_key_exists($propertyName, $checkAgainst) && $propertyName != 'rawData') { |
|
109
|
|
|
throw new EntityException(sprintf("Strict Entity %s has unmapped property '%s'", $calledClass, $propertyName)); |
|
110
|
|
|
} |
|
111
|
|
|
} |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
if (is_null($this->rawData)) { |
|
115
|
|
|
return; |
|
116
|
|
|
} |
|
117
|
|
|
// Если мы определяем класс с интерфейсом OnlyDeclaredPropertiesEntity и экстендим его |
|
118
|
|
|
// то по сути мы не можем знать какие переменные классам нам обязательны к обработке. |
|
119
|
|
|
// Ладно еще если это 2 класса, а если цепочка? |
|
120
|
|
|
//if($this instanceof OnlyDeclaredPropertiesEntity and !$reflectionObject->isFinal()) |
|
121
|
|
|
// throw new EntityException("Class " . $reflectionObject->getParentClass()->getShortName() . " which implements OnlyDeclaredPropertiesEntity interface must be final"); |
|
122
|
|
|
|
|
123
|
|
|
if ($currentLevel <= $maxLevels) { |
|
124
|
|
|
$this->setModelData($map, $maxLevels, $currentLevel); |
|
125
|
|
|
} |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
/** |
|
129
|
|
|
* @return Singleton|Mapper|static |
|
130
|
|
|
* @throws EntityException |
|
131
|
|
|
* @noinspection PhpDocMissingThrowsInspection ReflectionClass will never throw exception because of get_called_class() |
|
132
|
|
|
*/ |
|
133
|
|
|
final public static function map() |
|
134
|
|
|
{ |
|
135
|
|
|
$calledClass = get_called_class(); |
|
136
|
|
|
|
|
137
|
|
|
$mapClass = $calledClass . Mapper::POSTFIX; |
|
138
|
|
|
|
|
139
|
|
|
if (!class_exists($mapClass, false)) { |
|
140
|
|
|
throw new EntityException(sprintf("Class %s does not have Map definition", $calledClass)); |
|
141
|
|
|
} |
|
142
|
|
|
|
|
143
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */ |
|
144
|
|
|
$reflection = new ReflectionClass($calledClass); |
|
145
|
|
|
$interfaces = $reflection->getInterfaces(); |
|
146
|
|
|
|
|
147
|
|
|
if (isset($interfaces[SyntheticEntity::class])) { |
|
148
|
|
|
return $mapClass::meWithoutEnforcer(); |
|
149
|
|
|
} else { |
|
150
|
|
|
return $mapClass::me(); |
|
151
|
|
|
} |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
/** |
|
155
|
|
|
* @param ReflectionClass $reflectionObject |
|
156
|
|
|
* @param string $calledClass |
|
157
|
|
|
* @param string|null $parentClass |
|
158
|
|
|
*/ |
|
159
|
|
|
private function collectDeclarationsOnly(ReflectionClass $reflectionObject, string $calledClass, string $parentClass = null): void |
|
160
|
|
|
{ |
|
161
|
|
|
foreach ($reflectionObject->getProperties() as $property) { |
|
162
|
|
|
|
|
163
|
|
|
$declaringClass = $property->getDeclaringClass(); |
|
164
|
|
|
|
|
165
|
|
|
if ($declaringClass->name == $calledClass || $declaringClass->name == $parentClass) { |
|
166
|
|
|
EntityCache::$mapCache[$calledClass][EntityCache::DECLARED_PROPERTIES][$property->name] = true; |
|
167
|
|
|
} |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
$parentClass = $reflectionObject->getParentClass(); |
|
171
|
|
|
$parentInterfaces = $parentClass->getInterfaces(); |
|
172
|
|
|
|
|
173
|
|
|
if (isset($parentInterfaces[OnlyDeclaredPropertiesEntity::class])) { |
|
174
|
|
|
$this->collectDeclarationsOnly($parentClass, $calledClass, $parentClass->name); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
/** If we have defined declaredProperties key, we must exclude some keys from reverseMap and arrayMap */ |
|
178
|
|
|
if (isset(EntityCache::$mapCache[$calledClass][EntityCache::DECLARED_PROPERTIES])) { |
|
179
|
|
|
foreach (EntityCache::$mapCache[$calledClass][EntityCache::ARRAY_MAP] as $propertyName => $fieldName) { |
|
180
|
|
|
if (!array_key_exists($propertyName, EntityCache::$mapCache[$calledClass][EntityCache::DECLARED_PROPERTIES])) { |
|
181
|
|
|
unset(EntityCache::$mapCache[$calledClass][EntityCache::ARRAY_MAP][$propertyName]); |
|
182
|
|
|
unset(EntityCache::$mapCache[$calledClass][EntityCache::ARRAY_REVERSE_MAP][$fieldName]); |
|
183
|
|
|
} |
|
184
|
|
|
} |
|
185
|
|
|
} |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
/** |
|
189
|
|
|
* @param Mapper $map |
|
190
|
|
|
* @param int $maxLevels |
|
191
|
|
|
* @param int $currentLevel |
|
192
|
|
|
* |
|
193
|
|
|
* @throws EntityException |
|
194
|
|
|
*/ |
|
195
|
|
|
private function setModelData(Mapper $map, int $maxLevels, int $currentLevel): void |
|
196
|
|
|
{ |
|
197
|
|
|
$currentLevel++; |
|
198
|
|
|
|
|
199
|
|
|
$this->setBaseColumns($map); |
|
200
|
|
|
|
|
201
|
|
|
// TODO: check if I declare Constraint in Mapper and use same property name in Entity |
|
202
|
|
|
$this->setEmbedded($map, $maxLevels, $currentLevel); |
|
203
|
|
|
|
|
204
|
|
|
$this->setComplex($map, $maxLevels, $currentLevel); |
|
205
|
|
|
|
|
206
|
|
|
$this->postProcessing(); |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
/** |
|
210
|
|
|
* Reads public variables and set them to the self instance |
|
211
|
|
|
* |
|
212
|
|
|
* @param Mapper $mapper |
|
213
|
|
|
* |
|
214
|
|
|
* @throws EntityException |
|
215
|
|
|
* @throws \ReflectionException |
|
216
|
|
|
*/ |
|
217
|
|
|
private function setBaseColumns(Mapper $mapper) |
|
218
|
|
|
{ |
|
219
|
|
|
$calledClass = get_called_class(); |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* @var array $fieldMapping are public properties of Mapper |
|
223
|
|
|
* where KEY is database origin column name and VALUE is Entity class field declaration |
|
224
|
|
|
* Structure look like this: |
|
225
|
|
|
* { |
|
226
|
|
|
* "person_email": "email", |
|
227
|
|
|
* "person_id": "id", |
|
228
|
|
|
* "person_is_active": "isActive", |
|
229
|
|
|
* "person_name": "name", |
|
230
|
|
|
* "person_registration_date": "registrationDate" |
|
231
|
|
|
* } |
|
232
|
|
|
* EntityCache declaration happens in out constructor only once for time savings |
|
233
|
|
|
*/ |
|
234
|
|
|
$fieldMapping = EntityCache::$mapCache[$calledClass][EntityCache::ARRAY_REVERSE_MAP]; |
|
235
|
|
|
|
|
236
|
|
|
/** If it is FullEntity or StrictlyFilledEntity, we must ensure all database columns are provided */ |
|
237
|
|
|
if ($this instanceof FullEntity or $this instanceof StrictlyFilledEntity) { |
|
238
|
|
|
$intersection = array_intersect_key($fieldMapping, $this->rawData); |
|
239
|
|
|
if ($intersection != $fieldMapping) { |
|
240
|
|
|
throw new EntityException(sprintf("Missing columns for FullEntity or StrictlyFilledEntity '%s': %s", |
|
241
|
|
|
get_class($this), |
|
242
|
|
|
json_encode(array_keys(array_diff_key($fieldMapping, $intersection))) |
|
243
|
|
|
) |
|
244
|
|
|
); |
|
245
|
|
|
} |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
/** |
|
249
|
|
|
* @var string $originColumnName database origin column name |
|
250
|
|
|
* @var mixed $columnValue value of this columns |
|
251
|
|
|
*/ |
|
252
|
|
|
foreach ($this->rawData as $originColumnName => &$columnValue) { |
|
253
|
|
|
|
|
254
|
|
|
/** process only if Entity class has such field declaration */ |
|
255
|
|
|
if (!isset($fieldMapping[$originColumnName])) { |
|
256
|
|
|
continue; |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
/** @var string $property name of field declaration in Entity class */ |
|
260
|
|
|
$property = $fieldMapping[$originColumnName]; |
|
261
|
|
|
|
|
262
|
|
|
if (!property_exists($this, $property)) { |
|
263
|
|
|
continue; |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
/** Note: Function names are case-insensitive, though it is usually good form to call functions as they appear in their declaration. */ |
|
267
|
|
|
$setterMethod = sprintf("set%s", $property); |
|
268
|
|
|
|
|
269
|
|
|
/** @var Column $fieldDefinition */ |
|
270
|
|
|
$fieldDefinition = $mapper->$property; |
|
271
|
|
|
|
|
272
|
|
|
if (is_null($columnValue) and $fieldDefinition->nullable === false) { |
|
273
|
|
|
throw new EntityException(sprintf("Column %s of %s shouldn't accept null values according Mapper definition", $originColumnName, $calledClass)); |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
/** We can define setter method for field definition in Entity class, so let's check it first */ |
|
277
|
|
|
if (method_exists($this, $setterMethod)) { |
|
278
|
|
|
$this->$setterMethod($columnValue); |
|
279
|
|
|
} else { |
|
280
|
|
|
/** If initially column type is json, then let's parse it as JSON */ |
|
281
|
|
|
if (!is_null($columnValue) && !is_null($fieldDefinition->originType) && stripos($fieldDefinition->originType, "json") !== false) { |
|
282
|
|
|
$this->$property = json_decode($columnValue, true); |
|
283
|
|
|
} else { |
|
284
|
|
|
/** |
|
285
|
|
|
* Entity public variables should not have default values. |
|
286
|
|
|
* But sometimes we need to have default value for column in case of $rowData has null value |
|
287
|
|
|
* In this case we should not override default value if $columnValue is null |
|
288
|
|
|
* Иными словами нельзя переписывать дефолтное значение, если из базы пришло null |
|
289
|
|
|
* но, если нет дефолтного значения, то мы должны его проинизиализировать null значением |
|
290
|
|
|
*/ |
|
291
|
|
|
$reflection = new ReflectionObject($this); |
|
292
|
|
|
$reflectionProperty = $reflection->getProperty($property); |
|
293
|
|
|
|
|
294
|
|
|
// Если мы еще не инциализировали переменную и у нас есть значение для этой переменной |
|
295
|
|
|
//if (!isset($this->$property)) { |
|
296
|
|
|
|
|
297
|
|
|
// Если у нас есть значение, то ставим его |
|
298
|
|
|
if (isset($columnValue)) { |
|
299
|
|
|
$this->$property = &$columnValue; |
|
300
|
|
|
} else { |
|
301
|
|
|
// У нас нет прицепленного значения |
|
302
|
|
|
if (!$reflectionProperty->hasDefaultValue()) { |
|
303
|
|
|
$this->$property = $columnValue; // this is NULL value |
|
304
|
|
|
} |
|
305
|
|
|
} |
|
306
|
|
|
//} |
|
307
|
|
|
} |
|
308
|
|
|
} |
|
309
|
|
|
} |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
/** |
|
313
|
|
|
* @param Mapper $map |
|
314
|
|
|
* @param int $maxLevels |
|
315
|
|
|
* @param int $currentLevel |
|
316
|
|
|
* |
|
317
|
|
|
* @throws EntityException |
|
318
|
|
|
*/ |
|
319
|
|
|
private function setEmbedded(Mapper $map, int $maxLevels, int $currentLevel) |
|
320
|
|
|
{ |
|
321
|
|
|
if ($this instanceof FullEntity or $this instanceof StrictlyFilledEntity) { |
|
322
|
|
|
/** @var Embedded[] $embeddings */ |
|
323
|
|
|
$embeddings = MapperCache::me()->embedded[$map->name()]; |
|
324
|
|
|
$missingColumns = []; |
|
325
|
|
|
foreach ($embeddings as $embedding) { |
|
326
|
|
|
if ($embedding->name !== false and !array_key_exists($embedding->name, $this->rawData)) { |
|
327
|
|
|
$missingColumns[] = $embedding->name; |
|
328
|
|
|
} |
|
329
|
|
|
} |
|
330
|
|
|
if (count($missingColumns) > 0) { |
|
331
|
|
|
throw new EntityException(sprintf("Seems you forgot to select columns for FullEntity or StrictlyFilledEntity '%s': %s", |
|
332
|
|
|
get_class($this), |
|
333
|
|
|
json_encode($missingColumns) |
|
334
|
|
|
) |
|
335
|
|
|
); |
|
336
|
|
|
} |
|
337
|
|
|
} |
|
338
|
|
|
|
|
339
|
|
|
foreach ($map->getEmbedded() as $embeddedName => $embeddedValue) { |
|
340
|
|
|
if ($embeddedValue->name === false) { |
|
341
|
|
|
continue; |
|
342
|
|
|
} |
|
343
|
|
|
if ($currentLevel <= $maxLevels) { |
|
344
|
|
|
$setterMethod = "set" . ucfirst($embeddedName); |
|
345
|
|
|
|
|
346
|
|
|
if (method_exists($this, $setterMethod)) { |
|
347
|
|
|
$this->$setterMethod($this->rawData[$embeddedValue->name]); |
|
348
|
|
|
continue; |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
if (isset($embeddedValue->dbType) and $embeddedValue->dbType == Type::Json) { |
|
352
|
|
|
if (isset($this->rawData[$embeddedValue->name]) and is_string($this->rawData[$embeddedValue->name])) { |
|
353
|
|
|
$this->rawData[$embeddedValue->name] = json_decode($this->rawData[$embeddedValue->name], true); |
|
354
|
|
|
} |
|
355
|
|
|
} |
|
356
|
|
|
if (isset($embeddedValue->entityClass)) { |
|
357
|
|
|
if ($embeddedValue->isIterable) { |
|
358
|
|
|
$iterables = []; |
|
359
|
|
|
if (isset($this->rawData[$embeddedValue->name]) and !is_null($this->rawData[$embeddedValue->name])) { |
|
360
|
|
|
foreach ($this->rawData[$embeddedValue->name] as $value) { |
|
361
|
|
|
$iterables[] = new $embeddedValue->entityClass($value, $maxLevels, $currentLevel); |
|
362
|
|
|
} |
|
363
|
|
|
$this->$embeddedName = $iterables; |
|
364
|
|
|
} |
|
365
|
|
|
} else { |
|
366
|
|
|
$this->$embeddedName = new $embeddedValue->entityClass($this->rawData[$embeddedValue->name], $maxLevels, $currentLevel); |
|
367
|
|
|
} |
|
368
|
|
|
} else { |
|
369
|
|
|
$this->$embeddedName = &$this->rawData[$embeddedValue->name]; |
|
370
|
|
|
} |
|
371
|
|
|
} else { |
|
372
|
|
|
unset($this->$embeddedName); |
|
373
|
|
|
} |
|
374
|
|
|
} |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
/** |
|
378
|
|
|
* @param Mapper $map |
|
379
|
|
|
* @param int $maxLevels |
|
380
|
|
|
* @param int $currentLevel |
|
381
|
|
|
*/ |
|
382
|
|
|
private function setComplex(Mapper $map, int $maxLevels, int $currentLevel) |
|
383
|
|
|
{ |
|
384
|
|
|
foreach ($map->getComplex() as $complexName => $complexValue) { |
|
385
|
|
|
//if (!property_exists($this, $complexName) or isset(EntityCache::$mapCache[get_called_class()][EntityCache::UNSET_PROPERTIES][$complexName])) |
|
386
|
|
|
// continue; |
|
387
|
|
|
|
|
388
|
|
|
if ($currentLevel <= $maxLevels) { |
|
389
|
|
|
$this->$complexName = new $complexValue->complexClass($this->rawData, $maxLevels, $currentLevel); |
|
390
|
|
|
} else { |
|
391
|
|
|
unset($this->$complexName); |
|
392
|
|
|
} |
|
393
|
|
|
} |
|
394
|
|
|
} |
|
395
|
|
|
|
|
396
|
|
|
/** |
|
397
|
|
|
* If entity data should be modified after setModelData, create same function in Entity. |
|
398
|
|
|
* For example, it is heavy cost to aggregate some data in SQL side, any more cost-efficient will do that with PHP |
|
399
|
|
|
* |
|
400
|
|
|
* @see Embedded::$name |
|
401
|
|
|
* @see setModelData() |
|
402
|
|
|
*/ |
|
403
|
|
|
protected function postProcessing(): void |
|
404
|
|
|
{ |
|
405
|
|
|
} |
|
406
|
|
|
|
|
407
|
|
|
/** |
|
408
|
|
|
* get Entity table name |
|
409
|
|
|
* |
|
410
|
|
|
* @return string |
|
411
|
|
|
*/ |
|
412
|
|
|
public static function table(): string |
|
413
|
|
|
{ |
|
414
|
|
|
$calledClass = get_called_class(); |
|
415
|
|
|
|
|
416
|
|
|
return $calledClass::SCHEME . "." . $calledClass::TABLE; |
|
417
|
|
|
} |
|
418
|
|
|
|
|
419
|
|
|
/** |
|
420
|
|
|
* @return array|null |
|
421
|
|
|
*/ |
|
422
|
|
|
public function raw(): ?array |
|
423
|
|
|
{ |
|
424
|
|
|
return $this->rawData; |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
/** |
|
428
|
|
|
* Special getter to access properties with getters |
|
429
|
|
|
* For example, having method getName you can access $name property declared with (@)property annotation |
|
430
|
|
|
* @param string $methodName |
|
431
|
|
|
* @return mixed |
|
432
|
|
|
* @throws EntityException |
|
433
|
|
|
*/ |
|
434
|
|
|
public function __get(string $methodName) |
|
435
|
|
|
{ |
|
436
|
|
|
$lookupMethod = $methodName; |
|
437
|
|
|
|
|
438
|
|
|
if (ctype_lower($methodName[0])) { |
|
439
|
|
|
$lookupMethod = ucfirst($methodName); |
|
440
|
|
|
} |
|
441
|
|
|
|
|
442
|
|
|
$lookupMethod = "get" . $lookupMethod; |
|
443
|
|
|
|
|
444
|
|
|
if (!method_exists($this, $lookupMethod)) { |
|
445
|
|
|
throw new EntityException(sprintf("Can't find property or getter method for '\$%s' of '%s'", $methodName, get_class($this))); |
|
446
|
|
|
} |
|
447
|
|
|
|
|
448
|
|
|
$this->$methodName = $this->$lookupMethod(); |
|
449
|
|
|
|
|
450
|
|
|
return $this->$methodName; |
|
451
|
|
|
} |
|
452
|
|
|
} |
|
453
|
|
|
|