1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace EndorphinStudio\DataObject; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* BaseObject |
7
|
|
|
* @package EndorphinStudio\DataObject |
8
|
|
|
* @author Serhii Nekhaienko <[email protected]> |
9
|
|
|
*/ |
10
|
|
|
abstract class BaseObject implements \JsonSerializable |
11
|
|
|
{ |
12
|
|
|
public const CAMEL_CASE = ''; |
13
|
|
|
public const SNAKE_LASE = '_'; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* @var array $data |
17
|
|
|
* Array of data fields with data |
18
|
|
|
*/ |
19
|
|
|
protected array $data; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var string[] |
23
|
|
|
* Define mappings of fields |
24
|
|
|
*/ |
25
|
|
|
protected array $fieldTypeMapping = [ |
26
|
|
|
|
27
|
|
|
]; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var string[] |
31
|
|
|
* Define fields which are list of objects |
32
|
|
|
*/ |
33
|
|
|
protected array $listFields = [ |
34
|
|
|
|
35
|
|
|
]; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var string[] |
39
|
|
|
* List of primitives |
40
|
|
|
*/ |
41
|
|
|
protected array $primitiveTypes = [ |
42
|
|
|
'int', |
43
|
|
|
'integer', |
44
|
|
|
'string', |
45
|
|
|
'boolean', |
46
|
|
|
'double', |
47
|
|
|
'string', |
48
|
|
|
]; |
49
|
|
|
|
50
|
|
|
public function __construct(array $data = []) |
51
|
|
|
{ |
52
|
|
|
if (!is_array($data)) { |
|
|
|
|
53
|
|
|
throw new \RuntimeException('$data should be an array'); |
54
|
|
|
} |
55
|
|
|
$this->data = $data; |
56
|
|
|
|
57
|
|
|
foreach ($this->data as $key => $value) { |
58
|
|
|
if (is_array($value)) { |
59
|
|
|
$this->setProperty($key, $this->fromArray($key, $value)); |
60
|
|
|
} |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Call init$PropertyName if method exist |
65
|
|
|
*/ |
66
|
|
|
foreach (array_keys($this->data) as $option) { |
67
|
|
|
$key = $this->convertFieldName($option); |
68
|
|
|
|
69
|
|
|
$method = 'init' . ucfirst($key); |
70
|
|
|
if (method_exists($this, $method)) { |
71
|
|
|
$this->$method(); |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @param string $field |
78
|
|
|
* @param array $value |
79
|
|
|
* @return DataObject|mixed|BaseObject |
80
|
|
|
*/ |
81
|
|
|
private function fromArray(string $field, array $value) |
82
|
|
|
{ |
83
|
|
|
/** |
84
|
|
|
* Check if this field has type mapping |
85
|
|
|
*/ |
86
|
|
|
if (array_key_exists($field, $this->fieldTypeMapping)) { |
87
|
|
|
$className = $this->fieldTypeMapping[$field]; |
88
|
|
|
if (class_exists($className)) { |
89
|
|
|
return new $className($value); |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Check if this field is list of objects |
95
|
|
|
*/ |
96
|
|
|
if (array_key_exists($field, $this->listFields)) { |
97
|
|
|
$className = $this->listFields[$field]; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Check if class is primitive (int, string, etc.) |
101
|
|
|
*/ |
102
|
|
|
if (in_array($className, $this->primitiveTypes, true)) { |
103
|
|
|
return $value; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
if (class_exists($className)) { |
107
|
|
|
$list = []; |
108
|
|
|
foreach ($value as $item) { |
109
|
|
|
$list[] = new $className($item); |
110
|
|
|
} |
111
|
|
|
return $list; |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
return new DataObject($value); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* @param $name |
120
|
|
|
* @return mixed|null |
121
|
|
|
* Return object |
122
|
|
|
*/ |
123
|
|
|
public function __get($name) |
124
|
|
|
{ |
125
|
|
|
return $this->data[$name] ?? null; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* @param $name |
130
|
|
|
* @param $value |
131
|
|
|
* Set object |
132
|
|
|
*/ |
133
|
|
|
public function __set($name, $value) |
134
|
|
|
{ |
135
|
|
|
$this->data[$name] = $value; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @param $name |
140
|
|
|
* @return bool |
141
|
|
|
* Check if field exist |
142
|
|
|
*/ |
143
|
|
|
public function __isset($name): bool |
144
|
|
|
{ |
145
|
|
|
return $this->_has($name); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* @param $name |
150
|
|
|
* @return bool |
151
|
|
|
* Check if field exist |
152
|
|
|
*/ |
153
|
|
|
private function _has($name): bool |
154
|
|
|
{ |
155
|
|
|
return array_key_exists( |
156
|
|
|
$name, |
157
|
|
|
$this->data |
158
|
|
|
) && $this->data[$name]; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @param $name |
163
|
|
|
* @param $arguments |
164
|
|
|
* @return boolean|mixed|null |
165
|
|
|
*/ |
166
|
|
|
public function __call($name, $arguments) |
167
|
|
|
{ |
168
|
|
|
$words = preg_split('/(?=[A-Z])/', $name); |
169
|
|
|
$methodType = $words[0]; |
170
|
|
|
unset($words[0]); |
171
|
|
|
$field_name = $this->getFieldName(implode('', $words)); |
172
|
|
|
switch ($methodType) { |
173
|
|
|
case 'get': |
174
|
|
|
return $this->$field_name; |
175
|
|
|
break; |
|
|
|
|
176
|
|
|
case 'has': |
177
|
|
|
case 'is': |
178
|
|
|
return $this->_has($field_name); |
179
|
|
|
break; |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Return field name for data |
185
|
|
|
* @param $name |
186
|
|
|
* @return string |
187
|
|
|
*/ |
188
|
|
|
private function getFieldName($name): string |
189
|
|
|
{ |
190
|
|
|
$name = lcfirst($name); |
191
|
|
|
$camelName = $this->convertFieldName($name, self::CAMEL_CASE); |
192
|
|
|
$snakeName = $this->convertFieldName($name, self::SNAKE_LASE); |
193
|
|
|
if (array_key_exists($camelName, $this->data)) { |
194
|
|
|
return $camelName; |
195
|
|
|
} |
196
|
|
|
if (array_key_exists($snakeName, $this->data)) { |
197
|
|
|
return $snakeName; |
198
|
|
|
} |
199
|
|
|
return $name; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Convert MyField to my_field or my_field to MyField |
204
|
|
|
* @param string $name |
205
|
|
|
* @param string $replacement |
206
|
|
|
* @return string |
207
|
|
|
*/ |
208
|
|
|
private function convertFieldName($name, $replacement = ''): string |
209
|
|
|
{ |
210
|
|
|
$words = preg_split('/(?=[A-Z_])/', $name); |
211
|
|
|
foreach ($words as &$word) { |
212
|
|
|
$word = str_replace('_', '', $word); |
213
|
|
|
} |
214
|
|
|
if (!empty($replacement)) { |
215
|
|
|
return strtolower(implode($replacement, $words)); |
216
|
|
|
} |
217
|
|
|
foreach ($words as $i => &$word) { |
218
|
|
|
if ($i === 0) { |
219
|
|
|
continue; |
220
|
|
|
} |
221
|
|
|
$word = ucfirst($word); |
222
|
|
|
} |
223
|
|
|
return implode($replacement, $words); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* @param $key |
228
|
|
|
* @return bool |
229
|
|
|
* Check that fieldExist |
230
|
|
|
*/ |
231
|
|
|
public function has($key): bool |
232
|
|
|
{ |
233
|
|
|
return $this->_has($key); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* @return bool |
238
|
|
|
* Check if errors exist in object |
239
|
|
|
*/ |
240
|
|
|
public function hasErrors(): bool |
241
|
|
|
{ |
242
|
|
|
return $this->has('error') || $this->has('errors'); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @return string |
247
|
|
|
* Return error |
248
|
|
|
*/ |
249
|
|
|
public function getError(): string |
250
|
|
|
{ |
251
|
|
|
return ($this->has('error')) ? $this->data['error'] : $this->data['errors']; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @return array |
256
|
|
|
* Return all fields |
257
|
|
|
*/ |
258
|
|
|
public function getData(): array |
259
|
|
|
{ |
260
|
|
|
return $this->data; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* @param string $propertyName |
265
|
|
|
* @return mixed|null |
266
|
|
|
* Return field |
267
|
|
|
*/ |
268
|
|
|
public function getProperty(string $propertyName) |
269
|
|
|
{ |
270
|
|
|
if ($this->has($propertyName)) { |
271
|
|
|
return $this->$propertyName; |
272
|
|
|
} |
273
|
|
|
return null; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* @return array |
278
|
|
|
* Used during jsonSerialization |
279
|
|
|
*/ |
280
|
|
|
public function jsonSerialize(): array |
281
|
|
|
{ |
282
|
|
|
return $this->toArray(); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* @param string $propertyName |
287
|
|
|
* @param $value |
288
|
|
|
* Set field |
289
|
|
|
*/ |
290
|
|
|
public function setProperty(string $propertyName, $value): void |
291
|
|
|
{ |
292
|
|
|
$this->data[$propertyName] = $value; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* @return array |
297
|
|
|
* Return array with data |
298
|
|
|
*/ |
299
|
|
|
public function toArray(): array |
300
|
|
|
{ |
301
|
|
|
return $this->getData(); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* @param string $json |
306
|
|
|
* @return BaseObject |
307
|
|
|
* @throws \JsonException |
308
|
|
|
* Create object from JSON string |
309
|
|
|
*/ |
310
|
|
|
public static function fromJsonString(string $json): BaseObject |
311
|
|
|
{ |
312
|
|
|
$jsonObject = json_decode($json, true, 512, JSON_THROW_ON_ERROR); |
313
|
|
|
return new static($jsonObject); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* @return array |
318
|
|
|
* Return array with names of properties |
319
|
|
|
*/ |
320
|
|
|
public function getPropertyNames(): array |
321
|
|
|
{ |
322
|
|
|
return array_keys($this->toArray()); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* @param string $propertyName |
327
|
|
|
* @return bool |
328
|
|
|
* Return true if property is list or array |
329
|
|
|
*/ |
330
|
|
|
public function isList(string $propertyName): bool |
331
|
|
|
{ |
332
|
|
|
return is_array($this->getProperty($propertyName)); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* @param string $propertyName |
337
|
|
|
* @return bool |
338
|
|
|
* Return true if property is object or list of object |
339
|
|
|
*/ |
340
|
|
|
public function isObject(string $propertyName): bool |
341
|
|
|
{ |
342
|
|
|
$value = $this->getProperty($propertyName); |
343
|
|
|
if (is_object($value)) { |
344
|
|
|
return true; |
345
|
|
|
} |
346
|
|
|
if (in_array($propertyName, $this->listFields, true) && $this->isList($propertyName) && is_object($value[0])) { |
347
|
|
|
return true; |
348
|
|
|
} |
349
|
|
|
return false; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* @param string $propertyName |
354
|
|
|
* @return bool |
355
|
|
|
* Return true if property has primitive type (string, integer, boolean) |
356
|
|
|
*/ |
357
|
|
|
public function isPrimitive(string $propertyName): bool |
358
|
|
|
{ |
359
|
|
|
return in_array(gettype($this->getProperty($propertyName)), $this->primitiveTypes, true); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
protected function filterProperties(string $filterFunction): array |
363
|
|
|
{ |
364
|
|
|
if (!method_exists($this, $filterFunction)) { |
365
|
|
|
return []; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
$properties = $this->getPropertyNames(); |
369
|
|
|
$result = []; |
370
|
|
|
foreach ($properties as $property) { |
371
|
|
|
if($this->$filterFunction($property)) { |
372
|
|
|
$result[] = $property; |
373
|
|
|
} |
374
|
|
|
} |
375
|
|
|
return $result; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* @return array |
380
|
|
|
* Return property names which has primitive type (string, integer, boolean) |
381
|
|
|
*/ |
382
|
|
|
public function getPrimitivePropertyNames(): array |
383
|
|
|
{ |
384
|
|
|
return $this->filterProperties('isPrimitive'); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* @param array $names |
389
|
|
|
* @return array |
390
|
|
|
* Return array with desired properties and their values |
391
|
|
|
*/ |
392
|
|
|
protected function getPropertiesByNames(array $names): array |
393
|
|
|
{ |
394
|
|
|
$result = []; |
395
|
|
|
foreach ($names as $property) { |
396
|
|
|
$result[$property] = $this->getProperty($property); |
397
|
|
|
} |
398
|
|
|
return $result; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* @return array |
403
|
|
|
* Return array with properties which has primitive type (integer, string, boolean) |
404
|
|
|
*/ |
405
|
|
|
public function getPrimitiveProperties(): array |
406
|
|
|
{ |
407
|
|
|
return $this->getPropertiesByNames($this->getPrimitivePropertyNames()); |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* @return array |
412
|
|
|
* Return array with property names which are list or array |
413
|
|
|
*/ |
414
|
|
|
public function getListPropertyNames(): array |
415
|
|
|
{ |
416
|
|
|
return $this->filterProperties('isList'); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* @return array |
421
|
|
|
* Return array with property which are list or array and their values |
422
|
|
|
*/ |
423
|
|
|
public function getListProperties(): array |
424
|
|
|
{ |
425
|
|
|
return $this->getPropertiesByNames($this->getListPropertyNames()); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* @return array |
430
|
|
|
* Return array with property names which are objects or list of objects |
431
|
|
|
*/ |
432
|
|
|
public function getObjectPropertyNames(): array |
433
|
|
|
{ |
434
|
|
|
return $this->filterProperties('isObject'); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* @return array |
439
|
|
|
* Return array with properties which are objects or list of objects |
440
|
|
|
*/ |
441
|
|
|
public function getObjectProperties(): array |
442
|
|
|
{ |
443
|
|
|
return $this->getPropertiesByNames($this->getObjectPropertyNames()); |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
|