Completed
Pull Request — 3.4 (#46)
by David
36:52
created

AbstractTDBMObject::retrieveRelationshipsStorage()   C

Complexity

Conditions 8
Paths 5

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
rs 6.1403
cc 8
eloc 13
nc 5
nop 1
1
<?php
2
namespace Mouf\Database\TDBM;
3
4
/*
5
 Copyright (C) 2006-2015 David Négrier - THE CODING MACHINE
6
7
 This program is free software; you can redistribute it and/or modify
8
 it under the terms of the GNU General Public License as published by
9
 the Free Software Foundation; either version 2 of the License, or
10
 (at your option) any later version.
11
12
 This program is distributed in the hope that it will be useful,
13
 but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 GNU General Public License for more details.
16
17
 You should have received a copy of the GNU General Public License
18
 along with this program; if not, write to the Free Software
19
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
 */
21
use Doctrine\DBAL\Driver\Connection;
22
use Mouf\Database\TDBM\Filters\FilterInterface;
23
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 FilterInterface {
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
	 * @var DbRow[]
44
	 */
45
	protected $dbRows = array();
46
47
	/**
48
	 * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
49
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
50
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
51
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
52
	 *
53
	 * @var string
54
	 */
55
	private $status;
56
57
	/**
58
	 * True if an error has occurred while saving. The user will have to call save() explicitly or to modify one of its members to save it again.
59
	 * TODO: hide this with getters and setters
60
	 *
61
	 * @var boolean
62
	 */
63
	public $db_onerror;
64
65
	private $db_connection;
66
	
67
	/**
68
	 * True to automatically save the object.
69
	 * If false, the user must explicitly call the save() method to save the object. 
70
	 * TODO: hide this with getters and setters
71
	 * 
72
	 * @var boolean
73
	 */
74
	public $db_autosave;
75
76
	/**
77
	 * Array storing beans related via many to many relationships (pivot tables)
78
	 * @var \SplObjectStorage[] Key: pivot table name, value: SplObjectStorage
79
	 */
80
	private $relationships = [];
81
82
	/**
83
	 *
84
	 * @var bool[] Key: pivot table name, value: whether a query was performed to load the data.
85
	 */
86
	private $loadedRelationships = [];
87
88
	/**
89
	 * Used with $primaryKeys when we want to retrieve an existing object
90
	 * and $primaryKeys=[] if we want a new object
91
	 *
92
	 * @param string $tableName
93
	 * @param array $primaryKeys
94
	 * @param TDBMService $tdbmService
95
	 * @throws TDBMException
96
	 * @throws TDBMInvalidOperationException
97
	 */
98
	public function __construct($tableName=null, array $primaryKeys=array(), TDBMService $tdbmService=null) {
99
		// FIXME: lazy loading should be forbidden on tables with inheritance and dynamic type assignation...
100
		if (!empty($tableName)) {
101
			$this->dbRows[$tableName] = new DbRow($this, $tableName, $primaryKeys, $tdbmService);
102
		}
103
104
		if ($tdbmService === null) {
105
			$this->_setStatus(TDBMObjectStateEnum::STATE_DETACHED);
106
		} else {
107
			$this->_attach($tdbmService);
108
			if (!empty($primaryKeys)) {
109
				$this->_setStatus(TDBMObjectStateEnum::STATE_NOT_LOADED);
110
			} else {
111
				$this->_setStatus(TDBMObjectStateEnum::STATE_NEW);
112
			}
113
		}
114
	}
115
116
	/**
117
	 * Alternative constructor called when data is fetched from database via a SELECT.
118
	 *
119
	 * @param array $beanData array<table, array<column, value>>
120
	 * @param TDBMService $tdbmService
121
	 */
122
	public function _constructFromData(array $beanData, TDBMService $tdbmService) {
123
		$this->tdbmService = $tdbmService;
124
125
		foreach ($beanData as $table => $columns) {
126
			$this->dbRows[$table] = new DbRow($this, $table, $tdbmService->_getPrimaryKeysFromObjectData($table, $columns), $tdbmService, $columns);
127
		}
128
129
		$this->status = TDBMObjectStateEnum::STATE_LOADED;
130
	}
131
132
	/**
133
	 * Alternative constructor called when bean is lazily loaded.
134
	 *
135
	 * @param string $tableName
136
	 * @param array $primaryKeys
137
	 * @param TDBMService $tdbmService
138
	 */
139
	public function _constructLazy($tableName, array $primaryKeys, TDBMService $tdbmService) {
140
		$this->tdbmService = $tdbmService;
141
142
		$this->dbRows[$tableName] = new DbRow($this, $tableName, $primaryKeys, $tdbmService);
143
144
		$this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
145
	}
146
147
	public function _attach(TDBMService $tdbmService) {
148
		if ($this->status !== TDBMObjectStateEnum::STATE_DETACHED) {
149
			throw new TDBMInvalidOperationException('Cannot attach an object that is already attached to TDBM.');
150
		}
151
		$this->tdbmService = $tdbmService;
152
153
		// If we attach this object, we must work to make sure the tables are in ascending order (from low level to top level)
154
		$tableNames = array_keys($this->dbRows);
155
		$tableNames = $this->tdbmService->_getLinkBetweenInheritedTables($tableNames);
156
		$tableNames = array_reverse($tableNames);
157
158
		$newDbRows = [];
159
160
		foreach ($tableNames as $table) {
161
			if (!isset($this->dbRows[$table])) {
162
				$this->registerTable($table);
163
			}
164
			$newDbRows[$table] = $this->dbRows[$table];
165
		}
166
		$this->dbRows = $newDbRows;
167
168
		$this->status = TDBMObjectStateEnum::STATE_NEW;
169
		foreach ($this->dbRows as $dbRow) {
170
			$dbRow->_attach($tdbmService);
171
		}
172
	}
173
174
	/**
175
	 * Returns true if the object will save automatically, false if an explicit call to save() is required.
176
	 *
177
	 * @return boolean
178
	 */
179
	public function getAutoSaveMode() {
180
		return $this->db_autosave;
181
	}
182
	
183
	/**
184
	 * Sets the autosave mode:
185
	 * true if the object will save automatically,
186
	 * false if an explicit call to save() is required.
187
	 *
188
	 * @param boolean $autoSave
189
	 */
190
	public function setAutoSaveMode($autoSave) {
191
		$this->db_autosave = $autoSave;
192
	}
193
194
	/**
195
	 * Sets the state of the TDBM Object
196
	 * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
197
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
198
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
199
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
200
	 * @param string $state
201
	 */
202
	public function _setStatus($state){
203
		$this->status = $state;
204
205
		// TODO: we might ignore the loaded => dirty state here! dirty status comes from the db_row itself.
206
		foreach ($this->dbRows as $dbRow) {
207
			$dbRow->_setStatus($state);
208
		}
209
	}
210
211 View Code Duplication
	public function get($var, $tableName = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
212
		if ($tableName === null) {
213
			if (count($this->dbRows) > 1) {
214
				throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
215
			} elseif (count($this->dbRows) === 1) {
216
				$tableName = array_keys($this->dbRows)[0];
217
			}
218
		}
219
220
		if (!isset($this->dbRows[$tableName])) {
221
			if (count($this->dbRows[$tableName] === 0)) {
222
				throw new TDBMException('Object is not yet bound to any table.');
223
			} else {
224
				throw new TDBMException('Unknown table "'.$tableName.'"" in object.');
225
			}
226
		}
227
228
		return $this->dbRows[$tableName]->get($var);
229
	}
230
231
	/**
232
	 * Returns true if a column is set, false otherwise.
233
	 * 
234
	 * @param string $var
235
	 * @return boolean
236
	 */
237 View Code Duplication
	public function has($var, $tableName = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
238
		if ($tableName === null) {
239
			if (count($this->dbRows) > 1) {
240
				throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
241
			} elseif (count($this->dbRows) === 1) {
242
				$tableName = array_keys($this->dbRows)[0];
243
			}
244
		}
245
246
		if (!isset($this->dbRows[$tableName])) {
247
			if (count($this->dbRows[$tableName] === 0)) {
248
				throw new TDBMException('Object is not yet bound to any table.');
249
			} else {
250
				throw new TDBMException('Unknown table "'.$tableName.'"" in object.');
251
			}
252
		}
253
254
		return $this->dbRows[$tableName]->has($var);
0 ignored issues
show
Bug introduced by
The method has() does not seem to exist on object<Mouf\Database\TDBM\DbRow>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
255
	}
256
	
257 View Code Duplication
	public function set($var, $value, $tableName = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
258
		if ($tableName === null) {
259
			if (count($this->dbRows) > 1) {
260
				throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
261
			} elseif (count($this->dbRows) === 1) {
262
				$tableName = array_keys($this->dbRows)[0];
263
			} else {
264
				throw new TDBMException("Please specify a table for this object.");
265
			}
266
		}
267
268
		if (!isset($this->dbRows[$tableName])) {
269
			$this->registerTable($tableName);
270
		}
271
272
		$this->dbRows[$tableName]->set($var, $value);
273
		if ($this->dbRows[$tableName]->_getStatus() === TDBMObjectStateEnum::STATE_DIRTY) {
274
			$this->status = TDBMObjectStateEnum::STATE_DIRTY;
275
		}
276
	}
277
278
	/**
279
	 * @param string $foreignKeyName
280
	 * @param AbstractTDBMObject $bean
281
	 */
282 View Code Duplication
	public function setRef($foreignKeyName, AbstractTDBMObject $bean, $tableName = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
283
		if ($tableName === null) {
284
			if (count($this->dbRows) > 1) {
285
				throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
286
			} elseif (count($this->dbRows) === 1) {
287
				$tableName = array_keys($this->dbRows)[0];
288
			} else {
289
				throw new TDBMException("Please specify a table for this object.");
290
			}
291
		}
292
293
		if (!isset($this->dbRows[$tableName])) {
294
			$this->registerTable($tableName);
295
		}
296
297
		$this->dbRows[$tableName]->setRef($foreignKeyName, $bean);
298
		if ($this->dbRows[$tableName]->_getStatus() === TDBMObjectStateEnum::STATE_DIRTY) {
299
			$this->status = TDBMObjectStateEnum::STATE_DIRTY;
300
		}
301
	}
302
303
	/**
304
	 * @param string $foreignKeyName A unique name for this reference
305
	 * @return AbstractTDBMObject|null
306
	 */
307 View Code Duplication
	public function getRef($foreignKeyName, $tableName = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
308
		if ($tableName === null) {
309
			if (count($this->dbRows) > 1) {
310
				throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
311
			} elseif (count($this->dbRows) === 1) {
312
				$tableName = array_keys($this->dbRows)[0];
313
			}
314
		}
315
316
		if (!isset($this->dbRows[$tableName])) {
317
			if (count($this->dbRows[$tableName] === 0)) {
318
				throw new TDBMException('Object is not yet bound to any table.');
319
			} else {
320
				throw new TDBMException('Unknown table "'.$tableName.'"" in object.');
321
			}
322
		}
323
324
		return $this->dbRows[$tableName]->getRef($foreignKeyName);
325
	}
326
327
	/**
328
	 * Adds a many to many relationship to this bean.
329
	 * @param string $pivotTableName
330
	 * @param AbstractTDBMObject $remoteBean
331
	 */
332
	protected function addRelationship($pivotTableName, AbstractTDBMObject $remoteBean) {
333
		$this->setRelationship($pivotTableName, $remoteBean, 'new');
334
	}
335
336
	/**
337
	 * Returns true if there is a relationship to this bean.
338
	 * @param string $pivotTableName
339
	 * @param AbstractTDBMObject $remoteBean
340
	 * @return bool
341
	 */
342
	protected function hasRelationship($pivotTableName, AbstractTDBMObject $remoteBean) {
343
		$storage = $this->retrieveRelationshipsStorage($pivotTableName);
344
345
		if ($storage->contains($remoteBean)) {
346
			if ($storage[$remoteBean]['status'] !== 'delete') {
347
				return true;
348
			}
349
		}
350
		return false;
351
	}
352
353
	/**
354
	 * Internal TDBM method. Removes a many to many relationship from this bean.
355
	 * @param string $pivotTableName
356
	 * @param AbstractTDBMObject $remoteBean
357
	 */
358
	public function _removeRelationship($pivotTableName, AbstractTDBMObject $remoteBean) {
359
		if (isset($this->relationships[$pivotTableName][$remoteBean]) && $this->relationships[$pivotTableName][$remoteBean]['status'] === 'new') {
360
			unset($this->relationships[$pivotTableName][$remoteBean]);
361
			unset($remoteBean->relationships[$pivotTableName][$this]);
362
		} else {
363
			$this->setRelationship($pivotTableName, $remoteBean, 'delete');
364
		}
365
	}
366
367
	/**
368
	 * Returns the list of objects linked to this bean via $pivotTableName
369
	 * @param $pivotTableName
370
	 * @return \SplObjectStorage
371
	 */
372
	private function retrieveRelationshipsStorage($pivotTableName) {
373
		$storage = $this->getRelationshipStorage($pivotTableName);
374
		if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || isset($this->loadedRelationships[$pivotTableName]) && $this->loadedRelationships[$pivotTableName]) {
375
			return $storage;
376
		}
377
378
		$beans = $this->tdbmService->_getRelatedBeans($pivotTableName, $this);
379
		$this->loadedRelationships[$pivotTableName] = true;
380
381
		foreach ($beans as $bean) {
382
			if (isset($storage[$bean])) {
383
				$oldStatus = $storage[$bean]['status'];
384
				if ($oldStatus === 'delete') {
385
					// Keep deleted things deleted
386
					continue;
387
				}
388
			}
389
			$this->setRelationship($pivotTableName, $bean, "loaded");
390
		}
391
392
		return $storage;
393
394
	}
395
396
	/**
397
	 * Internal TDBM method. Returns the list of objects linked to this bean via $pivotTableName
398
	 * @access private
399
	 * @param $pivotTableName
400
	 * @return AbstractTDBMObject[]
401
	 */
402
	public function _getRelationships($pivotTableName) {
403
		return $this->relationshipStorageToArray($this->retrieveRelationshipsStorage($pivotTableName));
404
	}
405
406
	private function relationshipStorageToArray(\SplObjectStorage $storage) {
407
		$beans = [];
408
		foreach ($storage as $bean) {
409
			$statusArr = $storage[$bean];
410
			if ($statusArr['status'] !== 'delete') {
411
				$beans[] = $bean;
412
			}
413
		}
414
		return $beans;
415
	}
416
417
	/**
418
	 * Declares a relationship between
419
	 * @param string $pivotTableName
420
	 * @param AbstractTDBMObject $remoteBean
421
	 * @param string $status
422
	 */
423
	private function setRelationship($pivotTableName, AbstractTDBMObject $remoteBean, $status) {
424
		$storage = $this->getRelationshipStorage($pivotTableName);
425
		$storage->attach($remoteBean, [ 'status' => $status, 'reverse' => false ]);
426
		if ($this->status === TDBMObjectStateEnum::STATE_LOADED) {
427
			$this->_setStatus(TDBMObjectStateEnum::STATE_DIRTY);
428
		}
429
430
		$remoteStorage = $remoteBean->getRelationshipStorage($pivotTableName);
431
		$remoteStorage->attach($this, [ 'status' => $status, 'reverse' => true ]);
432
	}
433
434
	/**
435
	 * Returns the SplObjectStorage associated to this relationship (creates it if it does not exists)
436
	 * @param $pivotTableName
437
	 * @return \SplObjectStorage
438
	 */
439
	private function getRelationshipStorage($pivotTableName) {
440
		if (isset($this->relationships[$pivotTableName])) {
441
			$storage = $this->relationships[$pivotTableName];
442
		} else {
443
			$storage = new \SplObjectStorage();
444
			$this->relationships[$pivotTableName] = $storage;
445
		}
446
		return $storage;
447
	}
448
449
	/*public function __destruct() {
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% 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...
450
		// In a destructor, no exception can be thrown (PHP 5 limitation)
451
		// So we print the error instead
452
		try {
453
			if (!$this->db_onerror && $this->db_autosave)
454
			{
455
				$this->save();
456
			}
457
		} catch (\Exception $e) {
458
			trigger_error($e->getMessage(), E_USER_ERROR);
459
		}
460
	}*/
461
462
463
	/**
464
	 * Reverts any changes made to the object and resumes it to its DB state.
465
	 * This can only be called on objects that come from database and that have not been deleted.
466
	 * Otherwise, this will throw an exception.
467
	 *
468
	 */
469
	public function discardChanges() {
470
		if ($this->status == TDBMObjectStateEnum::STATE_NEW) {
471
			throw new TDBMException("You cannot call discardChanges() on an object that has been created with getNewObject and that has not yet been saved.");
472
		}
473
474
		if ($this->status == TDBMObjectStateEnum::STATE_DELETED) {
475
			throw new TDBMException("You cannot call discardChanges() on an object that has been deleted.");
476
		}
477
			
478
		$this->_setStatus(TDBMObjectStateEnum::STATE_NOT_LOADED);
479
	}
480
481
	/**
482
	 * Method used internally by TDBM. You should not use it directly.
483
	 * This method returns the status of the TDBMObject.
484
	 * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
485
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
486
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
487
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
488
	 *
489
	 * @return string
490
	 */
491
	public function _getStatus() {
492
		return $this->status;
493
	}
494
495
	/**
496
	 * Returns the SQL of the filter (the SQL WHERE clause).
497
	 *
498
	 * @param Connection $dbConnection
499
	 * @return string
500
	 */
501
	public function toSql(Connection $dbConnection) {
502
		return $this->getPrimaryKeyWhereStatement();
503
	}
504
505
	/**
506
	 * Returns the tables used in the filter in an array.
507
	 *
508
	 * @return array<string>
509
	 */
510
	public function getUsedTables() {
511
		return array_keys($this->dbRows);
512
	}
513
514
	/**
515
	 * Returns Where statement to query this object
516
	 *
517
	 * @return string
518
	 */
519
	private function getPrimaryKeyWhereStatement() {
520
		// Let's first get the primary keys
521
		$pk_table = $this->tdbmService->getPrimaryKeyColumns($this->dbTableName);
0 ignored issues
show
Bug introduced by
The property dbTableName does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
522
		// Now for the object_id
523
		$object_id = $this->TDBMObject_id;
0 ignored issues
show
Bug introduced by
The property TDBMObject_id does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
524
		// If there is only one primary key:
525
		if (count($pk_table)==1) {
526
			$sql_where = $this->db_connection->escapeDBItem($this->dbTableName).'.'.$this->db_connection->escapeDBItem($pk_table[0])."=".$this->db_connection->quoteSmart($this->TDBMObject_id);
527
		} else {
528
			$ids = unserialize($object_id);
529
			$i=0;
530
			$sql_where_array = array();
531
			foreach ($pk_table as $pk) {
532
				$sql_where_array[] = $this->db_connection->escapeDBItem($this->dbTableName).'.'.$this->db_connection->escapeDBItem($pk)."=".$this->db_connection->quoteSmart($ids[$i]);
533
				$i++;
534
			}
535
			$sql_where = implode(" AND ",$sql_where_array);
536
		}
537
		return $sql_where;
538
	}
539
540
    /**
541
     * Override the native php clone function for TDBMObjects
542
     */
543
    public function __clone() {
544
		// Let's clone the many to many relationships
545
		if ($this->status === TDBMObjectStateEnum::STATE_DETACHED) {
546
			$pivotTableList = array_keys($this->relationships);
547
		} else {
548
			$pivotTableList = $this->tdbmService->_getPivotTablesLinkedToBean($this);
549
		}
550
551
		foreach ($pivotTableList as $pivotTable) {
552
			$storage = $this->retrieveRelationshipsStorage($pivotTable);
553
554
			// Let's duplicate the reverse side of the relationship
555
			foreach ($storage as $remoteBean) {
556
				$metadata = $storage[$remoteBean];
557
558
				$remoteStorage = $remoteBean->getRelationshipStorage($pivotTable);
559
				$remoteStorage->attach($this, [ 'status' => $metadata['status'], 'reverse' => !$metadata['reverse'] ]);
560
			}
561
		}
562
563
		// Let's clone each row
564
		foreach ($this->dbRows as $key=>$dbRow) {
565
			$this->dbRows[$key] = clone $dbRow;
566
		}
567
568
		// Let's set the status to new (to enter the save function)
569
        $this->status = TDBMObjectStateEnum::STATE_DETACHED;
570
571
572
    }
573
574
	/**
575
	 * Returns raw database rows.
576
	 *
577
	 * @return DbRow[] Key: table name, Value: DbRow object
578
	 */
579
	public function _getDbRows() {
580
		return $this->dbRows;
581
	}
582
583
	private function registerTable($tableName) {
584
		$dbRow = new DbRow($this, $tableName);
585
586
		if (in_array($this->status, [ TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DIRTY ])) {
587
			// Let's get the primary key for the new table
588
			$anotherDbRow = array_values($this->dbRows)[0];
589
			/* @var $anotherDbRow DbRow */
590
			$indexedPrimaryKeys = array_values($anotherDbRow->_getPrimaryKeys());
591
			$primaryKeys = $this->tdbmService->_getPrimaryKeysFromIndexedPrimaryKeys($tableName, $indexedPrimaryKeys);
592
			$dbRow->_setPrimaryKeys($primaryKeys);
593
		}
594
595
		$dbRow->_setStatus($this->status);
596
597
		$this->dbRows[$tableName] = $dbRow;
598
		// TODO: look at status (if not new)=> get primary key from tdbmservice
599
	}
600
601
	/**
602
	 * Internal function: return the list of relationships
603
	 * @return \SplObjectStorage[]
604
	 */
605
	public function _getCachedRelationships() {
606
		return $this->relationships;
607
	}
608
}
609