Completed
Pull Request — 3.4 (#46)
by David
20:58
created

DbRow   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 387
Duplicated Lines 11.63 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 38
c 4
b 0
f 0
lcom 1
cbo 7
dl 45
loc 387
rs 8.4

19 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 29 5
A _attach() 0 8 2
A _setStatus() 0 3 1
A _dbLoadIfNotLoaded() 0 23 3
B get() 0 29 2
A set() 23 23 3
A setRef() 8 8 3
B getRef() 0 22 4
A _getDbTableName() 0 3 1
A _getStatus() 0 3 1
A jsonSerialize() 0 4 1
A toSql() 0 3 1
A getUsedTables() 0 3 1
A __clone() 14 14 2
A _getDbRow() 0 19 3
A _getReferences() 0 3 1
A _getPrimaryKeys() 0 4 1
A _setPrimaryKeys() 0 7 2
A getTDBMObject() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
		// Unset the error since something has changed (Insert or Update could work this time).
242
		$this->db_onerror = false;
0 ignored issues
show
Bug introduced by
The property db_onerror 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...
243
	}
244
245
	/**
246
	 * @param string $foreignKeyName
247
	 * @param AbstractTDBMObject $bean
248
	 */
249 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...
250
		$this->references[$foreignKeyName] = $bean;
251
252
        if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
253
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
254
            $this->tdbmService->_addToToSaveObjectList($this);
255
        }
256
	}
257
258
    /**
259
     * @param string $foreignKeyName A unique name for this reference
260
     * @return AbstractTDBMObject|null
261
     */
262
    public function getRef($foreignKeyName) {
263
		if (isset($this->references[$foreignKeyName])) {
264
			return $this->references[$foreignKeyName];
265
		} elseif ($this->status === TDBMObjectStateEnum::STATE_NEW) {
266
            // If the object is new and has no property, then it has to be empty.
267
            return null;
268
        } else {
269
            $this->_dbLoadIfNotLoaded();
270
271
            // Let's match the name of the columns to the primary key values
272
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
273
274
            $values = [];
275
            foreach ($fk->getLocalColumns() as $column) {
276
                $values[] = $this->dbRow[$column];
277
            }
278
279
            $filter = array_combine($this->tdbmService->getPrimaryKeyColumns($fk->getForeignTableName()), $values);
280
281
            return $this->tdbmService->findObjectByPk($fk->getForeignTableName(), $filter, [], true);
282
		}
283
	}
284
285
	/**
286
	 * Returns the name of the table this object comes from.
287
	 * 
288
	 * @return string
289
	 */
290
	public function _getDbTableName() {
291
		return $this->dbTableName;
292
	}
293
	
294
	/**
295
	 * Method used internally by TDBM. You should not use it directly.
296
	 * This method returns the status of the TDBMObject.
297
	 * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
298
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
299
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
300
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
301
	 *
302
	 * @return string
303
	 */
304
	public function _getStatus() {
305
		return $this->status;
306
	}
307
308
	
309
	/**
310
	 * Implement the unique JsonSerializable method
311
	 * @return array
312
	 */
313
	public function jsonSerialize(){
314
		$this->_dbLoadIfNotLoaded();
315
		return $this->dbRow;
316
	}
317
318
	/**
319
	 * Returns the SQL of the filter (the SQL WHERE clause).
320
	 *
321
	 * @param Connection $dbConnection
322
	 * @return string
323
	 */
324
	public function toSql(Connection $dbConnection) {
325
		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...
326
	}
327
328
	/**
329
	 * Returns the tables used in the filter in an array.
330
	 *
331
	 * @return array<string>
332
	 */
333
	public function getUsedTables() {
334
		return array($this->dbTableName);
335
	}
336
337
    /**
338
     * Override the native php clone function for TDBMObjects
339
     */
340 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...
341
        $this->_dbLoadIfNotLoaded();
342
        //First lets set the status to new (to enter the save function)
343
        $this->status = TDBMObjectStateEnum::STATE_NEW;
344
345
        // Add the current TDBMObject to the save object list
346
        $this->tdbmService->_addToToSaveObjectList($this);
347
348
        //Now unset the PK from the row
349
        $pk_array = $this->tdbmService->getPrimaryKeyColumns($this->dbTableName);
350
        foreach ($pk_array as $pk) {
351
            $this->dbRow[$pk] = null;
352
        }
353
    }
354
355
	/**
356
	 * Returns raw database row.
357
	 *
358
	 * @return array
359
	 */
360
	public function _getDbRow() {
361
        // Let's merge $dbRow and $references
362
        $dbRow = $this->dbRow;
363
364
        foreach ($this->references as $foreignKeyName => $reference) {
365
            // Let's match the name of the columns to the primary key values
366
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
367
            $refDbRows = $reference->_getDbRows();
368
            $firstRefDbRow = reset($refDbRows);
369
            $pkValues = array_values($firstRefDbRow->_getPrimaryKeys());
370
            $localColumns = $fk->getLocalColumns();
371
372
            for ($i=0, $count=count($localColumns); $i<$count; $i++) {
373
                $dbRow[$localColumns[$i]] = $pkValues[$i];
374
            }
375
        }
376
377
		return $dbRow;
378
	}
379
380
    /**
381
     * Returns references array.
382
     *
383
     * @return AbstractTDBMObject[]
384
     */
385
    public function _getReferences() {
386
        return $this->references;
387
    }
388
389
    /**
390
	 * @return array
391
	 */
392
	public function _getPrimaryKeys()
393
	{
394
		return $this->primaryKeys;
395
	}
396
397
	/**
398
	 * @param array $primaryKeys
399
	 */
400
	public function _setPrimaryKeys(array $primaryKeys)
401
	{
402
		$this->primaryKeys = $primaryKeys;
403
		foreach ($this->primaryKeys as $column => $value) {
404
			$this->dbRow[$column] = $value;
405
		}
406
	}
407
408
	/**
409
	 * Returns the TDBMObject this bean is associated to.
410
	 * @return AbstractTDBMObject
411
	 */
412
	public function getTDBMObject()
413
	{
414
		return $this->object;
415
	}
416
}
417