Completed
Pull Request — 3.4 (#46)
by David
06:17
created

DbRow::_getPrimaryKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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 row in a database.
27
 *
28
 * @author David Negrier
29
 */
30
class DbRow implements \JsonSerializable, FilterInterface {
31
32
	/**
33
	 * The service this object is bound to.
34
	 * 
35
	 * @var TDBMService
36
	 */
37
	protected $tdbmService;
38
39
	/**
40
	 * The object containing this db row.
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 array
56
	 */
57
	private $dbRow = array();
58
59
	/**
60
	 * @var AbstractTDBMObject[]
61
	 */
62
	private $references = array();
63
64
	/**
65
	 * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
66
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
67
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
68
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
69
	 *
70
	 * @var string
71
	 */
72
	private $status;
73
74
	/**
75
	 * The values of the primary key.
76
	 * This is set when the object is in "loaded" state.
77
	 *
78
	 * @var array An array of column => value
79
	 */
80
	private $primaryKeys;
81
82
83
	/**
84
	 * You should never call the constructor directly. Instead, you should use the
85
	 * TDBMService class that will create TDBMObjects for you.
86
	 *
87
	 * Used with id!=false when we want to retrieve an existing object
88
	 * and id==false if we want a new object
89
	 *
90
	 * @param AbstractTDBMObject $object The object containing this db row.
91
	 * @param string $table_name
92
	 * @param array $primaryKeys
93
	 * @param TDBMService $tdbmService
94
	 * @throws TDBMException
95
	 * @throws TDBMInvalidOperationException
96
	 */
97
	public function __construct(AbstractTDBMObject $object, $table_name, array $primaryKeys=array(), TDBMService $tdbmService=null, array $dbRow = array()) {
98
		$this->object = $object;
99
		$this->dbTableName = $table_name;
100
101
		$this->status = TDBMObjectStateEnum::STATE_DETACHED;
102
103
		if ($tdbmService === null) {
104
			if (!empty($primaryKeys)) {
105
				throw new TDBMException('You cannot pass an id to the DbRow constructor without passing also a TDBMService.');
106
			}
107
		} else {
108
			$this->tdbmService = $tdbmService;
109
110
			if (!empty($primaryKeys)) {
111
				$this->_setPrimaryKeys($primaryKeys);
112
				if (!empty($dbRow)) {
113
					$this->dbRow = $dbRow;
114
					$this->status = TDBMObjectStateEnum::STATE_LOADED;
115
				} else {
116
					$this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
117
				}
118
				$tdbmService->_addToCache($this);
119
			} else {
120
				$this->status = TDBMObjectStateEnum::STATE_NEW;
121
				$this->tdbmService->_addToToSaveObjectList($this);
122
			}
123
124
		}
125
	}
126
127
	public function _attach(TDBMService $tdbmService) {
128
		if ($this->status !== TDBMObjectStateEnum::STATE_DETACHED) {
129
			throw new TDBMInvalidOperationException('Cannot attach an object that is already attached to TDBM.');
130
		}
131
		$this->tdbmService = $tdbmService;
132
		$this->status = TDBMObjectStateEnum::STATE_NEW;
133
		$this->tdbmService->_addToToSaveObjectList($this);
134
	}
135
136
	/**
137
	 * Sets the state of the TDBM Object
138
	 * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
139
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
140
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
141
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
142
	 * @param string $state
143
	 */
144
	public function _setStatus($state){
145
		$this->status = $state;
146
	}
147
148
	/**
149
	 * This is an internal method. You should not call this method yourself. The TDBM library will do it for you.
150
	 * If the object is in state 'not loaded', this method performs a query in database to load the object.
151
	 *
152
	 * A TDBMException is thrown is no object can be retrieved (for instance, if the primary key specified
153
	 * cannot be found).
154
	 */
155
	public function _dbLoadIfNotLoaded() {
156
		if ($this->status == TDBMObjectStateEnum::STATE_NOT_LOADED)
157
		{
158
			$connection = $this->tdbmService->getConnection();
159
160
			/// buildFilterFromFilterBag($filter_bag)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
161
			list($sql_where, $parameters) = $this->tdbmService->buildFilterFromFilterBag($this->primaryKeys);
162
163
			$sql = "SELECT * FROM ".$connection->quoteIdentifier($this->dbTableName)." WHERE ".$sql_where;
0 ignored issues
show
Security introduced by
'SELECT * FROM ' . $conn... ' WHERE ' . $sql_where is used as a query on line 164. If $sql_where can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $sql_where).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
164
			$result = $connection->executeQuery($sql, $parameters);
165
166
			if ($result->rowCount()==0)
167
			{
168
				throw new TDBMException("Could not retrieve object from table \"$this->dbTableName\" with ID \"".$this->TDBMObject_id."\".");
0 ignored issues
show
Bug introduced by
The property TDBMObject_id does not seem to exist. Did you mean object?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
169
			}
170
171
			$this->dbRow = $result->fetch(\PDO::FETCH_ASSOC);
172
			
173
			$result->closeCursor();
174
				
175
			$this->status = TDBMObjectStateEnum::STATE_LOADED;
176
		}
177
	}
178
179
	public function get($var) {
180
		$this->_dbLoadIfNotLoaded();
181
182
		// Let's first check if the key exist.
183
		if (!isset($this->dbRow[$var])) {
184
			/*
185
			// Unable to find column.... this is an error if the object has been retrieved from database.
186
			// If it's a new object, well, that may not be an error after all!
187
			// Let's check if the column does exist in the table
188
			$column_exist = $this->db_connection->checkColumnExist($this->dbTableName, $var);
189
			// If the column DOES exist, then the object is new, and therefore, we should
190
			// return null.
191
			if ($column_exist === true) {
192
				return null;
193
			}
194
195
			// Let's try to be accurate in error reporting. The checkColumnExist returns an array of closest matches.
196
			$result_array = $column_exist;
197
198
			if (count($result_array)==1)
199
			$str = "Could not find column \"$var\" in table \"$this->dbTableName\". Maybe you meant this column: '".$result_array[0]."'";
200
			else
201
			$str = "Could not find column \"$var\" in table \"$this->dbTableName\". Maybe you meant one of those columns: '".implode("', '",$result_array)."'";
202
203
			throw new TDBMException($str);*/
204
			return null;
205
		}
206
		return $this->dbRow[$var];
207
	}
208
209
	/**
210
	 * Returns true if a column is set, false otherwise.
211
	 * 
212
	 * @param string $var
213
	 * @return boolean
214
	 */
215
	/*public function has($var) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
216
		$this->_dbLoadIfNotLoaded();
217
218
		return isset($this->dbRow[$var]);
219
	}*/
220
	
221 View Code Duplication
	public function set($var, $value) {
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...
222
		$this->_dbLoadIfNotLoaded();
223
224
		/*
225
		// Ok, let's start by checking the column type
226
		$type = $this->db_connection->getColumnType($this->dbTableName, $var);
227
228
		// Throws an exception if the type is not ok.
229
		if (!$this->db_connection->checkType($value, $type)) {
230
			throw new TDBMException("Error! Invalid value passed for attribute '$var' of table '$this->dbTableName'. Passed '$value', but expecting '$type'");
231
		}
232
		*/
233
234
		/*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...
235
			throw new TDBMException("Error! Changing primary key value is forbidden.");*/
236
		$this->dbRow[$var] = $value;
237
		if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
238
			$this->status = TDBMObjectStateEnum::STATE_DIRTY;
239
			$this->tdbmService->_addToToSaveObjectList($this);
240
		}
241
	}
242
243
	/**
244
	 * @param string $foreignKeyName
245
	 * @param AbstractTDBMObject $bean
246
	 */
247 View Code Duplication
	public function setRef($foreignKeyName, AbstractTDBMObject $bean = 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...
248
		$this->references[$foreignKeyName] = $bean;
249
250
        if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
251
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
252
            $this->tdbmService->_addToToSaveObjectList($this);
253
        }
254
	}
255
256
    /**
257
     * @param string $foreignKeyName A unique name for this reference
258
     * @return AbstractTDBMObject|null
259
     */
260
    public function getRef($foreignKeyName) {
261
		if (isset($this->references[$foreignKeyName])) {
262
			return $this->references[$foreignKeyName];
263
		} elseif ($this->status === TDBMObjectStateEnum::STATE_NEW) {
264
            // If the object is new and has no property, then it has to be empty.
265
            return null;
266
        } else {
267
            $this->_dbLoadIfNotLoaded();
268
269
            // Let's match the name of the columns to the primary key values
270
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
271
272
            $values = [];
273
            foreach ($fk->getLocalColumns() as $column) {
274
                $values[] = $this->dbRow[$column];
275
            }
276
277
            $filter = array_combine($this->tdbmService->getPrimaryKeyColumns($fk->getForeignTableName()), $values);
278
279
            return $this->tdbmService->findObjectByPk($fk->getForeignTableName(), $filter, [], true);
280
		}
281
	}
282
283
	/**
284
	 * Returns the name of the table this object comes from.
285
	 * 
286
	 * @return string
287
	 */
288
	public function _getDbTableName() {
289
		return $this->dbTableName;
290
	}
291
	
292
	/**
293
	 * Method used internally by TDBM. You should not use it directly.
294
	 * This method returns the status of the TDBMObject.
295
	 * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
296
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
297
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
298
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
299
	 *
300
	 * @return string
301
	 */
302
	public function _getStatus() {
303
		return $this->status;
304
	}
305
306
	
307
	/**
308
	 * Implement the unique JsonSerializable method
309
	 * @return array
310
	 */
311
	public function jsonSerialize(){
312
		$this->_dbLoadIfNotLoaded();
313
		return $this->dbRow;
314
	}
315
316
	/**
317
	 * Returns the SQL of the filter (the SQL WHERE clause).
318
	 *
319
	 * @param Connection $dbConnection
320
	 * @return string
321
	 */
322
	public function toSql(Connection $dbConnection) {
323
		return $this->getPrimaryKeyWhereStatement();
0 ignored issues
show
Bug introduced by
The method getPrimaryKeyWhereStatement() 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...
324
	}
325
326
	/**
327
	 * Returns the tables used in the filter in an array.
328
	 *
329
	 * @return array<string>
330
	 */
331
	public function getUsedTables() {
332
		return array($this->dbTableName);
333
	}
334
335
    /**
336
     * Override the native php clone function for TDBMObjects
337
     */
338 View Code Duplication
    public function __clone(){
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...
339
        $this->_dbLoadIfNotLoaded();
340
        //First lets set the status to new (to enter the save function)
341
        $this->status = TDBMObjectStateEnum::STATE_NEW;
342
343
        // Add the current TDBMObject to the save object list
344
        $this->tdbmService->_addToToSaveObjectList($this);
345
346
        //Now unset the PK from the row
347
        $pk_array = $this->tdbmService->getPrimaryKeyColumns($this->dbTableName);
348
        foreach ($pk_array as $pk) {
349
            $this->dbRow[$pk] = null;
350
        }
351
    }
352
353
	/**
354
	 * Returns raw database row.
355
	 *
356
	 * @return array
357
	 */
358
	public function _getDbRow() {
359
        // Let's merge $dbRow and $references
360
        $dbRow = $this->dbRow;
361
362
        foreach ($this->references as $foreignKeyName => $reference) {
363
            // Let's match the name of the columns to the primary key values
364
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
365
            $refDbRows = $reference->_getDbRows();
366
            $firstRefDbRow = reset($refDbRows);
367
            $pkValues = array_values($firstRefDbRow->_getPrimaryKeys());
368
            $localColumns = $fk->getLocalColumns();
369
370
            for ($i=0, $count=count($localColumns); $i<$count; $i++) {
371
                $dbRow[$localColumns[$i]] = $pkValues[$i];
372
            }
373
        }
374
375
		return $dbRow;
376
	}
377
378
    /**
379
     * Returns references array.
380
     *
381
     * @return AbstractTDBMObject[]
382
     */
383
    public function _getReferences() {
384
        return $this->references;
385
    }
386
387
    /**
388
	 * @return array
389
	 */
390
	public function _getPrimaryKeys()
391
	{
392
		return $this->primaryKeys;
393
	}
394
395
	/**
396
	 * @param array $primaryKeys
397
	 */
398
	public function _setPrimaryKeys(array $primaryKeys)
399
	{
400
		$this->primaryKeys = $primaryKeys;
401
		foreach ($this->primaryKeys as $column => $value) {
402
			$this->dbRow[$column] = $value;
403
		}
404
	}
405
406
	/**
407
	 * Returns the TDBMObject this bean is associated to.
408
	 * @return AbstractTDBMObject
409
	 */
410
	public function getTDBMObject()
411
	{
412
		return $this->object;
413
	}
414
}
415