Completed
Push — master ( 1337ee...b15437 )
by Wanderson
01:57
created

DAO::addSelectCollumn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
1
<?php
2
3
namespace Win\DAO;
4
5
use Exception;
6
use PDO;
7
use PDOException;
8
use PDOStatement;
9
use Win\Alert\AlertError;
10
use Win\Connection\Mysql;
11
use Win\Mvc\Application;
12
13
/**
14
 * Data Access Object
15
 */
16
abstract class DAO implements DAOInterface {
17
18
	/** @var PDO */
19
	protected $pdo;
20
21
	/** @var string */
22
	protected $primaryKey = null;
23
24
	/** @var string[] */
25
	protected $selectCollumns = ['*'];
26
27
	/** @var string[] */
28
	protected $joins = [];
29
30
	/** @var Where */
31
	protected $where;
32
33
	/** @var Option */
34
	protected $option;
35
36
	/** @var Pagination */
37
	protected $pagination;
38
39
	/** @var boolean */
40
	protected static $debug = false;
41
	protected static $instance = [];
42
43
	/**
44
	 * Valida os campos retornando string de Erro ou Null
45
	 * @return string|null
46
	 */
47
	abstract protected function validate();
48
49
	/**
50
	 * Retorna a instancia do DAO
51
	 * @return static
52
	 */
53 View Code Duplication
	public static function instance() {
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...
54
		$class = get_called_class();
55
		if (!isset(static::$instance[$class])):
56
			static::$instance[$class] = new $class();
57
		endif;
58
		return static::$instance[$class];
59
	}
60
61
	/** Inicia o DAO */
62
	final public function __construct() {
63
		$this->pdo = Mysql::instance()->getPDO();
64
		$this->primaryKey = static::TABLE . '_id';
65
		$this->option = new Option();
66
		$this->pagination = new Pagination();
67
		$this->where = new Where();
68
	}
69
70
	public function pagination() {
71
		return $this->pagination;
72
	}
73
74
	/**
75
	 * Define uma conexão manualmente
76
	 * @param PDO $pdo
77
	 */
78
	public function setPDO($pdo) {
79
		$this->pdo = $pdo;
80
	}
81
82
	/**
83
	 * Define quais colunas serão consultadas no SELECT
84
	 * @param string[] $collumns
85
	 */
86
	public function setSelectCollumns(array $collumns) {
87
		$this->selectCollumns = $collumns;
88
	}
89
90
	/**
91
	 * Adiciona nova coluna no SELECT
92
	 * @param string $collumn
93
	 */
94
	public function addSelectCollumn($collumn) {
95
		if (!in_array($collumn, $this->selectCollumns)) {
96
			$this->selectCollumns[] = $collumn;
97
		}
98
	}
99
100
	/**
101
	 * Salva o registro
102
	 * @param object $obj
103
	 * @return string|null
104
	 */
105
	public function save($obj) {
106
		$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...
107
		$error = $this->validate();
108
109
		if (is_null($error) and $this->pdo !== false) {
110
			$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...
111
			$mode = (!$this->objExists($obj)) ? 'insert' : 'update';
112
113
			if ($mode == 'insert' && is_null($error)) {
114
				$error = $this->insert();
115
				$this->obj->setId($this->pdo->lastInsertId());
116
			} elseif (is_null($error)) {
117
				$error = $this->update();
118
			}
119
120
			if (is_null($error)) {
121
				$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...
122
			}
123
			if (!is_null($error) && $mode == 'insert') {
124
				$this->delete($obj);
125
			}
126
		}
127
		return $error;
128
	}
129
130
	/** Insere o registro */
131
	protected function insert() {
132
		$mapRow = static::mapRow($this->obj);
133
		$keys = array_keys($mapRow);
134
		$params = str_split(str_repeat('?', count($keys)));
135
136
		$sql = 'INSERT INTO ' . static::TABLE . ' (' . implode(',', $keys) . ') VALUES (' . implode(', ', $params) . ') ';
137
		$stmt = $this->exec($sql, array_values($mapRow));
138
		return $this->errorSql($stmt);
0 ignored issues
show
Bug introduced by
It seems like $stmt defined by $this->exec($sql, array_values($mapRow)) on line 137 can be null; however, Win\DAO\DAO::errorSql() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
139
	}
140
141
	/** Atualiza o registro */
142
	protected function update() {
143
		$mapRow = static::mapRow($this->obj);
144
		$keys = array_keys($mapRow);
145
		$values = array_values($mapRow);
146
		$values[] = $this->obj->getId();
147
		$params = [];
148
149
		foreach ($keys as $key):
150
			$params[] = $key . ' = ?';
151
		endforeach;
152
153
		$sql = 'UPDATE ' . static::TABLE . ' SET ' . implode(', ', $params) . ' WHERE ' . $this->primaryKey . ' = ? ';
154
		$stmt = $this->exec($sql, $values);
155
		return $this->errorSql($stmt);
0 ignored issues
show
Bug introduced by
It seems like $stmt defined by $this->exec($sql, $values) on line 154 can be null; however, Win\DAO\DAO::errorSql() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
156
	}
157
158
	/**
159
	 * Exclui o registro
160
	 * @param object $obj
161
	 * @param mixed[] $filters
0 ignored issues
show
Bug introduced by
There is no parameter named $filters. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
162
	 */
163
	public function delete($obj) {
164
		$this->obj = $obj;
165
		$this->onDelete();
166
		$where = new Where();
167
		$where->filter($this->primaryKey . ' = ?', [$obj->getId()]);
168
		$sql = 'DELETE FROM ' . static::TABLE . ' ' . $this->where->toSql();
169
		$this->exec($sql, $this->where->values());
170
	}
171
172
	/**
173
	 * Exclui o registro por id
174
	 * @param int $id
175
	 */
176
	public function deleteById($id) {
177
		$this->deleteByField($this->primaryKey . '', $id);
178
	}
179
180
	/**
181
	 * Exclui o registro por id
182
	 * @param string $name
183
	 * * @param mixed $value
184
	 */
185
	public function deleteByField($name, $value) {
186
		$this->deleteAll([$name . ' = ?' => $value]);
187
	}
188
189
	/**
190
	 * Exclui todos os registros
191
	 * @param mixed[] $filters
192
	 */
193
	public function deleteAll($filters = []) {
194
		$fixed = $this->fixedFilters;
0 ignored issues
show
Bug introduced by
The property fixedFilters 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...
195
		$this->fixedFilters = [];
196
197
		$objList = $this->fetchAll($filters);
198
		foreach ($objList as $obj):
199
			$this->delete($obj);
200
		endforeach;
201
202
		$this->fixedFilters = $fixed;
203
	}
204
205
	/**
206
	 * Executa SQL via PDO
207
	 * @param string $sql
208
	 * @param mixed[] $values
209
	 * @return PDOStatement
210
	 */
211
	protected function exec($sql, $values) {
212
		if ($this->pdo) {
213
			$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
214
			$this->showDebug($sql, $values);
215
			$stmt = $this->pdo->prepare($sql);
216
			try {
217
				$stmt->execute($values);
218
			} catch (PDOException $e) {
219
				$alert = new AlertError($this->errorSql($stmt, $e));
0 ignored issues
show
Unused Code introduced by
$alert is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
220
				//$alert->load();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% 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...
221
			}
222
			return $stmt;
223
		}
224
	}
225
226
	/**
227
	 * @param $stmt \PDOStatement
228
	 * @param PDOException|null $e
229
	 * @return string erro
230
	 */
231
	protected function errorSql(PDOStatement $stmt, PDOException $e = null) {
232
		$error = null;
233
		if ($stmt->errorCode() !== '00000') {
234
			$error = 'Houve um durante a execução do comando SQL. [Erro ' . $stmt->errorCode() . ']';
235
			if ($e instanceof PDOException) {
236
				$error .= '<br /><small>' . $e->getMessage() . '</small>';
237
			} elseif (Application::app()->isLocalHost()) {
238
				$error .= '<br /><small>' . $stmt->errorInfo()[2] . '</small>';
239
			}
240
		}
241
		return $error;
242
	}
243
244
	/**
245
	 * Busca o objeto pelo id
246
	 * @param int $id
247
	 */
248
	public function fetchById($id) {
249
		return $this->fetchByField($this->primaryKey . '', $id);
250
	}
251
252
	/**
253
	 * Busca o objeto por um campo/atributo específico
254
	 * @param string $name Nome do atributo
255
	 * @param mixed $value Valor do atributo
256
	 */
257
	public function fetchByField($name, $value) {
258
		return $this->fetch([$name . ' = ?' => $value]);
259
	}
260
261
	/**
262
	 * Busca o objeto
263
	 * @param string[] $filters Array de filtros
264
	 */
265
	public function fetch($filters) {
266
		$this->validateArray($filters);
267
268
		$this->addFilters($filters);
269
		$sql = $this->selectSQL($this->selectCollumns) . $this->where->toSql() . $this->option->toSql();
270
271
		$result = [];
272
		if ($this->pdo) {
273
			$stmt = $this->exec($sql, $this->where->values());
274
			$result = $stmt->fetch();
275
		}
276
		return static::mapObject($result);
277
	}
278
279
	/**
280
	 * Retorna todos os registros
281
	 *
282
	 * <code>
283
	 * $dao->fetchAll( ['id = ?' => 10]);
284
	 * </code>
285
	 * @param string[] $filters Array de filtros
286
	 */
287
	public function fetchAll($filters = []) {
288
		$this->validateArray($filters);
289
290
		$this->addFilters($filters);
291
		$this->pagination->setTotal($this->numRows());
292
		$sql = $this->selectSQL($this->selectCollumns) . $this->where->toSql() . $this->option->toSql() . $this->pagination->toSql();
293
294
		$array = [];
295
		if ($this->pdo) {
296
			$stmt = $this->exec($sql, $this->where->values());
297
298
			$results = $stmt->fetchAll();
299
			foreach ($results as $result):
300
				$array[] = static::mapObject($result);
301
			endforeach;
302
		}
303
304
		return $array;
305
	}
306
307
	/**
308
	 * Retorna comando SELECT
309
	 * @param string $selectCollumns
310
	 * @return string
311
	 * @example "SELECT * FROM user"
312
	 */
313
	protected function selectSQL($selectCollumns = ['*']) {
314
		return 'SELECT ' . implode(', ', $selectCollumns) . ' FROM ' . static::TABLE . implode(' ', $this->joins);
315
	}
316
317
	/**
318
	 * Adiciona o array de filtros
319
	 * @param mixed[] $filters
320
	 */
321
	private function addFilters($filters = []) {
322
		$this->filter(implode(' AND ', array_keys($filters)), $filters);
323
	}
324
325
	/**
326
	 * Filtra a proxima busca
327
	 * @param string $condition
328
	 * @param mixed[] $values
329
	 */
330
	public function filter($condition, $values) {
331
		$this->where->filter($condition, $values);
332
	}
333
334
	/**
335
	 * Adiciona opções de busca
336
	 * @param string $option
337
	 */
338
	public function option($option) {
339
		$this->option->set($option);
340
	}
341
342
	/**
343
	 * Define a paginação dos itens
344
	 * @param int $totalPerPage
345
	 * @param int $currentPage
346
	 */
347
	public function paginate($totalPerPage, $currentPage = null) {
348
		$this->pagination = new Pagination($totalPerPage, $currentPage);
349
	}
350
351
352
	/**
353
	 * Retorna o total de registros
354
	 * @param string[] $filters Array de filtros
355
	 * @return int
356
	 */
357
	public function numRows($filters = []) {
358
		$this->validateArray($filters);
359
360
		$this->addFilters($filters);
361
		$sql = 'SELECT count(*) as total FROM ' . static::TABLE . implode(' ', $this->joins) . $this->where->toSql() . $this->option->toSql();
362
363
		$total = 0;
364
		if ($this->pdo) {
365
			$stmt = $this->exec($sql, $this->where->values());
366
			$result = $stmt->fetch();
367
			$total = $result['total'];
368
		}
369
		return (int) $total;
370
	}
371
372
	/**
373
	 * Retorna True se objeto existir
374
	 * @param mixed $obj
375
	 * @return boolean
376
	 */
377
	public function objExists($obj) {
378
		return ($obj->getId() > 0 && $this->numRows([$this->primaryKey . ' = ?' => $obj->getId()]));
379
	}
380
381
	/** Define como Página 404 se o objeto não existir */
382
	public function checkFoundRegistry($obj) {
383
		if (!$this->objExists($obj)) {
384
			Application::app()->pageNotFound();
385
			Application::app()->controller->reload();
386
		}
387
	}
388
389
	private function validateArray($filters) {
390
		if (!is_array($filters)):
391
			throw new Exception("Filter: '{$filters}' must be a array");
392
		endif;
393
	}
394
395
	/** Habilita o modo debug */
396
	final public static function debug() {
397
		static::$debug = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $debug was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
398
	}
399
400
	/**
401
	 * Exibe comando SQL, se debug está habilitado
402
	 * @param string $sql
403
	 * @param mixed[] $values
404
	 */
405
	protected function showDebug($sql, $values) {
406
407
		if (static::$debug) {
408
			foreach ($values as $value):
409
				$sql = preg_replace('/\?/', '<b style="color:#D22;">"' . $value . '"</b>', $sql, 1);
410
			endforeach;
411
412
			$find = [' WHERE ', ' ' . static::TABLE . ' '];
413
			$replace = [' <b style="color:#22D">WHERE</b> ', ' </b>' . static::TABLE . ' '];
414
			echo '<pre><b>' . str_replace($find, $replace, $sql) . '</pre>';
415
		}
416
	}
417
418
	protected function beforeSave() {
419
		
420
	}
421
422
	protected function afterSave() {
423
		
424
	}
425
426
	protected function onDelete() {
427
		
428
	}
429
430
}
431