Completed
Pull Request — 4.0 (#64)
by
unknown
05:04
created

DbRow   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 409
Duplicated Lines 7.09 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 15
Bugs 1 Features 0
Metric Value
wmc 40
c 15
b 1
f 0
lcom 1
cbo 8
dl 29
loc 409
rs 8.2608

17 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 29 5
A _attach() 0 9 2
A _setStatus() 0 4 1
B _dbLoadIfNotLoaded() 0 30 4
B get() 0 40 4
A set() 21 22 3
A setRef() 8 9 3
B getRef() 0 23 4
A _getDbTableName() 0 4 1
A _getStatus() 0 4 1
A __clone() 0 18 3
A _getDbRow() 0 20 3
A _getReferences() 0 4 1
A _getPrimaryKeys() 0 4 1
A _setPrimaryKeys() 0 7 2
A getTDBMObject() 0 4 1
A setTDBMObject() 0 4 1

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DbRow often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DbRow, and based on these observations, apply Extract Interface, too.

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
/**
24
 * Instances of this class represent a row in a database.
25
 *
26
 * @author David Negrier
27
 */
28
class DbRow
29
{
30
    /**
31
     * The service this object is bound to.
32
     *
33
     * @var TDBMService
34
     */
35
    protected $tdbmService;
36
37
    /**
38
     * The object containing this db row.
39
     *
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
     * You should never call the constructor directly. Instead, you should use the
83
     * TDBMService class that will create TDBMObjects for you.
84
     *
85
     * Used with id!=false when we want to retrieve an existing object
86
     * and id==false if we want a new object
87
     *
88
     * @param AbstractTDBMObject $object      The object containing this db row.
89
     * @param string             $table_name
90
     * @param array              $primaryKeys
91
     * @param TDBMService        $tdbmService
92
     *
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
    {
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
    public function _attach(TDBMService $tdbmService)
127
    {
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
     *
143
     * @param string $state
144
     */
145
    public function _setStatus($state)
146
    {
147
        $this->status = $state;
148
    }
149
150
    /**
151
     * This is an internal method. You should not call this method yourself. The TDBM library will do it for you.
152
     * If the object is in state 'not loaded', this method performs a query in database to load the object.
153
     *
154
     * A TDBMException is thrown is no object can be retrieved (for instance, if the primary key specified
155
     * cannot be found).
156
     */
157
    public function _dbLoadIfNotLoaded()
158
    {
159
        if ($this->status == TDBMObjectStateEnum::STATE_NOT_LOADED) {
160
            $connection = $this->tdbmService->getConnection();
161
162
            /// 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...
163
            list($sql_where, $parameters) = $this->tdbmService->buildFilterFromFilterBag($this->primaryKeys);
164
165
            $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 166. 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...
166
            $result = $connection->executeQuery($sql, $parameters);
167
168
            if ($result->rowCount() === 0) {
169
                throw new TDBMException("Could not retrieve object from table \"$this->dbTableName\" using filter \"\".");
170
            }
171
172
173
            $row = $result->fetch(\PDO::FETCH_ASSOC);
174
175
            $this->dbRow = [];
176
            $types = $this->tdbmService->_getColumnTypesForTable($this->dbTableName);
177
178
            foreach ($row as $key => $value) {
179
                $this->dbRow[$key] = $types[$key]->convertToPHPValue($value, $connection->getDatabasePlatform());
180
            }
181
182
            $result->closeCursor();
183
184
            $this->status = TDBMObjectStateEnum::STATE_LOADED;
185
        }
186
    }
187
188
    public function get($var)
189
    {
190
        $this->_dbLoadIfNotLoaded();
191
192
        // Let's first check if the key exist.
193
        if (!isset($this->dbRow[$var])) {
194
            /*
195
            // Unable to find column.... this is an error if the object has been retrieved from database.
196
            // If it's a new object, well, that may not be an error after all!
197
            // Let's check if the column does exist in the table
198
            $column_exist = $this->db_connection->checkColumnExist($this->dbTableName, $var);
199
            // If the column DOES exist, then the object is new, and therefore, we should
200
            // return null.
201
            if ($column_exist === true) {
202
                return null;
203
            }
204
205
            // Let's try to be accurate in error reporting. The checkColumnExist returns an array of closest matches.
206
            $result_array = $column_exist;
207
208
            if (count($result_array)==1)
209
            $str = "Could not find column \"$var\" in table \"$this->dbTableName\". Maybe you meant this column: '".$result_array[0]."'";
210
            else
211
            $str = "Could not find column \"$var\" in table \"$this->dbTableName\". Maybe you meant one of those columns: '".implode("', '",$result_array)."'";
212
213
            throw new TDBMException($str);*/
214
            return;
215
        }
216
217
        $value = $this->dbRow[$var];
218
        if ($value instanceof \DateTime) {
219
            if (method_exists('DateTimeImmutable', 'createFromMutable')) { // PHP 5.6+ only
220
                return \DateTimeImmutable::createFromMutable($value);
221
            } else {
222
                return new \DateTimeImmutable($value->format('c'));
223
            }
224
        }
225
226
        return $this->dbRow[$var];
227
    }
228
229
    /**
230
     * Returns true if a column is set, false otherwise.
231
     *
232
     * @param string $var
233
     *
234
     * @return bool
235
     */
236
    /*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...
237
        $this->_dbLoadIfNotLoaded();
238
239
        return isset($this->dbRow[$var]);
240
    }*/
241
242 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...
243
    {
244
        $this->_dbLoadIfNotLoaded();
245
246
        /*
247
        // Ok, let's start by checking the column type
248
        $type = $this->db_connection->getColumnType($this->dbTableName, $var);
249
250
        // Throws an exception if the type is not ok.
251
        if (!$this->db_connection->checkType($value, $type)) {
252
            throw new TDBMException("Error! Invalid value passed for attribute '$var' of table '$this->dbTableName'. Passed '$value', but expecting '$type'");
253
        }
254
        */
255
256
        /*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...
257
            throw new TDBMException("Error! Changing primary key value is forbidden.");*/
258
        $this->dbRow[$var] = $value;
259
        if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
260
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
261
            $this->tdbmService->_addToToSaveObjectList($this);
262
        }
263
    }
264
265
    /**
266
     * @param string             $foreignKeyName
267
     * @param AbstractTDBMObject $bean
268
     */
269 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...
270
    {
271
        $this->references[$foreignKeyName] = $bean;
272
273
        if ($this->tdbmService !== null && $this->status === TDBMObjectStateEnum::STATE_LOADED) {
274
            $this->status = TDBMObjectStateEnum::STATE_DIRTY;
275
            $this->tdbmService->_addToToSaveObjectList($this);
276
        }
277
    }
278
279
    /**
280
     * @param string $foreignKeyName A unique name for this reference
281
     *
282
     * @return AbstractTDBMObject|null
283
     */
284
    public function getRef($foreignKeyName)
285
    {
286
        if (isset($this->references[$foreignKeyName])) {
287
            return $this->references[$foreignKeyName];
288
        } elseif ($this->status === TDBMObjectStateEnum::STATE_NEW) {
289
            // If the object is new and has no property, then it has to be empty.
290
            return;
291
        } else {
292
            $this->_dbLoadIfNotLoaded();
293
294
            // Let's match the name of the columns to the primary key values
295
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
296
297
            $values = [];
298
            foreach ($fk->getLocalColumns() as $column) {
299
                $values[] = $this->dbRow[$column];
300
            }
301
302
            $filter = array_combine($this->tdbmService->getPrimaryKeyColumns($fk->getForeignTableName()), $values);
303
304
            return $this->tdbmService->findObjectByPk($fk->getForeignTableName(), $filter, [], true);
305
        }
306
    }
307
308
    /**
309
     * Returns the name of the table this object comes from.
310
     *
311
     * @return string
312
     */
313
    public function _getDbTableName()
314
    {
315
        return $this->dbTableName;
316
    }
317
318
    /**
319
     * Method used internally by TDBM. You should not use it directly.
320
     * This method returns the status of the TDBMObject.
321
     * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED.
322
     * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject.
323
     * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet.
324
     * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory.
325
     *
326
     * @return string
327
     */
328
    public function _getStatus()
329
    {
330
        return $this->status;
331
    }
332
333
    /**
334
     * Override the native php clone function for TDBMObjects.
335
     */
336
    public function __clone()
337
    {
338
        // Let's load the row (before we lose the ID!)
339
        $this->_dbLoadIfNotLoaded();
340
341
        //Let's set the status to detached
342
        $this->status = TDBMObjectStateEnum::STATE_DETACHED;
343
344
        $this->primaryKeys = [];
345
346
        //Now unset the PK from the row
347
        if ($this->tdbmService) {
348
            $pk_array = $this->tdbmService->getPrimaryKeyColumns($this->dbTableName);
349
            foreach ($pk_array as $pk) {
350
                $this->dbRow[$pk] = null;
351
            }
352
        }
353
    }
354
355
    /**
356
     * Returns raw database row.
357
     *
358
     * @return array
359
     */
360
    public function _getDbRow()
361
    {
362
        // Let's merge $dbRow and $references
363
        $dbRow = $this->dbRow;
364
365
        foreach ($this->references as $foreignKeyName => $reference) {
366
            // Let's match the name of the columns to the primary key values
367
            $fk = $this->tdbmService->_getForeignKeyByName($this->dbTableName, $foreignKeyName);
368
            $refDbRows = $reference->_getDbRows();
369
            $firstRefDbRow = reset($refDbRows);
370
            $pkValues = array_values($firstRefDbRow->_getPrimaryKeys());
371
            $localColumns = $fk->getLocalColumns();
372
373
            for ($i = 0, $count = count($localColumns); $i < $count; ++$i) {
374
                $dbRow[$localColumns[$i]] = $pkValues[$i];
375
            }
376
        }
377
378
        return $dbRow;
379
    }
380
381
    /**
382
     * Returns references array.
383
     *
384
     * @return AbstractTDBMObject[]
385
     */
386
    public function _getReferences()
387
    {
388
        return $this->references;
389
    }
390
391
    /**
392
     * Returns the values of the primary key.
393
     * This is set when the object is in "loaded" state.
394
     *
395
     * @return array
396
     */
397
    public function _getPrimaryKeys()
398
    {
399
        return $this->primaryKeys;
400
    }
401
402
    /**
403
     * Sets the values of the primary key.
404
     * This is set when the object is in "loaded" state.
405
     *
406
     * @param array $primaryKeys
407
     */
408
    public function _setPrimaryKeys(array $primaryKeys)
409
    {
410
        $this->primaryKeys = $primaryKeys;
411
        foreach ($this->primaryKeys as $column => $value) {
412
            $this->dbRow[$column] = $value;
413
        }
414
    }
415
416
    /**
417
     * Returns the TDBMObject this bean is associated to.
418
     *
419
     * @return AbstractTDBMObject
420
     */
421
    public function getTDBMObject()
422
    {
423
        return $this->object;
424
    }
425
426
    /**
427
     * Sets the TDBMObject this bean is associated to.
428
     * Only used when cloning.
429
     *
430
     * @param AbstractTDBMObject $object
431
     */
432
    public function setTDBMObject(AbstractTDBMObject $object)
433
    {
434
        $this->object = $object;
435
    }
436
}
437