Completed
Push — master ( a0b85b...774d12 )
by Wanderson
20:21
created

DAO   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 323
Duplicated Lines 8.67 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 3
dl 28
loc 323
rs 8.439
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
mapObject() 0 1 ?
mapRow() 0 1 ?
validate() 0 1 ?
A __construct() 0 3 1
A setPDO() 0 3 1
A setSelectCollumns() 0 3 1
A addSelectCollumn() 0 5 2
B save() 0 17 7
A insert() 5 14 2
A update() 5 18 3
A error() 0 10 3
A delete() 0 3 1
A deleteById() 0 3 1
A deleteByField() 0 9 2
A fetchById() 0 3 1
A fetchByField() 0 3 1
A fetch() 0 15 3
A fetchAll() 10 19 4
A selectSQL() 0 3 1
A whereSQL() 0 4 2
A numRows() 8 18 3
A objExists() 0 3 1
A checkFoundRegistry() 0 6 2
A debug() 0 7 2
A getFilterValues() 0 3 1
A beforeSave() 0 3 1
A afterSave() 0 3 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 DAO 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 DAO, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Win\DAO;
4
5
use Win\Mvc\Application;
6
use Win\Connection\MySQL;
7
8
/**
9
 * Data Access Object
10
 */
11
abstract class DAO implements DAOInterface {
12
13
	/** @var \PDO */
14
	protected $pdo;
15
16
	const JOIN = '';
17
18
	protected $fixedFilter = [];
19
20
	/** @var string[] */
21
	protected $selectCollumns = ['*'];
22
23
	/** @var boolean */
24
	public static $debug = false;
25
26
	/**
27
	 * Retorna um objeto a partir da linha da tabela
28
	 * @param array[] $row
29
	 */
30
	abstract protected function mapObject($row);
31
32
	/**
33
	 * Retorna a linha da tabela a partir de um objeto
34
	 * @param object $obj
35
	 */
36
	abstract protected function mapRow($obj);
37
38
	/**
39
	 * Valida os campos retornando string de Erro ou Null
40
	 * @return string|null
41
	 */
42
	abstract protected function validate();
43
44
	/** Inicia o DAO */
45
	public function __construct() {
46
		$this->pdo = MySQL::instance()->getPDO();
47
	}
48
49
	/**
50
	 * Define uma conexão manualmente
51
	 * @param \PDO $pdo
52
	 */
53
	public function setPDO($pdo) {
54
		$this->pdo = $pdo;
55
	}
56
57
	/**
58
	 * Define quais colunas serão consultadas no SELECT
59
	 * @param string[] $collumns
60
	 */
61
	public function setSelectCollumns(array $collumns) {
62
		$this->selectCollumns = $collumns;
63
	}
64
65
	/**
66
	 * Adiciona nova coluna no SELECT
67
	 * @param string $collumn
68
	 */
69
	public function addSelectCollumn($collumn) {
70
		if (!in_array($collumn, $this->selectCollumns)) {
71
			$this->selectCollumns[] = $collumn;
72
		}
73
	}
74
75
	/**
76
	 * Salva o registro
77
	 * @param object $obj
78
	 * @return string|null
79
	 */
80
	public function save($obj) {
81
		$this->obj = $obj;
0 ignored issues
show
Bug introduced by
The property obj 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...
82
		$error = $this->validate();
83
		if (is_null($error) and $this->pdo !== false) {
84
			$error = $this->beforeSave();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $error is correct as $this->beforeSave() (which targets Win\DAO\DAO::beforeSave()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
85
			if (!$this->objExists($obj) && is_null($error)) {
86
				$error = $this->insert();
87
				$this->obj->setId($this->pdo->lastInsertId());
88
			} elseif (is_null($error)) {
89
				$error = $this->update();
90
			}
91
			if (is_null($error)) {
92
				$error = $this->afterSave();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $error is correct as $this->afterSave() (which targets Win\DAO\DAO::afterSave()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
93
			}
94
		}
95
		return $error;
96
	}
97
98
	/** Insere o registro */
99
	protected function insert() {
100
		$mapRow = $this->mapRow($this->obj);
101
		$keys = array_keys($mapRow);
102
		$values = array_values($mapRow);
103
		$params = str_split(str_repeat('?', count($keys)));
104
105
		$sql = 'INSERT INTO ' . static::TABLE . ' (' . implode(',', $keys) . ') VALUES (' . implode(', ', $params) . ') ';
106 View Code Duplication
		if ($this->pdo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
107
			$this->debug($sql, $values);
108
			$stmt = $this->pdo->prepare($sql);
109
			$stmt->execute($values);
110
		}
111
		return $this->error($stmt);
0 ignored issues
show
Bug introduced by
The variable $stmt does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
112
	}
113
114
	/** Atualiza o registro */
115
	protected function update() {
116
		$mapRow = $this->mapRow($this->obj);
117
		$keys = array_keys($mapRow);
118
		$values = array_values($mapRow);
119
		$params = [];
120
		foreach ($keys as $key):
121
			$params[] = $key . ' = ?';
122
		endforeach;
123
		$values[] = $this->obj->getId();
124
125
		$sql = 'UPDATE ' . static::TABLE . ' SET ' . implode(', ', $params) . ' WHERE ' . static::TABLE . '_id = ? ';
126 View Code Duplication
		if ($this->pdo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
127
			$this->debug($sql, $values);
128
			$stmt = $this->pdo->prepare($sql);
129
			$stmt->execute($values);
130
		}
131
		return $this->error($stmt);
0 ignored issues
show
Bug introduced by
The variable $stmt does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
132
	}
133
134
	/**
135
	 * @param $stmt \PDOStatement
136
	 * @return string erro
137
	 */
138
	protected function error(\PDOStatement $stmt) {
139
		$error = null;
140
		if ($stmt->errorCode() !== '00000') {
141
			$error = 'Houve um erro ao salvar o registro. [Erro ' . $stmt->errorCode() . ']';
142
			if (Application::app()->isLocalHost()) {
143
				$error .= '<br /><small>' . $stmt->errorInfo()[2] . '</small>';
144
			}
145
		}
146
		return $error;
147
	}
148
149
	/**
150
	 * Exclui o registro
151
	 * @param object $obj
152
	 */
153
	public function delete($obj) {
154
		$this->deleteById($obj->getId());
155
	}
156
157
	/**
158
	 * Exclui o registro por id
159
	 * @param int $id
160
	 */
161
	public function deleteById($id) {
162
		$this->deleteByField(static::TABLE . '_id', $id);
163
	}
164
165
	/**
166
	 * Exclui o registro por id
167
	 * @param string $name
168
	 * * @param mixed $value
169
	 */
170
	public function deleteByField($name, $value) {
171
		$sql = 'DELETE FROM ' . static::TABLE . ' WHERE ' . $name . ' = :value';
172
		if ($this->pdo) {
173
			$this->debug($sql, $value);
174
			$stmt = $this->pdo->prepare($sql);
175
			$stmt->bindValue(':value', $value);
176
			$stmt->execute();
177
		}
178
	}
179
180
	/**
181
	 * Busca o objeto pelo id
182
	 * @param int $id
183
	 */
184
	public function fetchById($id) {
185
		return $this->fetchByField(static::TABLE . '_id', $id);
186
	}
187
188
	/**
189
	 * Busca o objeto por um campo/atributo específico
190
	 * @param string $name Nome do atributo
191
	 * @param mixed $value Valor do atributo
192
	 */
193
	public function fetchByField($name, $value) {
194
		return $this->fetch([$name . ' = ?' => $value]);
195
	}
196
197
	/**
198
	 * Busca o objeto
199
	 * @param string[] $filters Array de filtros
200
	 * @param string $option [Order by, Limit, etc]
201
	 */
202
	public function fetch($filters, $option = 'ORDER BY 1 DESC') {
203
		if (!is_array($filters)):
204
			throw new \Exception("Filter: '{$filters}' must be a array");
205
		endif;
206
		$sql = $this->selectSQL() . ' ' . ' ' . $this->whereSQL($filters) . ' ' . $option;
207
		$result = [];
208
		if ($this->pdo) {
209
			$this->debug($sql, $this->getFilterValues($filters));
210
			$stmt = $this->pdo->prepare($sql);
211
			$stmt->execute($this->getFilterValues($filters));
212
213
			$result = $stmt->fetch();
214
		}
215
		return $this->mapObject($result);
216
	}
217
218
	/**
219
	 * Retorna todos os registros
220
	 *
221
	 * <code>
222
	 * $dao->fetchAll( ['id = ?' => 10]);
223
	 * </code>
224
	 * @param string[] $filters Array de filtros
225
	 * @param string $option [Order by, Limit, etc]
226
	 */
227
	public function fetchAll($filters = [], $option = 'ORDER BY 1 DESC') {
228
		$array = [];
229
		if (!is_array($filters)):
230
			throw new \Exception("Filter: '{$filters}' must be a array");
231
		endif;
232
233
		$sql = $this->selectSQL($this->selectCollumns) . ' ' . $this->whereSQL($filters) . ' ' . $option;
234 View Code Duplication
		if ($this->pdo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
235
			$this->debug($sql, $this->getFilterValues($filters));
236
			$stmt = $this->pdo->prepare($sql);
237
			$stmt->execute($this->getFilterValues($filters));
238
239
			$results = $stmt->fetchAll();
240
			foreach ($results as $result):
241
				$array[] = $this->mapObject($result);
242
			endforeach;
243
		}
244
		return $array;
245
	}
246
247
	/**
248
	 * Retorna comando SELECT
249
	 * @param string $selectCollumns
250
	 * @return string
251
	 * @example "SELECT * FROM user"
252
	 */
253
	protected function selectSQL($selectCollumns = ['*']) {
254
		return 'SELECT ' . implode(', ', $selectCollumns) . ' FROM ' . static::TABLE . ' ' . static::JOIN;
255
	}
256
257
	/**
258
	 * Retorna comando WHERE
259
	 * @param string[] $filters
260
	 * @return string
261
	 */
262
	protected function whereSQL(&$filters) {
263
		$keys = array_keys($filters + $this->fixedFilter);
264
		return ($keys) ? 'WHERE ' . implode(' AND ', $keys) : '';
265
	}
266
267
	/**
268
	 * Retorna o total de registros
269
	 * @param string[] $filters Array de filtros
270
	 * @param string $option
271
	 * @return int
272
	 */
273
	public function numRows($filters = [], $option = 'ORDER BY 1 DESC') {
274
		$total = 0;
275
		if (!is_array($filters)):
276
			throw new \Exception("Filter: '{$filters}' must be a array");
277
		endif;
278
279
		$sql = 'SELECT count(*) as total FROM ' . static::TABLE . ' ' . static::JOIN . ' ' . $this->whereSQL($filters) . ' ' . $option;
280
281 View Code Duplication
		if ($this->pdo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
282
			$this->debug($sql, $this->getFilterValues($filters));
283
			$stmt = $this->pdo->prepare($sql);
284
			$stmt->execute($this->getFilterValues($filters));
285
286
			$result = $stmt->fetch();
287
			$total = $result['total'];
288
		}
289
		return (int) $total;
290
	}
291
292
	/**
293
	 * Retorna True se objeto existir
294
	 * @return boolean
295
	 */
296
	protected function objExists($obj) {
297
		return ($obj->getId() > 0);
298
	}
299
300
	/** Define como Página 404 se o objeto não existir */
301
	public function checkFoundRegistry($obj) {
302
		if (!$this->objExists($obj)) {
303
			Application::app()->pageNotFound();
304
			Application::app()->controller->reload();
305
		}
306
	}
307
308
	/**
309
	 * Exibe comando SQL, se debug está habilitado
310
	 * @param string $sql
311
	 * @param mixed[] $values
312
	 */
313
	protected function debug($sql, $values = []) {
314
		if (static::$debug) {
315
			var_dump($sql);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($sql); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
316
			var_dump($values);
317
			var_dump('...');
318
		}
319
	}
320
321
	private function getFilterValues($filters) {
322
		return array_values($filters + $this->fixedFilter);
323
	}
324
325
	protected function beforeSave() {
326
		
327
	}
328
329
	protected function afterSave() {
330
		
331
	}
332
333
}
334