Completed
Push — master ( 774d12...7a0ba2 )
by Wanderson
02:14
created

DAO::debug()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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