Completed
Push — master ( 1ef137...819ed0 )
by Thomas
02:27
created

Entity::reset()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 13
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
namespace ORM;
4
5
use ORM\Dbal\Error;
6
use ORM\Entity\GeneratesPrimaryKeys;
7
use ORM\Entity\Relations;
8
use ORM\Entity\Validation;
9
use ORM\EntityManager as EM;
10
use ORM\Exception\IncompletePrimaryKey;
11
use ORM\Exception\UnknownColumn;
12
13
/**
14
 * Definition of an entity
15
 *
16
 * The instance of an entity represents a row of the table and the statics variables and methods describe the database
17
 * table.
18
 *
19
 * This is the main part where your configuration efforts go. The following properties and methods are well documented
20
 * in the manual under [https://tflori.github.io/orm/entityDefinition.html](Entity Definition).
21
 *
22
 * @package ORM
23
 * @link    https://tflori.github.io/orm/entityDefinition.html Entity Definition
24
 * @author  Thomas Flori <[email protected]>
25
 */
26
abstract class Entity implements \Serializable
27
{
28
    use Validation, Relations;
1 ignored issue
show
Bug introduced by
The trait ORM\Entity\Validation requires the property $name which is not provided by ORM\Entity.
Loading history...
29
30
    /** @deprecated Use Relation::OPT_CLASS instead */
31
    const OPT_RELATION_CLASS       = 'class';
32
    /** @deprecated Use Relation::OPT_CARDINALITY instead */
33
    const OPT_RELATION_CARDINALITY = 'cardinality';
34
    /** @deprecated Use Relation::OPT_REFERENCE instead */
35
    const OPT_RELATION_REFERENCE   = 'reference';
36
    /** @deprecated Use Relation::OPT_OPPONENT instead */
37
    const OPT_RELATION_OPPONENT    = 'opponent';
38
    /** @deprecated Use Relation::OPT_TABLE instead */
39
    const OPT_RELATION_TABLE       = 'table';
40
41
    /** The template to use to calculate the table name.
42
     * @var string */
43
    protected static $tableNameTemplate;
44
45
    /** The naming scheme to use for table names.
46
     * @var string */
47
    protected static $namingSchemeTable;
48
49
    /** The naming scheme to use for column names.
50
     * @var string */
51
    protected static $namingSchemeColumn;
52
53
    /** The naming scheme to use for method names.
54
     * @var string */
55
    protected static $namingSchemeMethods;
56
57
    /** Fixed table name (ignore other settings)
58
     * @var string */
59
    protected static $tableName;
60
61
    /** The variable(s) used for primary key.
62
     * @var string[]|string */
63
    protected static $primaryKey = ['id'];
64
65
    /** Fixed column names (ignore other settings)
66
     * @var string[] */
67
    protected static $columnAliases = [];
68
69
    /** A prefix for column names.
70
     * @var string */
71
    protected static $columnPrefix;
72
73
    /** Whether or not the primary key is auto incremented.
74
     * @var bool */
75
    protected static $autoIncrement = true;
76
77
    /** The reflections of the classes.
78
     * @internal
79
     * @var \ReflectionClass[] */
80
    protected static $reflections = [];
81
82
    /** The current data of a row.
83
     * @var mixed[] */
84
    protected $data = [];
85
86
    /** The original data of the row.
87
     * @var mixed[] */
88
    protected $originalData = [];
89
90
    /** The entity manager from which this entity got created
91
     * @var EM */
92
    protected $entityManager;
93
94
    /**
95
     * Constructor
96
     *
97
     * It calls ::onInit() after initializing $data and $originalData.
98
     *
99
     * @param mixed[] $data          The current data
100
     * @param EM      $entityManager The EntityManager that created this entity
101
     * @param bool    $fromDatabase  Whether or not the data comes from database
102
     */
103
    final public function __construct(array $data = [], EM $entityManager = null, $fromDatabase = false)
104
    {
105
        if ($fromDatabase) {
106
            $this->originalData = $data;
107
        }
108
        $this->data          = array_merge($this->data, $data);
109
        $this->entityManager = $entityManager ?: EM::getInstance(static::class);
110
        $this->onInit(!$fromDatabase);
111
    }
112
113
    /**
114 122
     * Get the column name of $attribute
115
     *
116 122
     * The column names can not be specified by template. Instead they are constructed by $columnPrefix and enforced
117 14
     * to $namingSchemeColumn.
118
     *
119 122
     * **ATTENTION**: If your overwrite this method remember that getColumnName(getColumnName($name)) have to be exactly
120 122
     * the same as getColumnName($name).
121 122
     *
122 122
     * @param string $attribute
123
     * @return string
124
     */
125
    public static function getColumnName($attribute)
126
    {
127
        if (isset(static::$columnAliases[$attribute])) {
128
            return static::$columnAliases[$attribute];
129
        }
130
131
        return EM::getInstance(static::class)->getNamer()
132
            ->getColumnName(static::class, $attribute, static::$columnPrefix, static::$namingSchemeColumn);
133
    }
134
135
    /**
136
     * Create an entityFetcher for this entity
137 166
     *
138
     * @return EntityFetcher
139 166
     */
140 6
    public static function query()
141
    {
142
        return EM::getInstance(static::class)->fetch(static::class);
143 164
    }
144 164
145
    /**
146
     * Get the primary key vars
147
     *
148
     * The primary key can consist of multiple columns. You should configure the vars that are translated to these
149
     * columns.
150
     *
151
     * @return array
152
     */
153
    public static function getPrimaryKeyVars()
154
    {
155 72
        return !is_array(static::$primaryKey) ? [ static::$primaryKey ] : static::$primaryKey;
156
    }
157 72
158
    /**
159
     * Get the table name
160
     *
161
     * The table name is constructed by $tableNameTemplate and $namingSchemeTable. It can be overwritten by
162
     * $tableName.
163
     *
164
     * @return string
165
     */
166
    public static function getTableName()
167
    {
168
        if (static::$tableName) {
169 154
            return static::$tableName;
170
        }
171 154
172 11
        return EM::getInstance(static::class)->getNamer()
173
            ->getTableName(static::class, static::$tableNameTemplate, static::$namingSchemeTable);
174
    }
175 143
176 143
    /**
177
     * Check if the table has a auto increment column
178
     *
179
     * @return bool
180
     */
181
    public static function isAutoIncremented()
182
    {
183
        return count(static::getPrimaryKeyVars()) > 1 ? false : static::$autoIncrement;
184 19
    }
185
186 19
    /**
187
     * @param EM $entityManager
188
     * @return static
189
     */
190
    public function setEntityManager(EM $entityManager)
191
    {
192
        $this->entityManager = $entityManager;
193 12
        return $this;
194
    }
195 12
196 12
    /**
197
     * Get the value from $attribute
198
     *
199
     * If there is a custom getter this method get called instead.
200
     *
201
     * @param string $attribute The variable to get
202
     * @return mixed|null
203
     * @link https://tflori.github.io/orm/entities.html Working with entities
204
     */
205
    public function __get($attribute)
206
    {
207
        $em     = EM::getInstance(static::class);
208
        $getter = $em->getNamer()->getMethodName('get' . ucfirst($attribute), self::$namingSchemeMethods);
209
210 112
        if (method_exists($this, $getter) && is_callable([ $this, $getter ])) {
211
            return $this->$getter();
212 112
        } else {
213 112
            $col    = static::getColumnName($attribute);
214
            $result = isset($this->data[$col]) ? $this->data[$col] : null;
215 112
216 4
            if (!$result && isset(static::$relations[$attribute]) && isset($this->entityManager)) {
217
                return $this->getRelated($attribute);
218 108
            }
219 108
220
            return $result;
221 108
        }
222 1
    }
223
224
    /**
225 107
     * Check if a column is defined
226
     *
227
     * @param $attribute
228
     * @return bool
229
     */
230
    public function __isset($attribute)
231
    {
232
        $em     = EM::getInstance(static::class);
233
        $getter = $em->getNamer()->getMethodName('get' . ucfirst($attribute), self::$namingSchemeMethods);
234
235 3
        if (method_exists($this, $getter) && is_callable([ $this, $getter ])) {
236
            return $this->$getter() !== null;
237 3
        } else {
238 3
            $col = static::getColumnName($attribute);
239
            $isset = isset($this->data[$col]);
240 3
241 1
            if (!$isset && isset(static::$relations[$attribute])) {
242
                return !empty($this->getRelated($attribute));
243 2
            }
244 2
245
            return $isset;
246 2
        }
247 1
    }
248
249
    /**
250 1
     * Set $attribute to $value
251
     *
252
     * Tries to call custom setter before it stores the data directly. If there is a setter the setter needs to store
253
     * data that should be updated in the database to $data. Do not store data in $originalData as it will not be
254
     * written and give wrong results for dirty checking.
255
     *
256
     * The onChange event is called after something got changed.
257
     *
258
     * The method throws an error when the validation fails (also when the column does not exist).
259
     *
260
     * @param string $attribute The variable to change
261
     * @param mixed $value The value to store
262
     * @link https://tflori.github.io/orm/entities.html Working with entities
263
     * @throws Error
264
     */
265
    public function __set($attribute, $value)
266
    {
267
        $col = $this->getColumnName($attribute);
268
269
        $em     = EM::getInstance(static::class);
270 38
        $setter = $em->getNamer()->getMethodName('set' . ucfirst($attribute), self::$namingSchemeMethods);
271
272 38
        if (method_exists($this, $setter) && is_callable([ $this, $setter ])) {
273
            $oldValue   = $this->__get($attribute);
274 38
            $md5OldData = md5(serialize($this->data));
275 38
            $this->$setter($value);
276
            $changed = $md5OldData !== md5(serialize($this->data));
277 38
        } else {
278 3
            if (static::isValidatorEnabled()) {
279 3
                $error = static::validate($attribute, $value);
280 3
                if ($error instanceof Error) {
0 ignored issues
show
introduced by
$error is always a sub-type of ORM\Dbal\Error.
Loading history...
281 3
                    throw $error;
282
                }
283 35
            }
284 35
285
            $oldValue         = $this->__get($attribute);
286 1
            $changed          = (isset($this->data[$col]) ? $this->data[$col] : null) !== $value;
287
            $this->data[$col] = $value;
288
        }
289 31
290 31
        if ($changed) {
291 31
            $this->onChange($attribute, $oldValue, $this->__get($attribute));
292
        }
293
    }
294 34
295 31
    /**
296
     * Fill the entity with $data
297 34
     *
298
     * When $checkMissing is set to true it also proves that the absent columns are nullable.
299
     *
300
     * @param array $data
301
     * @param bool $ignoreUnknown
302
     * @param bool $checkMissing
303
     * @throws UnknownColumn
304
     */
305
    public function fill(array $data, $ignoreUnknown = false, $checkMissing = false)
306
    {
307
        foreach ($data as $attribute => $value) {
308
            try {
309
                $this->__set($attribute, $value);
310 8
            } catch (UnknownColumn $e) {
311
                if (!$ignoreUnknown) {
312 8
                    throw $e;
313
                }
314 7
            }
315 2
        }
316 2
317 7
        if ($checkMissing && is_array($errors = $this->isValid())) {
318
            throw $errors[0];
319
        }
320
    }
321
322 7
    /**
323 1
     * Resets the entity or $attribute to original data
324
     *
325 6
     * @param string $attribute Reset only this variable or all variables
326
     */
327
    public function reset($attribute = null)
328
    {
329
        if (!empty($attribute)) {
330
            $col = static::getColumnName($attribute);
331
            if (isset($this->originalData[$col])) {
332
                $this->data[$col] = $this->originalData[$col];
333 22
            } else {
334
                unset($this->data[$col]);
335 22
            }
336 3
            return;
337 3
        }
338 2
339
        $this->data = $this->originalData;
340 1
    }
341
342 3
    /**
343
     * Save the entity to EntityManager
344
     *
345 19
     * @return Entity
346 19
     * @throws IncompletePrimaryKey
347
     */
348
    public function save()
349
    {
350
        // @codeCoverageIgnoreStart
351
        if (func_num_args() === 1 && func_get_arg(0) instanceof EM) {
352
            trigger_error(
353
                'Passing EntityManager to save is deprecated. Use ->setEntityManager() to overwrite',
354
                E_USER_DEPRECATED
355
            );
356
        }
357
        // @codeCoverageIgnoreEnd
358
359
        $inserted = false;
360
        $updated  = false;
361 16
362
        try {
363
            // this may throw if the primary key is auto incremented but we using this to omit duplicated code
364
            if (!$this->entityManager->sync($this)) {
365
                $this->prePersist();
366
                $inserted = $this->entityManager->insert($this, false);
367
            } elseif ($this->isDirty()) {
368
                $this->preUpdate();
369
                $updated = $this->entityManager->update($this);
370
            }
371
        } catch (IncompletePrimaryKey $e) {
372 16
            if (static::isAutoIncremented()) {
373 16
                $this->prePersist();
374
                $inserted = $this->entityManager->insert($this);
375
            } elseif ($this instanceof GeneratesPrimaryKeys) {
376
                $this->generatePrimaryKey();
377 16
                $this->prePersist();
378 2
                $inserted = $this->entityManager->insert($this);
379 2
            } else {
380 4
                throw $e;
381 3
            }
382 6
        }
383
384 10
        $inserted && $this->postPersist();
385 10
        $updated && $this->postUpdate();
386 8
387 8
        return $this;
388
    }
389 1
390 1
    /**
391 1
     * Generates a primary key
392
     *
393 1
     * This method should only be executed from save method.
394
     * @codeCoverageIgnore no operations
395
     */
396
    protected function generatePrimaryKey()
397 14
    {
398 14
        // no operation by default
399
    }
400 14
401
    /**
402
     * Checks if entity or $attribute got changed
403
     *
404
     * @param string $attribute Check only this variable or all variables
405
     * @return bool
406
     */
407
    public function isDirty($attribute = null)
408
    {
409
        if (!empty($attribute)) {
410
            $col = static::getColumnName($attribute);
411
            return (isset($this->data[$col]) ? $this->data[$col] : null) !==
412
                   (isset($this->originalData[$col]) ? $this->originalData[$col] : null);
413
        }
414
415
        ksort($this->data);
416
        ksort($this->originalData);
417
418
        return serialize($this->data) !== serialize($this->originalData);
419
    }
420
421 20
    /**
422
     * Empty event handler
423 20
     *
424 4
     * Get called when the entity get initialized.
425 4
     *
426 4
     * @param bool $new Whether or not the entity is new or from database
427
     * @codeCoverageIgnore dummy event handler
428
     */
429 17
    public function onInit($new)
430 17
    {
431
    }
432 17
433
    /**
434
     * Empty event handler
435
     *
436
     * Get called when something is changed with magic setter.
437
     *
438
     * @param string $attribute The variable that got changed.merge(node.inheritedProperties)
439
     * @param mixed  $oldValue  The old value of the variable
440
     * @param mixed  $value     The new value of the variable
441
     * @codeCoverageIgnore dummy event handler
442
     */
443
    public function onChange($attribute, $oldValue, $value)
444
    {
445
    }
446
447
    /**
448
     * Empty event handler
449
     *
450
     * Get called before the entity get inserted in database.
451
     *
452
     * @codeCoverageIgnore dummy event handler
453
     */
454
    public function prePersist()
455
    {
456
    }
457
458
    /**
459
     * Empty event handler
460
     *
461
     * Get called before the entity get updated in database.
462
     *
463
     * @codeCoverageIgnore dummy event handler
464
     */
465
    public function preUpdate()
466
    {
467
    }
468
469
    /**
470
     * Empty event handler
471
     *
472
     * Get called after the entity got inserted in database.
473
     *
474
     * @codeCoverageIgnore dummy event handler
475
     */
476
    public function postPersist()
477
    {
478
    }
479
480
    /**
481
     * Empty event handler
482
     *
483
     * Get called after the entity got updated in database.
484
     *
485
     * @codeCoverageIgnore dummy event handler
486
     */
487
    public function postUpdate()
488
    {
489
    }
490
491
    /**
492
     * Get the primary key
493
     *
494
     * @return array
495
     * @throws IncompletePrimaryKey
496
     */
497
    public function getPrimaryKey()
498
    {
499
        $primaryKey = [];
500
        foreach (static::getPrimaryKeyVars() as $attribute) {
501
            $value = $this->$attribute;
502
            if ($value === null) {
503
                throw new IncompletePrimaryKey('Incomplete primary key - missing ' . $attribute);
504
            }
505
            $primaryKey[$attribute] = $value;
506
        }
507
        return $primaryKey;
508
    }
509
510
    /**
511
     * Get current data
512 53
     *
513
     * @return array
514 53
     * @internal
515 53
     */
516 53
    public function getData()
517 53
    {
518 9
        return $this->data;
519
    }
520 49
521
    /**
522 47
     * Set new original data
523
     *
524
     * @param array $data
525
     * @internal
526
     */
527
    public function setOriginalData(array $data)
528
    {
529
        $this->originalData = $data;
530
    }
531 31
532
    /**
533 31
     * String representation of data
534
     *
535
     * @link http://php.net/manual/en/serializable.serialize.php
536
     * @return string
537
     */
538
    public function serialize()
539
    {
540
        return serialize([ $this->data, $this->relatedObjects ]);
541
    }
542 38
543
    /**
544 38
     * Constructs the object
545 38
     *
546
     * @link http://php.net/manual/en/serializable.unserialize.php
547
     * @param string $serialized The string representation of data
548
     */
549
    public function unserialize($serialized)
550
    {
551
        list($this->data, $this->relatedObjects) = unserialize($serialized);
552
        $this->entityManager = EM::getInstance(static::class);
553 2
        $this->onInit(false);
554
    }
555 2
556
    // DEPRECATED stuff
557
558
    /**
559
     * @return string
560
     * @deprecated         use getOption from EntityManager
561
     * @codeCoverageIgnore deprecated
562
     */
563
    public static function getTableNameTemplate()
564 3
    {
565
        return static::$tableNameTemplate;
566 3
    }
567 3
568 3
    /**
569 3
     * @param string $tableNameTemplate
570
     * @deprecated         use setOption from EntityManager
571
     * @codeCoverageIgnore deprecated
572
     */
573
    public static function setTableNameTemplate($tableNameTemplate)
574
    {
575
        static::$tableNameTemplate = $tableNameTemplate;
576
    }
577
578
    /**
579
     * @return string
580
     * @deprecated         use getOption from EntityManager
581
     * @codeCoverageIgnore deprecated
582
     */
583
    public static function getNamingSchemeTable()
584
    {
585
        return static::$namingSchemeTable;
586
    }
587
588
    /**
589
     * @param string $namingSchemeTable
590
     * @deprecated         use setOption from EntityManager
591
     * @codeCoverageIgnore deprecated
592
     */
593
    public static function setNamingSchemeTable($namingSchemeTable)
594
    {
595
        static::$namingSchemeTable = $namingSchemeTable;
596
    }
597
598
    /**
599
     * @return string
600
     * @deprecated         use getOption from EntityManager
601
     * @codeCoverageIgnore deprecated
602
     */
603
    public static function getNamingSchemeColumn()
604
    {
605
        return static::$namingSchemeColumn;
606
    }
607
608
    /**
609
     * @param string $namingSchemeColumn
610
     * @deprecated         use setOption from EntityManager
611
     * @codeCoverageIgnore deprecated
612
     */
613
    public static function setNamingSchemeColumn($namingSchemeColumn)
614
    {
615
        static::$namingSchemeColumn = $namingSchemeColumn;
616
    }
617
618
    /**
619
     * @return string
620
     * @deprecated         use getOption from EntityManager
621
     * @codeCoverageIgnore deprecated
622
     */
623
    public static function getNamingSchemeMethods()
624
    {
625
        return static::$namingSchemeMethods;
626
    }
627
628
    /**
629
     * @param string $namingSchemeMethods
630
     * @deprecated         use setOption from EntityManager
631
     * @codeCoverageIgnore deprecated
632
     */
633
    public static function setNamingSchemeMethods($namingSchemeMethods)
634
    {
635
        static::$namingSchemeMethods = $namingSchemeMethods;
636
    }
637
}
638