Completed
Push — master ( a9fbcc...a7b0c2 )
by Wanderson
02:19
created

DAO::limitSQL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
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
	/** @var string[] */
17
	protected $selectCollumns = ['*'];
18
	protected $joins = [];
19
	protected $fixedFilters = [];
20
21
	/** @var string */
22
	protected $primaryKey = null;
23
24
	/** @var int */
25
	protected $totalPerPage = null;
26
27
	/** @var boolean */
28
	public static $debug = false;
29
30
	/**
31
	 * Valida os campos retornando string de Erro ou Null
32
	 * @return string|null
33
	 */
34
	abstract protected function validate();
35
36
	/** Inicia o DAO */
37
	public function __construct() {
38
		$this->pdo = MySQL::instance()->getPDO();
39
	}
40
41
	/**
42
	 * Define uma conexão manualmente
43
	 * @param \PDO $pdo
44
	 */
45
	public function setPDO($pdo) {
46
		$this->pdo = $pdo;
47
	}
48
49
	/**
50
	 * Define quais colunas serão consultadas no SELECT
51
	 * @param string[] $collumns
52
	 */
53
	public function setSelectCollumns(array $collumns) {
54
		$this->selectCollumns = $collumns;
55
	}
56
57
	/**
58
	 * Adiciona nova coluna no SELECT
59
	 * @param string $collumn
60
	 */
61
	public function addSelectCollumn($collumn) {
62
		if (!in_array($collumn, $this->selectCollumns)) {
63
			$this->selectCollumns[] = $collumn;
64
		}
65
	}
66
67
	/**
68
	 * Salva o registro
69
	 * @param object $obj
70
	 * @return string|null
71
	 */
72
	public function save($obj) {
73
		$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...
74
		$error = $this->validate();
75
76
		if (is_null($error) and $this->pdo !== false) {
77
			$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...
78
			$mode = (!$this->objExists($obj)) ? 'insert' : 'update';
79
80
			if ($mode == 'insert' && is_null($error)) {
81
				$error = $this->insert();
82
				$this->obj->setId($this->pdo->lastInsertId());
83
			} elseif (is_null($error)) {
84
				$error = $this->update();
85
			}
86
87
			if (is_null($error)) {
88
				$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...
89
			}
90
			if (!is_null($error) && $mode == 'insert') {
91
				$this->delete($obj);
92
			}
93
		}
94
		return $error;
95
	}
96
97
	/**
98
	 * Executa SQL via PDO
99
	 * @param string $sql
100
	 * @param mixed[] $values
101
	 * @return \PDOStatement
102
	 */
103
	protected function execSql($sql, $values) {
104
		if ($this->pdo) {
105
			$this->debug($sql, $values);
106
			$stmt = $this->pdo->prepare($sql);
107
			$stmt->execute($values);
108
			return $stmt;
109
		}
110
	}
111
112
	/** Insere o registro */
113
	protected function insert() {
114
		$mapRow = static::mapRow($this->obj);
115
		$keys = array_keys($mapRow);
116
		$params = str_split(str_repeat('?', count($keys)));
117
118
		$sql = 'INSERT INTO ' . static::TABLE . ' (' . implode(',', $keys) . ') VALUES (' . implode(', ', $params) . ') ';
119
		$stmt = $this->execSql($sql, array_values($mapRow));
120
		return $this->error($stmt);
0 ignored issues
show
Bug introduced by
It seems like $stmt defined by $this->execSql($sql, array_values($mapRow)) on line 119 can be null; however, Win\DAO\DAO::error() 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...
121
	}
122
123
	/** Atualiza o registro */
124
	protected function update() {
125
		$mapRow = static::mapRow($this->obj);
126
		$keys = array_keys($mapRow);
127
		$values = array_values($mapRow);
128
		$values[] = $this->obj->getId();
129
		$params = [];
130
131
		foreach ($keys as $key):
132
			$params[] = $key . ' = ?';
133
		endforeach;
134
135
		$sql = 'UPDATE ' . static::TABLE . ' SET ' . implode(', ', $params) . ' WHERE ' . $this->getPrimaryKey() . ' = ? ';
136
		$stmt = $this->execSql($sql, $values);
137
		return $this->error($stmt);
0 ignored issues
show
Bug introduced by
It seems like $stmt defined by $this->execSql($sql, $values) on line 136 can be null; however, Win\DAO\DAO::error() 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...
138
	}
139
140
	/**
141
	 * @param $stmt \PDOStatement
142
	 * @return string erro
143
	 */
144
	protected function error(\PDOStatement $stmt) {
145
		$error = null;
146
		if ($stmt->errorCode() !== '00000') {
147
			$error = 'Houve um erro ao salvar o registro. [Erro ' . $stmt->errorCode() . ']';
148
			if (Application::app()->isLocalHost()) {
149
				$error .= '<br /><small>' . $stmt->errorInfo()[2] . '</small>';
150
			}
151
		}
152
		return $error;
153
	}
154
155
	/**
156
	 * Exclui o registro
157
	 * @param object $obj
158
	 * @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...
159
	 */
160
	public function delete($obj) {
161
		$this->obj = $obj;
162
		$this->onDelete();
163
		$filters = [$this->getPrimaryKey() . ' = ?' => $obj->getId()];
164
		$sql = 'DELETE FROM ' . static::TABLE . ' ' . $this->whereSQL($filters);
165
		$this->execSql($sql, $this->getFilterValues($filters));
166
	}
167
168
	/**
169
	 * Exclui o registro por id
170
	 * @param int $id
171
	 */
172
	public function deleteById($id) {
173
		$this->deleteByField($this->getPrimaryKey() . '', $id);
174
	}
175
176
	/**
177
	 * Exclui o registro por id
178
	 * @param string $name
179
	 * * @param mixed $value
180
	 */
181
	public function deleteByField($name, $value) {
182
		$this->deleteAll([$name . ' = ?' => $value]);
183
	}
184
185
	/**
186
	 * Exclui todos os registros
187
	 * @param mixed[] $filters
188
	 */
189
	public function deleteAll($filters = []) {
190
		$fixed = $this->fixedFilters;
191
		$this->fixedFilters = [];
192
193
		$objList = $this->fetchAll($filters);
194
		foreach ($objList as $obj):
195
			$this->delete($obj);
196
		endforeach;
197
198
		$this->fixedFilters = $fixed;
199
	}
200
201
	/**
202
	 * Busca o objeto pelo id
203
	 * @param int $id
204
	 */
205
	public function fetchById($id) {
206
		return $this->fetchByField($this->getPrimaryKey() . '', $id);
207
	}
208
209
	/**
210
	 * Busca o objeto por um campo/atributo específico
211
	 * @param string $name Nome do atributo
212
	 * @param mixed $value Valor do atributo
213
	 */
214
	public function fetchByField($name, $value) {
215
		return $this->fetch([$name . ' = ?' => $value]);
216
	}
217
218
	/**
219
	 * Busca o objeto
220
	 * @param string[] $filters Array de filtros
221
	 * @param string $option [Order by, Group by, etc]
222
	 */
223
	public function fetch($filters, $option = 'ORDER BY 1 DESC') {
224
		if (!is_array($filters)):
225
			throw new \Exception("Filter: '{$filters}' must be a array");
226
		endif;
227
		$sql = $this->selectSQL($this->selectCollumns) . ' ' . ' ' . $this->whereSQL($filters) . ' ' . $option . ' ' . $this->limitSQL();
228
		$result = [];
229
		if ($this->pdo) {
230
			$stmt = $this->execSql($sql, $this->getFilterValues($filters));
231
			$result = $stmt->fetch();
232
		}
233
		return static::mapObject($result);
234
	}
235
236
	/**
237
	 * Retorna todos os registros
238
	 *
239
	 * <code>
240
	 * $dao->fetchAll( ['id = ?' => 10]);
241
	 * </code>
242
	 * @param string[] $filters Array de filtros
243
	 * @param string $option [Order by, Group by, etc]
244
	 */
245
	public function fetchAll($filters = [], $option = 'ORDER BY 1 DESC') {
246
		$array = [];
247
		if (!is_array($filters)):
248
			throw new \Exception("Filter: '{$filters}' must be a array");
249
		endif;
250
251
		$sql = $this->selectSQL($this->selectCollumns) . ' ' . $this->whereSQL($filters) . ' ' . $option . ' ' . $this->limitSQL();
252
253
		if ($this->pdo) {
254
			$stmt = $this->execSql($sql, $this->getFilterValues($filters));
255
256
			$results = $stmt->fetchAll();
257
			foreach ($results as $result):
258
				$array[] = static::mapObject($result);
259
			endforeach;
260
		}
261
		return $array;
262
	}
263
264
	/**
265
	 * Retorna comando SELECT
266
	 * @param string $selectCollumns
267
	 * @return string
268
	 * @example "SELECT * FROM user"
269
	 */
270
	protected function selectSQL($selectCollumns = ['*']) {
271
		return 'SELECT ' . implode(', ', $selectCollumns) . ' FROM ' . static::TABLE . ' ' . implode(' ', $this->joins);
272
	}
273
274
	/**
275
	 * Retorna comando WHERE
276
	 * @param string[] $filters
277
	 * @return string
278
	 */
279
	protected function whereSQL(&$filters) {
280
		$keys = array_keys($filters + $this->fixedFilters);
281
		return ($keys) ? 'WHERE ' . implode(' AND ', $keys) : '';
282
	}
283
284
	/**
285
	 * Retorna o total de registros
286
	 * @param string[] $filters Array de filtros
287
	 * @param string $option
288
	 * @return int
289
	 */
290
	public function numRows($filters = [], $option = 'ORDER BY 1 DESC') {
291
		$total = 0;
292
		if (!is_array($filters)):
293
			throw new \Exception("Filter: '{$filters}' must be a array");
294
		endif;
295
296
		$sql = 'SELECT count(*) as total FROM ' . static::TABLE . ' ' . implode(' ', $this->joins) . ' ' . $this->whereSQL($filters) . ' ' . $option . ' ' . $this->limitSQL();
297
298
		if ($this->pdo) {
299
			$stmt = $this->execSql($sql, $this->getFilterValues($filters));
300
			$result = $stmt->fetch();
301
			$total = $result['total'];
302
		}
303
		return (int) $total;
304
	}
305
306
	/**
307
	 * Retorna True se objeto existir
308
	 * @param mixed $obj
309
	 * @return boolean
310
	 */
311
	public function objExists($obj) {
312
		return ($obj->getId() > 0 && $this->numRows([$this->getPrimaryKey() . ' = ?' => $obj->getId()]));
313
	}
314
315
	/** Define como Página 404 se o objeto não existir */
316
	public function checkFoundRegistry($obj) {
317
		if (!$this->objExists($obj)) {
318
			Application::app()->pageNotFound();
319
			Application::app()->controller->reload();
320
		}
321
	}
322
323
	/**
324
	 * Exibe comando SQL, se debug está habilitado
325
	 * @param string $sql
326
	 * @param mixed[] $values
327
	 */
328
	protected function debug($sql, $values = []) {
329
330
		if (static::$debug) {
331
			foreach ($values as $value):
332
				$sql = preg_replace('/\?/', '<b style="color:#D22;">"' . $value . '"</b>', $sql, 1);
333
			endforeach;
334
335
			$find = [' WHERE ', ' ' . static::TABLE . ' '];
336
			$replace = [' <b style="color:#22D">WHERE</b> ', ' </b>' . static::TABLE . ' '];
337
			echo '<pre><b>' . str_replace($find, $replace, $sql) . '</pre>';
338
		}
339
	}
340
341
	private function getFilterValues($filters) {
342
		return array_values($filters + $this->fixedFilters);
343
	}
344
345
	protected function beforeSave() {
346
		
347
	}
348
349
	protected function afterSave() {
350
		
351
	}
352
353
	protected function onDelete() {
354
		
355
	}
356
357
	/** @return string Retorna o nome da PK */
358
	private function getPrimaryKey() {
359
		if (is_null($this->primaryKey)) {
360
			$this->primaryKey = static::TABLE . '_id';
361
		}
362
		return $this->primaryKey;
363
	}
364
365
	/**
366
	 * Define a paginação dos itens
367
	 * @param int totalPerPage
368
	 */
369
	public function paginate($totalPerPage) {
370
		$this->totalPerPage = $totalPerPage;
371
	}
372
373
	/**
374
	 * Busca os registros baseado na paginação definida
375
	 * @return string
376
	 */
377
	protected function limitSQL() {
378
		if (!is_null($this->totalPerPage)) {
379
			$begin = $this->totalPerPage * $this->getCurrentPage();
380
			return 'LIMIT ' . $begin . ',' . $this->totalPerPage;
381
		}
382
		return '';
383
	}
384
385
	/** @return int Paginação atual */
386
	private function getCurrentPage() {
387
		return (int) (isset($_GET['p']) && $_GET['p'] > 0) ? $_GET['p'] : 1;
388
	}
389
390
}
391