Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.11% |
311 / 334 |
|
75.56% |
34 / 45 |
CRAP | |
0.00% |
0 / 1 |
Record | |
93.11% |
311 / 334 |
|
75.56% |
34 / 45 |
175.00 | |
0.00% |
0 / 1 |
__construct | |
96.43% |
27 / 28 |
|
0.00% |
0 / 1 |
13 | |||
hasDb | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setDb | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
setDefaultDb | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDb | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
db | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSql | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
sql | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
table | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
start | |
62.50% |
5 / 8 |
|
0.00% |
0 / 1 |
3.47 | |||
commit | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
rollback | |
62.50% |
5 / 8 |
|
0.00% |
0 / 1 |
4.84 | |||
transaction | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
findById | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findOne | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findOneOrCreate | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
findLatest | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
findBy | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findByOrCreate | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
findIn | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findAll | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
7 | |||
query | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
6 | |||
getTotal | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
getTableInfo | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
with | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
7 | |||
getById | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getOne | |
88.89% |
16 / 18 |
|
0.00% |
0 / 1 |
7.07 | |||
getBy | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
6 | |||
getIn | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
6 | |||
getAll | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasOne | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
hasOneOf | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
hasMany | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
belongsTo | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
increment | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
decrement | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
replicate | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
copy | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isDirty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDirty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
resetDirty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
save | |
70.59% |
12 / 17 |
|
0.00% |
0 / 1 |
12.54 | |||
delete | |
77.78% |
14 / 18 |
|
0.00% |
0 / 1 |
8.70 | |||
__callStatic | |
100.00% |
69 / 69 |
|
100.00% |
1 / 1 |
24 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Db; |
15 | |
16 | use Pop\Db\Record\Collection; |
17 | use Pop\Utils\CallableObject; |
18 | |
19 | /** |
20 | * Record class |
21 | * |
22 | * @category Pop |
23 | * @package Pop\Db |
24 | * @author Nick Sagona, III <dev@nolainteractive.com> |
25 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
26 | * @license http://www.popphp.org/license New BSD License |
27 | * @version 6.5.0 |
28 | * @method static findWhereEquals($column, $value, array $options = null, bool $asArray = false) |
29 | * @method static findWhereNotEquals($column, $value, array $options = null, bool $asArray = false) |
30 | * @method static findWhereGreaterThan($column, $value, array $options = null, bool $asArray = false) |
31 | * @method static findWhereGreaterThanOrEqual($column, $value, array $options = null, bool $asArray = false) |
32 | * @method static findWhereLessThan($column, $value, array $options = null, bool $asArray = false) |
33 | * @method static findWhereLessThanOrEqual($column, $value, array $options = null, bool $asArray = false) |
34 | * @method static findWhereLike($column, $value, array $options = null, bool $asArray = false) |
35 | * @method static findWhereNotLike($column, $value, array $options = null, bool $asArray = false) |
36 | * @method static findWhereIn($column, $values, array $options = null, bool $asArray = false) |
37 | * @method static findWhereNotIn($column, $values, array $options = null, bool $asArray = false) |
38 | * @method static findWhereBetween($column, $values, array $options = null, bool $asArray = false) |
39 | * @method static findWhereNotBetween($column, $values, array $options = null, bool $asArray = false) |
40 | * @method static findWhereNull($column, array $options = null, bool $asArray = false) |
41 | * @method static findWhereNotNull($column, array $options = null, bool $asArray = false) |
42 | */ |
43 | class Record extends Record\AbstractRecord |
44 | { |
45 | |
46 | /** |
47 | * Constructor |
48 | * |
49 | * Instantiate the database record object |
50 | * |
51 | * Optional parameters are an array of column values, db adapter, or a table name |
52 | |
53 | * @throws Exception|Record\Exception |
54 | */ |
55 | public function __construct() |
56 | { |
57 | $args = func_get_args(); |
58 | $columns = null; |
59 | $table = null; |
60 | $db = null; |
61 | $class = get_class($this); |
62 | |
63 | foreach ($args as $arg) { |
64 | if (is_array($arg) || ($arg instanceof \ArrayAccess) || ($arg instanceof \ArrayObject)) { |
65 | $columns = $arg; |
66 | } else if ($arg instanceof Adapter\AbstractAdapter) { |
67 | $db = $arg; |
68 | } else if (is_string($arg)) { |
69 | $table = $arg; |
70 | } |
71 | } |
72 | |
73 | if ($table !== null) { |
74 | $this->setTable($table); |
75 | } else if ($this->table !== null) { |
76 | $this->setTable($this->table); |
77 | } else { |
78 | $this->setTableFromClassName($class); |
79 | } |
80 | |
81 | if ($db !== null) { |
82 | Db::setDb($db, $class, null, ($class === __CLASS__)); |
83 | } |
84 | |
85 | if (!Db::hasDb($class)) { |
86 | throw new Exception('Error: A database connection has not been set.'); |
87 | } else if (!Db::hasClassToTable($class)) { |
88 | Db::addClassToTable($class, $this->getFullTable()); |
89 | } |
90 | |
91 | $this->tableGateway = new Gateway\Table($this->getFullTable()); |
92 | $this->rowGateway = new Gateway\Row($this->getFullTable(), $this->primaryKeys); |
93 | |
94 | if ($columns !== null) { |
95 | $this->isNew = true; |
96 | $this->setColumns($columns); |
97 | } |
98 | } |
99 | |
100 | /* |
101 | * Static methods |
102 | */ |
103 | |
104 | /** |
105 | * Check for a DB adapter |
106 | * |
107 | * @return bool |
108 | */ |
109 | public static function hasDb(): bool |
110 | { |
111 | return Db::hasDb(get_called_class()); |
112 | } |
113 | |
114 | /** |
115 | * Set DB adapter |
116 | * |
117 | * @param Adapter\AbstractAdapter $db |
118 | * @param ?string $prefix |
119 | * @param bool $isDefault |
120 | * @return void |
121 | */ |
122 | public static function setDb(Adapter\AbstractAdapter $db, ?string $prefix = null, bool $isDefault = false): void |
123 | { |
124 | $class = get_called_class(); |
125 | if ($class == 'Pop\Db\Record') { |
126 | Db::setDefaultDb($db); |
127 | } else { |
128 | Db::setDb($db, $class, $prefix, $isDefault); |
129 | } |
130 | } |
131 | |
132 | /** |
133 | * Set DB adapter |
134 | * |
135 | * @param Adapter\AbstractAdapter $db |
136 | * @return void |
137 | */ |
138 | public static function setDefaultDb(Adapter\AbstractAdapter $db): void |
139 | { |
140 | Db::setDb($db, null, null, true); |
141 | } |
142 | |
143 | /** |
144 | * Get DB adapter |
145 | * |
146 | * @return Adapter\AbstractAdapter |
147 | */ |
148 | public static function getDb(): Adapter\AbstractAdapter |
149 | { |
150 | return Db::getDb(get_called_class()); |
151 | } |
152 | |
153 | /** |
154 | * Get DB adapter (alias) |
155 | * |
156 | * @return Adapter\AbstractAdapter |
157 | */ |
158 | public static function db(): Adapter\AbstractAdapter |
159 | { |
160 | return Db::db(get_called_class()); |
161 | } |
162 | |
163 | /** |
164 | * Get SQL builder |
165 | * |
166 | * @return Sql |
167 | */ |
168 | public static function getSql(): Sql |
169 | { |
170 | return Db::db(get_called_class())->createSql(); |
171 | } |
172 | |
173 | /** |
174 | * Get SQL builder (alias) |
175 | * |
176 | * @return Sql |
177 | */ |
178 | public static function sql(): Sql |
179 | { |
180 | return Db::db(get_called_class())->createSql(); |
181 | } |
182 | |
183 | /** |
184 | * Get table name |
185 | * |
186 | * @param bool $quotes |
187 | * @return string |
188 | */ |
189 | public static function table(bool $quotes = false): string |
190 | { |
191 | $table = (new static())->getFullTable(); |
192 | $sql = static::sql(); |
193 | if ($quotes) { |
194 | $table = $sql->quoteId($table); |
195 | } |
196 | return $table; |
197 | } |
198 | |
199 | /** |
200 | * Start transaction with the DB adapter. When called on a descendent class, construct |
201 | * a new object and use it for transaction management. |
202 | * |
203 | * @param mixed ...$constructorArgs Arguments passed to descendent class constructor |
204 | * @return static|null |
205 | * @throws Exception|Record\Exception |
206 | */ |
207 | public static function start(mixed ...$constructorArgs): static|null |
208 | { |
209 | $class = get_called_class(); |
210 | |
211 | if ($class !== Record::class) { |
212 | $record = new static(...$constructorArgs); |
213 | $record->startTransaction(); |
214 | return $record; |
215 | } else { |
216 | if (Db::hasDb($class)) { |
217 | Db::db($class)->beginTransaction(); |
218 | } |
219 | return null; |
220 | } |
221 | } |
222 | |
223 | /** |
224 | * Commit transaction with the DB adapter |
225 | * |
226 | * @throws Exception |
227 | * @return void |
228 | */ |
229 | public static function commit(): void |
230 | { |
231 | $class = get_called_class(); |
232 | if (Db::hasDb($class)) { |
233 | Db::db($class)->commit(); |
234 | } |
235 | } |
236 | |
237 | /** |
238 | * Rollback transaction with the DB adapter |
239 | * |
240 | * @param \Exception|null $exception |
241 | * @throws Exception |
242 | * @return \Exception|null |
243 | */ |
244 | public static function rollback(\Exception $exception = null): \Exception|null |
245 | { |
246 | $class = get_called_class(); |
247 | |
248 | if (Db::hasDb($class)) { |
249 | if (Db::db($class)->getTransactionDepth() == 1) { |
250 | Db::db($class)->rollback(); |
251 | } else { |
252 | if ($exception == null) { |
253 | $exception = new Exception('Error: A rollback has been executed from within a nested transaction.'); |
254 | } |
255 | return $exception; |
256 | } |
257 | } |
258 | |
259 | return null; |
260 | } |
261 | |
262 | /** |
263 | * Execute complete transaction with the DB adapter |
264 | * |
265 | * @param mixed $callable |
266 | * @param mixed $params |
267 | * @throws \Exception |
268 | * @return void |
269 | */ |
270 | public static function transaction(mixed $callable, mixed $params = null): void |
271 | { |
272 | if (!($callable instanceof CallableObject)) { |
273 | $callable = new CallableObject($callable, $params); |
274 | } |
275 | |
276 | try { |
277 | static::start(); |
278 | $callable->call(); |
279 | static::commit(); |
280 | } catch (\Exception $e) { |
281 | $result = static::rollback($e); |
282 | throw (!empty($result)) ? $result : $e; |
283 | } |
284 | } |
285 | |
286 | /** |
287 | * Find by ID static method |
288 | * |
289 | * @param mixed $id |
290 | * @param ?array $options |
291 | * @param bool $asArray |
292 | * @return static|array |
293 | */ |
294 | public static function findById(mixed $id, ?array $options = null, bool $asArray = false): array|static |
295 | { |
296 | return (new static())->getById($id, $options, $asArray); |
297 | } |
298 | |
299 | /** |
300 | * Find one static method |
301 | * |
302 | * @param ?array $columns |
303 | * @param ?array $options |
304 | * @param bool $asArray |
305 | * @return static|array |
306 | */ |
307 | public static function findOne(?array $columns = null, ?array $options = null, bool $asArray = false): array|static |
308 | { |
309 | return (new static())->getOne($columns, $options, $asArray); |
310 | } |
311 | |
312 | /** |
313 | * Find one or create static method |
314 | * |
315 | * @param ?array $columns |
316 | * @param ?array $options |
317 | * @param bool $asArray |
318 | * @return static|array |
319 | */ |
320 | public static function findOneOrCreate(?array $columns = null, ?array $options = null, bool $asArray = false): array|static |
321 | { |
322 | $result = (new static())->getOne($columns, $options); |
323 | |
324 | if (empty($result->toArray())) { |
325 | $newRecord = new static($columns); |
326 | $newRecord->save(); |
327 | $result = $newRecord; |
328 | } |
329 | |
330 | return ($asArray) ? $result->toArray() : $result; |
331 | } |
332 | |
333 | /** |
334 | * Find latest static method |
335 | * |
336 | * @param ?string $by |
337 | * @param ?array $columns |
338 | * @param ?array $options |
339 | * @param bool $asArray |
340 | * @return static|array |
341 | */ |
342 | public static function findLatest(?string $by = null, ?array $columns = null, ?array $options = null, bool $asArray = false): array|static |
343 | { |
344 | $record = new static(); |
345 | |
346 | if (($by === null) && (count($record->getPrimaryKeys()) == 1)) { |
347 | $by = $record->getPrimaryKeys()[0]; |
348 | } |
349 | |
350 | if ($by !== null) { |
351 | if ($options === null) { |
352 | $options = ['order' => $by . ' DESC']; |
353 | } else { |
354 | $options['order'] = $by . ' DESC'; |
355 | } |
356 | } |
357 | |
358 | return $record->getOne($columns, $options, $asArray); |
359 | } |
360 | |
361 | /** |
362 | * Find by static method |
363 | * |
364 | * @param ?array $columns |
365 | * @param ?array $options |
366 | * @param bool $asArray |
367 | * @return Collection|array |
368 | */ |
369 | public static function findBy(?array $columns = null, ?array $options = null, bool $asArray = false): Collection|array |
370 | { |
371 | return (new static())->getBy($columns, $options, $asArray); |
372 | } |
373 | |
374 | /** |
375 | * Find by or create static method |
376 | * |
377 | * @param ?array $columns |
378 | * @param ?array $options |
379 | * @param bool $asArray |
380 | * @return static|Collection|array |
381 | */ |
382 | public static function findByOrCreate(?array $columns = null, ?array $options = null, bool $asArray = false): Collection|array|static |
383 | { |
384 | $result = (new static())->getBy($columns, $options); |
385 | |
386 | if ($result->count() == 0) { |
387 | $newRecord = new static($columns); |
388 | $newRecord->save(); |
389 | $result = $newRecord; |
390 | } |
391 | |
392 | return ($asArray) ? $result->toArray() : $result; |
393 | } |
394 | |
395 | /** |
396 | * Find in static method |
397 | * |
398 | * @param string $key |
399 | * @param array $values |
400 | * @param ?array $columns |
401 | * @param ?array $options |
402 | * @param bool $asArray |
403 | * @return array |
404 | */ |
405 | public static function findIn(string $key, array $values, ?array $columns = null, ?array $options = null, bool $asArray = false): array |
406 | { |
407 | return (new static())->getIn($key, $values, $columns, $options, $asArray); |
408 | } |
409 | |
410 | /** |
411 | * Find all static method |
412 | * |
413 | * @param ?array $options |
414 | * @param bool $asArray |
415 | * @return Collection|array|static |
416 | */ |
417 | public static function findAll(?array $options = null, bool $asArray = false): Collection|array|static |
418 | { |
419 | return static::findBy(null, $options, $asArray); |
420 | } |
421 | |
422 | /** |
423 | * Static method to execute a custom prepared SQL statement. |
424 | * |
425 | * @param mixed $sql |
426 | * @param array $params |
427 | * @param bool $asArray |
428 | * @return Collection|array|null |
429 | */ |
430 | public static function execute(mixed $sql, array $params = [], bool $asArray = false): Collection|array|null |
431 | { |
432 | $record = new static(); |
433 | |
434 | if ($sql instanceof Sql) { |
435 | $sql = (string)$sql; |
436 | } |
437 | |
438 | $db = Db::getDb($record->getFullTable()); |
439 | $db->prepare($sql); |
440 | if (!empty($params)) { |
441 | $db->bindParams($params); |
442 | } |
443 | $db->execute(); |
444 | |
445 | $rows = []; |
446 | $isSelect = false; |
447 | |
448 | if (strtoupper(substr($sql, 0, 6)) == 'SELECT') { |
449 | $isSelect = true; |
450 | $rows = $db->fetchAll(); |
451 | foreach ($rows as $i => $row) { |
452 | $rows[$i] = $record->processRow($row, $asArray); |
453 | } |
454 | } |
455 | |
456 | if ($isSelect) { |
457 | $collection = new Record\Collection($rows); |
458 | return ($asArray) ? $collection->toArray() : $collection; |
459 | } else { |
460 | return null; |
461 | } |
462 | } |
463 | |
464 | /** |
465 | * Static method to execute a custom SQL query. |
466 | * |
467 | * @param mixed $sql |
468 | * @param bool $asArray |
469 | * @return Collection|array|null |
470 | */ |
471 | public static function query(mixed $sql, bool $asArray = false): Collection|array|null |
472 | { |
473 | $record = new static(); |
474 | |
475 | if ($sql instanceof Sql) { |
476 | $sql = (string)$sql; |
477 | } |
478 | |
479 | $db = Db::getDb($record->getFullTable()); |
480 | $db->query($sql); |
481 | |
482 | $rows = []; |
483 | $isSelect = false; |
484 | |
485 | if (strtoupper(substr($sql, 0, 6)) == 'SELECT') { |
486 | $isSelect = true; |
487 | while (($row = $db->fetch())) { |
488 | $rows[] = $record->processRow($row, $asArray); |
489 | } |
490 | } |
491 | |
492 | if ($isSelect) { |
493 | $collection = new Record\Collection($rows); |
494 | return ($asArray) ? $collection->toArray() : $collection; |
495 | } else { |
496 | return null; |
497 | } |
498 | } |
499 | |
500 | /** |
501 | * Static method to get the total count of a set from the DB table |
502 | * |
503 | * @param ?array $columns |
504 | * @param ?array $options |
505 | * @return int |
506 | */ |
507 | public static function getTotal(?array $columns = null, ?array $options = null): int |
508 | { |
509 | $record = new static(); |
510 | $expressions = null; |
511 | $params = null; |
512 | |
513 | if ($columns !== null) { |
514 | $db = Db::getDb($record->getFullTable()); |
515 | $sql = $db->createSql(); |
516 | ['expressions' => $expressions, 'params' => $params] = |
517 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
518 | } |
519 | |
520 | $rows = $record->getTableGateway()->select(['total_count' => 'COUNT(1)'], $expressions, $params, $options); |
521 | |
522 | return (isset($rows[0]) && isset($rows[0]['total_count'])) ? (int)$rows[0]['total_count'] : 0; |
523 | } |
524 | |
525 | /** |
526 | * Static method to get the total count of a set from the DB table |
527 | * |
528 | * @return array |
529 | */ |
530 | public static function getTableInfo(): array |
531 | { |
532 | return (new static())->getTableGateway()->getTableInfo(); |
533 | } |
534 | |
535 | /** |
536 | * With a 1:many relationship (eager-loading) |
537 | * |
538 | * @param mixed $name |
539 | * @param ?array $options |
540 | * @return static |
541 | */ |
542 | public static function with($name, ?array $options = null): static |
543 | { |
544 | $record = new static(); |
545 | |
546 | if (is_array($name)) { |
547 | foreach ($name as $key => $value) { |
548 | if (is_numeric($key) && is_string($value)) { |
549 | $record->addWith($value); |
550 | } else if (!is_numeric($key) && is_array($value)) { |
551 | $record->addWith($key, $value); |
552 | } |
553 | } |
554 | } else { |
555 | $record->addWith($name, $options); |
556 | } |
557 | |
558 | return $record; |
559 | } |
560 | |
561 | /* |
562 | * Instance methods |
563 | */ |
564 | |
565 | /** |
566 | * Get by ID method |
567 | * |
568 | * @param mixed $id |
569 | * @param ?array $options |
570 | * @param bool $asArray |
571 | * @return static|array |
572 | */ |
573 | public function getById($id, ?array $options = null, bool $asArray = false): Record|array|static |
574 | { |
575 | $this->setColumns($this->getRowGateway()->find($id, [], $options)); |
576 | if ($this->hasWiths()) { |
577 | $this->getWithRelationships(false); |
578 | } |
579 | return ($asArray) ? $this->toArray() : $this; |
580 | } |
581 | |
582 | /** |
583 | * Get one method |
584 | * |
585 | * @param ?array $columns |
586 | * @param ?array $options |
587 | * @param bool $asArray |
588 | * @return static|array |
589 | */ |
590 | public function getOne(?array $columns = null, ?array $options = null, bool $asArray = false): Record|array|static |
591 | { |
592 | if ($options === null) { |
593 | $options = ['limit' => 1]; |
594 | } else { |
595 | $options['limit'] = 1; |
596 | } |
597 | |
598 | $expressions = null; |
599 | $params = null; |
600 | $select = $options['select'] ?? null; |
601 | |
602 | if ($columns !== null) { |
603 | $db = Db::getDb($this->getFullTable()); |
604 | $sql = $db->createSql(); |
605 | ['expressions' => $expressions, 'params' => $params] = |
606 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
607 | } |
608 | |
609 | $rows = $this->getTableGateway()->select($select, $expressions, $params, $options); |
610 | |
611 | if ($this->hasWiths() && !empty($rows)) { |
612 | $this->getWithRelationships(); |
613 | $this->processWithRelationships($rows); |
614 | } |
615 | |
616 | if (isset($rows[0])) { |
617 | $this->setColumns($rows[0]); |
618 | } |
619 | |
620 | return ($asArray) ? $this->toArray() : $this; |
621 | } |
622 | |
623 | /** |
624 | * Get by method |
625 | * |
626 | * @param ?array $columns |
627 | * @param ?array $options |
628 | * @param bool $asArray |
629 | * @return Collection|array |
630 | */ |
631 | public function getBy(?array $columns = null, ?array $options = null, bool $asArray = false): Collection|array |
632 | { |
633 | $expressions = null; |
634 | $params = null; |
635 | $select = $options['select'] ?? null; |
636 | |
637 | if ($columns !== null) { |
638 | $db = Db::getDb($this->getFullTable()); |
639 | $sql = $db->createSql(); |
640 | ['expressions' => $expressions, 'params' => $params] = |
641 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
642 | } |
643 | |
644 | $rows = $this->getTableGateway()->select($select, $expressions, $params, $options); |
645 | |
646 | foreach ($rows as $i => $row) { |
647 | $rows[$i] = $this->processRow($row); |
648 | } |
649 | |
650 | if ($this->hasWiths() && !empty($rows)) { |
651 | $this->getWithRelationships(); |
652 | $this->processWithRelationships($rows); |
653 | } |
654 | |
655 | $collection = new Record\Collection($rows); |
656 | return ($asArray) ? $collection->toArray() : $collection; |
657 | } |
658 | |
659 | /** |
660 | * Get in method |
661 | * |
662 | * @param string $key |
663 | * @param array $values |
664 | * @param ?array $columns |
665 | * @param ?array $options |
666 | * @param bool $asArray |
667 | * @return array |
668 | */ |
669 | public function getIn(string $key, array $values, array $columns = null, array $options = null, bool $asArray = false): array |
670 | { |
671 | $columns = ($columns !== null) ? array_merge([$key => $values], $columns) : [$key => $values]; |
672 | $results = $this->getBy($columns, $options, $asArray); |
673 | $rows = []; |
674 | |
675 | foreach ($results as $row) { |
676 | if (isset($row[$key])) { |
677 | $rows[$row[$key]] = (($asArray) && ($row instanceof Record)) ? $row->toArray() : $row; |
678 | } |
679 | } |
680 | |
681 | return $rows; |
682 | } |
683 | |
684 | /** |
685 | * Get all method |
686 | * |
687 | * @param ?array $options |
688 | * @param bool $asArray |
689 | * @return Collection|array |
690 | */ |
691 | public function getAll(?array $options = null, bool $asArray = false): Collection|array |
692 | { |
693 | return $this->getBy(null, $options, $asArray); |
694 | } |
695 | |
696 | /** |
697 | * Has one relationship |
698 | * |
699 | * @param string $foreignTable |
700 | * @param string $foreignKey |
701 | * @param ?array $options |
702 | * @param bool $eager |
703 | * @return Record|Record\Relationships\HasOne |
704 | */ |
705 | public function hasOne(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Record|Record\Relationships\HasOne |
706 | { |
707 | $relationship = new Record\Relationships\HasOne($this, $foreignTable, $foreignKey, $options); |
708 | if (!empty($this->withChildren)) { |
709 | $relationship->setChildRelationships($this->withChildren); |
710 | } |
711 | return ($eager) ? $relationship : $relationship->getChild($options); |
712 | } |
713 | |
714 | /** |
715 | * Has one of relationship |
716 | * |
717 | * @param string $foreignTable |
718 | * @param string $foreignKey |
719 | * @param ?array $options |
720 | * @param bool $eager |
721 | * @return Record|Record\Relationships\HasOneOf |
722 | */ |
723 | public function hasOneOf(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Record|Record\Relationships\HasOneOf |
724 | { |
725 | $relationship = new Record\Relationships\HasOneOf($this, $foreignTable, $foreignKey, $options); |
726 | if (!empty($this->withChildren)) { |
727 | $relationship->setChildRelationships($this->withChildren); |
728 | } |
729 | return ($eager) ? $relationship : $relationship->getChild(); |
730 | } |
731 | |
732 | /** |
733 | * Has many relationship |
734 | * |
735 | * @param string $foreignTable |
736 | * @param string $foreignKey |
737 | * @param ?array $options |
738 | * @param bool $eager |
739 | * @return Collection|Record\Relationships\HasMany |
740 | */ |
741 | public function hasMany(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Collection|Record\Relationships\HasMany |
742 | { |
743 | $relationship = new Record\Relationships\HasMany($this, $foreignTable, $foreignKey, $options); |
744 | if (!empty($this->withChildren)) { |
745 | $relationship->setChildRelationships($this->withChildren); |
746 | } |
747 | return ($eager) ? $relationship : $relationship->getChildren($options); |
748 | } |
749 | |
750 | /** |
751 | * Belongs to relationship |
752 | * |
753 | * @param string $foreignTable |
754 | * @param string $foreignKey |
755 | * @param ?array $options |
756 | * @param bool $eager |
757 | * @return Record|Record\Relationships\BelongsTo |
758 | */ |
759 | public function belongsTo(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Record|Record\Relationships\BelongsTo |
760 | { |
761 | $relationship = new Record\Relationships\BelongsTo($this, $foreignTable, $foreignKey, $options); |
762 | if (!empty($this->withChildren)) { |
763 | $relationship->setChildRelationships($this->withChildren); |
764 | } |
765 | return ($eager) ? $relationship : $relationship->getParent($options); |
766 | } |
767 | |
768 | /** |
769 | * Increment the record column and save |
770 | * |
771 | * @param string $column |
772 | * @param int $amount |
773 | * @return void |
774 | */ |
775 | public function increment(string $column, int $amount = 1): void |
776 | { |
777 | $this->{$column} += (int)$amount; |
778 | $this->save(); |
779 | } |
780 | |
781 | /** |
782 | * Decrement the record column and save |
783 | * |
784 | * @param string $column |
785 | * @param int $amount |
786 | * @return void |
787 | */ |
788 | public function decrement(string $column, int $amount = 1): void |
789 | { |
790 | $this->{$column} -= (int)$amount; |
791 | $this->save(); |
792 | } |
793 | |
794 | /** |
795 | * Replicate the record |
796 | * |
797 | * @param array $replace |
798 | * @return static |
799 | */ |
800 | public function replicate(array $replace = []): static |
801 | { |
802 | $fields = $this->toArray(); |
803 | |
804 | foreach ($this->primaryKeys as $key) { |
805 | if (isset($fields[$key])) { |
806 | unset($fields[$key]); |
807 | } |
808 | } |
809 | |
810 | if (!empty($replace)) { |
811 | foreach ($replace as $key => $value) { |
812 | if (array_key_exists($key, $fields)) { |
813 | $fields[$key] = $value; |
814 | } |
815 | } |
816 | } |
817 | |
818 | $newRecord = new static($fields); |
819 | $newRecord->save(); |
820 | |
821 | return $newRecord; |
822 | } |
823 | |
824 | /** |
825 | * Copy the record (alias to replicate) |
826 | * |
827 | * @param array $replace |
828 | * @return static |
829 | */ |
830 | public function copy(array $replace = []): static |
831 | { |
832 | return $this->replicate($replace); |
833 | } |
834 | |
835 | /** |
836 | * Check if row is dirty |
837 | * |
838 | * @return bool |
839 | */ |
840 | public function isDirty(): bool |
841 | { |
842 | return $this->rowGateway->isDirty(); |
843 | } |
844 | |
845 | /** |
846 | * Get row's dirty columns |
847 | * |
848 | * @return array |
849 | */ |
850 | public function getDirty(): array |
851 | { |
852 | return $this->rowGateway->getDirty(); |
853 | } |
854 | |
855 | /** |
856 | * Reset row's dirty columns |
857 | * |
858 | * @return void |
859 | */ |
860 | public function resetDirty(): void |
861 | { |
862 | $this->rowGateway->resetDirty(); |
863 | } |
864 | |
865 | /** |
866 | * Save or update the record |
867 | * |
868 | * @param ?array $columns |
869 | * @param bool $commit |
870 | * @throws \Exception |
871 | * @return void |
872 | */ |
873 | public function save(array $columns = null, bool $commit = true): void |
874 | { |
875 | try { |
876 | // Save or update the record |
877 | if ($columns === null) { |
878 | if ($this->isNew) { |
879 | $this->rowGateway->save(); |
880 | $this->isNew = false; |
881 | } else { |
882 | $this->rowGateway->update(); |
883 | $record = $this->getById($this->rowGateway->getPrimaryValues()); |
884 | if (isset($record[0])) { |
885 | $this->setColumns($record[0]); |
886 | } |
887 | } |
888 | // Else, save multiple rows |
889 | } else { |
890 | if (isset($columns[0])) { |
891 | $this->tableGateway->insertRows($columns); |
892 | } else { |
893 | $this->tableGateway->insert($columns); |
894 | } |
895 | } |
896 | if (($this->isTransaction()) && ($commit)) { |
897 | $this->commitTransaction(); |
898 | } |
899 | } catch (\Exception $e) { |
900 | if (($this->isTransaction()) && ($commit)) { |
901 | $this->rollbackTransaction(); |
902 | } |
903 | throw $e; |
904 | } |
905 | } |
906 | |
907 | /** |
908 | * Delete the record |
909 | * |
910 | * @param ?array $columns |
911 | * @param bool $commit |
912 | * @return void |
913 | */ |
914 | public function delete(array $columns = null, bool $commit = true): void |
915 | { |
916 | try { |
917 | // Delete the record |
918 | if ($columns === null) { |
919 | $this->rowGateway->delete(); |
920 | // Delete multiple rows |
921 | } else { |
922 | $expressions = null; |
923 | $params = []; |
924 | |
925 | if ($columns !== null) { |
926 | $db = Db::getDb($this->getFullTable()); |
927 | $sql = $db->createSql(); |
928 | ['expressions' => $expressions, 'params' => $params] = |
929 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
930 | } |
931 | |
932 | $this->tableGateway->delete($expressions, $params); |
933 | } |
934 | |
935 | $this->setRows(); |
936 | $this->setColumns(); |
937 | |
938 | if (($this->isTransaction()) && ($commit)) { |
939 | $this->commitTransaction(); |
940 | } |
941 | } catch (\Exception $e) { |
942 | if (($this->isTransaction()) && ($commit)) { |
943 | $this->rollbackTransaction(); |
944 | } |
945 | throw $e; |
946 | } |
947 | |
948 | } |
949 | |
950 | /** |
951 | * Call static method for 'findWhere' |
952 | * |
953 | * $users = Users::findWhereUsername($value); |
954 | * |
955 | * $users = Users::findWhereEquals($column, $value); |
956 | * $users = Users::findWhereNotEquals($column, $value); |
957 | * $users = Users::findWhereGreaterThan($column, $value); |
958 | * $users = Users::findWhereGreaterThanOrEqual($column, $value); |
959 | * $users = Users::findWhereLessThan($column, $value); |
960 | * $users = Users::findWhereLessThanOrEqual($column, $value); |
961 | * |
962 | * $users = Users::findWhereLike($column, $value); |
963 | * $users = Users::findWhereNotLike($column, $value); |
964 | * |
965 | * $users = Users::findWhereIn($column, $values); |
966 | * $users = Users::findWhereNotIn($column, $values); |
967 | * |
968 | * $users = Users::findWhereBetween($column, $values); |
969 | * $users = Users::findWhereNotBetween($column, $values); |
970 | * |
971 | * $users = Users::findWhereNull($column); |
972 | * $users = Users::findWhereNotNull($column); |
973 | * |
974 | * @param string $name |
975 | * @param array $arguments |
976 | * @return Collection|array|null |
977 | */ |
978 | public static function __callStatic(string $name, array $arguments): Collection|array|null |
979 | { |
980 | $columns = null; |
981 | $options = null; |
982 | $asArray = false; |
983 | $conditions = [ |
984 | 'Equals', 'NotEquals', 'GreaterThan', 'GreaterThanOrEqual', 'LessThan', 'LessThanOrEqual', |
985 | 'Like', 'NotLike', 'In', 'NotIn', 'Between', 'NotBetween', 'Null', 'NotNull' |
986 | ]; |
987 | |
988 | if (str_starts_with($name, 'findWhere')) { |
989 | if (in_array(substr($name, 9), $conditions)) { |
990 | $condition = substr($name, 9); |
991 | $column = $arguments[0]; |
992 | |
993 | if (str_contains($condition, 'Null')) { |
994 | $value = null; |
995 | $options = $arguments[1] ?? null; |
996 | $asArray = $arguments[2] ?? false; |
997 | } else { |
998 | $value = $arguments[1]; |
999 | $options = $arguments[2] ?? null; |
1000 | $asArray = $arguments[3] ?? false; |
1001 | } |
1002 | |
1003 | switch ($condition) { |
1004 | case 'Equals': |
1005 | case 'In': |
1006 | case 'Between': |
1007 | case 'Null': |
1008 | $columns = [$column => $value]; |
1009 | break; |
1010 | case 'NotEquals': |
1011 | $columns = [$column . '!=' => $value]; |
1012 | break; |
1013 | case 'GreaterThan': |
1014 | $columns = [$column . '>' => $value]; |
1015 | break; |
1016 | case 'GreaterThanOrEqual': |
1017 | $columns = [$column . '>=' => $value]; |
1018 | break; |
1019 | case 'LessThan': |
1020 | $columns = [$column . '<' => $value]; |
1021 | break; |
1022 | case 'LessThanOrEqual': |
1023 | $columns = [$column . '<=' => $value]; |
1024 | break; |
1025 | case 'Like': |
1026 | if (str_starts_with($value, '%')) { |
1027 | $column = '%' . $column; |
1028 | $value = substr($value, 1); |
1029 | } |
1030 | if (str_ends_with($value, '%')) { |
1031 | $column .= '%'; |
1032 | $value = substr($value, 0, -1); |
1033 | } |
1034 | $columns = [$column => $value]; |
1035 | break; |
1036 | case 'NotLike': |
1037 | if (str_starts_with($value, '%')) { |
1038 | $column = '-%' . $column; |
1039 | $value = substr($value, 1); |
1040 | } |
1041 | if (str_ends_with($value, '%')) { |
1042 | $column .= '%-'; |
1043 | $value = substr($value, 0, -1); |
1044 | } |
1045 | $columns = [$column => $value]; |
1046 | break; |
1047 | case 'NotIn': |
1048 | case 'NotBetween': |
1049 | case 'NotNull': |
1050 | $columns = [$column . '-' => $value]; |
1051 | break; |
1052 | } |
1053 | } else { |
1054 | $column = Sql\Parser\Table::parse(substr($name, 9)); |
1055 | $value = $arguments[0] ?? null; |
1056 | $options = $arguments[1] ?? null; |
1057 | $asArray = $arguments[2] ?? false; |
1058 | |
1059 | if ($value !== null) { |
1060 | $columns = [$column => $value]; |
1061 | } |
1062 | } |
1063 | } |
1064 | |
1065 | return ($columns !== null) ? static::findBy($columns, $options, $asArray) : null; |
1066 | } |
1067 | |
1068 | } |