Completed
Push — develop ( 9c27c4...b0b61d )
by Michael
03:21
created

AbstractActiveRecord::searchFirst()   A

Complexity

Conditions 2
Paths 7

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 9.3142
cc 2
eloc 13
nc 7
nop 2
crap 2
1
<?php
2
3
/**
4
 * This file is part of the miBadger package.
5
 *
6
 * @author Michael Webbers <[email protected]>
7
 * @license http://opensource.org/licenses/Apache-2.0 Apache v2 License
8
 * @version 1.0.0
9
 */
10
11
namespace miBadger\ActiveRecord;
12
13
/**
14
 * The abstract active record class.
15
 *
16
 * @since 1.0.0
17
 */
18
abstract class AbstractActiveRecord implements ActiveRecordInterface
19
{
20
	/** @var \PDO The PDO object. */
21
	private $pdo;
22
23
	/** @var null|int The ID. */
24
	private $id;
25
26
	/**
27
	 * Construct an abstract pdo active record with the given pdo.
28
	 *
29
	 * @param \PDO $pdo
30
	 */
31 31
	public function __construct(\PDO $pdo)
32
	{
33 31
		$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
34 31
		$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
35
36 31
		$this->setPdo($pdo);
37 31
	}
38
39
	/**
40
	 * {@inheritdoc}
41
	 */
42 4
	public function create()
43
	{
44
		try {
45 4
			$pdoStatement = $this->getPdo()->prepare($this->getCreateQuery());
46 2
			$pdoStatement->execute($this->getActiveRecordAttributes());
47
48 2
			$this->setId(intval($this->getPdo()->lastInsertId()));
49 4
		} catch (\PDOException $e) {
50 2
			throw new ActiveRecordException(sprintf('Can not create a new active record entry in the `%s` table.', $this->getActiveRecordTable()), 0, $e);
51
		}
52
53 2
		return $this;
54
	}
55
56
	/**
57
	 * Returns the create query.
58
	 *
59
	 * @return string the create query.
60
	 */
61 4 View Code Duplication
	private function getCreateQuery()
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...
62
	{
63 4
		$columns = array_keys($this->getActiveRecordAttributes());
64 4
		$values = [];
65
66 4
		foreach ($columns as $key => $value) {
67 4
			$values[] = sprintf(':%s', $value);
68 4
		}
69
70 4
		return sprintf('INSERT INTO `%s` (`%s`) VALUES (%s)', $this->getActiveRecordTable(), implode('`, `', $columns), implode(', ', $values));
71
	}
72
73
	/**
74
	 * {@inheritdoc}
75
	 */
76 9
	public function read($id)
77
	{
78
		try {
79 9
			$pdoStatement = $this->getPdo()->prepare($this->getReadQuery());
80 8
			$pdoStatement->execute(['id' => $id]);
81 8
			$result = $pdoStatement->fetch();
82
83 8
			if ($result === false) {
84 1
				throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getActiveRecordTable()));
85
			}
86
87 7
			$this->fill($result);
88 6
			$this->setId($id);
89 9
		} catch (\PDOException $e) {
90 1
			throw new ActiveRecordException(sprintf('Can not read active record entry %d from the `%s` table.', $id, $this->getActiveRecordTable()), 0, $e);
91
		}
92
93 6
		return $this;
94
	}
95
96
	/**
97
	 * Returns the read query.
98
	 *
99
	 * @return string the read query.
100
	 */
101 9
	private function getReadQuery()
102
	{
103 9
		return sprintf('SELECT * FROM `%s` WHERE `id` = :id', $this->getActiveRecordTable());
104
	}
105
106
	/**
107
	 * {@inheritdoc}
108
	 */
109 5 View Code Duplication
	public function update()
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...
110
	{
111 5
		if (!$this->exists()) {
112 1
			throw new ActiveRecordException(sprintf('Can not update a non-existent active record entry to the `%s` table.', $this->getActiveRecordTable()));
113
		}
114
115
		try {
116 4
			$pdoStatement = $this->getPdo()->prepare($this->getUpdateQuery());
117 2
			$pdoStatement->execute(['id' => $this->getId()] + $this->getActiveRecordAttributes());
118 4
		} catch (\PDOException $e) {
119 2
			throw new ActiveRecordException(sprintf('Can not update active record entry %d to the `%s` table.', $this->getId(), $this->getActiveRecordTable()), 0, $e);
120
		}
121
122 2
		return $this;
123
	}
124
125
	/**
126
	 * Returns the update query.
127
	 *
128
	 * @return string the update query.
129
	 */
130 4 View Code Duplication
	private function getUpdateQuery()
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...
131
	{
132 4
		$values = [];
133
134 4
		foreach (array_keys($this->getActiveRecordAttributes()) as $key => $value) {
135 4
			$values[] = sprintf('`%s` = :%s', $value, $value);
136 4
		}
137
138 4
		return sprintf('UPDATE `%s` SET %s WHERE `id` = :id', $this->getActiveRecordTable(), implode(', ', $values));
139
	}
140
141
	/**
142
	 * {@inheritdoc}
143
	 */
144 4 View Code Duplication
	public function delete()
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...
145
	{
146 4
		if (!$this->exists()) {
147 1
			throw new ActiveRecordException(sprintf('Can not delete a non-existent active record entry from the `%s` table.', $this->getActiveRecordTable()));
148
		}
149
150
		try {
151 3
			$pdoStatement = $this->getPdo()->prepare($this->getDeleteQuery());
152 2
			$pdoStatement->execute(['id' => $this->getId()]);
153
154 2
			$this->setId(null);
155 3
		} catch (\PDOException $e) {
156 1
			throw new ActiveRecordException(sprintf('Can not delete active record entry %d from the `%s` table.', $this->getId(), $this->getActiveRecordTable()), 0, $e);
157
		}
158
159 2
		return $this;
160
	}
161
162
	/**
163
	 * Returns the delete query.
164
	 *
165
	 * @return string the delete query.
166
	 */
167 3
	private function getDeleteQuery()
168
	{
169 3
		return sprintf('DELETE FROM `%s` WHERE `id` = :id', $this->getActiveRecordTable());
170
	}
171
172
	/**
173
	 * {@inheritdoc}
174
	 */
175 2
	public function sync()
176
	{
177 2
		if (!$this->exists()) {
178 1
			return $this->create();
179
		}
180
181 1
		return $this->update();
182
	}
183
184
	/**
185
	 * {@inheritdoc}
186
	 */
187 10
	public function exists()
188
	{
189 10
		return $this->getId() !== null;
190
	}
191
192
	/**
193
	 * Fill the active record
194
	 *
195
	 * @param array $fetch
196
	 * @return null
197
	 */
198 17
	public function fill(array $fetch)
199
	{
200 17
		$data = $this->getActiveRecordAttributes();
201
202 17
		foreach ($data as $key => &$value) {
203 17
			if (!array_key_exists($key, $fetch)) {
204 1
				throw new ActiveRecordException(sprintf('Can not read the expected column `%s`. It\'s not returnd by the `%s` table', $key, $this->getActiveRecordTable()));
205
			}
206
207 17
			$value = $fetch[$key];
208 17
		}
209 16
	}
210
211
	/**
212
	 * {@inheritdoc}
213
	 */
214 2
	public function searchFirst(array $where = [], array $orderBy = [])
215
	{
216
		try {
217 2
			$pdoStatement = $this->getPdo()->prepare($this->getSearchQuery($where, $orderBy, 1, 0));
218
			array_walk_recursive($where, function(&$value) use ($pdoStatement) {
219 1
				static $index = 1;
220
221 1
				$pdoStatement->bindParam($index++, $value);
222 1
			});
223
224 1
			$pdoStatement->execute();
225 1
			$fetch = $pdoStatement->fetch();
226
227 1
			$this->setId(intval($fetch['id']));
228 1
			$this->fill($fetch);
229
230 1
			return $this;
231 1
		} catch (\PDOException $e) {
232 1
			throw new ActiveRecordException(sprintf('Can not search the record in the `%s` table.', $this->getActiveRecordTable()), 0, $e);
233
		}
234
	}
235
236
	/**
237
	 * {@inheritdoc}
238
	 */
239 11
	public function search(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
240
	{
241
		try {
242 11
			$pdoStatement = $this->getPdo()->prepare($this->getSearchQuery($where, $orderBy, $limit, $offset));
243 8
			array_walk_recursive($where, function(&$value) use ($pdoStatement) {
244 4
				static $index = 1;
245
246 4
				$pdoStatement->bindParam($index++, $value);
247 8
			});
248
249 8
			$pdoStatement->execute();
250 8
			$result = [];
251
252 8
			while ($fetch = $pdoStatement->fetch()) {
253 8
				$new = new static($this->getPdo());
254
255 8
				$new->setId(intval($fetch['id']));
256 8
				$new->fill($fetch);
257
258 8
				$result[] = $new;
259 8
			}
260
261 8
			return $result;
262 3
		} catch (\PDOException $e) {
263 1
			throw new ActiveRecordException(sprintf('Can not search the record in the `%s` table.', $this->getActiveRecordTable()), 0, $e);
264
		}
265
	}
266
267
	/**
268
	 * Returns the search query with the given where, order by, limit and offset clauses.
269
	 *
270
	 * @param array $where = []
271
	 * @param array $orderBy = []
272
	 * @param int $limit = -1
273
	 * @param int $offset = 0
274
	 * @return string the search query with the given where, order by, limit and offset clauses.
275
	 */
276 13
	private function getSearchQuery($where = [], $orderBy = [], $limit = -1, $offset = 0)
277
	{
278 13
		return sprintf(
279 13
			'SELECT * FROM `%s` %s %s %s',
280 13
			$this->getActiveRecordTable(),
281 13
			$this->getSearchQueryWhereClauses($where),
282 11
			$this->getSearchQueryOrderByClause($orderBy),
283 11
			$this->getSearchQueryLimitClause($limit, $offset)
284 11
		);
285
	}
286
287
	/**
288
	 * Returns the search query where clauses.
289
	 *
290
	 * @param array $where
291
	 * @return string the search query where clauses.
292
	 */
293 13
	private function getSearchQueryWhereClauses($where)
294
	{
295 13
		$columns = array_keys($this->getActiveRecordAttributes());
296 13
		$columns[] = 'id';
297 13
		$result = [];
298
299 13
		foreach ($where as $key => $value) {
300 7
			if (!in_array($key, $columns)) {
301 1
				throw new ActiveRecordException(sprintf('Search attribute `%s` does not exists.', $key));
302
			}
303
304 6
			$result[] = $this->getSearchQueryWhereClause($key, $value);
305 11
		}
306
307 11
		return empty($result) ? '' : 'WHERE ' . implode(' AND ', $result);
308
	}
309
310
	/**
311
	 * Returns the search query where clause.
312
	 *
313
	 * @param array $where
0 ignored issues
show
Bug introduced by
There is no parameter named $where. 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...
314
	 * @return string the search query where clause.
315
	 */
316 6
	private function getSearchQueryWhereClause($key, $value)
317
	{
318 6
		if (is_numeric($value)) {
319 1
			return sprintf('`%s` = ?', $key);
320 5
		} elseif (is_string($value)) {
321 2
			return sprintf('`%s` LIKE ?', $key);
322 3
		} elseif (is_null($value)) {
323 1
			return sprintf('`%s` IS ?', $key);
324 2
		} elseif (is_array($value) && !empty($value)) {
325 1
			return sprintf('`%s` IN (%s)', $key, implode(',', array_fill(0, count($value), '?')));
326
		}
327
328 1
		throw new ActiveRecordException(sprintf('Search attribute `%s` contains an unsupported type `%s`.', $key, gettype($value)));
329
	}
330
331
	/**
332
	 * Returns the search query order by clause.
333
	 *
334
	 * @param array $orderBy
335
	 * @return string the search query order by clause.
336
	 */
337 11
	private function getSearchQueryOrderByClause($orderBy)
338
	{
339 11
		$result = [];
340
341 11
		foreach ($orderBy as $key => $value) {
342 1
			$result[] = sprintf('`%s` %s', $key, $value == 'DESC' ? 'DESC' : 'ASC');
343 11
		}
344
345 11
		return empty($result) ? '' : 'ORDER BY ' . implode(', ', $result);
346
	}
347
348
	/**
349
	 * Returns the search query limit and clause.
350
	 *
351
	 * @param int $limit = -1
352
	 * @param int $offset = 0
353
	 * @return string the search query limit and clause.
354
	 */
355 11
	private function getSearchQueryLimitClause($limit, $offset)
356
	{
357 11
		if ($limit == -1) {
358 7
			return '';
359
		}
360
361 4
		return sprintf('LIMIT %d OFFSET %d', $limit, $offset);
362
	}
363
364
	/**
365
	 * Returns the PDO.
366
	 *
367
	 * @return \PDO the PDO.
368
	 */
369 28
	public function getPdo()
370
	{
371 28
		return $this->pdo;
372
	}
373
374
	/**
375
	 * Set the PDO.
376
	 *
377
	 * @param \PDO $pdo
378
	 * @return $this
379
	 */
380 31
	protected function setPdo($pdo)
381
	{
382 31
		$this->pdo = $pdo;
383
384 31
		return $this;
385
	}
386
387
	/**
388
	 * Returns the ID.
389
	 *
390
	 * @return null|int The ID.
391
	 */
392 9
	public function getId()
393
	{
394 9
		return $this->id;
395
	}
396
397
	/**
398
	 * Set the ID.
399
	 *
400
	 * @param int $id
401
	 * @return $this
402
	 */
403 16
	protected function setId($id)
404
	{
405 16
		$this->id = $id;
406
407 16
		return $this;
408
	}
409
410
	/**
411
	 * Returns the active record table.
412
	 *
413
	 * @return string the active record table.
414
	 */
415
	abstract protected function getActiveRecordTable();
416
417
	/**
418
	 * Returns the active record attributes.
419
	 *
420
	 * @return array the active record attributes.
421
	 */
422
	abstract protected function getActiveRecordAttributes();
423
}
424