Completed
Push — master ( a24d11...86afc5 )
by David
17s
created

DbRow::_getReferences()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 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
/**
25
 * Instances of this class represent a row in a database.
26
 *
27
 * @author David Negrier
28
 */
29
class DbRow
30
{
31
    /**
32
     * The service this object is bound to.
33
     *
34
     * @var TDBMService
35
     */
36
    protected $tdbmService;
37
38
    /**
39
     * The object containing this db row.
40
     *
41
     * @var AbstractTDBMObject
42
     */
43
    private $object;
44
45
    /**
46
     * The name of the table the object if issued from.
47
     *
48
     * @var string
49
     */
50
    private $dbTableName;
51
52
    /**
53
     * The array of columns returned from database.
54
     *
55
     * @var mixed[]
56
     */
57
    private $dbRow = [];
58
59
    /**
60
     * The array of beans this bean points to, indexed by foreign key name.
61
     *
62
     * @var AbstractTDBMObject[]
63
     */
64
    private $references = [];
65
66
    /**
67
     * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
68
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
69
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
70
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
71
     *
72
     * @var string
73
     */
74
    private $status;
75
76
    /**
77
     * The values of the primary key.
78
     * This is set when the object is in "loaded" state.
79
     *
80
     * @var array An array of column => value
81
     */
82
    private $primaryKeys;
83
84
    /**
85
     * A list of modified columns, indexed by column name. Value is always true.
86
     *
87
     * @var array
88
     */
89
    private $modifiedColumns = [];
90
91
    /**
92
     * A list of modified references, indexed by foreign key name. Value is always true.
93
     *
94
     * @var array
95
     */
96
    private $modifiedReferences = [];
97
98
    /**
99
     * You should never call the constructor directly. Instead, you should use the
100
     * TDBMService class that will create TDBMObjects for you.
101
     *
102
     * Used with id!=false when we want to retrieve an existing object
103
     * and id==false if we want a new object
104
     *
105
     * @param AbstractTDBMObject $object The object containing this db row
106
     * @param string $tableName
107
     * @param mixed[] $primaryKeys
108
     * @param TDBMService $tdbmService
109
     * @param mixed[] $dbRow
110
     * @throws TDBMException
111
     */
112
    public function __construct(AbstractTDBMObject $object, string $tableName, array $primaryKeys = array(), TDBMService $tdbmService = null, array $dbRow = [])
113
    {
114
        $this->object = $object;
115
        $this->dbTableName = $tableName;
116
117
        $this->status = TDBMObjectStateEnum::STATE_DETACHED;
118
119
        if ($tdbmService === null) {
120
            if (!empty($primaryKeys)) {
121
                throw new TDBMException('You cannot pass an id to the DbRow constructor without passing also a TDBMService.');
122
            }
123
        } else {
124
            $this->tdbmService = $tdbmService;
125
126
            if (!empty($primaryKeys)) {
127
                $this->_setPrimaryKeys($primaryKeys);
128
                if (!empty($dbRow)) {
129
                    $this->dbRow = $dbRow;
130
                    $this->status = TDBMObjectStateEnum::STATE_LOADED;
131
                } else {
132
                    $this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
133
                }
134
                $tdbmService->_addToCache($this);
135
            } else {
136
                $this->status = TDBMObjectStateEnum::STATE_NEW;
137
                $this->tdbmService->_addToToSaveObjectList($this);
138
            }
139
        }
140
    }
141
142
    public function _attach(TDBMService $tdbmService): void
143
    {
144
        if ($this->status !== TDBMObjectStateEnum::STATE_DETACHED) {
145
            throw new TDBMInvalidOperationException('Cannot attach an object that is already attached to TDBM.');
146
        }
147
        $this->tdbmService = $tdbmService;
148
        $this->status = TDBMObjectStateEnum::STATE_NEW;
149
        $this->tdbmService->_addToToSaveObjectList($this);
150
    }
151
152
    /**
153
     * Sets the state of the TDBM Object
154
     * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
155
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
156
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
157
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
158
     *
159
     * @param string $state
160
     */
161
    public function _setStatus(string $state) : void
162
    {
163
        $this->status = $state;
164
        if ($state === TDBMObjectStateEnum::STATE_LOADED) {
165
            // after saving we are back to a loaded state, hence unmodified.
166
            $this->modifiedColumns = [];
167
            $this->modifiedReferences = [];
168
        }
169
    }
170
171
    /**
172
     * This is an internal method. You should not call this method yourself. The TDBM library will do it for you.
173
     * If the object is in state 'not loaded', this method performs a query in database to load the object.
174
     *
175
     * A TDBMException is thrown is no object can be retrieved (for instance, if the primary key specified
176
     * cannot be found).
177
     */
178
    public function _dbLoadIfNotLoaded(): void
179
    {
180
        if ($this->status === TDBMObjectStateEnum::STATE_NOT_LOADED) {
181
            $connection = $this->tdbmService->getConnection();
182
183
            list($sql_where, $parameters) = $this->tdbmService->buildFilterFromFilterBag($this->primaryKeys, $connection->getDatabasePlatform());
184
185
            $sql = 'SELECT * FROM '.$connection->quoteIdentifier($this->dbTableName).' WHERE '.$sql_where;
186
            $result = $connection->executeQuery($sql, $parameters);
187
188
            $row = $result->fetch(\PDO::FETCH_ASSOC);
189
190
            if ($row === false) {
191
                throw new TDBMException("Could not retrieve object from table \"$this->dbTableName\" using filter \".$sql_where.\" with data \"".var_export($parameters, true)."\".");
192
            }
193
194
            $this->dbRow = [];
195
            $types = $this->tdbmService->_getColumnTypesForTable($this->dbTableName);
196
197
            foreach ($row as $key => $value) {
198
                $this->dbRow[$key] = $types[$key]->convertToPHPValue($value, $connection->getDatabasePlatform());
199
            }
200
201
            $result->closeCursor();
202
203
            $this->status = TDBMObjectStateEnum::STATE_LOADED;
204
        }
205
    }
206
207
    /**
208
     * @return mixed|null
209
     */
210
    public function get(string $var)
211
    {
212
        $this->_dbLoadIfNotLoaded();
213
214
        return $this->dbRow[$var] ?? null;
215
    }
216
217
    /**
218
     * @param string $var
219
     * @param mixed $value
220
     * @throws TDBMException
221
     */
222
    public function set(string $var, $value): void
223
    {
224
        $this->_dbLoadIfNotLoaded();
225
226
        /*
227
        // Ok, let's start by checking the column type
228
        $type = $this->db_connection->getColumnType($this->dbTableName, $var);
229
230
        // Throws an exception if the type is not ok.
231
        if (!$this->db_connection->checkType($value, $type)) {
232
            throw new TDBMException("Error! Invalid value passed for attribute '$var' of table '$this->dbTableName'. Passed '$value', but expecting '$type'");
233
        }
234
        */
235
236
        /*if ($var == $this->getPrimaryKey() && isset($this->dbRow[$var]))
0 ignored issues
show
Unused Code Comprehensibility introduced by
66% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
237
            throw new TDBMException("Error! Changing primary key value is forbidden.");*/
238
        $this->dbRow[$var] = $value;
239
        $this->modifiedColumns[$var] = true;
240
        if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
241
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
242
            $this->tdbmService->_addToToSaveObjectList($this);
243
        }
244
    }
245
246
    /**
247
     * @param string             $foreignKeyName
248
     * @param AbstractTDBMObject $bean
249
     */
250
    public function setRef(string $foreignKeyName, AbstractTDBMObject $bean = null): void
251
    {
252
        $this->references[$foreignKeyName] = $bean;
253
        $this->modifiedReferences[$foreignKeyName] = true;
254
255
        if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
256
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
257
            $this->tdbmService->_addToToSaveObjectList($this);
258
        }
259
    }
260
261
    /**
262
     * @param string $foreignKeyName A unique name for this reference
263
     *
264
     * @return AbstractTDBMObject|null
265
     */
266
    public function getRef(string $foreignKeyName) : ?AbstractTDBMObject
267
    {
268
        if (array_key_exists($foreignKeyName, $this->references)) {
269
            return $this->references[$foreignKeyName];
270
        } elseif ($this->status === TDBMObjectStateEnum::STATE_NEW || $this->tdbmService === null) {
271
            // If the object is new and has no property, then it has to be empty.
272
            return null;
273
        } else {
274
            $this->_dbLoadIfNotLoaded();
275
276
            // Let's match the name of the columns to the primary key values
277
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
278
279
            $values = [];
280
            foreach ($fk->getUnquotedLocalColumns() as $column) {
281
                if (!isset($this->dbRow[$column])) {
282
                    return null;
283
                }
284
                $values[] = $this->dbRow[$column];
285
            }
286
287
            $filter = array_combine($fk->getUnquotedForeignColumns(), $values);
288
289
            // If the foreign key points to the primary key, let's use findObjectByPk
290
            if ($this->tdbmService->getPrimaryKeyColumns($fk->getForeignTableName()) === $fk->getUnquotedForeignColumns()) {
291
                return $this->tdbmService->findObjectByPk($fk->getForeignTableName(), $filter, [], true);
292
            } else {
293
                return $this->tdbmService->findObject($fk->getForeignTableName(), $filter);
294
            }
295
        }
296
    }
297
298
    /**
299
     * Returns the name of the table this object comes from.
300
     *
301
     * @return string
302
     */
303
    public function _getDbTableName(): string
304
    {
305
        return $this->dbTableName;
306
    }
307
308
    /**
309
     * Method used internally by TDBM. You should not use it directly.
310
     * This method returns the status of the TDBMObject.
311
     * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
312
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
313
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
314
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
315
     *
316
     * @return string
317
     */
318
    public function _getStatus(): string
319
    {
320
        return $this->status;
321
    }
322
323
    /**
324
     * Override the native php clone function for TDBMObjects.
325
     */
326
    public function __clone()
327
    {
328
        // Let's load the row (before we lose the ID!)
329
        $this->_dbLoadIfNotLoaded();
330
331
        //Let's set the status to detached
332
        $this->status = TDBMObjectStateEnum::STATE_DETACHED;
333
334
        $this->primaryKeys = [];
335
336
        //Now unset the PK from the row
337
        if ($this->tdbmService) {
338
            $pk_array = $this->tdbmService->getPrimaryKeyColumns($this->dbTableName);
339
            foreach ($pk_array as $pk) {
340
                unset($this->dbRow[$pk]);
341
            }
342
        }
343
    }
344
345
    /**
346
     * Returns raw database row.
347
     *
348
     * @return mixed[]
349
     *
350
     * @throws TDBMMissingReferenceException
351
     */
352
    public function _getDbRow(): array
353
    {
354
        return $this->buildDbRow($this->dbRow, $this->references);
355
    }
356
357
    /**
358
     * Returns raw database row that needs to be updated.
359
     *
360
     * @return mixed[]
361
     *
362
     * @throws TDBMMissingReferenceException
363
     */
364
    public function _getUpdatedDbRow(): array
365
    {
366
        $dbRow = \array_intersect_key($this->dbRow, $this->modifiedColumns);
367
        $references = \array_intersect_key($this->references, $this->modifiedReferences);
368
        return $this->buildDbRow($dbRow, $references);
369
    }
370
371
    /**
372
     * Builds a raw db row from dbRow and references passed in parameters.
373
     *
374
     * @param mixed[] $dbRow
375
     * @param AbstractTDBMObject[] $references
376
     * @return mixed[]
377
     * @throws TDBMMissingReferenceException
378
     */
379
    private function buildDbRow(array $dbRow, array $references): array
380
    {
381
        // Let's merge $dbRow and $references
382
        foreach ($references as $foreignKeyName => $reference) {
383
            // Let's match the name of the columns to the primary key values
384
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
385
            $localColumns = $fk->getUnquotedLocalColumns();
386
387
            if ($reference !== null) {
388
                $refDbRows = $reference->_getDbRows();
389
                $firstRefDbRow = reset($refDbRows);
390
                if ($firstRefDbRow->_getStatus() === TDBMObjectStateEnum::STATE_DELETED) {
391
                    throw TDBMMissingReferenceException::referenceDeleted($this->dbTableName, $reference);
392
                }
393
                $foreignColumns = $fk->getUnquotedForeignColumns();
394
                $refBeanValues = $firstRefDbRow->dbRow;
395
                for ($i = 0, $count = \count($localColumns); $i < $count; ++$i) {
396
                    $dbRow[$localColumns[$i]] = $refBeanValues[$foreignColumns[$i]];
397
                }
398
            } else {
399
                for ($i = 0, $count = \count($localColumns); $i < $count; ++$i) {
400
                    $dbRow[$localColumns[$i]] = null;
401
                }
402
            }
403
        }
404
405
        return $dbRow;
406
    }
407
408
    /**
409
     * Returns references array.
410
     *
411
     * @return AbstractTDBMObject[]
412
     */
413
    public function _getReferences(): array
414
    {
415
        return $this->references;
416
    }
417
418
    /**
419
     * Returns the values of the primary key.
420
     * This is set when the object is in "loaded" state.
421
     *
422
     * @return mixed[]
423
     */
424
    public function _getPrimaryKeys(): array
425
    {
426
        return $this->primaryKeys;
427
    }
428
429
    /**
430
     * Sets the values of the primary key.
431
     * This is set when the object is in "loaded" state.
432
     *
433
     * @param mixed[] $primaryKeys
434
     */
435
    public function _setPrimaryKeys(array $primaryKeys): void
436
    {
437
        $this->primaryKeys = $primaryKeys;
438
        foreach ($this->primaryKeys as $column => $value) {
439
            $this->dbRow[$column] = $value;
440
        }
441
    }
442
443
    /**
444
     * Returns the TDBMObject this bean is associated to.
445
     *
446
     * @return AbstractTDBMObject
447
     */
448
    public function getTDBMObject(): AbstractTDBMObject
449
    {
450
        return $this->object;
451
    }
452
453
    /**
454
     * Sets the TDBMObject this bean is associated to.
455
     * Only used when cloning.
456
     *
457
     * @param AbstractTDBMObject $object
458
     */
459
    public function setTDBMObject(AbstractTDBMObject $object): void
460
    {
461
        $this->object = $object;
462
    }
463
}
464