Completed
Branch feature-relations (b11bfd)
by Thomas
02:28
created

Entity::save()   C

Complexity

Conditions 11
Paths 80

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 11

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 40
ccs 25
cts 25
cp 1
rs 5.2653
cc 11
eloc 27
nc 80
nop 1
crap 11

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\Exceptions\IncompletePrimaryKey;
6
use ORM\Exceptions\InvalidConfiguration;
7
use ORM\Exceptions\InvalidRelation;
8
use ORM\Exceptions\InvalidName;
9
use ORM\Exceptions\NoEntityManager;
10
use ORM\Exceptions\UndefinedRelation;
11
12
/**
13
 * Definition of an entity
14
 *
15
 * The instance of an entity represents a row of the table and the statics variables and methods describe the database
16
 * table.
17
 *
18
 * This is the main part where your configuration efforts go. The following properties and methods are well documented
19
 * in the manual under [https://tflori.github.io/orm/entityDefinition.html](Entity Definition).
20
 *
21
 * @package ORM
22
 * @link https://tflori.github.io/orm/entityDefinition.html Entity Definition
23
 * @author Thomas Flori <[email protected]>
24
 */
25
abstract class Entity implements \Serializable
26
{
27
    const OPT_RELATION_CLASS       = 'class';
28
    const OPT_RELATION_CARDINALITY = 'cardinality';
29
    const OPT_RELATION_REFERENCE   = 'reference';
30
    const OPT_RELATION_OPPONENT    = 'opponent';
31
    const OPT_RELATION_TABLE       = 'table';
32
33
    /** The template to use to calculate the table name.
34
     * @var string */
35
    protected static $tableNameTemplate = '%short%';
36
37
    /** The naming scheme to use for table names.
38
     * @var string */
39
    protected static $namingSchemeTable = 'snake_lower';
40
41
    /** The naming scheme to use for column names.
42
     * @var string */
43
    protected static $namingSchemeColumn = 'snake_lower';
44
45
    /** The naming scheme to use for method names.
46
     * @var string */
47
    protected static $namingSchemeMethods = 'camelCase';
48
49
    /** Whether or not the naming got used
50
     * @var bool */
51
    protected static $namingUsed = false;
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
    /** Relation definitions
74
     * @var array */
75
    protected static $relations = [];
76
77
    /** The current data of a row.
78
     * @var mixed[] */
79
    protected $data = [];
80
81
    /** The original data of the row.
82
     * @var mixed[] */
83
    protected $originalData = [];
84
85
    /** The entity manager from which this entity got created
86
     * @var EntityManager*/
87
    protected $entityManager;
88
89
    /** Related objects for getRelated
90
     * @var array */
91
    protected $relatedObjects = [];
92
93
    /** Calculated table names.
94
     * @internal
95
     * @var string[] */
96
    protected static $calculatedTableNames = [];
97
98
    /** Calculated column names.
99
     * @internal
100
     * @var string[][] */
101
    protected static $calculatedColumnNames = [];
102
103
    /** The reflections of the classes.
104
     * @internal
105
     * @var \ReflectionClass[] */
106
    protected static $reflections = [];
107
108
    /**
109
     * Get the table name
110
     *
111
     * The table name is constructed by $tableNameTemplate and $namingSchemeTable. It can be overwritten by
112
     * $tableName.
113
     *
114
     * @return string
115
     * @throws InvalidName|InvalidConfiguration
116
     */
117 128
    public static function getTableName()
118
    {
119 128
        if (static::$tableName) {
120 11
            return static::$tableName;
121
        }
122
123 117
        if (!isset(self::$calculatedTableNames[static::class])) {
124 117
            static::$namingUsed = true;
125 117
            $reflection = self::getReflection();
126
127
            $tableName = preg_replace_callback('/%([a-z]+)(\[(-?\d+)(\*)?\])?%/', function ($match) use ($reflection) {
128 117
                switch ($match[1]) {
129 117
                    case 'short':
130 103
                        $words = [$reflection->getShortName()];
131 103
                        break;
132
133 14
                    case 'namespace':
134 4
                        $words = explode('\\', $reflection->getNamespaceName());
135 4
                        break;
136
137 10
                    case 'name':
138 9
                        $words = preg_split('/[\\\\_]+/', $reflection->getName());
139 9
                        break;
140
141
                    default:
142 1
                        throw new InvalidConfiguration(
143 1
                            'Template invalid: Placeholder %' . $match[1] . '% is not allowed'
144
                        );
145
                }
146
147 116
                if (!isset($match[2])) {
148 105
                    return implode('_', $words);
149
                }
150 11
                $from = $match[3][0] === '-' ? count($words) - substr($match[3], 1) : $match[3];
151 11
                if (isset($words[$from])) {
152 9
                    return !isset($match[4]) ?
153 9
                        $words[$from] : implode('_', array_slice($words, $from));
154
                }
155 2
                return '';
156 117
            }, static::getTableNameTemplate());
157
158 116
            if (empty($tableName)) {
159 2
                throw new InvalidName('Table name can not be empty');
160
            }
161 114
            self::$calculatedTableNames[static::class] =
162 114
                self::forceNamingScheme($tableName, static::getNamingSchemeTable());
163
        }
164
165 113
        return self::$calculatedTableNames[static::class];
166
    }
167
168
    /**
169
     * Get the column name of $name
170
     *
171
     * The column names can not be specified by template. Instead they are constructed by $columnPrefix and enforced
172
     * to $namingSchemeColumn.
173
     *
174
     * **ATTENTION**: If your overwrite this method remember that getColumnName(getColumnName($name)) have to exactly
175
     * the same as getColumnName($name).
176
     *
177
     * @param string $var
178
     * @return string
179
     * @throws InvalidConfiguration
180
     */
181 131
    public static function getColumnName($var)
182
    {
183 131
        if (isset(static::$columnAliases[$var])) {
184 7
            return static::$columnAliases[$var];
185
        }
186
187 128
        if (!isset(self::$calculatedColumnNames[static::class][$var])) {
188 128
            static::$namingUsed = true;
189 128
            $colName = $var;
190
191 128
            if (static::$columnPrefix &&
192 23
                strpos(
193
                    $colName,
194 23
                    self::forceNamingScheme(static::$columnPrefix, static::getNamingSchemeColumn())
195 128
                ) !== 0) {
196 22
                $colName = static::$columnPrefix . $colName;
197
            }
198
199 128
            self::$calculatedColumnNames[static::class][$var] =
200 128
                self::forceNamingScheme($colName, static::getNamingSchemeColumn());
201
        }
202
203 128
        return self::$calculatedColumnNames[static::class][$var];
204
    }
205
206
    /**
207
     * Get the definition for $relation
208
     *
209
     * It normalize the short definition form and create a Relation object from it.
210
     *
211
     * @param string $relation
212
     * @return Relation
213
     * @throws InvalidConfiguration
214
     * @throws UndefinedRelation
215
     */
216 79
    public static function getRelation($relation)
217
    {
218 79
        if (!isset(static::$relations[$relation])) {
219 3
            throw new UndefinedRelation('Relation ' . $relation . ' is not defined');
220
        }
221
222 78
        $relDef = &static::$relations[$relation];
223
224 78
        if (!$relDef instanceof Relation) {
225 10
            $relDef = Relation::createRelation($relation, $relDef);
226
        }
227
228 77
        return $relDef;
229
    }
230
231
    /**
232
     * @return string
233
     */
234 118
    public static function getTableNameTemplate()
235
    {
236 118
        return static::$tableNameTemplate;
237
    }
238
239
    /**
240
     * @param string $tableNameTemplate
241
     * @throws InvalidConfiguration
242
     */
243 52
    public static function setTableNameTemplate($tableNameTemplate)
244
    {
245 52
        if (static::$namingUsed) {
246 1
            throw new InvalidConfiguration('Template can not be changed afterwards');
247
        }
248
249 51
        static::$tableNameTemplate = $tableNameTemplate;
250 51
    }
251
252
    /**
253
     * @return string
254
     */
255 115
    public static function getNamingSchemeTable()
256
    {
257 115
        return static::$namingSchemeTable;
258
    }
259
260
    /**
261
     * @param string $namingSchemeTable
262
     * @throws InvalidConfiguration
263
     */
264 52
    public static function setNamingSchemeTable($namingSchemeTable)
265
    {
266 52
        if (static::$namingUsed) {
267 1
            throw new InvalidConfiguration('Naming scheme can not be changed afterwards');
268
        }
269
270 51
        static::$namingSchemeTable = $namingSchemeTable;
271 51
    }
272
273
    /**
274
     * @return string
275
     */
276 129
    public static function getNamingSchemeColumn()
277
    {
278 129
        return static::$namingSchemeColumn;
279
    }
280
281
    /**
282
     * @param string $namingSchemeColumn
283
     * @throws InvalidConfiguration
284
     */
285 27
    public static function setNamingSchemeColumn($namingSchemeColumn)
286
    {
287 27
        if (static::$namingUsed) {
288 1
            throw new InvalidConfiguration('Naming scheme can not be changed afterwards');
289
        }
290
291 26
        static::$namingSchemeColumn = $namingSchemeColumn;
292 26
    }
293
294
    /**
295
     * @return string
296
     */
297 85
    public static function getNamingSchemeMethods()
298
    {
299 85
        return static::$namingSchemeMethods;
300
    }
301
302
    /**
303
     * @param string $namingSchemeMethods
304
     * @throws InvalidConfiguration
305
     */
306 3
    public static function setNamingSchemeMethods($namingSchemeMethods)
307
    {
308 3
        if (static::$namingUsed) {
309 1
            throw new InvalidConfiguration('Naming scheme can not be changed afterwards');
310
        }
311
312 3
        static::$namingSchemeMethods = $namingSchemeMethods;
313 3
    }
314
315
    /**
316
     * Get the primary key vars
317
     *
318
     * The primary key can consist of multiple columns. You should configure the vars that are translated to these
319
     * columns.
320
     *
321
     * @return array
322
     */
323 56
    public static function getPrimaryKeyVars()
324
    {
325 56
        return !is_array(static::$primaryKey) ? [static::$primaryKey] : static::$primaryKey;
326
    }
327
328
    /**
329
     * Check if the table has a auto increment column.
330
     *
331
     * @return bool
332
     */
333 14
    public static function isAutoIncremented()
334
    {
335 14
        return count(static::getPrimaryKeyVars()) > 1 ? false : static::$autoIncrement;
336
    }
337
338
    /**
339
     * Enforce $namingScheme to $name
340
     *
341
     * Supported naming schemes: snake_case, snake_lower, SNAKE_UPPER, Snake_Ucfirst, camelCase, StudlyCaps, lower
342
     * and UPPER.
343
     *
344
     * @param string $name         The name of the var / column
345
     * @param string $namingScheme The naming scheme to use
346
     * @return string
347
     * @throws InvalidConfiguration
348
     */
349 202
    protected static function forceNamingScheme($name, $namingScheme)
350
    {
351 202
        $words = explode('_', preg_replace(
352 202
            '/([a-z0-9])([A-Z])/',
353 202
            '$1_$2',
354 202
            preg_replace_callback('/([a-z0-9])?([A-Z]+)([A-Z][a-z])/', function ($d) {
355 21
                return ($d[1] ? $d[1] . '_' : '') . $d[2] . '_' . $d[3];
356 202
            }, $name)
357
        ));
358
359
        switch ($namingScheme) {
360 202
            case 'snake_case':
361 27
                $newName = implode('_', $words);
362 27
                break;
363
364 175
            case 'snake_lower':
365 142
                $newName = implode('_', array_map('strtolower', $words));
366 142
                break;
367
368 113
            case 'SNAKE_UPPER':
369 4
                $newName = implode('_', array_map('strtoupper', $words));
370 4
                break;
371
372 109
            case 'Snake_Ucfirst':
373 4
                $newName = implode('_', array_map('ucfirst', $words));
374 4
                break;
375
376 105
            case 'camelCase':
377 86
                $newName = lcfirst(implode('', array_map('ucfirst', array_map('strtolower', $words))));
378 86
                break;
379
380 19
            case 'StudlyCaps':
381 10
                $newName = implode('', array_map('ucfirst', array_map('strtolower', $words)));
382 10
                break;
383
384 9
            case 'lower':
385 4
                $newName = implode('', array_map('strtolower', $words));
386 4
                break;
387
388 5
            case 'UPPER':
389 4
                $newName = implode('', array_map('strtoupper', $words));
390 4
                break;
391
392
            default:
393 1
                throw new InvalidConfiguration('Naming scheme ' . $namingScheme . ' unknown');
394
        }
395
396 201
        return $newName;
397
    }
398
399
    /**
400
     * Get reflection of the entity
401
     *
402
     * @return \ReflectionClass
403
     */
404 117
    protected static function getReflection()
405
    {
406 117
        if (!isset(self::$reflections[static::class])) {
407 117
            self::$reflections[static::class] = new \ReflectionClass(static::class);
408
        }
409 117
        return self::$reflections[static::class];
410
    }
411
412
    /**
413
     * Constructor
414
     *
415
     * It calls ::onInit() after initializing $data and $originalData.
416
     *
417
     * @param mixed[]       $data          The current data
418
     * @param EntityManager $entityManager The EntityManager that created this entity
419
     * @param bool          $fromDatabase  Whether or not the data comes from database
420
     */
421 102
    final public function __construct(array $data = [], EntityManager $entityManager = null, $fromDatabase = false)
422
    {
423 102
        if ($fromDatabase) {
424 14
            $this->originalData = $data;
425
        }
426 102
        $this->data = array_merge($this->data, $data);
427 102
        $this->entityManager = $entityManager;
428 102
        $this->onInit(!$fromDatabase);
429 102
    }
430
431
    /**
432
     * @param EntityManager $entityManager
433
     * @return self
434
     */
435 1
    public function setEntityManager(EntityManager $entityManager)
436
    {
437 1
        $this->entityManager = $entityManager;
438 1
        return $this;
439
    }
440
441
    /**
442
     * Set $var to $value
443
     *
444
     * Tries to call custom setter before it stores the data directly. If there is a setter the setter needs to store
445
     * data that should be updated in the database to $data. Do not store data in $originalData as it will not be
446
     * written and give wrong results for dirty checking.
447
     *
448
     * The onChange event is called after something got changed.
449
     *
450
     * @param string $var   The variable to change
451
     * @param mixed  $value The value to store
452
     * @throws IncompletePrimaryKey
453
     * @throws InvalidConfiguration
454
     * @link https://tflori.github.io/orm/entities.html Working with entities
455
     */
456 18
    public function __set($var, $value)
457
    {
458 18
        $col = $this->getColumnName($var);
459
460 18
        static::$namingUsed = true;
461 18
        $setter = self::forceNamingScheme('set' . ucfirst($var), static::getNamingSchemeMethods());
462 18
        if (method_exists($this, $setter) && is_callable([$this, $setter])) {
463 4
            $oldValue = $this->__get($var);
464 4
            $md5OldData = md5(serialize($this->data));
465 4
            $this->$setter($value);
466 4
            $changed = $md5OldData !== md5(serialize($this->data));
467
        } else {
468 14
            $oldValue = $this->__get($var);
469 14
            $changed = @$this->data[$col] !== $value;
470 14
            $this->data[$col] = $value;
471
        }
472
473 18
        if ($changed) {
474 14
            $this->onChange($var, $oldValue, $this->__get($var));
475
        }
476 18
    }
477
478
    /**
479
     * Get the value from $var
480
     *
481
     * If there is a custom getter this method get called instead.
482
     *
483
     * @param string $var The variable to get
484
     * @return mixed|null
485
     * @throws IncompletePrimaryKey
486
     * @throws InvalidConfiguration
487
     * @link https://tflori.github.io/orm/entities.html Working with entities
488
     */
489 84
    public function __get($var)
490
    {
491 84
        $getter = self::forceNamingScheme('get' . ucfirst($var), static::getNamingSchemeMethods());
492 84
        if (method_exists($this, $getter) && is_callable([$this, $getter])) {
493 5
            return $this->$getter();
494
        } else {
495 79
            $col = static::getColumnName($var);
496 79
            $result = isset($this->data[$col]) ? $this->data[$col] : null;
497
498 79
            if (!$result && isset(static::$relations[$var]) && isset($this->entityManager)) {
499 1
                return $this->getRelated($var);
500
            }
501
502 78
            return $result;
503
        }
504
    }
505
506
    /**
507
     * Get related objects
508
     *
509
     * The difference between getRelated and fetch is that getRelated stores the fetched entities. To refresh set
510
     * $refresh to true.
511
     *
512
     * @param string $relation
513
     * @param bool   $refresh
514
     * @return mixed
515
     * @throws Exceptions\NoConnection
516
     * @throws Exceptions\NoEntity
517
     * @throws IncompletePrimaryKey
518
     * @throws InvalidConfiguration
519
     * @throws NoEntityManager
520
     * @throws UndefinedRelation
521
     */
522 10
    public function getRelated($relation, $refresh = false)
523
    {
524 10
        if ($refresh || !isset($this->relatedObjects[$relation])) {
525 9
            $this->relatedObjects[$relation] = $this->fetch($relation, null, true);
526
        }
527
528 10
        return $this->relatedObjects[$relation];
529
    }
530
531
    /**
532
     * Set $relation to $entity
533
     *
534
     * This method is only for the owner of a relation.
535
     *
536
     * @param string $relation
537
     * @param Entity $entity
538
     * @throws IncompletePrimaryKey
539
     * @throws InvalidRelation
540
     */
541 6
    public function setRelated($relation, Entity $entity = null)
542
    {
543 6
        $this::getRelation($relation)->setRelated($this, $entity);
544
545 3
        $this->relatedObjects[$relation] = $entity;
546 3
    }
547
548
    /**
549
     * Add relations for $relation to $entities
550
     *
551
     * This method is only for many-to-many relations.
552
     *
553
     * This method does not take care about already existing relations and will fail hard.
554
     *
555
     * @param string $relation
556
     * @param Entity[] $entities
557
     * @throws IncompletePrimaryKey
558
     * @throws InvalidRelation
559
     */
560 7
    public function addRelated($relation, array $entities)
561
    {
562 7
        $this::getRelation($relation)->addRelated($this, $entities, $this->entityManager);
0 ignored issues
show
Bug introduced by
It seems like $this->entityManager can be null; however, addRelated() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
563 3
    }
564
565
    /**
566
     * Delete relations for $relation to $entities
567
     *
568
     * This method is only for many-to-many relations.
569
     *
570
     * @param string $relation
571
     * @param Entity[] $entities
572
     * @throws IncompletePrimaryKey
573
     * @throws InvalidRelation
574
     */
575 7
    public function deleteRelations($relation, $entities)
576
    {
577 7
        $this::getRelation($relation)->deleteRelated($this, $entities, $this->entityManager);
0 ignored issues
show
Bug introduced by
It seems like $this->entityManager can be null; however, deleteRelated() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
578 3
    }
579
580
    /**
581
     * Checks if entity or $var got changed
582
     *
583
     * @param string $var Check only this variable or all variables
584
     * @return bool
585
     * @throws InvalidConfiguration
586
     */
587 18
    public function isDirty($var = null)
588
    {
589 18
        if (!empty($var)) {
590 4
            $col = static::getColumnName($var);
591 4
            return @$this->data[$col] !== @$this->originalData[$col];
592
        }
593
594 15
        ksort($this->data);
595 15
        ksort($this->originalData);
596
597 15
        return serialize($this->data) !== serialize($this->originalData);
598
    }
599
600
    /**
601
     * Resets the entity or $var to original data
602
     *
603
     * @param string $var Reset only this variable or all variables
604
     * @throws InvalidConfiguration
605
     */
606 8
    public function reset($var = null)
607
    {
608 8
        if (!empty($var)) {
609 3
            $col = static::getColumnName($var);
610 3
            if (isset($this->originalData[$col])) {
611 2
                $this->data[$col] = $this->originalData[$col];
612
            } else {
613 1
                unset($this->data[$col]);
614
            }
615 3
            return;
616
        }
617
618 5
        $this->data = $this->originalData;
619 5
    }
620
621
    /**
622
     * Save the entity to $entityManager
623
     *
624
     * @param EntityManager $entityManager
625
     * @return Entity
626
     * @throws Exceptions\NoConnection
627
     * @throws Exceptions\NoEntity
628
     * @throws Exceptions\NotScalar
629
     * @throws Exceptions\UnsupportedDriver
630
     * @throws IncompletePrimaryKey
631
     * @throws InvalidConfiguration
632
     * @throws InvalidName
633
     * @throws NoEntityManager
634
     */
635 13
    public function save(EntityManager $entityManager = null)
636
    {
637 13
        $entityManager = $entityManager ?: $this->entityManager;
638
639 13
        if (!$entityManager) {
640 1
            throw new NoEntityManager('No entity manager given');
641
        }
642
643 12
        $inserted = false;
644 12
        $updated = false;
645
646
        try {
647
            // this may throw if the primary key is auto incremented but we using this to omit duplicated code
648 12
            if (!$entityManager->sync($this)) {
649 2
                $entityManager->insert($this, false);
650 2
                $inserted = true;
651 5
            } elseif ($this->isDirty()) {
652 4
                $this->preUpdate();
653 4
                $entityManager->update($this);
654 7
                $updated = true;
655
            }
656 5
        } catch (IncompletePrimaryKey $e) {
657 5
            if (static::isAutoIncremented()) {
658 4
                $this->prePersist();
659 4
                $id = $entityManager->insert($this);
660 4
                $this->data[static::getColumnName(static::getPrimaryKeyVars()[0])] = $id;
661 4
                $inserted = true;
662
            } else {
663 1
                throw $e;
664
            }
665
        }
666
667 11
        if ($inserted || $updated) {
668 10
            $inserted && $this->postPersist();
669 10
            $updated && $this->postUpdate();
670 10
            $entityManager->sync($this, true);
671
        }
672
673 11
        return $this;
674
    }
675
676
    /**
677
     * Fetches related objects
678
     *
679
     * For relations with cardinality many it returns an EntityFetcher. Otherwise it returns the entity.
680
     *
681
     * It will throw an error for non owner when the key is incomplete.
682
     *
683
     * @param string $relation The relation to fetch
684
     * @param EntityManager $entityManager The EntityManager to use
685
     * @return Entity|EntityFetcher|Entity[]
686
     * @throws Exceptions\NoConnection
687
     * @throws Exceptions\NoEntity
688
     * @throws IncompletePrimaryKey
689
     * @throws InvalidConfiguration
690
     * @throws NoEntityManager
691
     * @throws UndefinedRelation
692
     */
693 19
    public function fetch($relation, EntityManager $entityManager = null, $getAll = false)
694
    {
695 19
        $entityManager = $entityManager ?: $this->entityManager;
696
697 19
        if (!$entityManager) {
698 1
            throw new NoEntityManager('No entity manager given');
699
        }
700
701 18
        $relation = $this::getRelation($relation);
702
703 18
        if ($getAll) {
704 4
            return $relation->fetchAll($this, $entityManager);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $relation->fetchA...$this, $entityManager); (array) is incompatible with the return type documented by ORM\Entity::fetch of type ORM\Entity|ORM\EntityFetcher|ORM\Entity[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
705
        } else {
706 14
            return $relation->fetch($this, $entityManager);
707
        }
708
    }
709
710
    /**
711
     * Get the primary key
712
     *
713
     * @return array
714
     * @throws IncompletePrimaryKey
715
     */
716 38
    public function getPrimaryKey()
717
    {
718 38
        $primaryKey = [];
719 38
        foreach (static::getPrimaryKeyVars() as $var) {
720 38
            $value = $this->$var;
721 38
            if ($value === null) {
722 4
                throw new IncompletePrimaryKey('Incomplete primary key - missing ' . $var);
723
            }
724 36
            $primaryKey[$var] = $value;
725
        }
726 34
        return $primaryKey;
727
    }
728
729
    /**
730
     * Get current data
731
     *
732
     * @return array
733
     * @internal
734
     */
735 17
    public function getData()
736
    {
737 17
        return $this->data;
738
    }
739
740
    /**
741
     * Set new original data
742
     *
743
     * @param array $data
744
     * @internal
745
     */
746 18
    public function setOriginalData(array $data)
747
    {
748 18
        $this->originalData = $data;
749 18
    }
750
751
    /**
752
     * Empty event handler
753
     *
754
     * Get called when something is changed with magic setter.
755
     *
756
     * @param string $var The variable that got changed.merge(node.inheritedProperties)
757
     * @param mixed  $oldValue The old value of the variable
758
     * @param mixed  $value The new value of the variable
759
     */
760 5
    public function onChange($var, $oldValue, $value)
761
    {
762 5
    }
763
764
    /**
765
     * Empty event handler
766
     *
767
     * Get called when the entity get initialized.
768
     *
769
     * @param bool $new Whether or not the entity is new or from database
770
     */
771 101
    public function onInit($new)
772
    {
773 101
    }
774
775
    /**
776
     * Empty event handler
777
     *
778
     * Get called before the entity get inserted in database.
779
     */
780 3
    public function prePersist()
781
    {
782 3
    }
783
784
    /**
785
     * Empty event handler
786
     *
787
     * Get called after the entity got inserted in database.
788
     */
789 5
    public function postPersist()
790
    {
791 5
    }
792
793
    /**
794
     * Empty event handler
795
     *
796
     * Get called before the entity get updated in database.
797
     */
798 3
    public function preUpdate()
799
    {
800 3
    }
801
802
    /**
803
     * Empty event handler
804
     *
805
     * Get called after the entity got updated in database.
806
     */
807 3
    public function postUpdate()
808
    {
809 3
    }
810
811
    /**
812
     * String representation of data
813
     *
814
     * @link http://php.net/manual/en/serializable.serialize.php
815
     * @return string
816
     */
817 1
    public function serialize()
818
    {
819 1
        return serialize($this->data);
820
    }
821
822
    /**
823
     * Constructs the object
824
     *
825
     * @link http://php.net/manual/en/serializable.unserialize.php
826
     * @param string $serialized The string representation of data
827
     */
828 2
    public function unserialize($serialized)
829
    {
830 2
        $this->data = unserialize($serialized);
831 2
        $this->onInit(false);
832 2
    }
833
}
834