1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework. |
4
|
|
|
* |
5
|
|
|
* @license MIT |
6
|
|
|
* @author Anton Titov (Wolfy-J) |
7
|
|
|
*/ |
8
|
|
|
namespace Spiral\Models\Prototypes; |
9
|
|
|
|
10
|
|
|
use Doctrine\Common\Inflector\Inflector; |
11
|
|
|
use Spiral\Models\AccessorInterface; |
12
|
|
|
use Spiral\Models\EntityInterface; |
13
|
|
|
use Spiral\Models\Events\EntityEvent; |
14
|
|
|
use Spiral\Models\Exceptions\AccessorExceptionInterface; |
15
|
|
|
use Spiral\Models\Exceptions\EntityException; |
16
|
|
|
use Spiral\Models\Exceptions\FieldExceptionInterface; |
17
|
|
|
use Spiral\Models\PublishableInterface; |
18
|
|
|
use Spiral\Validation\ValueInterface; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* AbstractEntity with ability to define field mutators and access |
22
|
|
|
*/ |
23
|
|
|
abstract class AbstractEntity extends MutableObject implements |
24
|
|
|
EntityInterface, |
25
|
|
|
\JsonSerializable, |
26
|
|
|
\IteratorAggregate, |
27
|
|
|
\ArrayAccess, |
28
|
|
|
ValueInterface, |
29
|
|
|
PublishableInterface |
30
|
|
|
{ |
31
|
|
|
/** |
32
|
|
|
* Field format declares how entity must process magic setters and getters. Available values: |
33
|
|
|
* camelCase, tableize. |
34
|
|
|
*/ |
35
|
|
|
const FIELD_FORMAT = 'camelCase'; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Field mutators. |
39
|
|
|
*/ |
40
|
|
|
const MUTATOR_GETTER = 'getter'; |
41
|
|
|
const MUTATOR_SETTER = 'setter'; |
42
|
|
|
const MUTATOR_ACCESSOR = 'accessor'; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var array |
46
|
|
|
*/ |
47
|
|
|
private $fields = []; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @param array $fields |
51
|
|
|
*/ |
52
|
|
|
public function __construct(array $fields = []) |
53
|
|
|
{ |
54
|
|
|
$this->fields = $fields; |
55
|
|
|
parent::__construct(); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Routes user function in format of (get|set)FieldName into (get|set)Field(fieldName, value). |
60
|
|
|
* |
61
|
|
|
* @see getFeld() |
62
|
|
|
* @see setField() |
63
|
|
|
* |
64
|
|
|
* @param string $method |
65
|
|
|
* @param array $arguments |
66
|
|
|
* |
67
|
|
|
* @return $this|mixed|null|AccessorInterface |
68
|
|
|
* |
69
|
|
|
* @throws EntityException |
70
|
|
|
*/ |
71
|
|
|
public function __call(string $method, array $arguments) |
72
|
|
|
{ |
73
|
|
|
if (method_exists($this, $method)) { |
74
|
|
|
throw new EntityException( |
75
|
|
|
"Method name '{$method}' is ambiguous and can not be used as magic setter" |
76
|
|
|
); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
if (strlen($method) <= 3) { |
80
|
|
|
//Get/set needs exactly 0-1 argument |
81
|
|
|
throw new EntityException("Undefined method {$method}"); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
$field = substr($method, 3); |
85
|
|
|
|
86
|
|
|
switch (static::FIELD_FORMAT) { |
87
|
|
|
case 'camelCase': |
88
|
|
|
$field = Inflector::camelize($field); |
89
|
|
|
break; |
90
|
|
|
case 'tableize': |
91
|
|
|
$field = Inflector::tableize($field); |
92
|
|
|
break; |
93
|
|
|
default: |
94
|
|
|
throw new EntityException( |
95
|
|
|
"Undefined field format '" . static::FIELD_FORMAT . "'" |
96
|
|
|
); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
switch (substr($method, 0, 3)) { |
100
|
|
|
case 'get': |
101
|
|
|
return $this->getField($field); |
102
|
|
|
case 'set': |
103
|
|
|
if (count($arguments) === 1) { |
104
|
|
|
$this->setField($field, $arguments[0]); |
105
|
|
|
|
106
|
|
|
//setFieldA($a)->setFieldB($b) |
|
|
|
|
107
|
|
|
return $this; |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
throw new EntityException("Undefined method {$method}."); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* {@inheritdoc} |
116
|
|
|
*/ |
117
|
|
|
public function hasField(string $name): bool |
118
|
|
|
{ |
119
|
|
|
return array_key_exists($name, $this->fields); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* {@inheritdoc} |
124
|
|
|
* |
125
|
|
|
* @param bool $filter If false, associated field setter or accessor will be ignored. |
126
|
|
|
* |
127
|
|
|
* @throws AccessorExceptionInterface |
128
|
|
|
*/ |
129
|
|
|
public function setField(string $name, $value, bool $filter = true) |
130
|
|
|
{ |
131
|
|
|
if ($value instanceof AccessorInterface) { |
132
|
|
|
//In case of non scalar values filters must be bypassed |
133
|
|
|
$this->fields[$name] = clone $value; |
134
|
|
|
|
135
|
|
|
return; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
if (!$filter) { |
139
|
|
|
|
140
|
|
|
//Bypassing all filters |
141
|
|
|
$this->fields[$name] = $value; |
142
|
|
|
|
143
|
|
|
return; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
//Checking if field have accessor |
147
|
|
|
$accessor = $this->getMutator($name, self::MUTATOR_ACCESSOR); |
148
|
|
|
|
149
|
|
|
if (!empty($accessor)) { |
150
|
|
|
$field = $this->fields[$name]; |
151
|
|
|
if (empty($field) || !($field instanceof AccessorInterface)) { |
152
|
|
|
$this->fields[$name] = $field = $this->createAccessor($accessor, $value); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
//Letting accessor to set value |
156
|
|
|
$field->setValue($value); |
157
|
|
|
|
158
|
|
|
return; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
//Checking field setter if any |
162
|
|
|
$setter = $this->getMutator($name, self::MUTATOR_SETTER); |
163
|
|
|
|
164
|
|
|
if (!empty($setter)) { |
165
|
|
|
try { |
166
|
|
|
$this->fields[$name] = call_user_func($setter, $value); |
167
|
|
|
} catch (\Exception $e) { |
168
|
|
|
//Exceptional situation, we are choosing to keep original field value |
169
|
|
|
} |
170
|
|
|
} else { |
171
|
|
|
$this->fields[$name] = $value; |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* {@inheritdoc} |
177
|
|
|
* |
178
|
|
|
* @param bool $filter If false, associated field getter will be ignored. |
179
|
|
|
* |
180
|
|
|
* @throws AccessorExceptionInterface |
181
|
|
|
*/ |
182
|
|
|
public function getField(string $name, $default = null, bool $filter = true) |
183
|
|
|
{ |
184
|
|
|
$value = $this->hasField($name) ? $this->fields[$name] : $default; |
185
|
|
|
|
186
|
|
|
if ($value instanceof AccessorInterface) { |
187
|
|
|
return $value; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
//Checking if field have accessor (decorator) |
191
|
|
|
$accessor = $this->getMutator($name, self::MUTATOR_ACCESSOR); |
192
|
|
|
|
193
|
|
|
if (!empty($accessor)) { |
194
|
|
|
return $this->fields[$name] = $this->createAccessor($accessor, $value); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
//Checking for getter |
198
|
|
|
$getter = $this->getMutator($name, self::MUTATOR_GETTER); |
199
|
|
|
|
200
|
|
|
if ($filter && !empty($getter)) { |
201
|
|
|
try { |
202
|
|
|
return call_user_func($getter, $value); |
203
|
|
|
} catch (\Exception $e) { |
204
|
|
|
//Trying to filter null value, every filter must support it |
205
|
|
|
return call_user_func($getter, null); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return $value; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* {@inheritdoc} |
214
|
|
|
* |
215
|
|
|
* @see $fillable |
216
|
|
|
* @see $secured |
217
|
|
|
* @see isFillable() |
218
|
|
|
* |
219
|
|
|
* @param array|\Traversable $fields |
220
|
|
|
* @param bool $all Fill all fields including non fillable. |
221
|
|
|
* |
222
|
|
|
* @return $this |
223
|
|
|
* |
224
|
|
|
* @throws AccessorExceptionInterface |
225
|
|
|
*/ |
226
|
|
|
public function setFields($fields = [], bool $all = false) |
227
|
|
|
{ |
228
|
|
|
if (!is_array($fields) && !$fields instanceof \Traversable) { |
229
|
|
|
return $this; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
foreach ($fields as $name => $value) { |
233
|
|
|
if ($all || $this->isFillable($name)) { |
234
|
|
|
try { |
235
|
|
|
$this->setField($name, $value, true); |
236
|
|
|
} catch (FieldExceptionInterface $e) { |
237
|
|
|
//We are supressing field setting exceptions |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return $this; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @return array |
247
|
|
|
*/ |
248
|
|
|
protected function getKeys(): array |
249
|
|
|
{ |
250
|
|
|
return array_keys($this->fields); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* {@inheritdoc} |
255
|
|
|
* |
256
|
|
|
* Every getter and accessor will be applied/constructed if filter argument set to true. |
257
|
|
|
* |
258
|
|
|
* @param bool $filter |
259
|
|
|
* |
260
|
|
|
* @throws AccessorExceptionInterface |
261
|
|
|
*/ |
262
|
|
|
public function getFields(bool $filter = true): array |
263
|
|
|
{ |
264
|
|
|
$result = []; |
265
|
|
|
foreach ($this->fields as $name => $field) { |
266
|
|
|
$result[$name] = $this->getField($name, null, $filter); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
return $result; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* @param mixed $offset |
274
|
|
|
* |
275
|
|
|
* @return bool |
276
|
|
|
*/ |
277
|
|
|
public function __isset($offset) |
278
|
|
|
{ |
279
|
|
|
return $this->hasField($offset); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* @param mixed $offset |
284
|
|
|
* |
285
|
|
|
* @return mixed |
286
|
|
|
*/ |
287
|
|
|
public function __get($offset) |
288
|
|
|
{ |
289
|
|
|
return $this->getField($offset); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* @param mixed $offset |
294
|
|
|
* @param mixed $value |
295
|
|
|
*/ |
296
|
|
|
public function __set($offset, $value) |
297
|
|
|
{ |
298
|
|
|
$this->setField($offset, $value); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @param mixed $offset |
303
|
|
|
*/ |
304
|
|
|
public function __unset($offset) |
305
|
|
|
{ |
306
|
|
|
unset($this->fields[$offset]); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* {@inheritdoc} |
311
|
|
|
*/ |
312
|
|
|
public function offsetExists($offset) |
313
|
|
|
{ |
314
|
|
|
return $this->__isset($offset); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* {@inheritdoc} |
319
|
|
|
*/ |
320
|
|
|
public function offsetGet($offset) |
321
|
|
|
{ |
322
|
|
|
return $this->getField($offset); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* {@inheritdoc} |
327
|
|
|
*/ |
328
|
|
|
public function offsetSet($offset, $value) |
329
|
|
|
{ |
330
|
|
|
$this->setField($offset, $value); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* {@inheritdoc} |
335
|
|
|
*/ |
336
|
|
|
public function offsetUnset($offset) |
337
|
|
|
{ |
338
|
|
|
$this->__unset($offset); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* {@inheritdoc} |
343
|
|
|
*/ |
344
|
|
|
public function getIterator(): \Iterator |
345
|
|
|
{ |
346
|
|
|
return new \ArrayIterator($this->getFields()); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Serialize entity data into plain array. |
351
|
|
|
* |
352
|
|
|
* @return array |
353
|
|
|
* |
354
|
|
|
* @throws AccessorExceptionInterface |
355
|
|
|
*/ |
356
|
|
|
public function serializeData() |
357
|
|
|
{ |
358
|
|
|
$result = []; |
359
|
|
|
foreach ($this->fields as $field => $value) { |
360
|
|
|
if ($value instanceof ValueInterface) { |
361
|
|
|
$result[$field] = $value->serializeData(); |
362
|
|
|
} else { |
363
|
|
|
$result[$field] = $value; |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
return $result; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Alias for serializeData. |
372
|
|
|
* |
373
|
|
|
* @return array |
374
|
|
|
*/ |
375
|
|
|
public function toArray(): array |
376
|
|
|
{ |
377
|
|
|
return $this->serializeData(); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* {@inheritdoc} |
382
|
|
|
* |
383
|
|
|
* By default use publicFields to be json serialized. |
384
|
|
|
*/ |
385
|
|
|
public function jsonSerialize() |
386
|
|
|
{ |
387
|
|
|
return $this->publicFields(); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Destruct data entity. |
392
|
|
|
*/ |
393
|
|
|
public function __destruct() |
394
|
|
|
{ |
395
|
|
|
$this->fields = []; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* Check if field is fillable. |
400
|
|
|
* |
401
|
|
|
* @param string $field |
402
|
|
|
* |
403
|
|
|
* @return bool |
404
|
|
|
*/ |
405
|
|
|
abstract protected function isFillable(string $field): bool; |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Get mutator associated with given field. |
409
|
|
|
* |
410
|
|
|
* @param string $field |
411
|
|
|
* @param string $type See MUTATOR_* constants |
412
|
|
|
* |
413
|
|
|
* @return mixed |
414
|
|
|
*/ |
415
|
|
|
abstract protected function getMutator(string $field, string $type); |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Create instance of field accessor. |
419
|
|
|
* |
420
|
|
|
* @param string $accessor |
421
|
|
|
* @param mixed $value |
422
|
|
|
* |
423
|
|
|
* @return AccessorInterface |
424
|
|
|
* |
425
|
|
|
* @throws AccessorExceptionInterface |
426
|
|
|
*/ |
427
|
|
|
protected function createAccessor(string $accessor, $value): AccessorInterface |
428
|
|
|
{ |
429
|
|
|
return new $accessor($value); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* Reset every field value. |
434
|
|
|
*/ |
435
|
|
|
protected function flushValues() |
436
|
|
|
{ |
437
|
|
|
$this->fields = []; |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* Create entity by passing fields thought setFields method |
442
|
|
|
* |
443
|
|
|
* @param array $fields |
444
|
|
|
* |
445
|
|
|
* @return AbstractEntity |
446
|
|
|
* |
447
|
|
|
* @event created($entity) |
448
|
|
|
*/ |
449
|
|
|
public static function create($fields = []) |
450
|
|
|
{ |
451
|
|
|
/** |
452
|
|
|
* @var self $entity |
453
|
|
|
*/ |
454
|
|
|
$entity = (new static([]))->setFields($fields); |
455
|
|
|
$entity->dispatch('created', new EntityEvent($entity)); |
456
|
|
|
|
457
|
|
|
return $entity; |
458
|
|
|
} |
459
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.