Passed
Branch feature-validator (1add37)
by Thomas
02:43
created

Entity::enableValidator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace ORM;
4
5
use ORM\Dbal\Column;
6
use ORM\Exceptions\IncompletePrimaryKey;
7
use ORM\Exceptions\InvalidConfiguration;
8
use ORM\Exceptions\InvalidRelation;
9
use ORM\Exceptions\InvalidName;
10
use ORM\Exceptions\NoEntityManager;
11
use ORM\Exceptions\UndefinedRelation;
12
use ORM\Dbal\Error;
13
use ORM\Dbal\Table;
14
use ORM\EntityManager as EM;
15
16
/**
17
 * Definition of an entity
18
 *
19
 * The instance of an entity represents a row of the table and the statics variables and methods describe the database
20
 * table.
21
 *
22
 * This is the main part where your configuration efforts go. The following properties and methods are well documented
23
 * in the manual under [https://tflori.github.io/orm/entityDefinition.html](Entity Definition).
24
 *
25
 * @package ORM
26
 * @link https://tflori.github.io/orm/entityDefinition.html Entity Definition
27
 * @author Thomas Flori <[email protected]>
28
 */
29
abstract class Entity implements \Serializable
30
{
31
    const OPT_RELATION_CLASS       = 'class';
32
    const OPT_RELATION_CARDINALITY = 'cardinality';
33
    const OPT_RELATION_REFERENCE   = 'reference';
34
    const OPT_RELATION_OPPONENT    = 'opponent';
35
    const OPT_RELATION_TABLE       = 'table';
36
37
    /** The template to use to calculate the table name.
38
     * @var string */
39
    protected static $tableNameTemplate;
40
41
    /** The naming scheme to use for table names.
42
     * @var string */
43
    protected static $namingSchemeTable;
44
45
    /** The naming scheme to use for column names.
46
     * @var string */
47
    protected static $namingSchemeColumn;
48
49
    /** The naming scheme to use for method names.
50
     * @var string */
51
    protected static $namingSchemeMethods;
52
53
    /** Fixed table name (ignore other settings)
54
     * @var string */
55
    protected static $tableName;
56
57
    /** The variable(s) used for primary key.
58
     * @var string[]|string */
59
    protected static $primaryKey = ['id'];
60
61
    /** Fixed column names (ignore other settings)
62
     * @var string[] */
63
    protected static $columnAliases = [];
64
65
    /** A prefix for column names.
66
     * @var string */
67
    protected static $columnPrefix;
68
69
    /** Whether or not the primary key is auto incremented.
70
     * @var bool */
71
    protected static $autoIncrement = true;
72
73
    /** Whether or not the validator for this class is enabled.
74
     * @var bool */
75
    protected static $enableValidator = false;
76
77
    /** Whether or not the validator for a class got enabled during runtime.
78
     * @var bool[] */
79
    protected static $enabledValidators = [];
80
81
    /** Relation definitions
82
     * @var array */
83
    protected static $relations = [];
84
85
    /** The reflections of the classes.
86
     * @internal
87
     * @var \ReflectionClass[] */
88
    protected static $reflections = [];
89
90
    /** The current data of a row.
91
     * @var mixed[] */
92
    protected $data = [];
93
94
    /** The original data of the row.
95
     * @var mixed[] */
96
    protected $originalData = [];
97
98
    /** The entity manager from which this entity got created
99
     * @var EM */
100
    protected $entityManager;
101
102
    /** Related objects for getRelated
103
     * @var array */
104
    protected $relatedObjects = [];
105
106
    /**
107
     * Constructor
108
     *
109
     * It calls ::onInit() after initializing $data and $originalData.
110
     *
111
     * @param mixed[] $data The current data
112
     * @param EM $entityManager The EntityManager that created this entity
113
     * @param bool $fromDatabase Whether or not the data comes from database
114
     */
115 106
    final public function __construct(array $data = [], EM $entityManager = null, $fromDatabase = false)
116
    {
117 106
        if ($fromDatabase) {
118 14
            $this->originalData = $data;
119
        }
120 106
        $this->data = array_merge($this->data, $data);
121 106
        $this->entityManager = $entityManager ?: EM::getInstance(static::class);
122 106
        $this->onInit(!$fromDatabase);
123 106
    }
124
125
    /**
126
     * Get a description for this table.
127
     *
128
     * @return Table|Column[]
129
     * @codeCoverageIgnore This is just a proxy
130
     */
131
    public static function describe()
132
    {
133
        return EM::getInstance(static::class)->describe(static::getTableName());
134
    }
135
136
    /**
137
     * Get the column name of $field
138
     *
139
     * The column names can not be specified by template. Instead they are constructed by $columnPrefix and enforced
140
     * to $namingSchemeColumn.
141
     *
142
     * **ATTENTION**: If your overwrite this method remember that getColumnName(getColumnName($name)) have to be exactly
143
     * the same as getColumnName($name).
144
     *
145
     * @param string $field
146
     * @return string
147
     * @throws InvalidConfiguration
148
     */
149 138
    public static function getColumnName($field)
150
    {
151 138
        if (isset(static::$columnAliases[$field])) {
152 6
            return static::$columnAliases[$field];
153
        }
154
155 136
        return EM::getInstance(static::class)->getNamer()
156 136
            ->getColumnName(static::class, $field, static::$columnPrefix, static::$namingSchemeColumn);
157
    }
158
159
    /**
160
     * Get the primary key vars
161
     *
162
     * The primary key can consist of multiple columns. You should configure the vars that are translated to these
163
     * columns.
164
     *
165
     * @return array
166
     */
167 56
    public static function getPrimaryKeyVars()
168
    {
169 56
        return !is_array(static::$primaryKey) ? [static::$primaryKey] : static::$primaryKey;
170
    }
171
172
    /**
173
     * Get the definition for $relation
174
     *
175
     * It normalize the short definition form and create a Relation object from it.
176
     *
177
     * @param string $relation
178
     * @return Relation
179
     * @throws InvalidConfiguration
180
     * @throws UndefinedRelation
181
     */
182 85
    public static function getRelation($relation)
183
    {
184 85
        if (!isset(static::$relations[$relation])) {
185 3
            throw new UndefinedRelation('Relation ' . $relation . ' is not defined');
186
        }
187
188 84
        $relDef = &static::$relations[$relation];
189
190 84
        if (!$relDef instanceof Relation) {
191 15
            $relDef = Relation::createRelation($relation, $relDef);
192
        }
193
194 83
        return $relDef;
195
    }
196
197
    /**
198
     * Get the table name
199
     *
200
     * The table name is constructed by $tableNameTemplate and $namingSchemeTable. It can be overwritten by
201
     * $tableName.
202
     *
203
     * @return string
204
     * @throws InvalidName|InvalidConfiguration
205
     */
206 140
    public static function getTableName()
207
    {
208 140
        if (static::$tableName) {
209 11
            return static::$tableName;
210
        }
211
212 129
        return EM::getInstance(static::class)->getNamer()
213 129
            ->getTableName(static::class, static::$tableNameTemplate, static::$namingSchemeTable);
214
    }
215
216
    /**
217
     * Check if the table has a auto increment column
218
     *
219
     * @return bool
220
     */
221 14
    public static function isAutoIncremented()
222
    {
223 14
        return count(static::getPrimaryKeyVars()) > 1 ? false : static::$autoIncrement;
224
    }
225
226
    /**
227
     * Check if the validator is enabled
228
     *
229
     * @return bool
230
     */
231 4
    public static function isValidatorEnabled()
232
    {
233 4
        return isset(self::$enabledValidators[static::class]) ?
234 4
            self::$enabledValidators[static::class] : static::$enableValidator;
235
    }
236
237
    /**
238
     * Enable validator
239
     *
240
     * @param bool $enable
241
     */
242 1
    public static function enableValidator($enable = true)
243
    {
244 1
        self::$enabledValidators[static::class] = $enable;
245 1
    }
246
247
    /**
248
     * Disable validator
249
     *
250
     * @param bool $disable
251
     */
252 1
    public static function disableValidator($disable = true)
253
    {
254 1
        self::$enabledValidators[static::class] = !$disable;
255 1
    }
256
257
    /**
258
     * Validate $value for $field
259
     *
260
     * @param string $field
261
     * @param mixed $value
262
     * @return bool|Error
263
     * @throws Exception
264
     */
265 3
    public static function validate($field, $value)
266
    {
267 3
        return static::describe()->validate(static::getColumnName($field), $value);
268
    }
269
270
    /**
271
     * Validate $fields
272
     *
273
     * $fields has to be an array of $field => $value
274
     *
275
     * @param array $fields
276
     * @return array
277
     */
278 1
    public static function validateArray(array $fields)
279
    {
280 1
        $result = $fields;
281 1
        foreach ($result as $field => &$value) {
282 1
            $value = static::validate($field, $value);
283
        }
284 1
        return $result;
285
    }
286
287
    /**
288
     * @param EM $entityManager
289
     * @return self
290
     */
291 2
    public function setEntityManager(EM $entityManager)
292
    {
293 2
        $this->entityManager = $entityManager;
294 2
        return $this;
295
    }
296
297
    /**
298
     * Get the value from $var
299
     *
300
     * If there is a custom getter this method get called instead.
301
     *
302
     * @param string $var The variable to get
303
     * @return mixed|null
304
     * @throws IncompletePrimaryKey
305
     * @throws InvalidConfiguration
306
     * @link https://tflori.github.io/orm/entities.html Working with entities
307
     */
308 87
    public function __get($var)
309
    {
310 87
        $em = EM::getInstance(static::class);
311 87
        $getter = $em->getNamer()->getMethodName('get' . ucfirst($var), self::$namingSchemeMethods);
312
313 87
        if (method_exists($this, $getter) && is_callable([$this, $getter])) {
314 4
            return $this->$getter();
315
        } else {
316 83
            $col = static::getColumnName($var);
317 83
            $result = isset($this->data[$col]) ? $this->data[$col] : null;
318
319 83
            if (!$result && isset(static::$relations[$var]) && isset($this->entityManager)) {
320 1
                return $this->getRelated($var);
321
            }
322
323 82
            return $result;
324
        }
325
    }
326
327
    /**
328
     * Set $var to $value
329
     *
330
     * Tries to call custom setter before it stores the data directly. If there is a setter the setter needs to store
331
     * data that should be updated in the database to $data. Do not store data in $originalData as it will not be
332
     * written and give wrong results for dirty checking.
333
     *
334
     * The onChange event is called after something got changed.
335
     *
336
     * @param string $var   The variable to change
337
     * @param mixed  $value The value to store
338
     * @throws IncompletePrimaryKey
339
     * @throws InvalidConfiguration
340
     * @link https://tflori.github.io/orm/entities.html Working with entities
341
     */
342 18
    public function __set($var, $value)
343
    {
344 18
        $col = $this->getColumnName($var);
345
346 18
        $em = EM::getInstance(static::class);
347 18
        $setter = $em->getNamer()->getMethodName('set' . ucfirst($var), self::$namingSchemeMethods);
348
349 18
        if (method_exists($this, $setter) && is_callable([$this, $setter])) {
350 3
            $oldValue = $this->__get($var);
351 3
            $md5OldData = md5(serialize($this->data));
352 3
            $this->$setter($value);
353 3
            $changed = $md5OldData !== md5(serialize($this->data));
354
        } else {
355 15
            $oldValue = $this->__get($var);
356 15
            $changed = (isset($this->data[$col]) ? $this->data[$col] : null) !== $value;
357 15
            $this->data[$col] = $value;
358
        }
359
360 18
        if ($changed) {
361 15
            $this->onChange($var, $oldValue, $this->__get($var));
362
        }
363 18
    }
364
365
    /**
366
     * Get related objects
367
     *
368
     * The difference between getRelated and fetch is that getRelated stores the fetched entities. To refresh set
369
     * $refresh to true.
370
     *
371
     * @param string $relation
372
     * @param bool   $refresh
373
     * @return mixed
374
     * @throws Exceptions\NoConnection
375
     * @throws Exceptions\NoEntity
376
     * @throws IncompletePrimaryKey
377
     * @throws InvalidConfiguration
378
     * @throws NoEntityManager
379
     * @throws UndefinedRelation
380
     */
381 11
    public function getRelated($relation, $refresh = false)
382
    {
383 11
        if ($refresh || !isset($this->relatedObjects[$relation])) {
384 9
            $this->relatedObjects[$relation] = $this->fetch($relation, true);
385
        }
386
387 11
        return $this->relatedObjects[$relation];
388
    }
389
390
    /**
391
     * Set $relation to $entity
392
     *
393
     * This method is only for the owner of a relation.
394
     *
395
     * @param string $relation
396
     * @param Entity $entity
397
     * @throws IncompletePrimaryKey
398
     * @throws InvalidRelation
399
     */
400 7
    public function setRelated($relation, Entity $entity = null)
401
    {
402 7
        $this::getRelation($relation)->setRelated($this, $entity);
403
404 4
        $this->relatedObjects[$relation] = $entity;
405 4
    }
406
407
    /**
408
     * Add relations for $relation to $entities
409
     *
410
     * This method is only for many-to-many relations.
411
     *
412
     * This method does not take care about already existing relations and will fail hard.
413
     *
414
     * @param string        $relation
415
     * @param Entity[]      $entities
416
     * @throws NoEntityManager
417
     */
418 8
    public function addRelated($relation, array $entities)
419
    {
420
        // @codeCoverageIgnoreStart
421
        if (func_num_args() === 3 && func_get_arg(2) instanceof EM) {
422
            trigger_error(
423
                'Passing EntityManager to addRelated is deprecated. Use ->setEntityManager() to overwrite',
424
                E_USER_DEPRECATED
425
            );
426
        }
427
        // @codeCoverageIgnoreEnd
428
429 8
        $this::getRelation($relation)->addRelated($this, $entities, $this->entityManager);
430 4
    }
431
432
    /**
433
     * Delete relations for $relation to $entities
434
     *
435
     * This method is only for many-to-many relations.
436
     *
437
     * @param string        $relation
438
     * @param Entity[]      $entities
439
     * @throws NoEntityManager
440
     */
441 8
    public function deleteRelated($relation, $entities)
442
    {
443
        // @codeCoverageIgnoreStart
444
        if (func_num_args() === 3 && func_get_arg(2) instanceof EM) {
445
            trigger_error(
446
                'Passing EntityManager to deleteRelated is deprecated. Use ->setEntityManager() to overwrite',
447
                E_USER_DEPRECATED
448
            );
449
        }
450
        // @codeCoverageIgnoreEnd
451
452 8
        $this::getRelation($relation)->deleteRelated($this, $entities, $this->entityManager);
453 4
    }
454
455
    /**
456
     * Resets the entity or $var to original data
457
     *
458
     * @param string $var Reset only this variable or all variables
459
     * @throws InvalidConfiguration
460
     */
461 8
    public function reset($var = null)
462
    {
463 8
        if (!empty($var)) {
464 3
            $col = static::getColumnName($var);
465 3
            if (isset($this->originalData[$col])) {
466 2
                $this->data[$col] = $this->originalData[$col];
467
            } else {
468 1
                unset($this->data[$col]);
469
            }
470 3
            return;
471
        }
472
473 5
        $this->data = $this->originalData;
474 5
    }
475
476
    /**
477
     * Save the entity to EntityManager
478
     *
479
     * @return Entity
480
     * @throws Exceptions\NoConnection
481
     * @throws Exceptions\NoEntity
482
     * @throws Exceptions\NotScalar
483
     * @throws Exceptions\UnsupportedDriver
484
     * @throws IncompletePrimaryKey
485
     * @throws InvalidConfiguration
486
     * @throws InvalidName
487
     * @throws NoEntityManager
488
     */
489 12
    public function save()
490
    {
491
        // @codeCoverageIgnoreStart
492
        if (func_num_args() === 1 && func_get_arg(0) instanceof EM) {
493
            trigger_error(
494
                'Passing EntityManager to save is deprecated. Use ->setEntityManager() to overwrite',
495
                E_USER_DEPRECATED
496
            );
497
        }
498
        // @codeCoverageIgnoreEnd
499
500 12
        $inserted = false;
501 12
        $updated = false;
502
503
        try {
504
            // this may throw if the primary key is auto incremented but we using this to omit duplicated code
505 12
            if (!$this->entityManager->sync($this)) {
506 2
                $this->entityManager->insert($this, false);
507 2
                $inserted = true;
508 5
            } elseif ($this->isDirty()) {
509 4
                $this->preUpdate();
510 4
                $this->entityManager->update($this);
511 7
                $updated = true;
512
            }
513 5
        } catch (IncompletePrimaryKey $e) {
514 5
            if (static::isAutoIncremented()) {
515 4
                $this->prePersist();
516 4
                $id = $this->entityManager->insert($this);
517 4
                $this->data[static::getColumnName(static::getPrimaryKeyVars()[0])] = $id;
518 4
                $inserted = true;
519
            } else {
520 1
                throw $e;
521
            }
522
        }
523
524 11
        if ($inserted || $updated) {
525 10
            $inserted && $this->postPersist();
526 10
            $updated && $this->postUpdate();
527 10
            $this->entityManager->sync($this, true);
528
        }
529
530 11
        return $this;
531
    }
532
533
    /**
534
     * Checks if entity or $var got changed
535
     *
536
     * @param string $var Check only this variable or all variables
537
     * @return bool
538
     * @throws InvalidConfiguration
539
     */
540 18
    public function isDirty($var = null)
541
    {
542 18
        if (!empty($var)) {
543 4
            $col = static::getColumnName($var);
544 4
            return (isset($this->data[$col]) ? $this->data[$col] : null) !==
545 4
                   (isset($this->originalData[$col]) ? $this->originalData[$col] : null);
546
        }
547
548 15
        ksort($this->data);
549 15
        ksort($this->originalData);
550
551 15
        return serialize($this->data) !== serialize($this->originalData);
552
    }
553
554
    /**
555
     * Empty event handler
556
     *
557
     * Get called when something is changed with magic setter.
558
     *
559
     * @param string $var The variable that got changed.merge(node.inheritedProperties)
560
     * @param mixed  $oldValue The old value of the variable
561
     * @param mixed  $value The new value of the variable
562
     */
563 6
    public function onChange($var, $oldValue, $value)
564
    {
565 6
    }
566
567
    /**
568
     * Empty event handler
569
     *
570
     * Get called when the entity get initialized.
571
     *
572
     * @param bool $new Whether or not the entity is new or from database
573
     */
574 105
    public function onInit($new)
575
    {
576 105
    }
577
578
    /**
579
     * Empty event handler
580
     *
581
     * Get called before the entity get updated in database.
582
     */
583 3
    public function preUpdate()
584
    {
585 3
    }
586
587
    /**
588
     * Empty event handler
589
     *
590
     * Get called before the entity get inserted in database.
591
     */
592 3
    public function prePersist()
593
    {
594 3
    }
595
596
597
    // DEPRECATED stuff
598
599
    /**
600
     * Empty event handler
601
     *
602
     * Get called after the entity got inserted in database.
603
     */
604 5
    public function postPersist()
605
    {
606 5
    }
607
608
    /**
609
     * Empty event handler
610
     *
611
     * Get called after the entity got updated in database.
612
     */
613 3
    public function postUpdate()
614
    {
615 3
    }
616
617
    /**
618
     * Fetches related objects
619
     *
620
     * For relations with cardinality many it returns an EntityFetcher. Otherwise it returns the entity.
621
     *
622
     * It will throw an error for non owner when the key is incomplete.
623
     *
624
     * @param string        $relation      The relation to fetch
625
     * @param bool          $getAll
626
     * @return Entity|Entity[]|EntityFetcher
627
     * @throws NoEntityManager
628
     */
629 19
    public function fetch($relation, $getAll = false)
630
    {
631
        // @codeCoverageIgnoreStart
632
        if (func_num_args() === 3 && ($getAll instanceof EM || $getAll === null)) {
633
            $getAll = func_get_arg(2);
634
            trigger_error(
635
                'Passing EntityManager to fetch is deprecated. Use ->setEntityManager() to overwrite',
636
                E_USER_DEPRECATED
637
            );
638
        }
639
        // @codeCoverageIgnoreEnd
640
641 19
        $relation = $this::getRelation($relation);
642
643 19
        if ($getAll) {
644 4
            return $relation->fetchAll($this, $this->entityManager);
645
        } else {
646 15
            return $relation->fetch($this, $this->entityManager);
647
        }
648
    }
649
650
    /**
651
     * Get the primary key
652
     *
653
     * @return array
654
     * @throws IncompletePrimaryKey
655
     */
656 38
    public function getPrimaryKey()
657
    {
658 38
        $primaryKey = [];
659 38
        foreach (static::getPrimaryKeyVars() as $var) {
660 38
            $value = $this->$var;
661 38
            if ($value === null) {
662 4
                throw new IncompletePrimaryKey('Incomplete primary key - missing ' . $var);
663
            }
664 36
            $primaryKey[$var] = $value;
665
        }
666 34
        return $primaryKey;
667
    }
668
669
    /**
670
     * Get current data
671
     *
672
     * @return array
673
     * @internal
674
     */
675 20
    public function getData()
676
    {
677 20
        return $this->data;
678
    }
679
680
    /**
681
     * Set new original data
682
     *
683
     * @param array $data
684
     * @internal
685
     */
686 18
    public function setOriginalData(array $data)
687
    {
688 18
        $this->originalData = $data;
689 18
    }
690
691
    /**
692
     * String representation of data
693
     *
694
     * @link http://php.net/manual/en/serializable.serialize.php
695
     * @return string
696
     */
697 2
    public function serialize()
698
    {
699 2
        return serialize([$this->data, $this->relatedObjects]);
700
    }
701
702
    /**
703
     * Constructs the object
704
     *
705
     * @link http://php.net/manual/en/serializable.unserialize.php
706
     * @param string $serialized The string representation of data
707
     */
708 3
    public function unserialize($serialized)
709
    {
710 3
        list($this->data, $this->relatedObjects) = unserialize($serialized);
711 3
        $this->entityManager = EM::getInstance(static::class);
712 3
        $this->onInit(false);
713 3
    }
714
715
    /**
716
     * @return string
717
     * @deprecated use getOption from EntityManager
718
     * @codeCoverageIgnore deprecated
719
     */
720
    public static function getTableNameTemplate()
721
    {
722
        return static::$tableNameTemplate;
723
    }
724
725
    /**
726
     * @param string $tableNameTemplate
727
     * @deprecated use setOption from EntityManager
728
     * @codeCoverageIgnore deprecated
729
     */
730
    public static function setTableNameTemplate($tableNameTemplate)
731
    {
732
        static::$tableNameTemplate = $tableNameTemplate;
733
    }
734
735
    /**
736
     * @return string
737
     * @deprecated use getOption from EntityManager
738
     * @codeCoverageIgnore deprecated
739
     */
740
    public static function getNamingSchemeTable()
741
    {
742
        return static::$namingSchemeTable;
743
    }
744
745
    /**
746
     * @param string $namingSchemeTable
747
     * @deprecated use setOption from EntityManager
748
     * @codeCoverageIgnore deprecated
749
     */
750
    public static function setNamingSchemeTable($namingSchemeTable)
751
    {
752
        static::$namingSchemeTable = $namingSchemeTable;
753
    }
754
755
    /**
756
     * @return string
757
     * @deprecated use getOption from EntityManager
758
     * @codeCoverageIgnore deprecated
759
     */
760
    public static function getNamingSchemeColumn()
761
    {
762
        return static::$namingSchemeColumn;
763
    }
764
765
    /**
766
     * @param string $namingSchemeColumn
767
     * @deprecated use setOption from EntityManager
768
     * @codeCoverageIgnore deprecated
769
     */
770
    public static function setNamingSchemeColumn($namingSchemeColumn)
771
    {
772
        static::$namingSchemeColumn = $namingSchemeColumn;
773
    }
774
775
    /**
776
     * @return string
777
     * @deprecated use getOption from EntityManager
778
     * @codeCoverageIgnore deprecated
779
     */
780
    public static function getNamingSchemeMethods()
781
    {
782
        return static::$namingSchemeMethods;
783
    }
784
785
    /**
786
     * @param string $namingSchemeMethods
787
     * @deprecated use setOption from EntityManager
788
     * @codeCoverageIgnore deprecated
789
     */
790
    public static function setNamingSchemeMethods($namingSchemeMethods)
791
    {
792
        static::$namingSchemeMethods = $namingSchemeMethods;
793
    }
794
}
795