Passed
Pull Request — master (#172)
by David
02:16
created

AbstractTDBMObject   F

Complexity

Total Complexity 94

Size/Duplication

Total Lines 663
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 94
eloc 184
c 0
b 0
f 0
dl 0
loc 663
rs 2

35 Methods

Rating   Name   Duplication   Size   Complexity  
A _constructFromData() 0 10 2
A _attach() 0 23 5
B retrieveRelationshipsStorage() 0 24 8
A setRelationships() 0 18 6
A _getManyToManyRelationshipDescriptorKeys() 0 3 1
A getForeignKeys() 0 3 1
B setRef() 0 28 8
A _constructLazy() 0 8 1
A registerTable() 0 20 3
A getManyToOneAlterableResultIterator() 0 5 1
A setRelationship() 0 10 2
A removeManyToOneRelationship() 0 4 1
A hasRelationship() 0 12 3
A retrieveManyToOneRelationshipsStorage() 0 13 5
A __construct() 0 15 4
A checkTableName() 0 11 4
A set() 0 19 6
A _getCachedRelationships() 0 3 1
A __clone() 0 12 2
A _getStatus() 0 7 2
A getRef() 0 9 2
A _setStatus() 0 13 4
A getRelationshipStorage() 0 3 1
A _getRelationshipsFromModel() 0 3 1
A discardChanges() 0 15 5
A _getRelationships() 0 4 1
A setManyToOneRelationship() 0 4 1
A _getManyToManyRelationshipDescriptor() 0 3 1
A addRelationship() 0 3 1
A get() 0 9 2
A onDelete() 0 2 1
A _removeRelationship() 0 7 3
A _getDbRows() 0 3 1
A relationshipStorageToArray() 0 11 3
A disableSmartEagerLoad() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractTDBMObject often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractTDBMObject, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM;
5
6
/*
7
 Copyright (C) 2006-2017 David Négrier - THE CODING MACHINE
8
9
 This program is free software; you can redistribute it and/or modify
10
 it under the terms of the GNU General Public License as published by
11
 the Free Software Foundation; either version 2 of the License, or
12
 (at your option) any later version.
13
14
 This program is distributed in the hope that it will be useful,
15
 but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 GNU General Public License for more details.
18
19
 You should have received a copy of the GNU General Public License
20
 along with this program; if not, write to the Free Software
21
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22
 */
23
24
use JsonSerializable;
25
use TheCodingMachine\TDBM\QueryFactory\SmartEagerLoad\Query\PartialQuery;
26
use TheCodingMachine\TDBM\QueryFactory\SmartEagerLoad\StorageNode;
27
use TheCodingMachine\TDBM\Schema\ForeignKeys;
28
use TheCodingMachine\TDBM\Utils\ManyToManyRelationshipPathDescriptor;
29
30
/**
31
 * Instances of this class represent a "bean". Usually, a bean is mapped to a row of one table.
32
 * In some special cases (where inheritance is used), beans can be scattered on several tables.
33
 * Therefore, a TDBMObject is really a set of DbRow objects that represent one row in a table.
34
 *
35
 * @author David Negrier
36
 */
37
abstract class AbstractTDBMObject implements JsonSerializable
38
{
39
    /**
40
     * The service this object is bound to.
41
     *
42
     * @var TDBMService
43
     */
44
    protected $tdbmService;
45
46
    /**
47
     * An array of DbRow, indexed by table name.
48
     *
49
     * @var DbRow[]
50
     */
51
    protected $dbRows = [];
52
53
    /**
54
     * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
55
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
56
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
57
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
58
     *
59
     * @var string|null
60
     */
61
    private $status;
62
63
    /**
64
     * Array storing beans related via many to many relationships (pivot tables).
65
     *
66
     * @var \SplObjectStorage[] Key: pivot table name, value: SplObjectStorage
67
     */
68
    private $relationships = [];
69
70
    /**
71
     * @var bool[] Key: pivot table name, value: whether a query was performed to load the data
72
     */
73
    private $loadedRelationships = [];
74
75
    /**
76
     * Array storing beans related via many to one relationships (this bean is pointed by external beans).
77
     *
78
     * @var AlterableResultIterator[] Key: [external_table]___[external_column], value: SplObjectStorage
79
     */
80
    private $manyToOneRelationships = [];
81
82
    /**
83
     * If this bean originates from a ResultArray, this points back to the result array to build smart eager load queries.
84
     *
85
     * @var PartialQuery|null
86
     */
87
    private $partialQuery;
88
89
    /**
90
     * Used with $primaryKeys when we want to retrieve an existing object
91
     * and $primaryKeys=[] if we want a new object.
92
     *
93
     * @param string      $tableName
94
     * @param mixed[]     $primaryKeys
95
     * @param TDBMService $tdbmService
96
     *
97
     * @throws TDBMException
98
     * @throws TDBMInvalidOperationException
99
     */
100
    public function __construct(?string $tableName = null, array $primaryKeys = [], TDBMService $tdbmService = null)
101
    {
102
        // FIXME: lazy loading should be forbidden on tables with inheritance and dynamic type assignation...
103
        if (!empty($tableName)) {
104
            $this->dbRows[$tableName] = new DbRow($this, $tableName, static::getForeignKeys($tableName), $primaryKeys, $tdbmService);
105
        }
106
107
        if ($tdbmService === null) {
108
            $this->_setStatus(TDBMObjectStateEnum::STATE_DETACHED);
109
        } else {
110
            $this->_attach($tdbmService);
111
            if (!empty($primaryKeys)) {
112
                $this->_setStatus(TDBMObjectStateEnum::STATE_NOT_LOADED);
113
            } else {
114
                $this->_setStatus(TDBMObjectStateEnum::STATE_NEW);
115
            }
116
        }
117
    }
118
119
    /**
120
     * Alternative constructor called when data is fetched from database via a SELECT.
121
     *
122
     * @param array[]     $beanData    array<table, array<column, value>>
123
     * @param TDBMService $tdbmService
124
     */
125
    public function _constructFromData(array $beanData, TDBMService $tdbmService, ?PartialQuery $partialQuery): void
126
    {
127
        $this->tdbmService = $tdbmService;
128
        $this->partialQuery = $partialQuery;
129
130
        foreach ($beanData as $table => $columns) {
131
            $this->dbRows[$table] = new DbRow($this, $table, static::getForeignKeys($table), $tdbmService->_getPrimaryKeysFromObjectData($table, $columns), $tdbmService, $columns, $partialQuery);
132
        }
133
134
        $this->status = TDBMObjectStateEnum::STATE_LOADED;
135
    }
136
137
    /**
138
     * Alternative constructor called when bean is lazily loaded.
139
     *
140
     * @param string      $tableName
141
     * @param mixed[]     $primaryKeys
142
     * @param TDBMService $tdbmService
143
     */
144
    public function _constructLazy(string $tableName, array $primaryKeys, TDBMService $tdbmService, ?PartialQuery $partialQuery): void
145
    {
146
        $this->tdbmService = $tdbmService;
147
        $this->partialQuery = $partialQuery;
148
149
        $this->dbRows[$tableName] = new DbRow($this, $tableName, static::getForeignKeys($tableName), $primaryKeys, $tdbmService, [], $partialQuery);
150
151
        $this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
152
    }
153
154
    public function _attach(TDBMService $tdbmService): void
155
    {
156
        if ($this->status !== TDBMObjectStateEnum::STATE_DETACHED) {
157
            throw new TDBMInvalidOperationException('Cannot attach an object that is already attached to TDBM.');
158
        }
159
        $this->tdbmService = $tdbmService;
160
161
        // If we attach this object, we must work to make sure the tables are in ascending order (from low level to top level)
162
        $tableNames = $this->getUsedTables();
163
164
        $newDbRows = [];
165
166
        foreach ($tableNames as $table) {
167
            if (!isset($this->dbRows[$table])) {
168
                $this->registerTable($table);
169
            }
170
            $newDbRows[$table] = $this->dbRows[$table];
171
        }
172
        $this->dbRows = $newDbRows;
173
174
        $this->status = TDBMObjectStateEnum::STATE_NEW;
175
        foreach ($this->dbRows as $dbRow) {
176
            $dbRow->_attach($tdbmService);
177
        }
178
    }
179
180
    /**
181
     * Sets the state of the TDBM Object
182
     * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
183
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
184
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
185
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
186
     *
187
     * @param string $state
188
     */
189
    public function _setStatus(string $state): void
190
    {
191
        $this->status = $state;
192
193
        // The dirty state comes from the db_row itself so there is no need to set it from the called.
194
        if ($state !== TDBMObjectStateEnum::STATE_DIRTY) {
195
            foreach ($this->dbRows as $dbRow) {
196
                $dbRow->_setStatus($state);
197
            }
198
        }
199
200
        if ($state === TDBMObjectStateEnum::STATE_DELETED) {
201
            $this->onDelete();
202
        }
203
    }
204
205
    /**
206
     * Checks that $tableName is ok, or returns the only possible table name if "$tableName = null"
207
     * or throws an error.
208
     *
209
     * @param string|null $tableName
210
     *
211
     * @return string
212
     */
213
    private function checkTableName(?string $tableName = null): string
214
    {
215
        if ($tableName === null) {
216
            if (count($this->dbRows) > 1) {
217
                throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
218
            } elseif (count($this->dbRows) === 1) {
219
                $tableName = array_keys($this->dbRows)[0];
220
            }
221
        }
222
223
        return (string) $tableName;
224
    }
225
226
    /**
227
     * @return mixed
228
     */
229
    protected function get(string $var, string $tableName = null)
230
    {
231
        $tableName = $this->checkTableName($tableName);
232
233
        if (!isset($this->dbRows[$tableName])) {
234
            return null;
235
        }
236
237
        return $this->dbRows[$tableName]->get($var);
238
    }
239
240
    /**
241
     * @param mixed $value
242
     */
243
    protected function set(string $var, $value, ?string $tableName = null): void
244
    {
245
        if ($tableName === null) {
246
            if (count($this->dbRows) > 1) {
247
                throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
248
            } elseif (count($this->dbRows) === 1) {
249
                $tableName = (string) array_keys($this->dbRows)[0];
250
            } else {
251
                throw new TDBMException('Please specify a table for this object.');
252
            }
253
        }
254
255
        if (!isset($this->dbRows[$tableName])) {
256
            $this->registerTable($tableName);
257
        }
258
259
        $this->dbRows[$tableName]->set($var, $value);
260
        if ($this->dbRows[$tableName]->_getStatus() === TDBMObjectStateEnum::STATE_DIRTY) {
261
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
262
        }
263
    }
264
265
    /**
266
     * @param string             $foreignKeyName
267
     * @param AbstractTDBMObject $bean
268
     */
269
    protected function setRef(string $foreignKeyName, AbstractTDBMObject $bean = null, string $tableName = null): void
270
    {
271
        if ($tableName === null) {
272
            if (count($this->dbRows) > 1) {
273
                throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
274
            } elseif (count($this->dbRows) === 1) {
275
                $tableName = (string) array_keys($this->dbRows)[0];
276
            } else {
277
                throw new TDBMException('Please specify a table for this object.');
278
            }
279
        }
280
281
        if (!isset($this->dbRows[$tableName])) {
282
            $this->registerTable($tableName);
283
        }
284
285
        $oldLinkedBean = $this->dbRows[$tableName]->getRef($foreignKeyName);
286
        if ($oldLinkedBean !== null) {
287
            $oldLinkedBean->removeManyToOneRelationship($tableName, $foreignKeyName, $this);
288
        }
289
290
        $this->dbRows[$tableName]->setRef($foreignKeyName, $bean);
291
        if ($this->dbRows[$tableName]->_getStatus() === TDBMObjectStateEnum::STATE_DIRTY) {
292
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
293
        }
294
295
        if ($bean !== null) {
296
            $bean->setManyToOneRelationship($tableName, $foreignKeyName, $this);
297
        }
298
    }
299
300
    /**
301
     * @param string $foreignKeyName A unique name for this reference
302
     *
303
     * @return AbstractTDBMObject|null
304
     */
305
    protected function getRef(string $foreignKeyName, ?string $tableName = null) : ?AbstractTDBMObject
306
    {
307
        $tableName = $this->checkTableName($tableName);
308
309
        if (!isset($this->dbRows[$tableName])) {
310
            return null;
311
        }
312
313
        return $this->dbRows[$tableName]->getRef($foreignKeyName);
314
    }
315
316
    /**
317
     * Adds a many to many relationship to this bean.
318
     *
319
     * @param string             $pivotTableName
320
     * @param AbstractTDBMObject $remoteBean
321
     */
322
    protected function addRelationship(string $pivotTableName, AbstractTDBMObject $remoteBean): void
323
    {
324
        $this->setRelationship($pivotTableName, $remoteBean, 'new');
325
    }
326
327
    /**
328
     * Returns true if there is a relationship to this bean.
329
     *
330
     * @return bool
331
     */
332
    protected function hasRelationship(string $pathKey, AbstractTDBMObject $remoteBean): bool
333
    {
334
        $pathModel = $this->_getManyToManyRelationshipDescriptor($pathKey);
335
        $storage = $this->retrieveRelationshipsStorage($pathModel);
336
337
        if ($storage->contains($remoteBean)) {
338
            if ($storage[$remoteBean]['status'] !== 'delete') {
339
                return true;
340
            }
341
        }
342
343
        return false;
344
    }
345
346
    /**
347
     * Internal TDBM method. Removes a many to many relationship from this bean.
348
     *
349
     * @param string             $pivotTableName
350
     * @param AbstractTDBMObject $remoteBean
351
     */
352
    public function _removeRelationship(string $pivotTableName, AbstractTDBMObject $remoteBean): void
353
    {
354
        if (isset($this->relationships[$pivotTableName][$remoteBean]) && $this->relationships[$pivotTableName][$remoteBean]['status'] === 'new') {
355
            unset($this->relationships[$pivotTableName][$remoteBean]);
356
            unset($remoteBean->relationships[$pivotTableName][$this]);
357
        } else {
358
            $this->setRelationship($pivotTableName, $remoteBean, 'delete');
359
        }
360
    }
361
362
    /**
363
     * Sets many to many relationships for this bean.
364
     * Adds new relationships and removes unused ones.
365
     *
366
     * @param AbstractTDBMObject[] $remoteBeans
367
     */
368
    protected function setRelationships(string $pathKey, array $remoteBeans): void
369
    {
370
        $pathModel = $this->_getManyToManyRelationshipDescriptor($pathKey);
371
        $pivotTableName = $pathModel->getPivotName();
372
        $storage = $this->retrieveRelationshipsStorage($pathModel);
373
374
        foreach ($storage as $oldRemoteBean) {
375
            /* @var $oldRemoteBean AbstractTDBMObject */
376
            if (!in_array($oldRemoteBean, $remoteBeans, true)) {
377
                // $oldRemoteBean must be removed
378
                $this->_removeRelationship($pivotTableName, $oldRemoteBean);
379
            }
380
        }
381
382
        foreach ($remoteBeans as $remoteBean) {
383
            if (!$storage->contains($remoteBean) || $storage[$remoteBean]['status'] === 'delete') {
384
                // $remoteBean must be added
385
                $this->addRelationship($pivotTableName, $remoteBean);
386
            }
387
        }
388
    }
389
390
    /**
391
     * Returns the list of objects linked to this bean via $pivotTableName.
392
     *
393
     * @return \SplObjectStorage
394
     */
395
    private function retrieveRelationshipsStorage(ManyToManyRelationshipPathDescriptor $pathModel): \SplObjectStorage
396
    {
397
        $pivotTableName = $pathModel->getPivotName();
398
399
        $storage = $this->getRelationshipStorage($pathModel->getPivotName());
400
        if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->loadedRelationships[$pivotTableName]) && $this->loadedRelationships[$pivotTableName])) {
401
            return $storage;
402
        }
403
404
        $beans = $this->tdbmService->_getRelatedBeans($pathModel, $this);
405
        $this->loadedRelationships[$pivotTableName] = true;
406
407
        foreach ($beans as $bean) {
408
            if (isset($storage[$bean])) {
409
                $oldStatus = $storage[$bean]['status'];
410
                if ($oldStatus === 'delete') {
411
                    // Keep deleted things deleted
412
                    continue;
413
                }
414
            }
415
            $this->setRelationship($pivotTableName, $bean, 'loaded');
416
        }
417
418
        return $storage;
419
    }
420
421
    /**
422
     * Internal TDBM method. Returns the list of objects linked to this bean via $pivotTableName.
423
     *
424
     * @return AbstractTDBMObject[]
425
     */
426
    public function _getRelationships(string $pathKey): array
427
    {
428
        $pathModel = $this->_getManyToManyRelationshipDescriptor($pathKey);
429
        return $this->_getRelationshipsFromModel($pathModel);
430
    }
431
432
    /**
433
     * @return AbstractTDBMObject[]
434
     */
435
    public function _getRelationshipsFromModel(ManyToManyRelationshipPathDescriptor $pathModel): array
436
    {
437
        return $this->relationshipStorageToArray($this->retrieveRelationshipsStorage($pathModel));
438
    }
439
440
    /**
441
     * @param \SplObjectStorage $storage
442
     * @return AbstractTDBMObject[]
443
     */
444
    private function relationshipStorageToArray(\SplObjectStorage $storage): array
445
    {
446
        $beans = [];
447
        foreach ($storage as $bean) {
448
            $statusArr = $storage[$bean];
449
            if ($statusArr['status'] !== 'delete') {
450
                $beans[] = $bean;
451
            }
452
        }
453
454
        return $beans;
455
    }
456
457
    /**
458
     * Declares a relationship between.
459
     *
460
     * @param string             $pivotTableName
461
     * @param AbstractTDBMObject $remoteBean
462
     * @param string             $status
463
     */
464
    private function setRelationship(string $pivotTableName, AbstractTDBMObject $remoteBean, string $status): void
465
    {
466
        $storage = $this->getRelationshipStorage($pivotTableName);
467
        $storage->attach($remoteBean, ['status' => $status, 'reverse' => false]);
468
        if ($this->status === TDBMObjectStateEnum::STATE_LOADED) {
469
            $this->_setStatus(TDBMObjectStateEnum::STATE_DIRTY);
470
        }
471
472
        $remoteStorage = $remoteBean->getRelationshipStorage($pivotTableName);
473
        $remoteStorage->attach($this, ['status' => $status, 'reverse' => true]);
474
    }
475
476
    /**
477
     * Returns the SplObjectStorage associated to this relationship (creates it if it does not exists).
478
     *
479
     * @param string $pivotTableName
480
     *
481
     * @return \SplObjectStorage
482
     */
483
    private function getRelationshipStorage(string $pivotTableName) : \SplObjectStorage
484
    {
485
        return $this->relationships[$pivotTableName] ?? $this->relationships[$pivotTableName] = new \SplObjectStorage();
486
    }
487
488
    /**
489
     * Returns the SplObjectStorage associated to this relationship (creates it if it does not exists).
490
     *
491
     * @param string $tableName
492
     * @param string $foreignKeyName
493
     *
494
     * @return AlterableResultIterator
495
     */
496
    private function getManyToOneAlterableResultIterator(string $tableName, string $foreignKeyName) : AlterableResultIterator
497
    {
498
        $key = $tableName.'___'.$foreignKeyName;
499
500
        return $this->manyToOneRelationships[$key] ?? $this->manyToOneRelationships[$key] = new AlterableResultIterator();
501
    }
502
503
    /**
504
     * Declares a relationship between this bean and the bean pointing to it.
505
     *
506
     * @param string             $tableName
507
     * @param string             $foreignKeyName
508
     * @param AbstractTDBMObject $remoteBean
509
     */
510
    private function setManyToOneRelationship(string $tableName, string $foreignKeyName, AbstractTDBMObject $remoteBean): void
511
    {
512
        $alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
513
        $alterableResultIterator->add($remoteBean);
514
    }
515
516
    /**
517
     * Declares a relationship between this bean and the bean pointing to it.
518
     *
519
     * @param string             $tableName
520
     * @param string             $foreignKeyName
521
     * @param AbstractTDBMObject $remoteBean
522
     */
523
    private function removeManyToOneRelationship(string $tableName, string $foreignKeyName, AbstractTDBMObject $remoteBean): void
524
    {
525
        $alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
526
        $alterableResultIterator->remove($remoteBean);
527
    }
528
529
    /**
530
     * Returns the list of objects linked to this bean via a given foreign key.
531
     *
532
     * @param string $tableName
533
     * @param string $foreignKeyName
534
     * @param mixed[] $searchFilter
535
     * @param string $orderString     The ORDER BY part of the query. All columns must be prefixed by the table name (in the form: table.column). WARNING : This parameter is not kept when there is an additionnal or removal object !
536
     *
537
     * @return AlterableResultIterator
538
     */
539
    protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $searchFilter, string $orderString = null) : AlterableResultIterator
540
    {
541
        $key = $tableName.'___'.$foreignKeyName;
542
        $alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
543
        if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->manyToOneRelationships[$key]) && $this->manyToOneRelationships[$key]->getUnderlyingResultIterator() !== null)) {
544
            return $alterableResultIterator;
545
        }
546
547
        $unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString);
548
549
        $alterableResultIterator->setResultIterator($unalteredResultIterator->getIterator());
550
551
        return $alterableResultIterator;
552
    }
553
554
    /**
555
     * Reverts any changes made to the object and resumes it to its DB state.
556
     * This can only be called on objects that come from database and that have not been deleted.
557
     * Otherwise, this will throw an exception.
558
     *
559
     * @throws TDBMException
560
     */
561
    public function discardChanges(): void
562
    {
563
        if ($this->status === TDBMObjectStateEnum::STATE_NEW || $this->status === TDBMObjectStateEnum::STATE_DETACHED) {
564
            throw new TDBMException("You cannot call discardChanges() on an object that has been created with the 'new' keyword and that has not yet been saved.");
565
        }
566
567
        if ($this->status === TDBMObjectStateEnum::STATE_DELETED) {
568
            throw new TDBMException('You cannot call discardChanges() on an object that has been deleted.');
569
        }
570
571
        $this->_setStatus(TDBMObjectStateEnum::STATE_NOT_LOADED);
572
        foreach ($this->dbRows as $row) {
573
            $row->disableSmartEagerLoad();
574
        }
575
        $this->partialQuery = null;
576
    }
577
578
    /**
579
     * Prevents smart eager loading of related entities.
580
     * If this bean was loaded through a result iterator, smart eager loading loads all entities of related beans at once.
581
     * You can disable it with this function.
582
     */
583
    public function disableSmartEagerLoad(): void
584
    {
585
        $this->partialQuery = null;
586
    }
587
588
    /**
589
     * Method used internally by TDBM. You should not use it directly.
590
     * This method returns the status of the TDBMObject.
591
     * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
592
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
593
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
594
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
595
     *
596
     * @return string
597
     */
598
    public function _getStatus() : string
599
    {
600
        if ($this->status === null) {
601
            throw new TDBMException(sprintf('Your bean for class %s has no status. It is likely that you overloaded the __construct method and forgot to call parent::__construct.', get_class($this)));
602
        }
603
604
        return $this->status;
605
    }
606
607
    /**
608
     * Override the native php clone function for TDBMObjects.
609
     */
610
    public function __clone()
611
    {
612
        // Let's clone each row
613
        foreach ($this->dbRows as $key => &$dbRow) {
614
            $dbRow = clone $dbRow;
615
            $dbRow->setTDBMObject($this);
616
        }
617
618
        $this->manyToOneRelationships = [];
619
620
        // Let's set the status to new (to enter the save function)
621
        $this->status = TDBMObjectStateEnum::STATE_DETACHED;
622
    }
623
624
    /**
625
     * Returns raw database rows.
626
     *
627
     * @return DbRow[] Key: table name, Value: DbRow object
628
     */
629
    public function _getDbRows(): array
630
    {
631
        return $this->dbRows;
632
    }
633
634
    private function registerTable(string $tableName): void
635
    {
636
        $dbRow = new DbRow($this, $tableName, static::getForeignKeys($tableName));
637
638
        if (in_array($this->status, [TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DIRTY], true)) {
639
            // Let's get the primary key for the new table
640
            $anotherDbRow = array_values($this->dbRows)[0];
641
            /* @var $anotherDbRow DbRow */
642
            $indexedPrimaryKeys = array_values($anotherDbRow->_getPrimaryKeys());
643
            $primaryKeys = $this->tdbmService->_getPrimaryKeysFromIndexedPrimaryKeys($tableName, $indexedPrimaryKeys);
644
            $dbRow->_setPrimaryKeys($primaryKeys);
645
        }
646
647
        if ($this->status === null) {
648
            throw new TDBMException(sprintf('Your bean for class %s has no status. It is likely that you overloaded the __construct method and forgot to call parent::__construct.', get_class($this)));
649
        }
650
651
        $dbRow->_setStatus($this->status);
652
653
        $this->dbRows[$tableName] = $dbRow;
654
        // TODO: look at status (if not new)=> get primary key from tdbmservice
655
    }
656
657
    /**
658
     * Internal function: return the list of relationships.
659
     *
660
     * @return \SplObjectStorage[]
661
     */
662
    public function _getCachedRelationships(): array
663
    {
664
        return $this->relationships;
665
    }
666
667
    /**
668
     * Returns an array of used tables by this bean (from parent to child relationship).
669
     *
670
     * @return string[]
671
     */
672
    abstract protected function getUsedTables() : array;
673
674
    /**
675
     * Method called when the bean is removed from database.
676
     */
677
    protected function onDelete() : void
678
    {
679
    }
680
681
    /**
682
     * Returns the foreign keys used by this bean.
683
     */
684
    protected static function getForeignKeys(string $tableName): ForeignKeys
0 ignored issues
show
Unused Code introduced by
The parameter $tableName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

684
    protected static function getForeignKeys(/** @scrutinizer ignore-unused */ string $tableName): ForeignKeys

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

Loading history...
685
    {
686
        return new ForeignKeys([]);
687
    }
688
689
    public function _getManyToManyRelationshipDescriptor(string $pathKey): ManyToManyRelationshipPathDescriptor
690
    {
691
        throw new TDBMException('Could not find many to many relationship descriptor key for "'.$pathKey.'"');
692
    }
693
694
    /**
695
     * @return string[]
696
     */
697
    public function _getManyToManyRelationshipDescriptorKeys(): array
698
    {
699
        return [];
700
    }
701
}
702