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() |
|
|
|
|
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() |
|
|
|
|
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() |
|
|
|
|
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() |
|
|
|
|
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 |
|
|
|
|
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
|
|
|
|
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.