Completed
Pull Request — master (#61)
by Anton
04:22
created

RecordEntity::setContext()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 24
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 24
rs 8.5126
cc 5
eloc 9
nc 6
nop 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A RecordEntity::solidState() 0 14 3
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\ORM;
9
10
use Spiral\Core\Exceptions\SugarException;
11
use Spiral\Core\Traits\SaturateTrait;
12
use Spiral\Database\Entities\Table;
13
use Spiral\Models\AccessorInterface;
14
use Spiral\Models\EntityInterface;
15
use Spiral\Models\Events\EntityEvent;
16
use Spiral\Models\Exceptions\AccessorExceptionInterface;
17
use Spiral\Models\SchematicEntity;
18
use Spiral\ORM\Exceptions\FieldException;
19
use Spiral\ORM\Exceptions\RecordException;
20
use Spiral\ORM\Exceptions\RelationException;
21
use Spiral\Validation\ValidatesInterface;
22
23
/**
24
 * Record is base data entity for ORM component, it used to describe related table schema,
25
 * filters, validations and relations to other records. You can count Record class as ActiveRecord
26
 * pattern. ORM component will automatically analyze existed Records and create cached version of
27
 * their schema.
28
 *
29
 * @TODO: Add ability to set primary key manually, for example fpr uuid like fields.
30
 */
31
class RecordEntity extends SchematicEntity implements RecordInterface
32
{
33
    /**
34
     * Static container fallback.
35
     */
36
    use SaturateTrait;
37
38
    /**
39
     * Field format declares how entity must process magic setters and getters. Available values:
40
     * camelCase, tableize.
41
     */
42
    const FIELD_FORMAT = 'tableize';
43
44
    /**
45
     * We are going to inherit parent validation rules, this will let spiral translator know about
46
     * it and merge i18n messages.
47
     *
48
     * @see TranslatorTrait
49
     */
50
    const I18N_INHERIT_MESSAGES = true;
51
52
    /**
53
     * ORM records are be divided by two sections: active and passive records. When record is active
54
     * ORM allowed to modify associated record table using declared schema and created relations.
55
     *
56
     * Passive records (ACTIVE_SCHEMA = false) however can only read table schema from database and
57
     * forbidden to do any schema modification either by record or by relations.
58
     *
59
     * You can use ACTIVE_SCHEMA = false in cases where you need to create an ActiveRecord for
60
     * existed table.
61
     *
62
     * @see RecordSchema
63
     * @see \Spiral\ORM\Entities\SchemaBuilder
64
     */
65
    const ACTIVE_SCHEMA = true;
66
67
    /**
68
     * Indication that record were deleted.
69
     */
70
    const DELETED = 900;
71
72
    /**
73
     * Default ORM relation types, see ORM configuration and documentation for more information,
74
     * i had to remove 200 lines of comments to make record little bit smaller.
75
     *
76
     * @see RelationSchemaInterface
77
     * @see RelationSchema
78
     */
79
    const HAS_ONE      = 101;
80
    const HAS_MANY     = 102;
81
    const BELONGS_TO   = 103;
82
    const MANY_TO_MANY = 104;
83
84
    /**
85
     * Morphed relation types are usually created by inversion or equivalent of primary relation
86
     * types.
87
     *
88
     * @see RelationSchemaInterface
89
     * @see RelationSchema
90
     * @see MorphedRelation
91
     */
92
    const BELONGS_TO_MORPHED = 108;
93
    const MANY_TO_MORPHED    = 109;
94
95
    /**
96
     * Constants used to declare relations in record schema, used in normalized relation schema.
97
     *
98
     * @see RelationSchemaInterface
99
     */
100
    const OUTER_KEY         = 901; //Outer key name
101
    const INNER_KEY         = 902; //Inner key name
102
    const MORPH_KEY         = 903; //Morph key name
103
    const PIVOT_TABLE       = 904; //Pivot table name
104
    const PIVOT_COLUMNS     = 905; //Pre-defined pivot table columns
105
    const PIVOT_DEFAULTS    = 906; //Pre-defined pivot table default values
106
    const THOUGHT_INNER_KEY = 907; //Pivot table options
107
    const THOUGHT_OUTER_KEY = 908; //Pivot table options
108
    const WHERE             = 909; //Where conditions
109
    const WHERE_PIVOT       = 910; //Where pivot conditions
110
111
    /**
112
     * Additional constants used to control relation schema behaviour.
113
     *
114
     * @see Record::$schema
115
     * @see RelationSchemaInterface
116
     */
117
    const INVERSE           = 1001; //Relation should be inverted to parent record
118
    const CONSTRAINT        = 1002; //Relation should create foreign keys (default)
119
    const CONSTRAINT_ACTION = 1003; //Default relation foreign key delete/update action (CASCADE)
120
    const CREATE_PIVOT      = 1004; //Many-to-Many should create pivot table automatically (default)
121
    const NULLABLE          = 1005; //Relation can be nullable (default)
122
    const CREATE_INDEXES    = 1006; //Indication that relation is allowed to create required indexes
123
    const MORPHED_ALIASES   = 1007; //Aliases for morphed sub-relations
124
125
    /**
126
     * Relations marked as embedded will be automatically saved/validated with parent model. In
127
     * addition such models data can be set using setFields method (only for ONE relations).
128
     *
129
     * @see setFields()
130
     * @see save()
131
     * @see validate()
132
     */
133
    const EMBEDDED_RELATION = 1008;
134
135
    /**
136
     * Constants used to declare indexes in record schema.
137
     *
138
     * @see Record::$indexes
139
     */
140
    const INDEX  = 1000;            //Default index type
141
    const UNIQUE = 2000;            //Unique index definition
142
143
    /**
144
     * Errors in relations and acessors.
145
     *
146
     * @var array
147
     */
148
    private $nestedErrors = [];
149
150
    /**
151
     * Indicates that record data were loaded from database (not recently created).
152
     *
153
     * @var bool
154
     */
155
    private $loaded = false;
156
157
    /**
158
     * Schema provided by ORM component.
159
     *
160
     * @var array
161
     */
162
    private $ormSchema = [];
163
164
    /**
165
     * SolidState will force record data to be saved as one big update set without any generating
166
     * separate update statements for changed columns.
167
     *
168
     * @var bool
169
     */
170
    private $solidState = false;
171
172
    /**
173
     * Populated when record loaded using many-to-many connection. Property will include every
174
     * column of connection row in pivot table.
175
     *
176
     * @see setContext()
177
     * @see getPivot();
178
     * @var array
179
     */
180
    private $pivotData = [];
181
182
    /**
183
     * Record field updates (changed values).
184
     *
185
     * @var array
186
     */
187
    private $updates = [];
188
189
    /**
190
     * Constructed and pre-cached set of record relations. Relation will be in a form of data array
191
     * to be created on demand.
192
     *
193
     * @see relation()
194
     * @see __call()
195
     * @see __set()
196
     * @see __get()
197
     * @var RelationInterface[]|array
198
     */
199
    protected $relations = [];
200
201
    /**
202
     * Table name (without database prefix) record associated to, RecordSchema will generate table
203
     * name automatically using class name, however i'm strongly recommend to declare table name
204
     * manually as it gives more readable code.
205
     *
206
     * @var string
207
     */
208
    protected $table = null;
209
210
    /**
211
     * Database name/id where record table located in. By default database will be used if nothing
212
     * else is specified.
213
     *
214
     * @var string|null
215
     */
216
    protected $database = null;
217
218
    /**
219
     * Set of indexes to be created for associated record table, indexes only created when record is
220
     * not abstract and has active schema set to true.
221
     *
222
     * Use constants INDEX and UNIQUE to describe indexes, you can also create compound indexes:
223
     * protected $indexes = [
224
     *      [self::UNIQUE, 'email'],
225
     *      [self::INDEX, 'board_id'],
226
     *      [self::INDEX, 'board_id', 'check_id']
227
     * ];
228
     *
229
     * @var array
230
     */
231
    protected $indexes = [];
232
233
    /**
234
     * Record relations and columns can be described in one place - record schema.
235
     * Attention: while defining table structure make sure that ACTIVE_SCHEMA constant is set to t
236
     * rue.
237
     *
238
     * Example:
239
     * protected $schema = [
240
     *      'id'        => 'primary',
241
     *      'name'      => 'string',
242
     *      'biography' => 'text'
243
     * ];
244
     *
245
     * You can pass additional options for some of your columns:
246
     * protected $schema = [
247
     *      'pinCode' => 'string(128)',         //String length
248
     *      'status'  => 'enum(active, hidden)', //Enum values
249
     *      'balance' => 'decimal(10, 2)'       //Decimal size and precision
250
     * ];
251
     *
252
     * Every created column will be stated as NOT NULL with forced default value, if you want to
253
     * have nullable columns, specify special data key: protected $schema = [
254
     *      'name'      => 'string, nullable'
255
     * ];
256
     *
257
     * You can easily combine table and relations definition in one schema:
258
     * protected $schema = [
259
     *
260
     *      //Table schema
261
     *      'id'          => 'bigPrimary',
262
     *      'name'        => 'string',
263
     *      'email'       => 'string',
264
     *      'phoneNumber' => 'string(32)',
265
     *
266
     *      //Relations
267
     *      'profile'     => [
268
     *          self::HAS_ONE => 'Records\Profile',
269
     *          self::INVERSE => 'user'
270
     *      ],
271
     *      'roles'       => [
272
     *          self::MANY_TO_MANY => 'Records\Role',
273
     *          self::INVERSE => 'users'
274
     *      ]
275
     * ];
276
     *
277
     * @var array
278
     */
279
    protected $schema = [];
280
281
    /**
282
     * Default field values.
283
     *
284
     * @var array
285
     */
286
    protected $defaults = [];
287
288
    /**
289
     * @invisible
290
     * @var ORM
291
     */
292
    protected $orm = null;
293
294
    /**
295
     * Due setContext() method and entity cache of ORM any custom initiation code in constructor
296
     * must not depends on database data.
297
     *
298
     * @see setContext
299
     * @param array      $data
300
     * @param bool|false $loaded
301
     * @param ORM|null   $orm
302
     * @param array      $ormSchema
303
     * @throws SugarException
304
     */
305
    public function __construct(
306
        array $data = [],
307
        $loaded = false,
308
        ORM $orm = null,
309
        array $ormSchema = []
310
    ) {
311
        $this->loaded = $loaded;
312
313
        //We can use global container as fallback if no default values were provided
314
        $this->orm = $this->saturate($orm, ORM::class);
315
316
        $this->ormSchema = !empty($ormSchema) ? $ormSchema : $this->orm->schema(static::class);
317
318
        if (isset($data[ORM::PIVOT_DATA])) {
319
            $this->pivotData = $data[ORM::PIVOT_DATA];
320
            unset($data[ORM::PIVOT_DATA]);
321
        }
322
323
        foreach (array_intersect_key($data,
324
            $this->ormSchema[ORM::M_RELATIONS]) as $name => $relation) {
325
            $this->relations[$name] = $relation;
326
            unset($data[$name]);
327
        }
328
329
        parent::__construct($data + $this->ormSchema[ORM::M_COLUMNS], $this->ormSchema);
330
331
        if (!$this->isLoaded()) {
332
            //Non loaded records should be in solid state by default and require initial validation
333
            $this->solidState(true)->invalidate();
334
        }
335
    }
336
337
    /**
338
     * Change record solid state. SolidState will force record data to be saved as one big update
339
     * set without any generating separate update statements for changed columns.
340
     *
341
     * Attention, you have to carefully use forceUpdate flag with records without primary keys due
342
     * update criteria (WHERE condition) can not be easy constructed for records with primary key.
343
     *
344
     * @param bool $solidState
345
     * @param bool $forceUpdate Mark all fields as changed to force update later.
346
     * @return $this
347
     */
348
    public function solidState($solidState, $forceUpdate = false)
349
    {
350
        $this->solidState = $solidState;
351
352
        if ($forceUpdate) {
353
            if ($this->ormSchema[ORM::M_PRIMARY_KEY]) {
354
                $this->updates = $this->stateCriteria();
355
            } else {
356
                $this->updates = $this->ormSchema[ORM::M_COLUMNS];
357
            }
358
        }
359
360
        return $this;
361
    }
362
363
    /**
364
     * Is record is solid state?
365
     *
366
     * @see solidState()
367
     * @return bool
368
     */
369
    public function isSolid()
370
    {
371
        return $this->solidState;
372
    }
373
374
    /**
375
     * {@inheritdoc}
376
     */
377
    public function recordRole()
378
    {
379
        return $this->ormSchema[ORM::M_ROLE_NAME];
380
    }
381
382
    /**
383
     * {@inheritdoc}
384
     */
385
    public function primaryKey()
386
    {
387
        return isset($this->fields[$this->ormSchema[ORM::M_PRIMARY_KEY]])
388
            ? $this->fields[$this->ormSchema[ORM::M_PRIMARY_KEY]]
389
            : null;
390
    }
391
392
    /**
393
     * {@inheritdoc}
394
     */
395
    public function isLoaded()
396
    {
397
        return (bool)$this->loaded && !$this->isDeleted();
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403
    public function isDeleted()
404
    {
405
        return $this->loaded === self::DELETED;
406
    }
407
408
    /**
409
     * Pivot data associated with record instance, populated only in cases when record loaded using
410
     * Many-to-Many relation.
411
     *
412
     * @return array
413
     */
414
    public function getPivot()
415
    {
416
        return $this->pivotData;
417
    }
418
419
    /**
420
     * {@inheritdoc}
421
     *
422
     * @see   $fillable
423
     * @see   $secured
424
     * @see   isFillable()
425
     * @param array|\Traversable $fields
426
     * @param bool               $all Fill all fields including non fillable.
427
     * @return $this
428
     * @throws AccessorExceptionInterface
429
     * @event setFields($fields)
430
     */
431
    public function setFields($fields = [], $all = false)
432
    {
433
        parent::setFields($fields, $all);
434
435
        foreach ($fields as $name => $nested) {
436
            //We can fill data of embedded of relations (usually HAS ONE)
437
            if ($this->isEmbedded($name)) {
438
                //Getting relation instance
439
                $relation = $this->relation($name);
440
441
                //Getting related object
442
                $related = $relation->getRelated();
443
                if ($related instanceof EntityInterface) {
444
                    $related->setFields($nested);
445
                }
446
            }
447
        }
448
449
        return $this;
450
    }
451
452
    /**
453
     * {@inheritdoc}
454
     *
455
     * Must track field updates. In addition Records will not allow to set unknown field.
456
     *
457
     * @throws RecordException
458
     */
459
    public function setField($name, $value, $filter = true)
460
    {
461 View Code Duplication
        if (!array_key_exists($name, $this->fields)) {
462
            throw new FieldException("Undefined field '{$name}' in '" . static::class . "'.");
463
        }
464
465
        $original = isset($this->fields[$name]) ? $this->fields[$name] : null;
466
        if ($value === null && in_array($name, $this->ormSchema[ORM::M_NULLABLE])) {
467
            //We must bypass setters and accessors when null value assigned to nullable column
468
            $this->fields[$name] = null;
469
        } else {
470
            parent::setField($name, $value, $filter);
471
        }
472
473 View Code Duplication
        if (!array_key_exists($name, $this->updates)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
474
            $this->updates[$name] = $original instanceof AccessorInterface
475
                ? $original->serializeData()
476
                : $original;
477
        }
478
    }
479
480
    /**
481
     * {@inheritdoc}
482
     *
483
     * Record will skip filtration for nullable fields.
484
     */
485
    public function getField($name, $default = null, $filter = true)
486
    {
487 View Code Duplication
        if (!array_key_exists($name, $this->fields)) {
488
            throw new FieldException("Undefined field '{$name}' in '" . static::class . "'.");
489
        }
490
491
        $value = $this->fields[$name];
492
        if ($value === null && in_array($name, $this->ormSchema[ORM::M_NULLABLE])) {
493
            if (!isset($this->ormSchema[ORM::M_MUTATORS]['accessor'][$name])) {
494
                //We can skip setters for null values, but not accessors
495
                return $value;
496
            }
497
        }
498
499
        return parent::getField($name, $default, $filter);
500
    }
501
502
    /**
503
     * Get or create record relation by it's name and pre-loaded (optional) set of data.
504
     *
505
     * @param string $name
506
     * @param mixed  $data
507
     * @param bool   $loaded
508
     * @return RelationInterface
509
     * @throws RelationException
510
     * @throws RecordException
511
     */
512
    public function relation($name, $data = null, $loaded = false)
513
    {
514
        if (array_key_exists($name, $this->relations)) {
515
            if (!is_object($this->relations[$name])) {
516
                $data = $this->relations[$name];
517
                unset($this->relations[$name]);
518
519
                //Loaded relation
520
                return $this->relation($name, $data, true);
521
            }
522
523
            //Already created
524
            return $this->relations[$name];
525
        }
526
527
528
        //Constructing relation
529
        if (!isset($this->ormSchema[ORM::M_RELATIONS][$name])) {
530
            throw new RecordException(
531
                "Undefined relation {$name} in record " . static::class . "."
532
            );
533
        }
534
535
        $relation = $this->ormSchema[ORM::M_RELATIONS][$name];
536
537
        return $this->relations[$name] = $this->orm->relation(
538
            $relation[ORM::R_TYPE], $this, $relation[ORM::R_DEFINITION], $data, $loaded
539
        );
540
    }
541
542
    /**
543
     * {@inheritdoc}
544
     *
545
     * @param string $field Specific field name to check for updates.
546
     */
547
    public function hasUpdates($field = null)
548
    {
549 View Code Duplication
        if (empty($field)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
550
            if (!empty($this->updates)) {
551
                return true;
552
            }
553
554
            foreach ($this->fields as $field => $value) {
555
                if ($value instanceof RecordAccessorInterface && $value->hasUpdates()) {
556
                    return true;
557
                }
558
            }
559
560
            return false;
561
        }
562
563
        if (array_key_exists($field, $this->updates)) {
564
            return true;
565
        }
566
567
        $value = $this->getField($field);
568
        if ($value instanceof RecordAccessorInterface && $value->hasUpdates()) {
569
            return true;
570
        }
571
572
        return false;
573
    }
574
575
    /**
576
     * {@inheritdoc}
577
     */
578
    public function flushUpdates()
579
    {
580
        $this->updates = [];
581
582
        foreach ($this->fields as $value) {
583
            if ($value instanceof RecordAccessorInterface) {
584
                $value->flushUpdates();
585
            }
586
        }
587
    }
588
589
    /**
590
     * {@inheritdoc}
591
     */
592
    public function isValid()
593
    {
594
        $this->validate();
595
596
        return empty($this->errors) && empty($this->nestedErrors);
597
    }
598
599
    /**
600
     * {@inheritdoc}
601
     */
602
    public function getErrors($reset = false)
603
    {
604
        return parent::getErrors($reset) + $this->nestedErrors;
605
    }
606
607
    /**
608
     * {@inheritdoc}
609
     */
610
    public function __isset($name)
611
    {
612
        if (isset($this->ormSchema[ORM::M_RELATIONS][$name])) {
613
            return !empty($this->relation($name)->getRelated());
614
        }
615
616
        return parent::__isset($name);
617
    }
618
619
    /**
620
     * {@inheritdoc}
621
     *
622
     * @throws RecordException
623
     */
624
    public function __unset($offset)
625
    {
626
        throw new FieldException("Records fields can not be unsetted.");
627
    }
628
629
    /**
630
     * {@inheritdoc}
631
     *
632
     * @see relation()
633
     */
634
    public function __get($offset)
635
    {
636
        if (isset($this->ormSchema[ORM::M_RELATIONS][$offset])) {
637
            //Bypassing call to relation
638
            return $this->relation($offset)->getRelated();
639
        }
640
641
        return $this->getField($offset, true);
642
    }
643
644
    /**
645
     * {@inheritdoc}
646
     *
647
     * @see relation()
648
     */
649
    public function __set($offset, $value)
650
    {
651
        if (isset($this->ormSchema[ORM::M_RELATIONS][$offset])) {
652
            //Bypassing call to relation
653
            $this->relation($offset)->associate($value);
654
655
            return;
656
        }
657
658
        $this->setField($offset, $value, true);
659
    }
660
661
    /**
662
     * Direct access to relation by it's name.
663
     *
664
     * @see relation()
665
     * @param string $method
666
     * @param array  $arguments
667
     * @return RelationInterface|mixed|AccessorInterface
668
     */
669
    public function __call($method, array $arguments)
670
    {
671
        if (isset($this->ormSchema[ORM::M_RELATIONS][$method])) {
672
            return $this->relation($method);
673
        }
674
675
        //See FIELD_FORMAT constant
676
        return parent::__call($method, $arguments);
677
    }
678
679
    /**
680
     * @return array
681
     */
682
    public function __debugInfo()
683
    {
684
        $info = [
685
            'table'     => $this->ormSchema[ORM::M_DB] . '/' . $this->ormSchema[ORM::M_TABLE],
686
            'pivotData' => $this->pivotData,
687
            'fields'    => $this->getFields(),
688
            'errors'    => $this->getErrors()
689
        ];
690
691
        if (empty($this->pivotData)) {
692
            unset($info['pivotData']);
693
        }
694
695
        return $info;
696
    }
697
698
    /**
699
     * Get associated Database\Table instance.
700
     *
701
     * @see save()
702
     * @see delete()
703
     * @return Table
704
     */
705
    protected function sourceTable()
706
    {
707
        return $this->orm->database($this->ormSchema[ORM::M_DB])->table(
708
            $this->ormSchema[ORM::M_TABLE]
709
        );
710
    }
711
712
    /**
713
     * Get WHERE array to be used to perform record data update or deletion. Usually will include
714
     * record primary key.
715
     *
716
     * @return array
717
     */
718
    protected function stateCriteria()
719
    {
720
        if (!empty($primaryKey = $this->ormSchema()[ORM::M_PRIMARY_KEY])) {
721
            return [$primaryKey => $this->primaryKey()];
722
        }
723
724
        //We have to serialize record data
725
        return $this->updates + $this->serializeData();
726
    }
727
728
    /**
729
     * {@inheritdoc}
730
     */
731
    protected function container()
732
    {
733
        if (empty($this->orm)) {
734
            return parent::container();
735
        }
736
737
        return $this->orm->container();
738
    }
739
740
    /**
741
     * Create set of fields to be sent to UPDATE statement.
742
     *
743
     * @see save()
744
     * @return array
745
     */
746
    protected function compileUpdates()
747
    {
748
        if (!$this->hasUpdates() && !$this->isSolid()) {
749
            return [];
750
        }
751
752
        if ($this->isSolid()) {
753
            return $this->solidUpdate();
754
        }
755
756
        $updates = [];
757
        foreach ($this->fields as $field => $value) {
758 View Code Duplication
            if ($value instanceof RecordAccessorInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
759
                if ($value->hasUpdates()) {
760
                    $updates[$field] = $value->compileUpdates($field);
761
                    continue;
762
                }
763
764
                //Will be handled as normal update if needed
765
                $value = $value->serializeData();
766
            }
767
768
            if (array_key_exists($field, $this->updates)) {
769
                $updates[$field] = $value;
770
            }
771
        }
772
773
        //Primary key should not present in update set
774
        unset($updates[$this->ormSchema[ORM::M_PRIMARY_KEY]]);
775
776
        return $updates;
777
    }
778
779
    /**
780
     * {@inheritdoc}
781
     *
782
     * Will validate every loaded and embedded relation.
783
     */
784 View Code Duplication
    protected function validate($reset = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
785
    {
786
        $this->nestedErrors = [];
787
788
        //Validating all compositions/accessors
789
        foreach ($this->fields as $field => $value) {
790
            //Ensuring value state
791
            $value = $this->getField($field);
792
            if (!$value instanceof ValidatesInterface) {
793
                continue;
794
            }
795
796
            if (!$value->isValid()) {
797
                $this->nestedErrors[$field] = $value->getErrors($reset);
798
            }
799
        }
800
801
        //We have to validate some relations before saving them
802
        $this->validateRelations($reset);
803
804
        parent::validate($reset);
805
806
        return empty($this->errors + $this->nestedErrors);
807
    }
808
809
    /**
810
     * {@inheritdoc}
811
     *
812
     * @see   Component::staticContainer()
813
     * @param array $fields Record fields to set, will be passed thought filters.
814
     * @param ORM   $orm    ORM component, global container will be called if not instance provided.
815
     * @event created()
816
     */
817 View Code Duplication
    public static function create($fields = [], ORM $orm = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
818
    {
819
        /**
820
         * @var RecordEntity $record
821
         */
822
        $record = new static([], false, $orm);
823
824
        //Forcing validation (empty set of fields is not valid set of fields)
825
        $record->setFields($fields)->dispatch('created', new EntityEvent($record));
826
827
        return $record;
828
    }
829
830
    /**
831
     * Change record loaded state.
832
     *
833
     * @param bool|mixed $loader
834
     * @return $this
835
     */
836
    protected function loadedState($loader)
837
    {
838
        $this->loaded = $loader;
839
840
        return $this;
841
    }
842
843
    /**
844
     * Related and cached ORM schema.
845
     *
846
     * @return array
847
     */
848
    protected function ormSchema()
849
    {
850
        return $this->ormSchema;
851
    }
852
853
    /**
854
     * Check if relation is embedded.
855
     *
856
     * @param string $relation
857
     * @return bool
858
     */
859
    protected function isEmbedded($relation)
860
    {
861
        return !empty(
862
        $this->ormSchema[ORM::M_RELATIONS][$relation][ORM::R_DEFINITION][self::EMBEDDED_RELATION]
863
        );
864
    }
865
866
    /**
867
     * Full structure update.
868
     *
869
     * @return array
870
     */
871
    private function solidUpdate()
872
    {
873
        $updates = [];
874
        foreach ($this->fields as $field => $value) {
875 View Code Duplication
            if ($value instanceof RecordAccessorInterface && $value->hasUpdates()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
876
                if ($value->hasUpdates()) {
877
                    $updates[$field] = $value->compileUpdates($field);
878
                } else {
879
                    $updates[$field] = $value->serializeData();
880
                }
881
                continue;
882
            }
883
884
            $updates[$field] = $value;
885
        }
886
887
        return $updates;
888
    }
889
890
    /**
891
     * Validate embedded relations.
892
     *
893
     * @param bool $reset
894
     */
895
    private function validateRelations($reset)
896
    {
897
        foreach ($this->relations as $name => $relation) {
898
            if (!$relation instanceof ValidatesInterface) {
899
                //Never constructed
900
                continue;
901
            }
902
903
            if ($this->isEmbedded($name) && !$relation->isValid()) {
904
                $this->nestedErrors[$name] = $relation->getErrors($reset);
905
            }
906
        }
907
    }
908
}
909