Completed
Pull Request — 3.4 (#46)
by David
12:52 queued 01:27
created

DbRow::_setPrimaryKeys()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
namespace Mouf\Database\TDBM;
3
4
/*
5
 Copyright (C) 2006-2016 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
23
24
/**
25
 * Instances of this class represent a row in a database.
26
 *
27
 * @author David Negrier
28
 */
29
class DbRow {
30
31
	/**
32
	 * The service this object is bound to.
33
	 * 
34
	 * @var TDBMService
35
	 */
36
	protected $tdbmService;
37
38
	/**
39
	 * The object containing this db row.
40
	 * @var AbstractTDBMObject
41
	 */
42
	private $object;
43
44
	/**
45
	 * The name of the table the object if issued from
46
	 *
47
	 * @var string
48
	 */
49
	private $dbTableName;
50
51
	/**
52
	 * The array of columns returned from database.
53
	 *
54
	 * @var array
55
	 */
56
	private $dbRow = array();
57
58
	/**
59
	 * @var AbstractTDBMObject[]
60
	 */
61
	private $references = array();
62
63
	/**
64
	 * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
65
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
66
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
67
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
68
	 *
69
	 * @var string
70
	 */
71
	private $status;
72
73
	/**
74
	 * The values of the primary key.
75
	 * This is set when the object is in "loaded" state.
76
	 *
77
	 * @var array An array of column => value
78
	 */
79
	private $primaryKeys;
80
81
82
	/**
83
	 * You should never call the constructor directly. Instead, you should use the
84
	 * TDBMService class that will create TDBMObjects for you.
85
	 *
86
	 * Used with id!=false when we want to retrieve an existing object
87
	 * and id==false if we want a new object
88
	 *
89
	 * @param AbstractTDBMObject $object The object containing this db row.
90
	 * @param string $table_name
91
	 * @param array $primaryKeys
92
	 * @param TDBMService $tdbmService
93
	 * @throws TDBMException
94
	 * @throws TDBMInvalidOperationException
95
	 */
96
	public function __construct(AbstractTDBMObject $object, $table_name, array $primaryKeys=array(), TDBMService $tdbmService=null, array $dbRow = array()) {
97
		$this->object = $object;
98
		$this->dbTableName = $table_name;
99
100
		$this->status = TDBMObjectStateEnum::STATE_DETACHED;
101
102
		if ($tdbmService === null) {
103
			if (!empty($primaryKeys)) {
104
				throw new TDBMException('You cannot pass an id to the DbRow constructor without passing also a TDBMService.');
105
			}
106
		} else {
107
			$this->tdbmService = $tdbmService;
108
109
			if (!empty($primaryKeys)) {
110
				$this->_setPrimaryKeys($primaryKeys);
111
				if (!empty($dbRow)) {
112
					$this->dbRow = $dbRow;
113
					$this->status = TDBMObjectStateEnum::STATE_LOADED;
114
				} else {
115
					$this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
116
				}
117
				$tdbmService->_addToCache($this);
118
			} else {
119
				$this->status = TDBMObjectStateEnum::STATE_NEW;
120
				$this->tdbmService->_addToToSaveObjectList($this);
121
			}
122
123
		}
124
	}
125
126
	public function _attach(TDBMService $tdbmService) {
127
		if ($this->status !== TDBMObjectStateEnum::STATE_DETACHED) {
128
			throw new TDBMInvalidOperationException('Cannot attach an object that is already attached to TDBM.');
129
		}
130
		$this->tdbmService = $tdbmService;
131
		$this->status = TDBMObjectStateEnum::STATE_NEW;
132
		$this->tdbmService->_addToToSaveObjectList($this);
133
	}
134
135
	/**
136
	 * Sets the state of the TDBM Object
137
	 * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
138
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
139
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
140
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
141
	 * @param string $state
142
	 */
143
	public function _setStatus($state){
144
		$this->status = $state;
145
	}
146
147
	/**
148
	 * This is an internal method. You should not call this method yourself. The TDBM library will do it for you.
149
	 * If the object is in state 'not loaded', this method performs a query in database to load the object.
150
	 *
151
	 * A TDBMException is thrown is no object can be retrieved (for instance, if the primary key specified
152
	 * cannot be found).
153
	 */
154
	public function _dbLoadIfNotLoaded() {
155
		if ($this->status == TDBMObjectStateEnum::STATE_NOT_LOADED)
156
		{
157
			$connection = $this->tdbmService->getConnection();
158
159
			/// 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...
160
			list($sql_where, $parameters) = $this->tdbmService->buildFilterFromFilterBag($this->primaryKeys);
161
162
			$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 163. 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...
163
			$result = $connection->executeQuery($sql, $parameters);
164
165
			if ($result->rowCount()==0)
166
			{
167
				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...
168
			}
169
170
			$this->dbRow = $result->fetch(\PDO::FETCH_ASSOC);
171
			
172
			$result->closeCursor();
173
				
174
			$this->status = TDBMObjectStateEnum::STATE_LOADED;
175
		}
176
	}
177
178
	public function get($var) {
179
		$this->_dbLoadIfNotLoaded();
180
181
		// Let's first check if the key exist.
182
		if (!isset($this->dbRow[$var])) {
183
			/*
184
			// Unable to find column.... this is an error if the object has been retrieved from database.
185
			// If it's a new object, well, that may not be an error after all!
186
			// Let's check if the column does exist in the table
187
			$column_exist = $this->db_connection->checkColumnExist($this->dbTableName, $var);
188
			// If the column DOES exist, then the object is new, and therefore, we should
189
			// return null.
190
			if ($column_exist === true) {
191
				return null;
192
			}
193
194
			// Let's try to be accurate in error reporting. The checkColumnExist returns an array of closest matches.
195
			$result_array = $column_exist;
196
197
			if (count($result_array)==1)
198
			$str = "Could not find column \"$var\" in table \"$this->dbTableName\". Maybe you meant this column: '".$result_array[0]."'";
199
			else
200
			$str = "Could not find column \"$var\" in table \"$this->dbTableName\". Maybe you meant one of those columns: '".implode("', '",$result_array)."'";
201
202
			throw new TDBMException($str);*/
203
			return null;
204
		}
205
		return $this->dbRow[$var];
206
	}
207
208
	/**
209
	 * Returns true if a column is set, false otherwise.
210
	 * 
211
	 * @param string $var
212
	 * @return boolean
213
	 */
214
	/*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...
215
		$this->_dbLoadIfNotLoaded();
216
217
		return isset($this->dbRow[$var]);
218
	}*/
219
	
220 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...
221
		$this->_dbLoadIfNotLoaded();
222
223
		/*
224
		// Ok, let's start by checking the column type
225
		$type = $this->db_connection->getColumnType($this->dbTableName, $var);
226
227
		// Throws an exception if the type is not ok.
228
		if (!$this->db_connection->checkType($value, $type)) {
229
			throw new TDBMException("Error! Invalid value passed for attribute '$var' of table '$this->dbTableName'. Passed '$value', but expecting '$type'");
230
		}
231
		*/
232
233
		/*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...
234
			throw new TDBMException("Error! Changing primary key value is forbidden.");*/
235
		$this->dbRow[$var] = $value;
236
		if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
237
			$this->status = TDBMObjectStateEnum::STATE_DIRTY;
238
			$this->tdbmService->_addToToSaveObjectList($this);
239
		}
240
	}
241
242
	/**
243
	 * @param string $foreignKeyName
244
	 * @param AbstractTDBMObject $bean
245
	 */
246 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...
247
		$this->references[$foreignKeyName] = $bean;
248
249
        if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
250
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
251
            $this->tdbmService->_addToToSaveObjectList($this);
252
        }
253
	}
254
255
    /**
256
     * @param string $foreignKeyName A unique name for this reference
257
     * @return AbstractTDBMObject|null
258
     */
259
    public function getRef($foreignKeyName) {
260
		if (isset($this->references[$foreignKeyName])) {
261
			return $this->references[$foreignKeyName];
262
		} elseif ($this->status === TDBMObjectStateEnum::STATE_NEW) {
263
            // If the object is new and has no property, then it has to be empty.
264
            return null;
265
        } else {
266
            $this->_dbLoadIfNotLoaded();
267
268
            // Let's match the name of the columns to the primary key values
269
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
270
271
            $values = [];
272
            foreach ($fk->getLocalColumns() as $column) {
273
                $values[] = $this->dbRow[$column];
274
            }
275
276
            $filter = array_combine($this->tdbmService->getPrimaryKeyColumns($fk->getForeignTableName()), $values);
277
278
            return $this->tdbmService->findObjectByPk($fk->getForeignTableName(), $filter, [], true);
279
		}
280
	}
281
282
	/**
283
	 * Returns the name of the table this object comes from.
284
	 * 
285
	 * @return string
286
	 */
287
	public function _getDbTableName() {
288
		return $this->dbTableName;
289
	}
290
	
291
	/**
292
	 * Method used internally by TDBM. You should not use it directly.
293
	 * This method returns the status of the TDBMObject.
294
	 * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
295
	 * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
296
	 * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
297
	 * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
298
	 *
299
	 * @return string
300
	 */
301
	public function _getStatus() {
302
		return $this->status;
303
	}
304
305
    /**
306
     * Override the native php clone function for TDBMObjects
307
     */
308
    public function __clone(){
309
		// Let's load the row (before we lose the ID!)
310
		$this->_dbLoadIfNotLoaded();
311
312
		//Let's set the status to detached
313
        $this->status = TDBMObjectStateEnum::STATE_DETACHED;
314
315
		$this->primaryKeys = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $primaryKeys.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
316
317
        //Now unset the PK from the row
318
		if ($this->tdbmService) {
319
			$pk_array = $this->tdbmService->getPrimaryKeyColumns($this->dbTableName);
320
			foreach ($pk_array as $pk) {
321
				$this->dbRow[$pk] = null;
322
			}
323
		}
324
    }
325
326
	/**
327
	 * Returns raw database row.
328
	 *
329
	 * @return array
330
	 */
331
	public function _getDbRow() {
332
        // Let's merge $dbRow and $references
333
        $dbRow = $this->dbRow;
334
335
        foreach ($this->references as $foreignKeyName => $reference) {
336
            // Let's match the name of the columns to the primary key values
337
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
338
            $refDbRows = $reference->_getDbRows();
339
            $firstRefDbRow = reset($refDbRows);
340
            $pkValues = array_values($firstRefDbRow->_getPrimaryKeys());
341
            $localColumns = $fk->getLocalColumns();
342
343
            for ($i=0, $count=count($localColumns); $i<$count; $i++) {
344
                $dbRow[$localColumns[$i]] = $pkValues[$i];
345
            }
346
        }
347
348
		return $dbRow;
349
	}
350
351
    /**
352
     * Returns references array.
353
     *
354
     * @return AbstractTDBMObject[]
355
     */
356
    public function _getReferences() {
357
        return $this->references;
358
    }
359
360
    /**
361
	 * Returns the values of the primary key.
362
	 * This is set when the object is in "loaded" state.
363
	 *
364
	 * @return array
365
	 */
366
	public function _getPrimaryKeys()
367
	{
368
		return $this->primaryKeys;
369
	}
370
371
	/**
372
	 * Sets the values of the primary key.
373
	 * This is set when the object is in "loaded" state.
374
	 *
375
	 * @param array $primaryKeys
376
	 */
377
	public function _setPrimaryKeys(array $primaryKeys)
378
	{
379
		$this->primaryKeys = $primaryKeys;
380
		foreach ($this->primaryKeys as $column => $value) {
381
			$this->dbRow[$column] = $value;
382
		}
383
	}
384
385
	/**
386
	 * Returns the TDBMObject this bean is associated to.
387
	 * @return AbstractTDBMObject
388
	 */
389
	public function getTDBMObject()
390
	{
391
		return $this->object;
392
	}
393
}
394