1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework. |
4
|
|
|
* |
5
|
|
|
* @license MIT |
6
|
|
|
* @author Anton Titov (Wolfy-J) |
7
|
|
|
*/ |
8
|
|
|
namespace Spiral\ODM; |
9
|
|
|
|
10
|
|
|
use Spiral\Core\Component; |
11
|
|
|
use Spiral\Core\Traits\SaturateTrait; |
12
|
|
|
use Spiral\Models\AccessorInterface; |
13
|
|
|
use Spiral\Models\EntityInterface; |
14
|
|
|
use Spiral\Models\Events\EntityEvent; |
15
|
|
|
use Spiral\Models\PublishableInterface; |
16
|
|
|
use Spiral\Models\SchematicEntity; |
17
|
|
|
use Spiral\ODM\Exceptions\DefinitionException; |
18
|
|
|
use Spiral\ODM\Exceptions\DocumentException; |
19
|
|
|
use Spiral\ODM\Exceptions\FieldException; |
20
|
|
|
use Spiral\ODM\Exceptions\ODMException; |
21
|
|
|
use Spiral\Validation\ValidatesInterface; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Primary class for spiral ODM, provides ability to pack it's own updates in a form of atomic |
25
|
|
|
* updates. |
26
|
|
|
* |
27
|
|
|
* You can use same properties to configure entity as in DataEntity + schema property. |
28
|
|
|
* |
29
|
|
|
* Example: |
30
|
|
|
* |
31
|
|
|
* class Test extends DocumentEntity |
32
|
|
|
* { |
33
|
|
|
* private $schema = [ |
34
|
|
|
* 'name' => 'string' |
35
|
|
|
* ]; |
36
|
|
|
* } |
37
|
|
|
* |
38
|
|
|
* Configuration properties: |
39
|
|
|
* - schema |
40
|
|
|
* - defaults |
41
|
|
|
* - secured (* by default) |
42
|
|
|
* - fillable |
43
|
|
|
* - validates |
44
|
|
|
*/ |
45
|
|
|
abstract class DocumentEntity extends SchematicEntity implements CompositableInterface |
46
|
|
|
{ |
47
|
|
|
use SaturateTrait; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* We are going to inherit parent validation rules, this will let spiral translator know about |
51
|
|
|
* it and merge i18n messages. |
52
|
|
|
* |
53
|
|
|
* @see TranslatorTrait |
54
|
|
|
*/ |
55
|
|
|
const I18N_INHERIT_MESSAGES = true; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Helper constant to identify atomic SET operations. |
59
|
|
|
*/ |
60
|
|
|
const ATOMIC_SET = '$set'; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Tells ODM component that Document class must be resolved using document fields. ODM must |
64
|
|
|
* match fields to every child of this documents and find best match. This is default definition |
65
|
|
|
* behaviour. |
66
|
|
|
* |
67
|
|
|
* Example: |
68
|
|
|
* > Class A: _id, name, address |
69
|
|
|
* > Class B extends A: _id, name, address, email |
70
|
|
|
* < Class B will be used to represent all documents with existed email field. |
71
|
|
|
* |
72
|
|
|
* @see DocumentSchema |
73
|
|
|
*/ |
74
|
|
|
const DEFINITION_FIELDS = 1; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Tells ODM that logical method (defineClass) must be used to define document class. Method |
78
|
|
|
* will receive document fields as input and must return document class name. |
79
|
|
|
* |
80
|
|
|
* Example: |
81
|
|
|
* > Class A: _id, name, type (a) |
82
|
|
|
* > Class B extends A: _id, name, type (b) |
83
|
|
|
* > Class C extends B: _id, name, type (c) |
84
|
|
|
* < Static method in class A (parent) should return A, B or C based on type field value (as |
85
|
|
|
* example). |
86
|
|
|
* |
87
|
|
|
* Attention, ODM will always ask TOP PARENT (in collection) to define class when you loading |
88
|
|
|
* documents from collections. |
89
|
|
|
* |
90
|
|
|
* @see defineClass($fields) |
91
|
|
|
* @see DocumentSchema |
92
|
|
|
*/ |
93
|
|
|
const DEFINITION_LOGICAL = 2; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Indication to ODM component of method to resolve Document class using it's fieldset. This |
97
|
|
|
* constant is required due Document can inherit another Document. |
98
|
|
|
*/ |
99
|
|
|
const DEFINITION = self::DEFINITION_FIELDS; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Automatically convert "_id" to "id" in publicFields() method. |
103
|
|
|
*/ |
104
|
|
|
const REMOVE_ID_UNDERSCORE = true; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Constants used to describe aggregation relations. |
108
|
|
|
* |
109
|
|
|
* Example: |
110
|
|
|
* 'items' => [self::MANY => 'Models\Database\Item', [ |
111
|
|
|
* 'parentID' => 'key::_id' |
112
|
|
|
* ]] |
113
|
|
|
* |
114
|
|
|
* @see odmSchema::$schema |
115
|
|
|
*/ |
116
|
|
|
const MANY = 778; |
117
|
|
|
const ONE = 899; |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Errors in nested documents and accessors. |
121
|
|
|
* |
122
|
|
|
* @var array |
123
|
|
|
*/ |
124
|
|
|
private $innerErrors = []; |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* SolidState will force document to be saved as one big data set without any atomic operations |
128
|
|
|
* (dirty fields). |
129
|
|
|
* |
130
|
|
|
* @var bool |
131
|
|
|
*/ |
132
|
|
|
private $solidState = false; |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Document field updates (changed values). |
136
|
|
|
* |
137
|
|
|
* @var array |
138
|
|
|
*/ |
139
|
|
|
private $updates = []; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* User specified set of atomic operation to be applied to document on save() call. |
143
|
|
|
* |
144
|
|
|
* @var array |
145
|
|
|
*/ |
146
|
|
|
private $atomics = []; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* @invisible |
150
|
|
|
* |
151
|
|
|
* @todo change this concept in future |
152
|
|
|
* @var EntityInterface |
153
|
|
|
*/ |
154
|
|
|
protected $parent = null; |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* @var ODMInterface|ODM |
158
|
|
|
*/ |
159
|
|
|
protected $odm = null; |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Model schema provided by ODM component. |
163
|
|
|
* |
164
|
|
|
* @var array |
165
|
|
|
*/ |
166
|
|
|
protected $odmSchema = []; |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* {@inheritdoc} |
170
|
|
|
* |
171
|
|
|
* @param array|null $schema |
172
|
|
|
*/ |
173
|
|
|
public function __construct( |
174
|
|
|
$fields, |
175
|
|
|
EntityInterface $parent = null, |
176
|
|
|
ODMInterface $odm = null, |
177
|
|
|
$schema = null |
178
|
|
|
) { |
179
|
|
|
$this->parent = $parent; |
180
|
|
|
|
181
|
|
|
//We can use global container as fallback if no default values were provided |
182
|
|
|
$this->odm = $this->saturate($odm, ODMInterface::class); |
183
|
|
|
$this->odmSchema = !empty($schema) ? $schema : $this->odm->schema(static::class); |
184
|
|
|
|
185
|
|
|
if (empty($fields)) { |
186
|
|
|
//Default state for an empty model - invalid |
187
|
|
|
$this->invalidate(); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$fields = is_array($fields) ? $fields : []; |
191
|
|
|
if (!empty($this->odmSchema[ODM::D_DEFAULTS])) { |
192
|
|
|
/* |
193
|
|
|
* Merging with default values |
194
|
|
|
*/ |
195
|
|
|
$fields = array_replace_recursive($this->odmSchema[ODM::D_DEFAULTS], $fields); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
parent::__construct($fields, $this->odmSchema); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Change document solid state. SolidState will force document to be saved as one big data set |
203
|
|
|
* without any atomic operations (dirty fields). |
204
|
|
|
* |
205
|
|
|
* @param bool $solidState |
206
|
|
|
* @param bool $forceUpdate Mark all fields as changed to force update later. |
207
|
|
|
* |
208
|
|
|
* @return $this |
209
|
|
|
*/ |
210
|
|
|
public function solidState($solidState, $forceUpdate = false) |
211
|
|
|
{ |
212
|
|
|
$this->solidState = $solidState; |
213
|
|
|
|
214
|
|
|
if ($forceUpdate) { |
215
|
|
|
$this->updates = $this->odmSchema[ODM::D_DEFAULTS]; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
return $this; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Is document is solid state? |
223
|
|
|
* |
224
|
|
|
* @see solidState() |
225
|
|
|
* |
226
|
|
|
* @return bool |
227
|
|
|
*/ |
228
|
|
|
public function isSolid() |
229
|
|
|
{ |
230
|
|
|
return $this->solidState; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Check if document has parent. |
235
|
|
|
* |
236
|
|
|
* @return bool |
237
|
|
|
*/ |
238
|
|
|
public function isEmbedded() |
239
|
|
|
{ |
240
|
|
|
return !empty($this->parent); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* {@inheritdoc} |
245
|
|
|
*/ |
246
|
|
|
public function embed(EntityInterface $parent) |
247
|
|
|
{ |
248
|
|
|
if (empty($this->parent)) { |
249
|
|
|
$this->parent = $parent; |
250
|
|
|
|
251
|
|
|
//Moving under new parent |
252
|
|
|
return $this->solidState(true, true); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
if ($parent === $this->parent) { |
256
|
|
|
return $this; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* @var DocumentEntity $document |
261
|
|
|
*/ |
262
|
|
|
$document = new static( |
263
|
|
|
$this->serializeData(), |
264
|
|
|
$parent, |
265
|
|
|
$this->odm, |
266
|
|
|
$this->odmSchema |
267
|
|
|
); |
268
|
|
|
|
269
|
|
|
return $document->solidState(true, true); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* {@inheritdoc} |
274
|
|
|
*/ |
275
|
|
|
public function setValue($data) |
276
|
|
|
{ |
277
|
|
|
return $this->setFields($data); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* {@inheritdoc} |
282
|
|
|
* |
283
|
|
|
* Must track field updates. |
284
|
|
|
*/ |
285
|
|
|
public function setField($name, $value, $filter = true) |
286
|
|
|
{ |
287
|
|
|
if (!$this->hasField($name)) { |
288
|
|
|
throw new FieldException("Undefined field '{$name}' in '" . static::class . "'"); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
//Original field value |
292
|
|
|
$original = $this->getField($name, null, false); |
293
|
|
|
|
294
|
|
|
parent::setField($name, $value, $filter); |
295
|
|
|
|
296
|
|
View Code Duplication |
if (!array_key_exists($name, $this->updates)) { |
|
|
|
|
297
|
|
|
$this->updates[$name] = $original instanceof AccessorInterface |
298
|
|
|
? $original->serializeData() |
299
|
|
|
: $original; |
300
|
|
|
} |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* {@inheritdoc} |
305
|
|
|
* |
306
|
|
|
* Will restore default value if presented. |
307
|
|
|
*/ |
308
|
|
|
public function __unset($offset) |
309
|
|
|
{ |
310
|
|
|
if (!array_key_exists($offset, $this->updates)) { |
311
|
|
|
//Let document know that field value changed, but without overwriting previous change |
312
|
|
|
$this->updates[$offset] = isset($this->odmSchema[ODM::D_DEFAULTS][$offset]) |
313
|
|
|
? $this->odmSchema[ODM::D_DEFAULTS][$offset] |
314
|
|
|
: null; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
$this->setField($offset, null, false); |
318
|
|
|
if (isset($this->odmSchema[ODM::D_DEFAULTS][$offset])) { |
319
|
|
|
$this->setField($offset, $this->odmSchema[ODM::D_DEFAULTS][$offset], false); |
320
|
|
|
} |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Alias for atomic operation $set. Attention, this operation is not identical to setField() |
325
|
|
|
* method, it performs low level operation and can be used only on simple fields. No filters |
326
|
|
|
* will be applied to field! |
327
|
|
|
* |
328
|
|
|
* @param string $field |
329
|
|
|
* @param mixed $value |
330
|
|
|
* |
331
|
|
|
* @return $this |
332
|
|
|
* |
333
|
|
|
* @throws DocumentException |
334
|
|
|
*/ |
335
|
|
|
public function set($field, $value) |
336
|
|
|
{ |
337
|
|
|
if ($this->hasUpdates($field, true)) { |
338
|
|
|
throw new FieldException( |
339
|
|
|
"Unable to apply multiple atomic operation to field '{$field}'" |
340
|
|
|
); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
$this->setField($field, $value); |
344
|
|
|
|
345
|
|
|
//Filtered |
346
|
|
|
$this->atomics[self::ATOMIC_SET][$field] = $this->getFields($value); |
347
|
|
|
|
348
|
|
|
return $this; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Alias for atomic operation $inc. |
353
|
|
|
* |
354
|
|
|
* @param string $field |
355
|
|
|
* @param string $value |
356
|
|
|
* |
357
|
|
|
* @return $this |
358
|
|
|
* |
359
|
|
|
* @throws DocumentException |
360
|
|
|
*/ |
361
|
|
|
public function inc($field, $value) |
362
|
|
|
{ |
363
|
|
|
if ($this->hasUpdates($field, true) && !isset($this->atomics['$inc'][$field])) { |
364
|
|
|
throw new FieldException( |
365
|
|
|
"Unable to apply multiple atomic operation to field '{$field}'" |
366
|
|
|
); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
if (!isset($this->atomics['$inc'][$field])) { |
370
|
|
|
$this->atomics['$inc'][$field] = 0; |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
$this->atomics['$inc'][$field] += $value; |
374
|
|
|
$this->setField($field, $this->getField($field) + $value); |
375
|
|
|
|
376
|
|
|
return $this; |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* {@inheritdoc} |
381
|
|
|
*/ |
382
|
|
|
public function defaultValue() |
383
|
|
|
{ |
384
|
|
|
return $this->odmSchema[ODM::D_DEFAULTS]; |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* {@inheritdoc} |
389
|
|
|
* |
390
|
|
|
* Include every composition public data into result. |
391
|
|
|
*/ |
392
|
|
|
public function publicFields() |
393
|
|
|
{ |
394
|
|
|
$result = []; |
395
|
|
|
|
396
|
|
|
foreach ($this->getKeys() as $field) { |
397
|
|
|
if (in_array($field, $this->odmSchema[ODM::D_HIDDEN])) { |
398
|
|
|
//We might need to use isset in future, for performance |
399
|
|
|
continue; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/* |
403
|
|
|
* @var mixed|array|DocumentAccessorInterface|CompositableInterface |
404
|
|
|
*/ |
405
|
|
|
$value = $this->getField($field); |
406
|
|
|
|
407
|
|
|
if ($value instanceof PublishableInterface) { |
408
|
|
|
$result[$field] = $value->publicFields(); |
409
|
|
|
continue; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
if ($value instanceof \MongoId) { |
413
|
|
|
$value = (string)$value; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
if (is_array($value)) { |
417
|
|
|
array_walk_recursive($value, function (&$value) { |
418
|
|
|
if ($value instanceof \MongoId) { |
419
|
|
|
$value = (string)$value; |
420
|
|
|
} |
421
|
|
|
}); |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
if (static::REMOVE_ID_UNDERSCORE && $field == '_id') { |
425
|
|
|
$field = 'id'; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
$result[$field] = $value; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
return $result; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* {@inheritdoc} |
436
|
|
|
* |
437
|
|
|
* @param string $field Specific field name to check for updates. |
438
|
|
|
* @param bool $atomicsOnly Check if field has any atomic operation associated with. |
439
|
|
|
*/ |
440
|
|
|
public function hasUpdates($field = null, $atomicsOnly = false) |
441
|
|
|
{ |
442
|
|
View Code Duplication |
if (empty($field)) { |
|
|
|
|
443
|
|
|
if (!empty($this->updates) || !empty($this->atomics)) { |
444
|
|
|
return true; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
foreach ($this->getFields(false) as $value) { |
448
|
|
|
if ($value instanceof DocumentAccessorInterface && $value->hasUpdates()) { |
449
|
|
|
return true; |
450
|
|
|
} |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
return false; |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
foreach ($this->atomics as $operations) { |
457
|
|
|
if (array_key_exists($field, $operations)) { |
458
|
|
|
//Property already changed by atomic operation |
459
|
|
|
return true; |
460
|
|
|
} |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
if ($atomicsOnly) { |
464
|
|
|
return false; |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
if (array_key_exists($field, $this->updates)) { |
468
|
|
|
return true; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
$value = $this->getField($field); |
472
|
|
|
if ($value instanceof DocumentAccessorInterface && $value->hasUpdates()) { |
473
|
|
|
return true; |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
return false; |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* {@inheritdoc} |
481
|
|
|
*/ |
482
|
|
|
public function flushUpdates() |
483
|
|
|
{ |
484
|
|
|
$this->updates = $this->atomics = []; |
485
|
|
|
|
486
|
|
|
foreach ($this->getFields(false) as $value) { |
487
|
|
|
if ($value instanceof DocumentAccessorInterface) { |
488
|
|
|
$value->flushUpdates(); |
489
|
|
|
} |
490
|
|
|
} |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
/** |
494
|
|
|
* {@inheritdoc} |
495
|
|
|
*/ |
496
|
|
|
public function buildAtomics($container = '') |
497
|
|
|
{ |
498
|
|
|
if (!$this->hasUpdates() && !$this->isSolid()) { |
499
|
|
|
return []; |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
if ($this->isSolid()) { |
503
|
|
|
if (!empty($container)) { |
504
|
|
|
//Simple nested document in solid state |
505
|
|
|
return [self::ATOMIC_SET => [$container => $this->serializeData()]]; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
//Direct document save |
509
|
|
|
$atomics = [self::ATOMIC_SET => $this->serializeData()]; |
510
|
|
|
unset($atomics[self::ATOMIC_SET]['_id']); |
511
|
|
|
|
512
|
|
|
return $atomics; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
if (empty($container)) { |
516
|
|
|
$atomics = $this->atomics; |
517
|
|
|
} else { |
518
|
|
|
$atomics = []; |
519
|
|
|
|
520
|
|
|
foreach ($this->atomics as $atomic => $fields) { |
521
|
|
|
foreach ($fields as $field => $value) { |
522
|
|
|
$atomics[$atomic][$container . '.' . $field] = $value; |
523
|
|
|
} |
524
|
|
|
} |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
foreach ($this->getFields(false) as $field => $value) { |
528
|
|
|
if ($field == '_id') { |
529
|
|
|
continue; |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
if ($value instanceof DocumentAccessorInterface) { |
533
|
|
|
$atomics = array_merge_recursive( |
534
|
|
|
$atomics, |
535
|
|
|
$value->buildAtomics(($container ? $container . '.' : '') . $field) |
536
|
|
|
); |
537
|
|
|
|
538
|
|
|
continue; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
foreach ($atomics as $atomic => $operations) { |
542
|
|
|
if (array_key_exists($field, $operations) && $atomic != self::ATOMIC_SET) { |
543
|
|
|
//Property already changed by atomic operation |
544
|
|
|
continue; |
545
|
|
|
} |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
if (array_key_exists($field, $this->updates)) { |
549
|
|
|
//Generating set operation for changed field |
550
|
|
|
$atomics[self::ATOMIC_SET][($container ? $container . '.' : '') . $field] = $value; |
551
|
|
|
} |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
return $atomics; |
555
|
|
|
} |
556
|
|
|
|
557
|
|
|
/** |
558
|
|
|
* @return array |
559
|
|
|
*/ |
560
|
|
|
public function __debugInfo() |
561
|
|
|
{ |
562
|
|
|
return [ |
563
|
|
|
'fields' => $this->getFields(), |
564
|
|
|
'atomics' => $this->hasUpdates() ? $this->buildAtomics() : [], |
565
|
|
|
'errors' => $this->getErrors(), |
566
|
|
|
]; |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
/** |
570
|
|
|
* {@inheritdoc} |
571
|
|
|
*/ |
572
|
|
|
public function isValid() |
573
|
|
|
{ |
574
|
|
|
return parent::isValid() && empty($this->innerErrors); |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* {@inheritdoc} |
579
|
|
|
*/ |
580
|
|
|
public function getErrors($reset = false) |
581
|
|
|
{ |
582
|
|
|
return parent::getErrors($reset) + $this->innerErrors; |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
/** |
586
|
|
|
* {@inheritdoc} |
587
|
|
|
* |
588
|
|
|
* Will validate every CompositableInterface instance. |
589
|
|
|
* |
590
|
|
|
* @param bool $reset |
591
|
|
|
* |
592
|
|
|
* @throws DocumentException |
593
|
|
|
*/ |
594
|
|
View Code Duplication |
protected function validate($reset = false) |
|
|
|
|
595
|
|
|
{ |
596
|
|
|
$this->innerErrors = []; |
597
|
|
|
|
598
|
|
|
//Validating all compositions |
599
|
|
|
foreach ($this->odmSchema[ODM::D_COMPOSITIONS] as $field) { |
600
|
|
|
|
601
|
|
|
$composition = $this->getField($field); |
602
|
|
|
if (!$composition instanceof ValidatesInterface) { |
603
|
|
|
//Something weird. |
604
|
|
|
continue; |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
if (!$composition->isValid()) { |
608
|
|
|
$this->innerErrors[$field] = $composition->getErrors($reset); |
609
|
|
|
} |
610
|
|
|
} |
611
|
|
|
|
612
|
|
|
parent::validate($reset); |
613
|
|
|
|
614
|
|
|
return $this->hasErrors() && empty($this->innerErrors); |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
/** |
618
|
|
|
* {@inheritdoc} |
619
|
|
|
* |
620
|
|
|
* Accessor options include field type resolved by DocumentSchema. |
621
|
|
|
* |
622
|
|
|
* @throws ODMException |
623
|
|
|
* @throws DefinitionException |
624
|
|
|
*/ |
625
|
|
|
protected function createAccessor($accessor, $value) |
626
|
|
|
{ |
627
|
|
|
$options = null; |
628
|
|
|
if (is_array($accessor)) { |
629
|
|
|
list($accessor, $options) = $accessor; |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
if ($accessor == ODM::CMP_ONE) { |
633
|
|
|
//Pointing to document instance |
634
|
|
|
return $this->odm->document($options, $value, $this); |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
//Additional options are supplied for CompositableInterface |
638
|
|
|
return new $accessor($value, $this, $this->odm, $options); |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
/** |
642
|
|
|
* {@inheritdoc} |
643
|
|
|
*/ |
644
|
|
View Code Duplication |
protected function container() |
|
|
|
|
645
|
|
|
{ |
646
|
|
|
if (empty($this->odm) || !$this->odm instanceof Component) { |
647
|
|
|
return parent::container(); |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
return $this->odm->container(); |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
/** |
654
|
|
|
* Create document entity using given ODM instance or load parent ODM via shared container. |
655
|
|
|
* |
656
|
|
|
* @see Component::staticContainer() |
657
|
|
|
* |
658
|
|
|
* @param array $fields Model fields to set, will be passed thought filters. |
659
|
|
|
* @param ODMInterface $odm ODMInterface component, global container will be called if not |
660
|
|
|
* instance provided. |
661
|
|
|
* |
662
|
|
|
* @return DocumentEntity |
663
|
|
|
* |
664
|
|
|
* @event created($document) |
665
|
|
|
*/ |
666
|
|
View Code Duplication |
public static function create($fields = [], ODMInterface $odm = null) |
|
|
|
|
667
|
|
|
{ |
668
|
|
|
/** |
669
|
|
|
* @var DocumentEntity $document |
670
|
|
|
*/ |
671
|
|
|
$document = new static([], null, $odm); |
672
|
|
|
|
673
|
|
|
//Forcing validation (empty set of fields is not valid set of fields) |
674
|
|
|
$document->setFields($fields)->dispatch('created', new EntityEvent($document)); |
675
|
|
|
|
676
|
|
|
return $document; |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
/** |
680
|
|
|
* Called by ODM with set of loaded fields. Must return name of appropriate class. |
681
|
|
|
* |
682
|
|
|
* @param array $fields |
683
|
|
|
* @param ODMInterface $odm |
684
|
|
|
* |
685
|
|
|
* @return string |
686
|
|
|
* |
687
|
|
|
* @throws DefinitionException |
688
|
|
|
*/ |
689
|
|
|
public static function defineClass(array $fields, ODMInterface $odm) |
|
|
|
|
690
|
|
|
{ |
691
|
|
|
throw new DefinitionException('Class definition method has not been implemented'); |
692
|
|
|
} |
693
|
|
|
} |
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.