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