Completed
Branch feature-relations (7f62b3)
by Thomas
03:51
created

Entity::setRelated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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