Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.13% |
312 / 335 |
|
75.56% |
34 / 45 |
CRAP | |
0.00% |
0 / 1 |
Record | |
93.13% |
312 / 335 |
|
75.56% |
34 / 45 |
176.03 | |
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% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
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|array $toArray = false) |
29 | * @method static findWhereNotEquals($column, $value, array $options = null, bool|array $toArray = false) |
30 | * @method static findWhereGreaterThan($column, $value, array $options = null, bool|array $toArray = false) |
31 | * @method static findWhereGreaterThanOrEqual($column, $value, array $options = null, bool|array $toArray = false) |
32 | * @method static findWhereLessThan($column, $value, array $options = null, bool|array $toArray = false) |
33 | * @method static findWhereLessThanOrEqual($column, $value, array $options = null, bool|array $toArray = false) |
34 | * @method static findWhereLike($column, $value, array $options = null, bool|array $toArray = false) |
35 | * @method static findWhereNotLike($column, $value, array $options = null, bool|array $toArray = false) |
36 | * @method static findWhereIn($column, $values, array $options = null, bool|array $toArray = false) |
37 | * @method static findWhereNotIn($column, $values, array $options = null, bool|array $toArray = false) |
38 | * @method static findWhereBetween($column, $values, array $options = null, bool|array $toArray = false) |
39 | * @method static findWhereNotBetween($column, $values, array $options = null, bool|array $toArray = false) |
40 | * @method static findWhereNull($column, array $options = null, bool|array $toArray = false) |
41 | * @method static findWhereNotNull($column, array $options = null, bool|array $toArray = 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 $toArray |
292 | * @return static|array |
293 | */ |
294 | public static function findById(mixed $id, ?array $options = null, bool $toArray = false): array|static |
295 | { |
296 | return (new static())->getById($id, $options, $toArray); |
297 | } |
298 | |
299 | /** |
300 | * Find one static method |
301 | * |
302 | * @param ?array $columns |
303 | * @param ?array $options |
304 | * @param bool $toArray |
305 | * @return static|array |
306 | */ |
307 | public static function findOne(?array $columns = null, ?array $options = null, bool $toArray = false): array|static |
308 | { |
309 | return (new static())->getOne($columns, $options, $toArray); |
310 | } |
311 | |
312 | /** |
313 | * Find one or create static method |
314 | * |
315 | * @param ?array $columns |
316 | * @param ?array $options |
317 | * @param bool $toArray |
318 | * @return static|array |
319 | */ |
320 | public static function findOneOrCreate(?array $columns = null, ?array $options = null, bool $toArray = 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 ($toArray) ? $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 $toArray |
340 | * @return static|array |
341 | */ |
342 | public static function findLatest(?string $by = null, ?array $columns = null, ?array $options = null, bool $toArray = 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, $toArray); |
359 | } |
360 | |
361 | /** |
362 | * Find by static method |
363 | * |
364 | * @param ?array $columns |
365 | * @param ?array $options |
366 | * @param bool|array $toArray |
367 | * @return Collection|array |
368 | */ |
369 | public static function findBy(?array $columns = null, ?array $options = null, bool|array $toArray = false): Collection|array |
370 | { |
371 | return (new static())->getBy($columns, $options, $toArray); |
372 | } |
373 | |
374 | /** |
375 | * Find by or create static method |
376 | * |
377 | * @param ?array $columns |
378 | * @param ?array $options |
379 | * @param bool|array $toArray |
380 | * @return static|Collection|array |
381 | */ |
382 | public static function findByOrCreate(?array $columns = null, ?array $options = null, bool|array $toArray = 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 | return ($toArray !== false) ? $result->toArray() : $result; |
391 | } else { |
392 | return ($toArray !== false) ? $result->toArray($toArray) : $result; |
393 | } |
394 | } |
395 | |
396 | /** |
397 | * Find in static method |
398 | * |
399 | * @param string $key |
400 | * @param array $values |
401 | * @param ?array $columns |
402 | * @param ?array $options |
403 | * @param bool $toArray |
404 | * @return array |
405 | */ |
406 | public static function findIn(string $key, array $values, ?array $columns = null, ?array $options = null, bool|array $toArray = false): array |
407 | { |
408 | return (new static())->getIn($key, $values, $columns, $options, $toArray); |
409 | } |
410 | |
411 | /** |
412 | * Find all static method |
413 | * |
414 | * @param ?array $options |
415 | * @param bool $toArray |
416 | * @return Collection|array|static |
417 | */ |
418 | public static function findAll(?array $options = null, bool|array $toArray = false): Collection|array|static |
419 | { |
420 | return static::findBy(null, $options, $toArray); |
421 | } |
422 | |
423 | /** |
424 | * Static method to execute a custom prepared SQL statement. |
425 | * |
426 | * @param mixed $sql |
427 | * @param array $params |
428 | * @param bool $toArray |
429 | * @return Collection|array|null |
430 | */ |
431 | public static function execute(mixed $sql, array $params = [], bool|array $toArray = false): Collection|array|null |
432 | { |
433 | $record = new static(); |
434 | |
435 | if ($sql instanceof Sql) { |
436 | $sql = (string)$sql; |
437 | } |
438 | |
439 | $db = Db::getDb($record->getFullTable()); |
440 | $db->prepare($sql); |
441 | if (!empty($params)) { |
442 | $db->bindParams($params); |
443 | } |
444 | $db->execute(); |
445 | |
446 | $rows = []; |
447 | $isSelect = false; |
448 | |
449 | if (strtoupper(substr($sql, 0, 6)) == 'SELECT') { |
450 | $isSelect = true; |
451 | $rows = $db->fetchAll(); |
452 | foreach ($rows as $i => $row) { |
453 | $rows[$i] = $record->processRow($row, $toArray); |
454 | } |
455 | } |
456 | |
457 | if ($isSelect) { |
458 | $collection = new Record\Collection($rows); |
459 | return ($toArray !== false) ? $collection->toArray($toArray) : $collection; |
460 | } else { |
461 | return null; |
462 | } |
463 | } |
464 | |
465 | /** |
466 | * Static method to execute a custom SQL query. |
467 | * |
468 | * @param mixed $sql |
469 | * @param bool $toArray |
470 | * @return Collection|array|null |
471 | */ |
472 | public static function query(mixed $sql, bool|array $toArray = false): Collection|array|null |
473 | { |
474 | $record = new static(); |
475 | |
476 | if ($sql instanceof Sql) { |
477 | $sql = (string)$sql; |
478 | } |
479 | |
480 | $db = Db::getDb($record->getFullTable()); |
481 | $db->query($sql); |
482 | |
483 | $rows = []; |
484 | $isSelect = false; |
485 | |
486 | if (strtoupper(substr($sql, 0, 6)) == 'SELECT') { |
487 | $isSelect = true; |
488 | while (($row = $db->fetch())) { |
489 | $rows[] = $record->processRow($row, $toArray); |
490 | } |
491 | } |
492 | |
493 | if ($isSelect) { |
494 | $collection = new Record\Collection($rows); |
495 | return ($toArray !== false) ? $collection->toArray($toArray) : $collection; |
496 | } else { |
497 | return null; |
498 | } |
499 | } |
500 | |
501 | /** |
502 | * Static method to get the total count of a set from the DB table |
503 | * |
504 | * @param ?array $columns |
505 | * @param ?array $options |
506 | * @return int |
507 | */ |
508 | public static function getTotal(?array $columns = null, ?array $options = null): int |
509 | { |
510 | $record = new static(); |
511 | $expressions = null; |
512 | $params = null; |
513 | |
514 | if ($columns !== null) { |
515 | $db = Db::getDb($record->getFullTable()); |
516 | $sql = $db->createSql(); |
517 | ['expressions' => $expressions, 'params' => $params] = |
518 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
519 | } |
520 | |
521 | $rows = $record->getTableGateway()->select(['total_count' => 'COUNT(1)'], $expressions, $params, $options); |
522 | |
523 | return (isset($rows[0]) && isset($rows[0]['total_count'])) ? (int)$rows[0]['total_count'] : 0; |
524 | } |
525 | |
526 | /** |
527 | * Static method to get the total count of a set from the DB table |
528 | * |
529 | * @return array |
530 | */ |
531 | public static function getTableInfo(): array |
532 | { |
533 | return (new static())->getTableGateway()->getTableInfo(); |
534 | } |
535 | |
536 | /** |
537 | * With a 1:many relationship (eager-loading) |
538 | * |
539 | * @param mixed $name |
540 | * @param ?array $options |
541 | * @return static |
542 | */ |
543 | public static function with($name, ?array $options = null): static |
544 | { |
545 | $record = new static(); |
546 | |
547 | if (is_array($name)) { |
548 | foreach ($name as $key => $value) { |
549 | if (is_numeric($key) && is_string($value)) { |
550 | $record->addWith($value); |
551 | } else if (!is_numeric($key) && is_array($value)) { |
552 | $record->addWith($key, $value); |
553 | } |
554 | } |
555 | } else { |
556 | $record->addWith($name, $options); |
557 | } |
558 | |
559 | return $record; |
560 | } |
561 | |
562 | /* |
563 | * Instance methods |
564 | */ |
565 | |
566 | /** |
567 | * Get by ID method |
568 | * |
569 | * @param mixed $id |
570 | * @param ?array $options |
571 | * @param bool $toArray |
572 | * @return static|array |
573 | */ |
574 | public function getById($id, ?array $options = null, bool $toArray = false): Record|array|static |
575 | { |
576 | $this->setColumns($this->getRowGateway()->find($id, [], $options)); |
577 | if ($this->hasWiths()) { |
578 | $this->getWithRelationships(false); |
579 | } |
580 | return ($toArray) ? $this->toArray() : $this; |
581 | } |
582 | |
583 | /** |
584 | * Get one method |
585 | * |
586 | * @param ?array $columns |
587 | * @param ?array $options |
588 | * @param bool $toArray |
589 | * @return static|array |
590 | */ |
591 | public function getOne(?array $columns = null, ?array $options = null, bool $toArray = false): Record|array|static |
592 | { |
593 | if ($options === null) { |
594 | $options = ['limit' => 1]; |
595 | } else { |
596 | $options['limit'] = 1; |
597 | } |
598 | |
599 | $expressions = null; |
600 | $params = null; |
601 | $select = $options['select'] ?? null; |
602 | |
603 | if ($columns !== null) { |
604 | $db = Db::getDb($this->getFullTable()); |
605 | $sql = $db->createSql(); |
606 | ['expressions' => $expressions, 'params' => $params] = |
607 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
608 | } |
609 | |
610 | $rows = $this->getTableGateway()->select($select, $expressions, $params, $options); |
611 | |
612 | if ($this->hasWiths() && !empty($rows)) { |
613 | $this->getWithRelationships(); |
614 | $this->processWithRelationships($rows); |
615 | } |
616 | |
617 | if (isset($rows[0])) { |
618 | $this->setColumns($rows[0]); |
619 | } |
620 | |
621 | return ($toArray) ? $this->toArray() : $this; |
622 | } |
623 | |
624 | /** |
625 | * Get by method |
626 | * |
627 | * @param ?array $columns |
628 | * @param ?array $options |
629 | * @param bool $toArray |
630 | * @return Collection|array |
631 | */ |
632 | public function getBy(?array $columns = null, ?array $options = null, bool|array $toArray = false): Collection|array |
633 | { |
634 | $expressions = null; |
635 | $params = null; |
636 | $select = $options['select'] ?? null; |
637 | |
638 | if ($columns !== null) { |
639 | $db = Db::getDb($this->getFullTable()); |
640 | $sql = $db->createSql(); |
641 | ['expressions' => $expressions, 'params' => $params] = |
642 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
643 | } |
644 | |
645 | $rows = $this->getTableGateway()->select($select, $expressions, $params, $options); |
646 | |
647 | foreach ($rows as $i => $row) { |
648 | $rows[$i] = $this->processRow($row); |
649 | } |
650 | |
651 | if ($this->hasWiths() && !empty($rows)) { |
652 | $this->getWithRelationships(); |
653 | $this->processWithRelationships($rows); |
654 | } |
655 | |
656 | $collection = new Record\Collection($rows); |
657 | return ($toArray !== false) ? $collection->toArray($toArray) : $collection; |
658 | } |
659 | |
660 | /** |
661 | * Get in method |
662 | * |
663 | * @param string $key |
664 | * @param array $values |
665 | * @param ?array $columns |
666 | * @param ?array $options |
667 | * @param bool $toArray |
668 | * @return array |
669 | */ |
670 | public function getIn(string $key, array $values, array $columns = null, array $options = null, bool|array $toArray = false): array |
671 | { |
672 | $columns = ($columns !== null) ? array_merge([$key => $values], $columns) : [$key => $values]; |
673 | $results = $this->getBy($columns, $options, $toArray); |
674 | $rows = []; |
675 | |
676 | foreach ($results as $row) { |
677 | if (isset($row[$key])) { |
678 | $rows[$row[$key]] = (($toArray !== false) && ($row instanceof Record)) ? $row->toArray() : $row; |
679 | } |
680 | } |
681 | |
682 | return $rows; |
683 | } |
684 | |
685 | /** |
686 | * Get all method |
687 | * |
688 | * @param ?array $options |
689 | * @param bool $toArray |
690 | * @return Collection|array |
691 | */ |
692 | public function getAll(?array $options = null, bool|array $toArray = false): Collection|array |
693 | { |
694 | return $this->getBy(null, $options, $toArray); |
695 | } |
696 | |
697 | /** |
698 | * Has one relationship |
699 | * |
700 | * @param string $foreignTable |
701 | * @param string $foreignKey |
702 | * @param ?array $options |
703 | * @param bool $eager |
704 | * @return Record|Record\Relationships\HasOne |
705 | */ |
706 | public function hasOne(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Record|Record\Relationships\HasOne |
707 | { |
708 | $relationship = new Record\Relationships\HasOne($this, $foreignTable, $foreignKey, $options); |
709 | if (!empty($this->withChildren)) { |
710 | $relationship->setChildRelationships($this->withChildren); |
711 | } |
712 | return ($eager) ? $relationship : $relationship->getChild($options); |
713 | } |
714 | |
715 | /** |
716 | * Has one of relationship |
717 | * |
718 | * @param string $foreignTable |
719 | * @param string $foreignKey |
720 | * @param ?array $options |
721 | * @param bool $eager |
722 | * @return Record|Record\Relationships\HasOneOf |
723 | */ |
724 | public function hasOneOf(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Record|Record\Relationships\HasOneOf |
725 | { |
726 | $relationship = new Record\Relationships\HasOneOf($this, $foreignTable, $foreignKey, $options); |
727 | if (!empty($this->withChildren)) { |
728 | $relationship->setChildRelationships($this->withChildren); |
729 | } |
730 | return ($eager) ? $relationship : $relationship->getChild(); |
731 | } |
732 | |
733 | /** |
734 | * Has many relationship |
735 | * |
736 | * @param string $foreignTable |
737 | * @param string $foreignKey |
738 | * @param ?array $options |
739 | * @param bool $eager |
740 | * @return Collection|Record\Relationships\HasMany |
741 | */ |
742 | public function hasMany(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Collection|Record\Relationships\HasMany |
743 | { |
744 | $relationship = new Record\Relationships\HasMany($this, $foreignTable, $foreignKey, $options); |
745 | if (!empty($this->withChildren)) { |
746 | $relationship->setChildRelationships($this->withChildren); |
747 | } |
748 | return ($eager) ? $relationship : $relationship->getChildren($options); |
749 | } |
750 | |
751 | /** |
752 | * Belongs to relationship |
753 | * |
754 | * @param string $foreignTable |
755 | * @param string $foreignKey |
756 | * @param ?array $options |
757 | * @param bool $eager |
758 | * @return Record|Record\Relationships\BelongsTo |
759 | */ |
760 | public function belongsTo(string $foreignTable, string $foreignKey, ?array $options = null, bool $eager = false): Record|Record\Relationships\BelongsTo |
761 | { |
762 | $relationship = new Record\Relationships\BelongsTo($this, $foreignTable, $foreignKey, $options); |
763 | if (!empty($this->withChildren)) { |
764 | $relationship->setChildRelationships($this->withChildren); |
765 | } |
766 | return ($eager) ? $relationship : $relationship->getParent($options); |
767 | } |
768 | |
769 | /** |
770 | * Increment the record column and save |
771 | * |
772 | * @param string $column |
773 | * @param int $amount |
774 | * @return void |
775 | */ |
776 | public function increment(string $column, int $amount = 1): void |
777 | { |
778 | $this->{$column} += (int)$amount; |
779 | $this->save(); |
780 | } |
781 | |
782 | /** |
783 | * Decrement the record column and save |
784 | * |
785 | * @param string $column |
786 | * @param int $amount |
787 | * @return void |
788 | */ |
789 | public function decrement(string $column, int $amount = 1): void |
790 | { |
791 | $this->{$column} -= (int)$amount; |
792 | $this->save(); |
793 | } |
794 | |
795 | /** |
796 | * Replicate the record |
797 | * |
798 | * @param array $replace |
799 | * @return static |
800 | */ |
801 | public function replicate(array $replace = []): static |
802 | { |
803 | $fields = $this->toArray(); |
804 | |
805 | foreach ($this->primaryKeys as $key) { |
806 | if (isset($fields[$key])) { |
807 | unset($fields[$key]); |
808 | } |
809 | } |
810 | |
811 | if (!empty($replace)) { |
812 | foreach ($replace as $key => $value) { |
813 | if (array_key_exists($key, $fields)) { |
814 | $fields[$key] = $value; |
815 | } |
816 | } |
817 | } |
818 | |
819 | $newRecord = new static($fields); |
820 | $newRecord->save(); |
821 | |
822 | return $newRecord; |
823 | } |
824 | |
825 | /** |
826 | * Copy the record (alias to replicate) |
827 | * |
828 | * @param array $replace |
829 | * @return static |
830 | */ |
831 | public function copy(array $replace = []): static |
832 | { |
833 | return $this->replicate($replace); |
834 | } |
835 | |
836 | /** |
837 | * Check if row is dirty |
838 | * |
839 | * @return bool |
840 | */ |
841 | public function isDirty(): bool |
842 | { |
843 | return $this->rowGateway->isDirty(); |
844 | } |
845 | |
846 | /** |
847 | * Get row's dirty columns |
848 | * |
849 | * @return array |
850 | */ |
851 | public function getDirty(): array |
852 | { |
853 | return $this->rowGateway->getDirty(); |
854 | } |
855 | |
856 | /** |
857 | * Reset row's dirty columns |
858 | * |
859 | * @return void |
860 | */ |
861 | public function resetDirty(): void |
862 | { |
863 | $this->rowGateway->resetDirty(); |
864 | } |
865 | |
866 | /** |
867 | * Save or update the record |
868 | * |
869 | * @param ?array $columns |
870 | * @param bool $commit |
871 | * @throws \Exception |
872 | * @return void |
873 | */ |
874 | public function save(array $columns = null, bool $commit = true): void |
875 | { |
876 | try { |
877 | // Save or update the record |
878 | if ($columns === null) { |
879 | if ($this->isNew) { |
880 | $this->rowGateway->save(); |
881 | $this->isNew = false; |
882 | } else { |
883 | $this->rowGateway->update(); |
884 | $record = $this->getById($this->rowGateway->getPrimaryValues()); |
885 | if (isset($record[0])) { |
886 | $this->setColumns($record[0]); |
887 | } |
888 | } |
889 | // Else, save multiple rows |
890 | } else { |
891 | if (isset($columns[0])) { |
892 | $this->tableGateway->insertRows($columns); |
893 | } else { |
894 | $this->tableGateway->insert($columns); |
895 | } |
896 | } |
897 | if (($this->isTransaction()) && ($commit)) { |
898 | $this->commitTransaction(); |
899 | } |
900 | } catch (\Exception $e) { |
901 | if (($this->isTransaction()) && ($commit)) { |
902 | $this->rollbackTransaction(); |
903 | } |
904 | throw $e; |
905 | } |
906 | } |
907 | |
908 | /** |
909 | * Delete the record |
910 | * |
911 | * @param ?array $columns |
912 | * @param bool $commit |
913 | * @return void |
914 | */ |
915 | public function delete(array $columns = null, bool $commit = true): void |
916 | { |
917 | try { |
918 | // Delete the record |
919 | if ($columns === null) { |
920 | $this->rowGateway->delete(); |
921 | // Delete multiple rows |
922 | } else { |
923 | $expressions = null; |
924 | $params = []; |
925 | |
926 | if ($columns !== null) { |
927 | $db = Db::getDb($this->getFullTable()); |
928 | $sql = $db->createSql(); |
929 | ['expressions' => $expressions, 'params' => $params] = |
930 | Sql\Parser\Expression::parseShorthand($columns, $sql->getPlaceholder()); |
931 | } |
932 | |
933 | $this->tableGateway->delete($expressions, $params); |
934 | } |
935 | |
936 | $this->setRows(); |
937 | $this->setColumns(); |
938 | |
939 | if (($this->isTransaction()) && ($commit)) { |
940 | $this->commitTransaction(); |
941 | } |
942 | } catch (\Exception $e) { |
943 | if (($this->isTransaction()) && ($commit)) { |
944 | $this->rollbackTransaction(); |
945 | } |
946 | throw $e; |
947 | } |
948 | |
949 | } |
950 | |
951 | /** |
952 | * Call static method for 'findWhere' |
953 | * |
954 | * $users = Users::findWhereUsername($value); |
955 | * |
956 | * $users = Users::findWhereEquals($column, $value); |
957 | * $users = Users::findWhereNotEquals($column, $value); |
958 | * $users = Users::findWhereGreaterThan($column, $value); |
959 | * $users = Users::findWhereGreaterThanOrEqual($column, $value); |
960 | * $users = Users::findWhereLessThan($column, $value); |
961 | * $users = Users::findWhereLessThanOrEqual($column, $value); |
962 | * |
963 | * $users = Users::findWhereLike($column, $value); |
964 | * $users = Users::findWhereNotLike($column, $value); |
965 | * |
966 | * $users = Users::findWhereIn($column, $values); |
967 | * $users = Users::findWhereNotIn($column, $values); |
968 | * |
969 | * $users = Users::findWhereBetween($column, $values); |
970 | * $users = Users::findWhereNotBetween($column, $values); |
971 | * |
972 | * $users = Users::findWhereNull($column); |
973 | * $users = Users::findWhereNotNull($column); |
974 | * |
975 | * @param string $name |
976 | * @param array $arguments |
977 | * @return Collection|array|null |
978 | */ |
979 | public static function __callStatic(string $name, array $arguments): Collection|array|null |
980 | { |
981 | $columns = null; |
982 | $options = null; |
983 | $toArray = false; |
984 | $conditions = [ |
985 | 'Equals', 'NotEquals', 'GreaterThan', 'GreaterThanOrEqual', 'LessThan', 'LessThanOrEqual', |
986 | 'Like', 'NotLike', 'In', 'NotIn', 'Between', 'NotBetween', 'Null', 'NotNull' |
987 | ]; |
988 | |
989 | if (str_starts_with($name, 'findWhere')) { |
990 | if (in_array(substr($name, 9), $conditions)) { |
991 | $condition = substr($name, 9); |
992 | $column = $arguments[0]; |
993 | |
994 | if (str_contains($condition, 'Null')) { |
995 | $value = null; |
996 | $options = $arguments[1] ?? null; |
997 | $toArray = $arguments[2] ?? false; |
998 | } else { |
999 | $value = $arguments[1]; |
1000 | $options = $arguments[2] ?? null; |
1001 | $toArray = $arguments[3] ?? false; |
1002 | } |
1003 | |
1004 | switch ($condition) { |
1005 | case 'Equals': |
1006 | case 'In': |
1007 | case 'Between': |
1008 | case 'Null': |
1009 | $columns = [$column => $value]; |
1010 | break; |
1011 | case 'NotEquals': |
1012 | $columns = [$column . '!=' => $value]; |
1013 | break; |
1014 | case 'GreaterThan': |
1015 | $columns = [$column . '>' => $value]; |
1016 | break; |
1017 | case 'GreaterThanOrEqual': |
1018 | $columns = [$column . '>=' => $value]; |
1019 | break; |
1020 | case 'LessThan': |
1021 | $columns = [$column . '<' => $value]; |
1022 | break; |
1023 | case 'LessThanOrEqual': |
1024 | $columns = [$column . '<=' => $value]; |
1025 | break; |
1026 | case 'Like': |
1027 | if (str_starts_with($value, '%')) { |
1028 | $column = '%' . $column; |
1029 | $value = substr($value, 1); |
1030 | } |
1031 | if (str_ends_with($value, '%')) { |
1032 | $column .= '%'; |
1033 | $value = substr($value, 0, -1); |
1034 | } |
1035 | $columns = [$column => $value]; |
1036 | break; |
1037 | case 'NotLike': |
1038 | if (str_starts_with($value, '%')) { |
1039 | $column = '-%' . $column; |
1040 | $value = substr($value, 1); |
1041 | } |
1042 | if (str_ends_with($value, '%')) { |
1043 | $column .= '%-'; |
1044 | $value = substr($value, 0, -1); |
1045 | } |
1046 | $columns = [$column => $value]; |
1047 | break; |
1048 | case 'NotIn': |
1049 | case 'NotBetween': |
1050 | case 'NotNull': |
1051 | $columns = [$column . '-' => $value]; |
1052 | break; |
1053 | } |
1054 | } else { |
1055 | $column = Sql\Parser\Table::parse(substr($name, 9)); |
1056 | $value = $arguments[0] ?? null; |
1057 | $options = $arguments[1] ?? null; |
1058 | $toArray = $arguments[2] ?? false; |
1059 | |
1060 | if ($value !== null) { |
1061 | $columns = [$column => $value]; |
1062 | } |
1063 | } |
1064 | } |
1065 | |
1066 | return ($columns !== null) ? static::findBy($columns, $options, $toArray) : null; |
1067 | } |
1068 | |
1069 | } |