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

AbstractTDBMObject::disableSmartEagerLoad()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
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
use function array_combine;
30
31
/**
32
 * Instances of this class represent a "bean". Usually, a bean is mapped to a row of one table.
33
 * In some special cases (where inheritance is used), beans can be scattered on several tables.
34
 * Therefore, a TDBMObject is really a set of DbRow objects that represent one row in a table.
35
 *
36
 * @author David Negrier
37
 */
38
abstract class AbstractTDBMObject implements JsonSerializable
39
{
40
    /**
41
     * The service this object is bound to.
42
     *
43
     * @var TDBMService
44
     */
45
    protected $tdbmService;
46
47
    /**
48
     * An array of DbRow, indexed by table name.
49
     *
50
     * @var DbRow[]
51
     */
52
    protected $dbRows = [];
53
54
    /**
55
     * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
56
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
57
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
58
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
59
     *
60
     * @var string|null
61
     */
62
    private $status;
63
64
    /**
65
     * Array storing beans related via many to many relationships (pivot tables).
66
     *
67
     * @var \SplObjectStorage[] Key: pivot table name, value: SplObjectStorage
68
     */
69
    private $relationships = [];
70
71
    /**
72
     * @var bool[] Key: pivot table name, value: whether a query was performed to load the data
73
     */
74
    private $loadedRelationships = [];
75
76
    /**
77
     * Array storing beans related via many to one relationships (this bean is pointed by external beans).
78
     *
79
     * @var AlterableResultIterator[] Key: [external_table]___[external_column], value: SplObjectStorage
80
     */
81
    private $manyToOneRelationships = [];
82
83
    /**
84
     * If this bean originates from a ResultArray, this points back to the result array to build smart eager load queries.
85
     *
86
     * @var PartialQuery|null
87
     */
88
    private $partialQuery;
89
90
    /**
91
     * Used with $primaryKeys when we want to retrieve an existing object
92
     * and $primaryKeys=[] if we want a new object.
93
     *
94
     * @param string      $tableName
95
     * @param mixed[]     $primaryKeys
96
     * @param TDBMService $tdbmService
97
     *
98
     * @throws TDBMException
99
     * @throws TDBMInvalidOperationException
100
     */
101
    public function __construct(?string $tableName = null, array $primaryKeys = [], TDBMService $tdbmService = null)
102
    {
103
        // FIXME: lazy loading should be forbidden on tables with inheritance and dynamic type assignation...
104
        if (!empty($tableName)) {
105
            $this->dbRows[$tableName] = new DbRow($this, $tableName, static::getForeignKeys($tableName), $primaryKeys, $tdbmService);
106
        }
107
108
        if ($tdbmService === null) {
109
            $this->_setStatus(TDBMObjectStateEnum::STATE_DETACHED);
110
        } else {
111
            $this->_attach($tdbmService);
112
            if (!empty($primaryKeys)) {
113
                $this->_setStatus(TDBMObjectStateEnum::STATE_NOT_LOADED);
114
            } else {
115
                $this->_setStatus(TDBMObjectStateEnum::STATE_NEW);
116
            }
117
        }
118
    }
119
120
    /**
121
     * Alternative constructor called when data is fetched from database via a SELECT.
122
     *
123
     * @param array[]     $beanData    array<table, array<column, value>>
124
     * @param TDBMService $tdbmService
125
     */
126
    public function _constructFromData(array $beanData, TDBMService $tdbmService, ?PartialQuery $partialQuery): void
127
    {
128
        $this->tdbmService = $tdbmService;
129
        $this->partialQuery = $partialQuery;
130
131
        foreach ($beanData as $table => $columns) {
132
            $this->dbRows[$table] = new DbRow($this, $table, static::getForeignKeys($table), $tdbmService->_getPrimaryKeysFromObjectData($table, $columns), $tdbmService, $columns, $partialQuery);
133
        }
134
135
        $this->status = TDBMObjectStateEnum::STATE_LOADED;
136
    }
137
138
    /**
139
     * Alternative constructor called when bean is lazily loaded.
140
     *
141
     * @param string      $tableName
142
     * @param mixed[]     $primaryKeys
143
     * @param TDBMService $tdbmService
144
     */
145
    public function _constructLazy(string $tableName, array $primaryKeys, TDBMService $tdbmService, ?PartialQuery $partialQuery): void
146
    {
147
        $this->tdbmService = $tdbmService;
148
        $this->partialQuery = $partialQuery;
149
150
        $this->dbRows[$tableName] = new DbRow($this, $tableName, static::getForeignKeys($tableName), $primaryKeys, $tdbmService, [], $partialQuery);
151
152
        $this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
153
    }
154
155
    public function _attach(TDBMService $tdbmService): void
156
    {
157
        if ($this->status !== TDBMObjectStateEnum::STATE_DETACHED) {
158
            throw new TDBMInvalidOperationException('Cannot attach an object that is already attached to TDBM.');
159
        }
160
        $this->tdbmService = $tdbmService;
161
162
        // If we attach this object, we must work to make sure the tables are in ascending order (from low level to top level)
163
        $tableNames = $this->getUsedTables();
164
165
        $newDbRows = [];
166
167
        foreach ($tableNames as $table) {
168
            if (!isset($this->dbRows[$table])) {
169
                $this->registerTable($table);
170
            }
171
            $newDbRows[$table] = $this->dbRows[$table];
172
        }
173
        $this->dbRows = $newDbRows;
174
175
        $this->status = TDBMObjectStateEnum::STATE_NEW;
176
        foreach ($this->dbRows as $dbRow) {
177
            $dbRow->_attach($tdbmService);
178
        }
179
    }
180
181
    /**
182
     * Sets the state of the TDBM Object
183
     * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
184
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
185
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
186
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
187
     *
188
     * @param string $state
189
     */
190
    public function _setStatus(string $state): void
191
    {
192
        $this->status = $state;
193
194
        // The dirty state comes from the db_row itself so there is no need to set it from the called.
195
        if ($state !== TDBMObjectStateEnum::STATE_DIRTY) {
196
            foreach ($this->dbRows as $dbRow) {
197
                $dbRow->_setStatus($state);
198
            }
199
        }
200
201
        if ($state === TDBMObjectStateEnum::STATE_DELETED) {
202
            $this->onDelete();
203
        }
204
    }
205
206
    /**
207
     * Checks that $tableName is ok, or returns the only possible table name if "$tableName = null"
208
     * or throws an error.
209
     *
210
     * @param string|null $tableName
211
     *
212
     * @return string
213
     */
214
    private function checkTableName(?string $tableName = null): string
215
    {
216
        if ($tableName === null) {
217
            if (count($this->dbRows) > 1) {
218
                throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
219
            } elseif (count($this->dbRows) === 1) {
220
                $tableName = array_keys($this->dbRows)[0];
221
            }
222
        }
223
224
        return (string) $tableName;
225
    }
226
227
    /**
228
     * @return mixed
229
     */
230
    protected function get(string $var, string $tableName = null)
231
    {
232
        $tableName = $this->checkTableName($tableName);
233
234
        if (!isset($this->dbRows[$tableName])) {
235
            return null;
236
        }
237
238
        return $this->dbRows[$tableName]->get($var);
239
    }
240
241
    /**
242
     * @param mixed $value
243
     */
244
    protected function set(string $var, $value, ?string $tableName = null): void
245
    {
246
        if ($tableName === null) {
247
            if (count($this->dbRows) > 1) {
248
                throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
249
            } elseif (count($this->dbRows) === 1) {
250
                $tableName = (string) array_keys($this->dbRows)[0];
251
            } else {
252
                throw new TDBMException('Please specify a table for this object.');
253
            }
254
        }
255
256
        if (!isset($this->dbRows[$tableName])) {
257
            $this->registerTable($tableName);
258
        }
259
260
        $this->dbRows[$tableName]->set($var, $value);
261
        if ($this->dbRows[$tableName]->_getStatus() === TDBMObjectStateEnum::STATE_DIRTY) {
262
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
263
        }
264
    }
265
266
    /**
267
     * @param string             $foreignKeyName
268
     * @param AbstractTDBMObject $bean
269
     */
270
    protected function setRef(string $foreignKeyName, AbstractTDBMObject $bean = null, string $tableName = null): void
271
    {
272
        if ($tableName === null) {
273
            if (count($this->dbRows) > 1) {
274
                throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
275
            } elseif (count($this->dbRows) === 1) {
276
                $tableName = (string) array_keys($this->dbRows)[0];
277
            } else {
278
                throw new TDBMException('Please specify a table for this object.');
279
            }
280
        }
281
282
        if (!isset($this->dbRows[$tableName])) {
283
            $this->registerTable($tableName);
284
        }
285
286
        $oldLinkedBean = $this->dbRows[$tableName]->getRef($foreignKeyName);
287
        if ($oldLinkedBean !== null) {
288
            $oldLinkedBean->removeManyToOneRelationship($tableName, $foreignKeyName, $this);
289
        }
290
291
        $this->dbRows[$tableName]->setRef($foreignKeyName, $bean);
292
        if ($this->dbRows[$tableName]->_getStatus() === TDBMObjectStateEnum::STATE_DIRTY) {
293
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
294
        }
295
296
        if ($bean !== null) {
297
            $bean->setManyToOneRelationship($tableName, $foreignKeyName, $this);
298
        }
299
    }
300
301
    /**
302
     * @param string $foreignKeyName A unique name for this reference
303
     *
304
     * @return AbstractTDBMObject|null
305
     */
306
    protected function getRef(string $foreignKeyName, ?string $tableName = null) : ?AbstractTDBMObject
307
    {
308
        $tableName = $this->checkTableName($tableName);
309
310
        if (!isset($this->dbRows[$tableName])) {
311
            return null;
312
        }
313
314
        return $this->dbRows[$tableName]->getRef($foreignKeyName);
315
    }
316
317
    /**
318
     * Adds a many to many relationship to this bean.
319
     *
320
     * @param string             $pivotTableName
321
     * @param AbstractTDBMObject $remoteBean
322
     */
323
    protected function addRelationship(string $pivotTableName, AbstractTDBMObject $remoteBean): void
324
    {
325
        $this->setRelationship($pivotTableName, $remoteBean, 'new');
326
    }
327
328
    /**
329
     * Returns true if there is a relationship to this bean.
330
     *
331
     * @return bool
332
     */
333
    protected function hasRelationship(string $pathKey, AbstractTDBMObject $remoteBean): bool
334
    {
335
        $pathModel = $this->_getManyToManyRelationshipDescriptor($pathKey);
336
        $storage = $this->retrieveRelationshipsStorage($pathModel);
337
338
        if ($storage->contains($remoteBean)) {
339
            if ($storage[$remoteBean]['status'] !== 'delete') {
340
                return true;
341
            }
342
        }
343
344
        return false;
345
    }
346
347
    /**
348
     * Internal TDBM method. Removes a many to many relationship from this bean.
349
     *
350
     * @param string             $pivotTableName
351
     * @param AbstractTDBMObject $remoteBean
352
     */
353
    public function _removeRelationship(string $pivotTableName, AbstractTDBMObject $remoteBean): void
354
    {
355
        if (isset($this->relationships[$pivotTableName][$remoteBean]) && $this->relationships[$pivotTableName][$remoteBean]['status'] === 'new') {
356
            unset($this->relationships[$pivotTableName][$remoteBean]);
357
            unset($remoteBean->relationships[$pivotTableName][$this]);
358
        } else {
359
            $this->setRelationship($pivotTableName, $remoteBean, 'delete');
360
        }
361
    }
362
363
    /**
364
     * Sets many to many relationships for this bean.
365
     * Adds new relationships and removes unused ones.
366
     *
367
     * @param AbstractTDBMObject[] $remoteBeans
368
     */
369
    protected function setRelationships(string $pathKey, array $remoteBeans): void
370
    {
371
        $pathModel = $this->_getManyToManyRelationshipDescriptor($pathKey);
372
        $pivotTableName = $pathModel->getPivotName();
373
        $storage = $this->retrieveRelationshipsStorage($pathModel);
374
375
        foreach ($storage as $oldRemoteBean) {
376
            /* @var $oldRemoteBean AbstractTDBMObject */
377
            if (!in_array($oldRemoteBean, $remoteBeans, true)) {
378
                // $oldRemoteBean must be removed
379
                $this->_removeRelationship($pivotTableName, $oldRemoteBean);
380
            }
381
        }
382
383
        foreach ($remoteBeans as $remoteBean) {
384
            if (!$storage->contains($remoteBean) || $storage[$remoteBean]['status'] === 'delete') {
385
                // $remoteBean must be added
386
                $this->addRelationship($pivotTableName, $remoteBean);
387
            }
388
        }
389
    }
390
391
    /**
392
     * Returns the list of objects linked to this bean via $pivotTableName.
393
     *
394
     * @return \SplObjectStorage
395
     */
396
    private function retrieveRelationshipsStorage(ManyToManyRelationshipPathDescriptor $pathModel): \SplObjectStorage
397
    {
398
        $pivotTableName = $pathModel->getPivotName();
399
400
        $storage = $this->getRelationshipStorage($pathModel->getPivotName());
401
        if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->loadedRelationships[$pivotTableName]) && $this->loadedRelationships[$pivotTableName])) {
402
            return $storage;
403
        }
404
405
        $beans = $this->tdbmService->_getRelatedBeans($pathModel, $this);
406
        $this->loadedRelationships[$pivotTableName] = true;
407
408
        foreach ($beans as $bean) {
409
            if (isset($storage[$bean])) {
410
                $oldStatus = $storage[$bean]['status'];
411
                if ($oldStatus === 'delete') {
412
                    // Keep deleted things deleted
413
                    continue;
414
                }
415
            }
416
            $this->setRelationship($pivotTableName, $bean, 'loaded');
417
        }
418
419
        return $storage;
420
    }
421
422
    /**
423
     * Internal TDBM method. Returns the list of objects linked to this bean via $pivotTableName.
424
     *
425
     * @return AbstractTDBMObject[]
426
     */
427
    public function _getRelationships(string $pathKey): array
428
    {
429
        $pathModel = $this->_getManyToManyRelationshipDescriptor($pathKey);
430
        return $this->_getRelationshipsFromModel($pathModel);
431
    }
432
433
    /**
434
     * @return AbstractTDBMObject[]
435
     */
436
    public function _getRelationshipsFromModel(ManyToManyRelationshipPathDescriptor $pathModel): array
437
    {
438
        return $this->relationshipStorageToArray($this->retrieveRelationshipsStorage($pathModel));
439
    }
440
441
    /**
442
     * @param \SplObjectStorage $storage
443
     * @return AbstractTDBMObject[]
444
     */
445
    private function relationshipStorageToArray(\SplObjectStorage $storage): array
446
    {
447
        $beans = [];
448
        foreach ($storage as $bean) {
449
            $statusArr = $storage[$bean];
450
            if ($statusArr['status'] !== 'delete') {
451
                $beans[] = $bean;
452
            }
453
        }
454
455
        return $beans;
456
    }
457
458
    /**
459
     * Declares a relationship between.
460
     *
461
     * @param string             $pivotTableName
462
     * @param AbstractTDBMObject $remoteBean
463
     * @param string             $status
464
     */
465
    private function setRelationship(string $pivotTableName, AbstractTDBMObject $remoteBean, string $status): void
466
    {
467
        $storage = $this->getRelationshipStorage($pivotTableName);
468
        $storage->attach($remoteBean, ['status' => $status, 'reverse' => false]);
469
        if ($this->status === TDBMObjectStateEnum::STATE_LOADED) {
470
            $this->_setStatus(TDBMObjectStateEnum::STATE_DIRTY);
471
        }
472
473
        $remoteStorage = $remoteBean->getRelationshipStorage($pivotTableName);
474
        $remoteStorage->attach($this, ['status' => $status, 'reverse' => true]);
475
    }
476
477
    /**
478
     * Returns the SplObjectStorage associated to this relationship (creates it if it does not exists).
479
     *
480
     * @param string $pivotTableName
481
     *
482
     * @return \SplObjectStorage
483
     */
484
    private function getRelationshipStorage(string $pivotTableName) : \SplObjectStorage
485
    {
486
        return $this->relationships[$pivotTableName] ?? $this->relationships[$pivotTableName] = new \SplObjectStorage();
487
    }
488
489
    /**
490
     * Returns the SplObjectStorage associated to this relationship (creates it if it does not exists).
491
     *
492
     * @param string $tableName
493
     * @param string $foreignKeyName
494
     *
495
     * @return AlterableResultIterator
496
     */
497
    private function getManyToOneAlterableResultIterator(string $tableName, string $foreignKeyName) : AlterableResultIterator
498
    {
499
        $key = $tableName.'___'.$foreignKeyName;
500
501
        return $this->manyToOneRelationships[$key] ?? $this->manyToOneRelationships[$key] = new AlterableResultIterator();
502
    }
503
504
    /**
505
     * Declares a relationship between this bean and the bean pointing to it.
506
     *
507
     * @param string             $tableName
508
     * @param string             $foreignKeyName
509
     * @param AbstractTDBMObject $remoteBean
510
     */
511
    private function setManyToOneRelationship(string $tableName, string $foreignKeyName, AbstractTDBMObject $remoteBean): void
512
    {
513
        $alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
514
        $alterableResultIterator->add($remoteBean);
515
    }
516
517
    /**
518
     * Declares a relationship between this bean and the bean pointing to it.
519
     *
520
     * @param string             $tableName
521
     * @param string             $foreignKeyName
522
     * @param AbstractTDBMObject $remoteBean
523
     */
524
    private function removeManyToOneRelationship(string $tableName, string $foreignKeyName, AbstractTDBMObject $remoteBean): void
525
    {
526
        $alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
527
        $alterableResultIterator->remove($remoteBean);
528
    }
529
530
    /**
531
     * Returns the list of objects linked to this bean via a given foreign key.
532
     *
533
     * @param string $tableName
534
     * @param string $foreignKeyName
535
     * @param array<int, string> $localColumns
536
     * @param array<int, string> $foreignColumns
537
     * @param string $foreignTableName
538
     * @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 additional or removal object !
539
     *
540
     * @return AlterableResultIterator
541
     * @throws TDBMException
542
     */
543
    protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $localColumns, array $foreignColumns, string $foreignTableName, string $orderString = null) : AlterableResultIterator
544
    {
545
        $key = $tableName.'___'.$foreignKeyName;
546
        $alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
547
        if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->manyToOneRelationships[$key]) && $this->manyToOneRelationships[$key]->getUnderlyingResultIterator() !== null)) {
548
            return $alterableResultIterator;
549
        }
550
551
        $ids = [];
552
        foreach ($foreignColumns as $foreignColumn) {
553
            $ids[] = $this->get($foreignColumn, $foreignTableName);
554
        }
555
556
        $searchFilter = array_combine($localColumns, $ids);
557
558
        $unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString);
0 ignored issues
show
Bug introduced by
It seems like $searchFilter can also be of type false; however, parameter $filter of TheCodingMachine\TDBM\TDBMService::findObjects() does only seem to accept array|null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

558
        $unalteredResultIterator = $this->tdbmService->findObjects($tableName, /** @scrutinizer ignore-type */ $searchFilter, [], $orderString);
Loading history...
559
560
        $alterableResultIterator->setResultIterator($unalteredResultIterator->getIterator());
561
562
        return $alterableResultIterator;
563
    }
564
565
    /**
566
     * Reverts any changes made to the object and resumes it to its DB state.
567
     * This can only be called on objects that come from database and that have not been deleted.
568
     * Otherwise, this will throw an exception.
569
     *
570
     * @throws TDBMException
571
     */
572
    public function discardChanges(): void
573
    {
574
        if ($this->status === TDBMObjectStateEnum::STATE_NEW || $this->status === TDBMObjectStateEnum::STATE_DETACHED) {
575
            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.");
576
        }
577
578
        if ($this->status === TDBMObjectStateEnum::STATE_DELETED) {
579
            throw new TDBMException('You cannot call discardChanges() on an object that has been deleted.');
580
        }
581
582
        $this->_setStatus(TDBMObjectStateEnum::STATE_NOT_LOADED);
583
        foreach ($this->dbRows as $row) {
584
            $row->disableSmartEagerLoad();
585
        }
586
        $this->partialQuery = null;
587
    }
588
589
    /**
590
     * Prevents smart eager loading of related entities.
591
     * If this bean was loaded through a result iterator, smart eager loading loads all entities of related beans at once.
592
     * You can disable it with this function.
593
     */
594
    public function disableSmartEagerLoad(): void
595
    {
596
        $this->partialQuery = null;
597
    }
598
599
    /**
600
     * Method used internally by TDBM. You should not use it directly.
601
     * This method returns the status of the TDBMObject.
602
     * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
603
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
604
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
605
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
606
     *
607
     * @return string
608
     */
609
    public function _getStatus() : string
610
    {
611
        if ($this->status === null) {
612
            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)));
613
        }
614
615
        return $this->status;
616
    }
617
618
    /**
619
     * Override the native php clone function for TDBMObjects.
620
     */
621
    public function __clone()
622
    {
623
        // Let's clone each row
624
        foreach ($this->dbRows as $key => &$dbRow) {
625
            $dbRow = clone $dbRow;
626
            $dbRow->setTDBMObject($this);
627
        }
628
629
        $this->manyToOneRelationships = [];
630
631
        // Let's set the status to new (to enter the save function)
632
        $this->status = TDBMObjectStateEnum::STATE_DETACHED;
633
    }
634
635
    /**
636
     * Returns raw database rows.
637
     *
638
     * @return DbRow[] Key: table name, Value: DbRow object
639
     */
640
    public function _getDbRows(): array
641
    {
642
        return $this->dbRows;
643
    }
644
645
    private function registerTable(string $tableName): void
646
    {
647
        $dbRow = new DbRow($this, $tableName, static::getForeignKeys($tableName));
648
649
        if (in_array($this->status, [TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DIRTY], true)) {
650
            // Let's get the primary key for the new table
651
            $anotherDbRow = array_values($this->dbRows)[0];
652
            /* @var $anotherDbRow DbRow */
653
            $indexedPrimaryKeys = array_values($anotherDbRow->_getPrimaryKeys());
654
            $primaryKeys = $this->tdbmService->_getPrimaryKeysFromIndexedPrimaryKeys($tableName, $indexedPrimaryKeys);
655
            $dbRow->_setPrimaryKeys($primaryKeys);
656
        }
657
658
        if ($this->status === null) {
659
            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)));
660
        }
661
662
        $dbRow->_setStatus($this->status);
663
664
        $this->dbRows[$tableName] = $dbRow;
665
        // TODO: look at status (if not new)=> get primary key from tdbmservice
666
    }
667
668
    /**
669
     * Internal function: return the list of relationships.
670
     *
671
     * @return \SplObjectStorage[]
672
     */
673
    public function _getCachedRelationships(): array
674
    {
675
        return $this->relationships;
676
    }
677
678
    /**
679
     * Returns an array of used tables by this bean (from parent to child relationship).
680
     *
681
     * @return string[]
682
     */
683
    abstract protected function getUsedTables() : array;
684
685
    /**
686
     * Method called when the bean is removed from database.
687
     */
688
    protected function onDelete() : void
689
    {
690
    }
691
692
    /**
693
     * Returns the foreign keys used by this bean.
694
     */
695
    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

695
    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...
696
    {
697
        return new ForeignKeys([]);
698
    }
699
700
    public function _getManyToManyRelationshipDescriptor(string $pathKey): ManyToManyRelationshipPathDescriptor
701
    {
702
        throw new TDBMException('Could not find many to many relationship descriptor key for "'.$pathKey.'"');
703
    }
704
705
    /**
706
     * @return string[]
707
     */
708
    public function _getManyToManyRelationshipDescriptorKeys(): array
709
    {
710
        return [];
711
    }
712
}
713