Completed
Push — master ( fff5f3...fc4fc8 )
by Thomas
02:20
created

Entity::save()   B

Complexity

Conditions 10
Paths 114

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 20
cts 20
cp 1
rs 7.5499
c 0
b 0
f 0
cc 10
nc 114
nop 0
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace ORM;
4
5
use ORM\Dbal\Column;
6
use ORM\Dbal\Error;
7
use ORM\Dbal\Table;
8
use ORM\Entity\GeneratesPrimaryKeys;
9
use ORM\Entity\Relations;
10
use ORM\Entity\Validation;
11
use ORM\EntityManager as EM;
12
use ORM\Exception\IncompletePrimaryKey;
13
use ORM\Exception\InvalidConfiguration;
14
use ORM\Exception\InvalidName;
15
use ORM\Exception\NoEntityManager;
16
use ORM\Exception\UnknownColumn;
17
18
/**
19
 * Definition of an entity
20
 *
21
 * The instance of an entity represents a row of the table and the statics variables and methods describe the database
22
 * table.
23
 *
24
 * This is the main part where your configuration efforts go. The following properties and methods are well documented
25
 * in the manual under [https://tflori.github.io/orm/entityDefinition.html](Entity Definition).
26
 *
27
 * @package ORM
28
 * @link    https://tflori.github.io/orm/entityDefinition.html Entity Definition
29
 * @author  Thomas Flori <[email protected]>
30
 */
31
abstract class Entity implements \Serializable
32
{
33
    use Validation, Relations;
34
35
    /** @deprecated Use Relation::OPT_CLASS instead */
36
    const OPT_RELATION_CLASS       = 'class';
37
    /** @deprecated Use Relation::OPT_CARDINALITY instead */
38
    const OPT_RELATION_CARDINALITY = 'cardinality';
39
    /** @deprecated Use Relation::OPT_REFERENCE instead */
40
    const OPT_RELATION_REFERENCE   = 'reference';
41
    /** @deprecated Use Relation::OPT_OPPONENT instead */
42
    const OPT_RELATION_OPPONENT    = 'opponent';
43
    /** @deprecated Use Relation::OPT_TABLE instead */
44
    const OPT_RELATION_TABLE       = 'table';
45
46
    /** The template to use to calculate the table name.
47
     * @var string */
48
    protected static $tableNameTemplate;
49
50
    /** The naming scheme to use for table names.
51
     * @var string */
52
    protected static $namingSchemeTable;
53
54
    /** The naming scheme to use for column names.
55
     * @var string */
56
    protected static $namingSchemeColumn;
57
58
    /** The naming scheme to use for method names.
59
     * @var string */
60
    protected static $namingSchemeMethods;
61
62
    /** Fixed table name (ignore other settings)
63
     * @var string */
64
    protected static $tableName;
65
66
    /** The variable(s) used for primary key.
67
     * @var string[]|string */
68
    protected static $primaryKey = ['id'];
69
70
    /** Fixed column names (ignore other settings)
71
     * @var string[] */
72
    protected static $columnAliases = [];
73
74
    /** A prefix for column names.
75
     * @var string */
76
    protected static $columnPrefix;
77
78
    /** Whether or not the primary key is auto incremented.
79
     * @var bool */
80
    protected static $autoIncrement = true;
81
82
    /** Whether or not the validator for this class is enabled.
83
     * @var bool */
84
    protected static $enableValidator = false;
85
86
    /** Whether or not the validator for a class got enabled during runtime.
87
     * @var bool[] */
88
    protected static $enabledValidators = [];
89
90
    /** The reflections of the classes.
91
     * @internal
92
     * @var \ReflectionClass[] */
93
    protected static $reflections = [];
94
95
    /** The current data of a row.
96
     * @var mixed[] */
97
    protected $data = [];
98
99
    /** The original data of the row.
100
     * @var mixed[] */
101
    protected $originalData = [];
102
103
    /** The entity manager from which this entity got created
104
     * @var EM */
105
    protected $entityManager;
106
107
    /**
108
     * Constructor
109
     *
110
     * It calls ::onInit() after initializing $data and $originalData.
111
     *
112
     * @param mixed[] $data          The current data
113
     * @param EM      $entityManager The EntityManager that created this entity
114
     * @param bool    $fromDatabase  Whether or not the data comes from database
115
     */
116 122
    final public function __construct(array $data = [], EM $entityManager = null, $fromDatabase = false)
117
    {
118 122
        if ($fromDatabase) {
119 14
            $this->originalData = $data;
120
        }
121 122
        $this->data          = array_merge($this->data, $data);
122 122
        $this->entityManager = $entityManager ?: EM::getInstance(static::class);
123 122
        $this->onInit(!$fromDatabase);
124 122
    }
125
126
    /**
127
     * Get the column name of $attribute
128
     *
129
     * The column names can not be specified by template. Instead they are constructed by $columnPrefix and enforced
130
     * to $namingSchemeColumn.
131
     *
132
     * **ATTENTION**: If your overwrite this method remember that getColumnName(getColumnName($name)) have to be exactly
133
     * the same as getColumnName($name).
134
     *
135
     * @param string $attribute
136
     * @return string
137
     * @throws InvalidConfiguration
138
     */
139 166
    public static function getColumnName($attribute)
140
    {
141 166
        if (isset(static::$columnAliases[$attribute])) {
142 6
            return static::$columnAliases[$attribute];
143
        }
144
145 164
        return EM::getInstance(static::class)->getNamer()
146 164
            ->getColumnName(static::class, $attribute, static::$columnPrefix, static::$namingSchemeColumn);
147
    }
148
149
    /**
150
     * Get the primary key vars
151
     *
152
     * The primary key can consist of multiple columns. You should configure the vars that are translated to these
153
     * columns.
154
     *
155
     * @return array
156
     */
157 72
    public static function getPrimaryKeyVars()
158
    {
159 72
        return !is_array(static::$primaryKey) ? [ static::$primaryKey ] : static::$primaryKey;
160
    }
161
162
    /**
163
     * Get the table name
164
     *
165
     * The table name is constructed by $tableNameTemplate and $namingSchemeTable. It can be overwritten by
166
     * $tableName.
167
     *
168
     * @return string
169
     * @throws InvalidName|InvalidConfiguration
170
     */
171 154
    public static function getTableName()
172
    {
173 154
        if (static::$tableName) {
174 11
            return static::$tableName;
175
        }
176
177 143
        return EM::getInstance(static::class)->getNamer()
178 143
            ->getTableName(static::class, static::$tableNameTemplate, static::$namingSchemeTable);
179
    }
180
181
    /**
182
     * Check if the table has a auto increment column
183
     *
184
     * @return bool
185
     */
186 19
    public static function isAutoIncremented()
187
    {
188 19
        return count(static::getPrimaryKeyVars()) > 1 ? false : static::$autoIncrement;
189
    }
190
191
    /**
192
     * @param EM $entityManager
193
     * @return self
194
     */
195 12
    public function setEntityManager(EM $entityManager)
196
    {
197 12
        $this->entityManager = $entityManager;
198 12
        return $this;
199
    }
200
201
    /**
202
     * Get the value from $attribute
203
     *
204
     * If there is a custom getter this method get called instead.
205
     *
206
     * @param string $attribute The variable to get
207
     * @return mixed|null
208
     * @throws IncompletePrimaryKey
209
     * @throws InvalidConfiguration
210
     * @link https://tflori.github.io/orm/entities.html Working with entities
211
     */
212 112
    public function __get($attribute)
213
    {
214 112
        $em     = EM::getInstance(static::class);
215 112
        $getter = $em->getNamer()->getMethodName('get' . ucfirst($attribute), self::$namingSchemeMethods);
216
217 112
        if (method_exists($this, $getter) && is_callable([ $this, $getter ])) {
218 4
            return $this->$getter();
219
        } else {
220 108
            $col    = static::getColumnName($attribute);
221 108
            $result = isset($this->data[$col]) ? $this->data[$col] : null;
222
223 108
            if (!$result && isset(static::$relations[$attribute]) && isset($this->entityManager)) {
224 1
                return $this->getRelated($attribute);
225
            }
226
227 107
            return $result;
228
        }
229
    }
230
231
    /**
232
     * Check if a column is defined
233
     *
234
     * @param $attribute
235
     * @return bool
236
     */
237 3
    public function __isset($attribute)
238
    {
239 3
        $em     = EM::getInstance(static::class);
240 3
        $getter = $em->getNamer()->getMethodName('get' . ucfirst($attribute), self::$namingSchemeMethods);
241
242 3
        if (method_exists($this, $getter) && is_callable([ $this, $getter ])) {
243 1
            return $this->$getter() !== null;
244
        } else {
245 2
            $col = static::getColumnName($attribute);
246 2
            $isset = isset($this->data[$col]);
247
248 2
            if (!$isset && isset(static::$relations[$attribute])) {
249 1
                return !empty($this->getRelated($attribute));
250
            }
251
252 1
            return $isset;
253
        }
254
    }
255
256
    /**
257
     * Set $attribute to $value
258
     *
259
     * Tries to call custom setter before it stores the data directly. If there is a setter the setter needs to store
260
     * data that should be updated in the database to $data. Do not store data in $originalData as it will not be
261
     * written and give wrong results for dirty checking.
262
     *
263
     * The onChange event is called after something got changed.
264
     *
265
     * The method throws an error when the validation fails (also when the column does not exist).
266
     *
267
     * @param string $attribute The variable to change
268
     * @param mixed  $value     The value to store
269
     * @throws Error
270
     * @link https://tflori.github.io/orm/entities.html Working with entities
271
     */
272 38
    public function __set($attribute, $value)
273
    {
274 38
        $col = $this->getColumnName($attribute);
275
276 38
        $em     = EM::getInstance(static::class);
277 38
        $setter = $em->getNamer()->getMethodName('set' . ucfirst($attribute), self::$namingSchemeMethods);
278
279 38
        if (method_exists($this, $setter) && is_callable([ $this, $setter ])) {
280 3
            $oldValue   = $this->__get($attribute);
281 3
            $md5OldData = md5(serialize($this->data));
282 3
            $this->$setter($value);
283 3
            $changed = $md5OldData !== md5(serialize($this->data));
284
        } else {
285 35
            if (static::isValidatorEnabled() &&
286 35
                ($error = static::validate($attribute, $value)) instanceof Error
287
            ) {
288 1
                throw $error;
289
            }
290
291 31
            $oldValue         = $this->__get($attribute);
292 31
            $changed          = (isset($this->data[$col]) ? $this->data[$col] : null) !== $value;
293 31
            $this->data[$col] = $value;
294
        }
295
296 34
        if ($changed) {
297 31
            $this->onChange($attribute, $oldValue, $this->__get($attribute));
298
        }
299 34
    }
300
301
    /**
302
     * Fill the entity with $data
303
     *
304
     * When $checkMissing is set to true it also proves that the absent columns are nullable.
305
     *
306
     * @param array $data
307
     * @param bool  $ignoreUnknown
308
     * @param bool  $checkMissing
309
     * @throws Error
310
     * @throws UnknownColumn
311
     */
312 8
    public function fill(array $data, $ignoreUnknown = false, $checkMissing = false)
313
    {
314 8
        foreach ($data as $attribute => $value) {
315
            try {
316 7
                $this->__set($attribute, $value);
317 2
            } catch (UnknownColumn $e) {
318 2
                if (!$ignoreUnknown) {
319 7
                    throw $e;
320
                }
321
            }
322
        }
323
324 7
        if ($checkMissing && is_array($errors = $this->isValid())) {
325 1
            throw $errors[0];
326
        }
327 6
    }
328
329
    /**
330
     * Resets the entity or $attribute to original data
331
     *
332
     * @param string $attribute Reset only this variable or all variables
333
     * @throws InvalidConfiguration
334
     */
335 22
    public function reset($attribute = null)
336
    {
337 22
        if (!empty($attribute)) {
338 3
            $col = static::getColumnName($attribute);
339 3
            if (isset($this->originalData[$col])) {
340 2
                $this->data[$col] = $this->originalData[$col];
341
            } else {
342 1
                unset($this->data[$col]);
343
            }
344 3
            return;
345
        }
346
347 19
        $this->data = $this->originalData;
348 19
    }
349
350
    /**
351
     * Save the entity to EntityManager
352
     *
353
     * @return Entity
354
     * @throws Exception\NoConnection
355
     * @throws Exception\NoEntity
356
     * @throws Exception\NotScalar
357
     * @throws Exception\UnsupportedDriver
358
     * @throws IncompletePrimaryKey
359
     * @throws InvalidConfiguration
360
     * @throws InvalidName
361
     * @throws NoEntityManager
362
     */
363 16
    public function save()
364
    {
365
        // @codeCoverageIgnoreStart
366
        if (func_num_args() === 1 && func_get_arg(0) instanceof EM) {
367
            trigger_error(
368
                'Passing EntityManager to save is deprecated. Use ->setEntityManager() to overwrite',
369
                E_USER_DEPRECATED
370
            );
371
        }
372
        // @codeCoverageIgnoreEnd
373
374 16
        $inserted = false;
375 16
        $updated  = false;
376
377
        try {
378
            // this may throw if the primary key is auto incremented but we using this to omit duplicated code
379 16
            if (!$this->entityManager->sync($this)) {
380 2
                $this->prePersist();
381 2
                $inserted = $this->entityManager->insert($this, false);
382 4
            } elseif ($this->isDirty()) {
383 3
                $this->preUpdate();
384 6
                $updated = $this->entityManager->update($this);
385
            }
386 10
        } catch (IncompletePrimaryKey $e) {
387 10
            if (static::isAutoIncremented()) {
388 8
                $this->prePersist();
389 8
                $inserted = $this->entityManager->insert($this);
390
            } elseif ($this instanceof GeneratesPrimaryKeys) {
391 1
                $this->generatePrimaryKey();
392 1
                $this->prePersist();
393 1
                $inserted = $this->entityManager->insert($this);
394
            } else {
395 1
                throw $e;
396
            }
397
        }
398
399 14
        $inserted && $this->postPersist();
400 14
        $updated && $this->postUpdate();
401
402 14
        return $this;
403
    }
404
405
    /**
406
     * Generates a primary key
407
     *
408
     * This method should only be executed from save method.
409
     * @codeCoverageIgnore no operations
410
     */
411
    protected function generatePrimaryKey()
412
    {
413
        // no operation by default
414
    }
415
416
    /**
417
     * Checks if entity or $attribute got changed
418
     *
419
     * @param string $attribute Check only this variable or all variables
420
     * @return bool
421
     * @throws InvalidConfiguration
422
     */
423 20
    public function isDirty($attribute = null)
424
    {
425 20
        if (!empty($attribute)) {
426 4
            $col = static::getColumnName($attribute);
427 4
            return (isset($this->data[$col]) ? $this->data[$col] : null) !==
428 4
                   (isset($this->originalData[$col]) ? $this->originalData[$col] : null);
429
        }
430
431 17
        ksort($this->data);
432 17
        ksort($this->originalData);
433
434 17
        return serialize($this->data) !== serialize($this->originalData);
435
    }
436
437
    /**
438
     * Empty event handler
439
     *
440
     * Get called when something is changed with magic setter.
441
     *
442
     * @param string $attribute The variable that got changed.merge(node.inheritedProperties)
443
     * @param mixed  $oldValue  The old value of the variable
444
     * @param mixed  $value     The new value of the variable
445
     * @codeCoverageIgnore dummy event handler
446
     */
447
    public function onChange($attribute, $oldValue, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $attribute is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $oldValue is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
448
    {
449
    }
450
451
    /**
452
     * Empty event handler
453
     *
454
     * Get called when the entity get initialized.
455
     *
456
     * @param bool $new Whether or not the entity is new or from database
457
     * @codeCoverageIgnore dummy event handler
458
     */
459
    public function onInit($new)
0 ignored issues
show
Unused Code introduced by
The parameter $new is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
460
    {
461
    }
462
463
    /**
464
     * Empty event handler
465
     *
466
     * Get called before the entity get updated in database.
467
     *
468
     * @codeCoverageIgnore dummy event handler
469
     */
470
    public function preUpdate()
471
    {
472
    }
473
474
    /**
475
     * Empty event handler
476
     *
477
     * Get called before the entity get inserted in database.
478
     *
479
     * @codeCoverageIgnore dummy event handler
480
     */
481
    public function prePersist()
482
    {
483
    }
484
485
486
    /**
487
     * Empty event handler
488
     *
489
     * Get called after the entity got inserted in database.
490
     *
491
     * @codeCoverageIgnore dummy event handler
492
     */
493
    public function postPersist()
494
    {
495
    }
496
497
    /**
498
     * Empty event handler
499
     *
500
     * Get called after the entity got updated in database.
501
     *
502
     * @codeCoverageIgnore dummy event handler
503
     */
504
    public function postUpdate()
505
    {
506
    }
507
508
    /**
509
     * Get the primary key
510
     *
511
     * @return array
512
     * @throws IncompletePrimaryKey
513
     */
514 53
    public function getPrimaryKey()
515
    {
516 53
        $primaryKey = [];
517 53
        foreach (static::getPrimaryKeyVars() as $attribute) {
518 53
            $value = $this->$attribute;
519 53
            if ($value === null) {
520 9
                throw new IncompletePrimaryKey('Incomplete primary key - missing ' . $attribute);
521
            }
522 49
            $primaryKey[$attribute] = $value;
523
        }
524 47
        return $primaryKey;
525
    }
526
527
    /**
528
     * Get current data
529
     *
530
     * @return array
531
     * @internal
532
     */
533 31
    public function getData()
534
    {
535 31
        return $this->data;
536
    }
537
538
    /**
539
     * Set new original data
540
     *
541
     * @param array $data
542
     * @internal
543
     */
544 38
    public function setOriginalData(array $data)
545
    {
546 38
        $this->originalData = $data;
547 38
    }
548
549
    /**
550
     * String representation of data
551
     *
552
     * @link http://php.net/manual/en/serializable.serialize.php
553
     * @return string
554
     */
555 2
    public function serialize()
556
    {
557 2
        return serialize([ $this->data, $this->relatedObjects ]);
558
    }
559
560
    /**
561
     * Constructs the object
562
     *
563
     * @link http://php.net/manual/en/serializable.unserialize.php
564
     * @param string $serialized The string representation of data
565
     */
566 3
    public function unserialize($serialized)
567
    {
568 3
        list($this->data, $this->relatedObjects) = unserialize($serialized);
569 3
        $this->entityManager = EM::getInstance(static::class);
570 3
        $this->onInit(false);
571 3
    }
572
573
    // DEPRECATED stuff
574
575
    /**
576
     * @return string
577
     * @deprecated         use getOption from EntityManager
578
     * @codeCoverageIgnore deprecated
579
     */
580
    public static function getTableNameTemplate()
581
    {
582
        return static::$tableNameTemplate;
583
    }
584
585
    /**
586
     * @param string $tableNameTemplate
587
     * @deprecated         use setOption from EntityManager
588
     * @codeCoverageIgnore deprecated
589
     */
590
    public static function setTableNameTemplate($tableNameTemplate)
591
    {
592
        static::$tableNameTemplate = $tableNameTemplate;
593
    }
594
595
    /**
596
     * @return string
597
     * @deprecated         use getOption from EntityManager
598
     * @codeCoverageIgnore deprecated
599
     */
600
    public static function getNamingSchemeTable()
601
    {
602
        return static::$namingSchemeTable;
603
    }
604
605
    /**
606
     * @param string $namingSchemeTable
607
     * @deprecated         use setOption from EntityManager
608
     * @codeCoverageIgnore deprecated
609
     */
610
    public static function setNamingSchemeTable($namingSchemeTable)
611
    {
612
        static::$namingSchemeTable = $namingSchemeTable;
613
    }
614
615
    /**
616
     * @return string
617
     * @deprecated         use getOption from EntityManager
618
     * @codeCoverageIgnore deprecated
619
     */
620
    public static function getNamingSchemeColumn()
621
    {
622
        return static::$namingSchemeColumn;
623
    }
624
625
    /**
626
     * @param string $namingSchemeColumn
627
     * @deprecated         use setOption from EntityManager
628
     * @codeCoverageIgnore deprecated
629
     */
630
    public static function setNamingSchemeColumn($namingSchemeColumn)
631
    {
632
        static::$namingSchemeColumn = $namingSchemeColumn;
633
    }
634
635
    /**
636
     * @return string
637
     * @deprecated         use getOption from EntityManager
638
     * @codeCoverageIgnore deprecated
639
     */
640
    public static function getNamingSchemeMethods()
641
    {
642
        return static::$namingSchemeMethods;
643
    }
644
645
    /**
646
     * @param string $namingSchemeMethods
647
     * @deprecated         use setOption from EntityManager
648
     * @codeCoverageIgnore deprecated
649
     */
650
    public static function setNamingSchemeMethods($namingSchemeMethods)
651
    {
652
        static::$namingSchemeMethods = $namingSchemeMethods;
653
    }
654
}
655