AbstractTDBMObject   F
last analyzed

Complexity

Total Complexity 91

Size/Duplication

Total Lines 636
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 173
c 1
b 0
f 0
dl 0
loc 636
rs 2
wmc 91

34 Methods

Rating   Name   Duplication   Size   Complexity  
A _constructFromData() 0 9 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 20 7
A _constructLazy() 0 7 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 14 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 12 4
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

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

656
    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...
657
    {
658
        return new ForeignKeys([]);
659
    }
660
661
    public function _getManyToManyRelationshipDescriptor(string $pathKey): ManyToManyRelationshipPathDescriptor
662
    {
663
        throw new TDBMException('Could not find many to many relationship descriptor key for "'.$pathKey.'"');
664
    }
665
666
    /**
667
     * @return string[]
668
     */
669
    public function _getManyToManyRelationshipDescriptorKeys(): array
670
    {
671
        return [];
672
    }
673
}
674