1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
/** |
3
|
|
|
* @package Terah\FluentPdoModel |
4
|
|
|
* |
5
|
|
|
* Original work Copyright (c) 2014 Mardix (http://github.com/mardix) |
6
|
|
|
* Modified work Copyright (c) 2015 Terry Cullen (http://github.com/terah) |
7
|
|
|
* |
8
|
|
|
* Licensed under The MIT License |
9
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
10
|
|
|
* Redistributions of files must retain the above copyright notice. |
11
|
|
|
* |
12
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
13
|
|
|
*/ |
14
|
|
|
namespace Terah\FluentPdoModel; |
15
|
|
|
|
16
|
|
|
use Closure; |
17
|
|
|
use PDOException; |
18
|
|
|
use Exception; |
19
|
|
|
use PDO; |
20
|
|
|
use PDOStatement; |
21
|
|
|
use stdClass; |
22
|
|
|
use DateTime; |
23
|
|
|
use Terah\FluentPdoModel\Drivers\AbstractPdo; |
24
|
|
|
use Psr\Log\LoggerInterface; |
25
|
|
|
use Terah\RedisCache\CacheInterface; |
26
|
|
|
use function Terah\Assert\Assert; |
27
|
|
|
use function Terah\Assert\Validate; |
28
|
|
|
/** |
29
|
|
|
* Class FluentPdoModel |
30
|
|
|
* |
31
|
|
|
* @package Terah\FluentPdoModel |
32
|
|
|
* @author Terry Cullen - [email protected] |
33
|
|
|
*/ |
34
|
|
|
class FluentPdoModel |
35
|
|
|
{ |
36
|
|
|
const ACTIVE = 1; |
37
|
|
|
const INACTIVE = 0; |
38
|
|
|
const ARCHIVED = -1; |
39
|
|
|
const GZIP_PREFIX = 'gzipped|'; |
40
|
|
|
const OPERATOR_AND = ' AND '; |
41
|
|
|
const OPERATOR_OR = ' OR '; |
42
|
|
|
const ORDERBY_ASC = 'ASC'; |
43
|
|
|
const ORDERBY_DESC = 'DESC'; |
44
|
|
|
const SAVE_INSERT = 'INSERT'; |
45
|
|
|
const SAVE_UPDATE = 'UPDATE'; |
46
|
|
|
const LEFT_JOIN = 'LEFT'; |
47
|
|
|
const INNER_JOIN = 'INNER'; |
48
|
|
|
const ONE_DAY = 86400; |
49
|
|
|
const ONE_WEEK = 60480060; |
50
|
|
|
const ONE_HOUR = 3600; |
51
|
|
|
const TEN_MINS = 600; |
52
|
|
|
const CACHE_NO = -1; |
53
|
|
|
const CACHE_DEFAULT = 0; |
54
|
|
|
|
55
|
|
|
const CREATOR_ID_FIELD = 'creator_id'; |
56
|
|
|
const CREATOR_FIELD = 'creator'; |
57
|
|
|
const CREATED_TS_FIELD = 'created_ts'; |
58
|
|
|
const MODIFIER_ID_FIELD = 'modifier_id'; |
59
|
|
|
const MODIFIER_FIELD = 'modifier'; |
60
|
|
|
const MODIFIED_TS_FIELD = 'modified_ts'; |
61
|
|
|
const DELETER_ID_FIELD = 'deleter_id'; |
62
|
|
|
const DELETER_FIELD = 'deleter'; |
63
|
|
|
const DELETED_TS_FIELD = 'deleted_ts'; |
64
|
|
|
const STATUS_FIELD = 'active'; |
65
|
|
|
|
66
|
|
|
/** @var AbstractPdo $connection */ |
67
|
|
|
protected $connection = null; |
68
|
|
|
|
69
|
|
|
/** @var string */ |
70
|
|
|
protected $primaryKey = 'id'; |
71
|
|
|
|
72
|
|
|
/** @var array */ |
73
|
|
|
protected $whereParameters = []; |
74
|
|
|
|
75
|
|
|
/** @var array */ |
76
|
|
|
protected $selectFields = []; |
77
|
|
|
|
78
|
|
|
/** @var array */ |
79
|
|
|
protected $joinSources = []; |
80
|
|
|
|
81
|
|
|
/** @var array */ |
82
|
|
|
protected $joinAliases = []; |
83
|
|
|
|
84
|
|
|
/** @var array $associations */ |
85
|
|
|
protected $associations = [ |
86
|
|
|
'belongsTo' => [], |
87
|
|
|
]; |
88
|
|
|
|
89
|
|
|
/** @var array */ |
90
|
|
|
protected $whereConditions = []; |
91
|
|
|
|
92
|
|
|
/** @var string */ |
93
|
|
|
protected $rawSql = ''; |
94
|
|
|
|
95
|
|
|
/** @var int */ |
96
|
|
|
protected $limit = 0; |
97
|
|
|
|
98
|
|
|
/** @var int */ |
99
|
|
|
protected $offset = 0; |
100
|
|
|
|
101
|
|
|
/** @var array */ |
102
|
|
|
protected $orderBy = []; |
103
|
|
|
|
104
|
|
|
/** @var array */ |
105
|
|
|
protected $groupBy = []; |
106
|
|
|
|
107
|
|
|
/** @var string */ |
108
|
|
|
protected $andOrOperator = self::OPERATOR_AND; |
109
|
|
|
|
110
|
|
|
/** @var array */ |
111
|
|
|
protected $having = []; |
112
|
|
|
|
113
|
|
|
/** @var bool */ |
114
|
|
|
protected $wrapOpen = false; |
115
|
|
|
|
116
|
|
|
/** @var int */ |
117
|
|
|
protected $lastWrapPosition = 0; |
118
|
|
|
|
119
|
|
|
/** @var PDOStatement $pdoStmt */ |
120
|
|
|
protected $pdoStmt = null; |
121
|
|
|
|
122
|
|
|
/** @var bool */ |
123
|
|
|
protected $distinct = false; |
124
|
|
|
|
125
|
|
|
/** @var null */ |
126
|
|
|
protected $requestedFields = []; |
127
|
|
|
|
128
|
|
|
/** @var null */ |
129
|
|
|
protected $filterMeta = []; |
130
|
|
|
|
131
|
|
|
/** @var bool */ |
132
|
|
|
protected $logQueries = false; |
133
|
|
|
|
134
|
|
|
/** @var array */ |
135
|
|
|
protected $timer = []; |
136
|
|
|
|
137
|
|
|
/** @var int */ |
138
|
|
|
protected $slowQuerySecs = 5; |
139
|
|
|
|
140
|
|
|
/** @var array */ |
141
|
|
|
protected $paginationAttribs = [ |
142
|
|
|
'_limit', |
143
|
|
|
'_offset', |
144
|
|
|
'_order', |
145
|
|
|
'_fields', |
146
|
|
|
'_search' |
147
|
|
|
]; |
148
|
|
|
|
149
|
|
|
/** @var string $tableName */ |
150
|
|
|
protected $tableName = ''; |
151
|
|
|
|
152
|
|
|
/** @var string $tableAlias */ |
153
|
|
|
protected $tableAlias = ''; |
154
|
|
|
|
155
|
|
|
/** @var string $displayColumn */ |
156
|
|
|
protected $displayColumn = ''; |
157
|
|
|
|
158
|
|
|
/** @var string $connectionName */ |
159
|
|
|
protected $connectionName = ''; |
160
|
|
|
|
161
|
|
|
/** @var array $schema */ |
162
|
|
|
protected $schema = []; |
163
|
|
|
|
164
|
|
|
/** @var array $virtualFields */ |
165
|
|
|
protected $virtualFields = []; |
166
|
|
|
|
167
|
|
|
/** @var array $errors */ |
168
|
|
|
protected $errors = []; |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @var int - true = connection default x days |
172
|
|
|
* - false = no cache |
173
|
|
|
* - int = a specific amount |
174
|
|
|
*/ |
175
|
|
|
protected $cacheTtl = self::CACHE_NO; |
176
|
|
|
|
177
|
|
|
/** @var array */ |
178
|
|
|
protected $flushCacheTables = []; |
179
|
|
|
|
180
|
|
|
/** @var string */ |
181
|
|
|
protected $tmpTablePrefix = 'tmp_'; |
182
|
|
|
|
183
|
|
|
/** @var null|string */ |
184
|
|
|
protected $builtQuery = ''; |
185
|
|
|
|
186
|
|
|
/** @var array */ |
187
|
|
|
protected $handlers = []; |
188
|
|
|
|
189
|
|
|
/** @var bool User want to directly specify the fields */ |
190
|
|
|
protected $explicitSelectMode = false; |
191
|
|
|
|
192
|
|
|
/** @var string[] */ |
193
|
|
|
protected $updateRaw = []; |
194
|
|
|
|
195
|
|
|
/** @var int */ |
196
|
|
|
protected $maxCallbackFails = -1; |
197
|
|
|
|
198
|
|
|
/** @var int */ |
199
|
|
|
protected $numCallbackFails = 0; |
200
|
|
|
|
201
|
|
|
/** @var bool */ |
202
|
|
|
protected $filterOnFetch = false; |
203
|
|
|
|
204
|
|
|
/** @var bool */ |
205
|
|
|
protected $logFilterChanges = true; |
206
|
|
|
|
207
|
|
|
/** @var bool */ |
208
|
|
|
protected $includeCount = false; |
209
|
|
|
|
210
|
|
|
/** @var string */ |
211
|
|
|
static protected $modelNamespace = ''; |
212
|
|
|
|
213
|
|
|
/** @var bool */ |
214
|
|
|
protected $validationExceptions = true; |
215
|
|
|
|
216
|
|
|
/** @var array */ |
217
|
|
|
protected $pagingMeta = []; |
218
|
|
|
|
219
|
|
|
/** @var bool */ |
220
|
|
|
protected $softDeletes = true; |
221
|
|
|
|
222
|
|
|
/** @var bool */ |
223
|
|
|
protected $allowMetaOverride = false; |
224
|
|
|
|
225
|
|
|
/** @var bool */ |
226
|
|
|
protected $skipMetaUpdates = false; |
227
|
|
|
|
228
|
|
|
/** @var bool */ |
229
|
|
|
protected $addUpdateAlias = false; |
230
|
|
|
|
231
|
|
|
/** @var int */ |
232
|
|
|
protected $defaultMax = 250; |
233
|
|
|
|
234
|
|
|
/** @var array */ |
235
|
|
|
protected $removeUnauthorisedFields = []; |
236
|
|
|
|
237
|
|
|
/** @var bool */ |
238
|
|
|
protected $canGenericUpdate = true; |
239
|
|
|
|
240
|
|
|
/** @var bool */ |
241
|
|
|
protected $canGenericCreate = true; |
242
|
|
|
|
243
|
|
|
/** @var bool */ |
244
|
|
|
protected $canGenericDelete = true; |
245
|
|
|
|
246
|
|
|
/** @var array */ |
247
|
|
|
protected $rowMetaData = []; |
248
|
|
|
|
249
|
|
|
/** @var array */ |
250
|
|
|
protected $excludedSearchCols = []; |
251
|
|
|
|
252
|
|
|
|
253
|
|
|
/** @var array */ |
254
|
|
|
protected $globalRemoveUnauthorisedFields = [ |
255
|
|
|
'/global_table_meta#view' => [ |
256
|
|
|
self::CREATOR_ID_FIELD, |
257
|
|
|
self::CREATOR_FIELD, |
258
|
|
|
self::CREATED_TS_FIELD, |
259
|
|
|
self::MODIFIER_ID_FIELD, |
260
|
|
|
self::MODIFIER_FIELD, |
261
|
|
|
self::MODIFIED_TS_FIELD, |
262
|
|
|
self::STATUS_FIELD, |
263
|
|
|
], |
264
|
|
|
]; |
265
|
|
|
|
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* @param AbstractPdo|null $connection |
269
|
|
|
*/ |
270
|
|
|
public function __construct(AbstractPdo $connection=null) |
271
|
|
|
{ |
272
|
|
|
$connection = $connection ?: ConnectionPool::get($this->connectionName); |
273
|
|
|
$this->connection = $connection; |
274
|
|
|
$this->logQueries = $connection->logQueries(); |
275
|
|
|
$this->init(); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
public function init() |
279
|
|
|
{} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* @return AbstractPdo |
283
|
|
|
* @throws Exception |
284
|
|
|
*/ |
285
|
|
|
public function getPdo() : AbstractPdo |
286
|
|
|
{ |
287
|
|
|
return $this->connection; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* @return LoggerInterface |
292
|
|
|
*/ |
293
|
|
|
public function getLogger() : LoggerInterface |
294
|
|
|
{ |
295
|
|
|
return $this->connection->getLogger(); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* @return CacheInterface |
300
|
|
|
*/ |
301
|
|
|
public function getCache() : CacheInterface |
302
|
|
|
{ |
303
|
|
|
return $this->connection->getCache(); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* Define the working table and create a new instance |
308
|
|
|
* |
309
|
|
|
* @param string $tableName - Table name |
310
|
|
|
* @param string $alias - The table alias name |
311
|
|
|
* @param string $displayColumn |
312
|
|
|
* @param string $primaryKeyName |
313
|
|
|
* |
314
|
|
|
* @return FluentPdoModel|$this |
315
|
|
|
*/ |
316
|
|
|
public function table(string $tableName, string $alias='', string $displayColumn='', string $primaryKeyName='id') : FluentPdoModel |
317
|
|
|
{ |
318
|
|
|
return $this->reset() |
319
|
|
|
->tableName($tableName) |
320
|
|
|
->tableAlias($alias) |
321
|
|
|
->displayColumn($displayColumn) |
322
|
|
|
->primaryKeyName($primaryKeyName); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* @param string $primaryKeyName |
327
|
|
|
* @return FluentPdoModel|$this |
328
|
|
|
*/ |
329
|
|
|
public function primaryKeyName(string $primaryKeyName) : FluentPdoModel |
330
|
|
|
{ |
331
|
|
|
$this->primaryKey = $primaryKeyName; |
332
|
|
|
|
333
|
|
|
return $this; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* @param string $tableName |
338
|
|
|
* |
339
|
|
|
* @return FluentPdoModel|$this |
340
|
|
|
*/ |
341
|
|
|
public function tableName(string $tableName) : FluentPdoModel |
342
|
|
|
{ |
343
|
|
|
$this->tableName = $tableName; |
344
|
|
|
|
345
|
|
|
return $this; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* @param $explicitSelect |
350
|
|
|
* |
351
|
|
|
* @return FluentPdoModel|$this |
352
|
|
|
*/ |
353
|
|
|
public function explicitSelectMode(bool $explicitSelect=true) : FluentPdoModel |
354
|
|
|
{ |
355
|
|
|
$this->explicitSelectMode = (bool)$explicitSelect; |
356
|
|
|
|
357
|
|
|
return $this; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* @param bool $filterOnFetch |
362
|
|
|
* |
363
|
|
|
* @return FluentPdoModel|$this |
364
|
|
|
*/ |
365
|
|
|
public function filterOnFetch(bool $filterOnFetch=true) : FluentPdoModel |
366
|
|
|
{ |
367
|
|
|
$this->filterOnFetch = (bool)$filterOnFetch; |
368
|
|
|
|
369
|
|
|
return $this; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* @param bool $logFilterChanges |
374
|
|
|
* |
375
|
|
|
* @return FluentPdoModel|$this |
376
|
|
|
*/ |
377
|
|
|
public function logFilterChanges(bool $logFilterChanges=true) : FluentPdoModel |
378
|
|
|
{ |
379
|
|
|
$this->logFilterChanges = (bool)$logFilterChanges; |
380
|
|
|
|
381
|
|
|
return $this; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Return the name of the table |
386
|
|
|
* |
387
|
|
|
* @return string |
388
|
|
|
*/ |
389
|
|
|
public function getTableName() : string |
390
|
|
|
{ |
391
|
|
|
return $this->tableName; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* @return string |
396
|
|
|
*/ |
397
|
|
|
public function getDisplayColumn() : string |
398
|
|
|
{ |
399
|
|
|
return $this->displayColumn; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Set the display column |
404
|
|
|
* |
405
|
|
|
* @param string $column |
406
|
|
|
* |
407
|
|
|
* @return FluentPdoModel|$this |
408
|
|
|
*/ |
409
|
|
|
public function displayColumn(string $column) : FluentPdoModel |
410
|
|
|
{ |
411
|
|
|
$this->displayColumn = $column; |
412
|
|
|
|
413
|
|
|
return $this; |
414
|
|
|
} |
415
|
|
|
/** |
416
|
|
|
* Set the table alias |
417
|
|
|
* |
418
|
|
|
* @param string $alias |
419
|
|
|
* |
420
|
|
|
* @return FluentPdoModel|$this |
421
|
|
|
*/ |
422
|
|
|
public function tableAlias(string $alias) : FluentPdoModel |
423
|
|
|
{ |
424
|
|
|
$this->tableAlias = $alias; |
425
|
|
|
|
426
|
|
|
return $this; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* @param int $cacheTtl |
431
|
|
|
* @return FluentPdoModel|$this |
432
|
|
|
* @throws Exception |
433
|
|
|
*/ |
434
|
|
|
protected function cacheTtl(int $cacheTtl) : FluentPdoModel |
435
|
|
|
{ |
436
|
|
|
Assert($cacheTtl)->int('Cache ttl must be either -1 for no cache, 0 for default ttl or an integer for a custom ttl'); |
437
|
|
|
if ( $cacheTtl !== self::CACHE_NO && ! is_null($this->pdoStmt) ) |
438
|
|
|
{ |
439
|
|
|
throw new Exception("You cannot cache pre-executed queries"); |
440
|
|
|
} |
441
|
|
|
$this->cacheTtl = $cacheTtl; |
442
|
|
|
|
443
|
|
|
return $this; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* @return string |
448
|
|
|
*/ |
449
|
|
|
public function getTableAlias() : string |
450
|
|
|
{ |
451
|
|
|
return $this->tableAlias; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
/** |
455
|
|
|
* @param array $associations |
456
|
|
|
* |
457
|
|
|
* @return FluentPdoModel|$this |
458
|
|
|
*/ |
459
|
|
|
public function associations(array $associations) : FluentPdoModel |
460
|
|
|
{ |
461
|
|
|
$this->associations = $associations; |
462
|
|
|
|
463
|
|
|
return $this; |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* @param string $alias |
468
|
|
|
* @param array $definition |
469
|
|
|
* @return FluentPdoModel|$this |
470
|
|
|
*/ |
471
|
|
|
public function setBelongsTo(string $alias, array $definition) : FluentPdoModel |
472
|
|
|
{ |
473
|
|
|
Assert($alias)->notEmpty(); |
474
|
|
|
Assert($definition)->isArray()->count(4); |
475
|
|
|
|
476
|
|
|
$this->associations['belongsTo'][$alias] = $definition; |
477
|
|
|
|
478
|
|
|
return $this; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* @param $alias |
483
|
|
|
* @param $displayField |
484
|
|
|
* @return FluentPdoModel|$this |
485
|
|
|
* @throws \Terah\Assert\AssertionFailedException |
486
|
|
|
*/ |
487
|
|
|
public function setBelongsToDisplayField(string $alias, string $displayField) : FluentPdoModel |
488
|
|
|
{ |
489
|
|
|
Assert($alias)->notEmpty(); |
490
|
|
|
Assert($this->associations['belongsTo'])->keyExists($alias); |
491
|
|
|
Assert($displayField)->notEmpty(); |
492
|
|
|
|
493
|
|
|
$this->associations['belongsTo'][$alias][2] = $displayField; |
494
|
|
|
|
495
|
|
|
return $this; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* @param PDOStatement $stmt |
500
|
|
|
* @param Closure $fnCallback |
501
|
|
|
* @return bool|stdClass |
502
|
|
|
*/ |
503
|
|
|
public function fetchRow(PDOStatement $stmt, Closure $fnCallback=null) |
504
|
|
|
{ |
505
|
|
|
if ( ! ( $record = $stmt->fetch(PDO::FETCH_OBJ) ) ) |
506
|
|
|
{ |
507
|
|
|
$this->rowMetaData = []; |
508
|
|
|
|
509
|
|
|
return false; |
510
|
|
|
} |
511
|
|
|
$this->rowMetaData = $this->rowMetaData ?: $this->getColumnMeta($stmt, $record); |
512
|
|
|
$record = $this->onFetch($record); |
513
|
|
|
if ( empty($fnCallback) ) |
514
|
|
|
{ |
515
|
|
|
return $record; |
516
|
|
|
} |
517
|
|
|
$record = $fnCallback($record); |
518
|
|
|
if ( is_null($record) ) |
519
|
|
|
{ |
520
|
|
|
$this->getLogger()->warning("The callback is not returning any data which might be causing early termination of the result iteration"); |
521
|
|
|
} |
522
|
|
|
unset($fnCallback); |
523
|
|
|
|
524
|
|
|
return $record; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
/** |
528
|
|
|
* @param PDOStatement $stmt |
529
|
|
|
* @param $record |
530
|
|
|
* @return array |
531
|
|
|
*/ |
532
|
|
|
protected function getColumnMeta(PDOStatement $stmt, $record) : array |
533
|
|
|
{ |
534
|
|
|
$meta = []; |
535
|
|
|
if ( ! $this->connection->supportsColumnMeta() ) |
536
|
|
|
{ |
537
|
|
|
return $meta; |
538
|
|
|
} |
539
|
|
|
foreach(range(0, $stmt->columnCount() - 1) as $index) |
540
|
|
|
{ |
541
|
|
|
$data = $stmt->getColumnMeta($index); |
542
|
|
|
$meta[$data['name']] = $data; |
543
|
|
|
} |
544
|
|
|
foreach ( $record as $field => $value ) |
545
|
|
|
{ |
546
|
|
|
Assert($meta)->keyExists($field); |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
return $meta; |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
/** |
553
|
|
|
* @param array $schema |
554
|
|
|
* |
555
|
|
|
* @return FluentPdoModel|$this |
556
|
|
|
*/ |
557
|
|
|
public function schema(array $schema) : FluentPdoModel |
558
|
|
|
{ |
559
|
|
|
$this->schema = $schema; |
560
|
|
|
|
561
|
|
|
return $this; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* @param string|array $field |
566
|
|
|
* @param $type |
567
|
|
|
* @return FluentPdoModel|$this |
568
|
|
|
*/ |
569
|
|
|
public function addSchema($field, string $type) : FluentPdoModel |
570
|
|
|
{ |
571
|
|
|
if ( is_array($field) ) |
572
|
|
|
{ |
573
|
|
|
foreach ( $field as $fieldName => $typeDef ) |
574
|
|
|
{ |
575
|
|
|
$this->addSchema($fieldName, $typeDef); |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
return $this; |
579
|
|
|
} |
580
|
|
|
Assert($field)->string()->notEmpty(); |
581
|
|
|
$this->schema[$field] = $type; |
582
|
|
|
|
583
|
|
|
return $this; |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
/** |
587
|
|
|
* @param bool $getForeign |
588
|
|
|
* @return array |
589
|
|
|
*/ |
590
|
|
|
public function getSchema(bool $getForeign=false) : array |
591
|
|
|
{ |
592
|
|
|
if ( $getForeign ) |
593
|
|
|
{ |
594
|
|
|
return $this->schema; |
595
|
|
|
} |
596
|
|
|
return array_filter($this->schema, function(string $type) { |
597
|
|
|
|
598
|
|
|
return $type !== 'foreign'; |
599
|
|
|
}); |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
/** |
603
|
|
|
* @param $keysOnly |
604
|
|
|
* @return array |
605
|
|
|
*/ |
606
|
|
|
public function getColumns(bool $keysOnly=true) : array |
607
|
|
|
{ |
608
|
|
|
$schema = $this->getSchema(); |
609
|
|
|
|
610
|
|
|
return $keysOnly ? array_keys($schema) : $schema; |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* Get the primary key name |
615
|
|
|
* |
616
|
|
|
* @return string |
617
|
|
|
*/ |
618
|
|
|
public function getPrimaryKeyName() : string |
619
|
|
|
{ |
620
|
|
|
return $this->formatKeyName($this->primaryKey, $this->tableName); |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* @param string $query |
625
|
|
|
* @param array $parameters |
626
|
|
|
* |
627
|
|
|
* @return bool |
628
|
|
|
* @throws Exception |
629
|
|
|
*/ |
630
|
|
|
public function execute(string $query, array $parameters=[]) : bool |
631
|
|
|
{ |
632
|
|
|
list($this->builtQuery, $ident) = $this->logQuery($query, $parameters); |
633
|
|
|
try |
634
|
|
|
{ |
635
|
|
|
$this->pdoStmt = $this->getPdo()->prepare($query); |
636
|
|
|
$result = $this->pdoStmt->execute($parameters); |
637
|
|
|
if ( false === $result ) |
638
|
|
|
{ |
639
|
|
|
$this->pdoStmt = null; |
640
|
|
|
|
641
|
|
|
throw new PDOException("The query failed to execute."); |
642
|
|
|
} |
643
|
|
|
} |
644
|
|
|
catch( Exception $e ) |
645
|
|
|
{ |
646
|
|
|
$builtQuery = $this->builtQuery ? $this->builtQuery : $this->buildQuery($query, $parameters); |
647
|
|
|
$this->getLogger()->error("FAILED: \n\n{$builtQuery}\n WITH ERROR:\n" . $e->getMessage()); |
648
|
|
|
$this->pdoStmt = null; |
649
|
|
|
|
650
|
|
|
if ( preg_match('/FOREIGN KEY \(`([a-zA-Z0-9_]+)`\) REFERENCES \`([a-zA-Z0-9_]+)\` \(\`id\`\)/', $e->getMessage(), $matches) ) |
651
|
|
|
{ |
652
|
|
|
$field = $matches[1] ??0?: 'Unknown'; |
653
|
|
|
$reference = $matches[2] ??0?: 'Unknown'; |
654
|
|
|
$errors = [ |
655
|
|
|
$field => ["Could not find {$reference} for {$field}"], |
656
|
|
|
]; |
657
|
|
|
|
658
|
|
|
throw new ModelFailedValidationException("Validation of data failed", $errors, 422); |
659
|
|
|
} |
660
|
|
|
throw $e; |
661
|
|
|
} |
662
|
|
|
$this->logSlowQueries($ident, $this->builtQuery); |
663
|
|
|
|
664
|
|
|
return $result; |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* @param string $query |
669
|
|
|
* @param array $params |
670
|
|
|
* @return FluentPdoModel|$this |
671
|
|
|
*/ |
672
|
|
|
public function query(string $query, array $params=[]) : FluentPdoModel |
673
|
|
|
{ |
674
|
|
|
$this->rawSql = $query; |
675
|
|
|
$this->whereParameters = $params; |
676
|
|
|
|
677
|
|
|
return $this; |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
/** |
681
|
|
|
* @param string $sql |
682
|
|
|
* @param array $params |
683
|
|
|
* |
684
|
|
|
* @return string |
685
|
|
|
*/ |
686
|
|
|
public function buildQuery(string $sql, array $params=[]) : string |
687
|
|
|
{ |
688
|
|
|
$indexed = $params == array_values($params); |
689
|
|
|
if ( $indexed ) |
690
|
|
|
{ |
691
|
|
|
foreach ( $params as $key => $val ) |
692
|
|
|
{ |
693
|
|
|
$val = is_string($val) ? "'{$val}'" : $val; |
694
|
|
|
$val = is_null($val) ? 'NULL' : $val; |
695
|
|
|
$sql = preg_replace('/\?/', $val, $sql, 1); |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
return $sql; |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
uksort($params, function ($a, $b) { |
702
|
|
|
return strlen($b) - strlen($a); |
703
|
|
|
}); |
704
|
|
|
foreach ( $params as $key => $val ) |
705
|
|
|
{ |
706
|
|
|
$val = is_string($val) ? "'{$val}'" : $val; |
707
|
|
|
$val = is_null($val) ? 'NULL' : $val; |
708
|
|
|
$sql = str_replace(":$key", $val, $sql); |
709
|
|
|
//$sql = str_replace("$key", $val, $sql); |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
return $sql; |
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
/** |
716
|
|
|
* @param stdClass $record |
717
|
|
|
* |
718
|
|
|
* @return stdClass |
719
|
|
|
*/ |
720
|
|
|
protected function trimAndLowerCaseKeys(stdClass $record) : stdClass |
721
|
|
|
{ |
722
|
|
|
$fnTrimStrings = function($value) { |
723
|
|
|
|
724
|
|
|
return is_string($value) ? trim($value) : $value; |
725
|
|
|
}; |
726
|
|
|
$record = array_map($fnTrimStrings, array_change_key_case((array)$record, CASE_LOWER)); |
727
|
|
|
unset($fnTrimStrings); |
728
|
|
|
|
729
|
|
|
return (object)$record; |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
/** |
733
|
|
|
* Return the number of affected row by the last statement |
734
|
|
|
* |
735
|
|
|
* @return int |
736
|
|
|
*/ |
737
|
|
|
public function rowCount() : int |
738
|
|
|
{ |
739
|
|
|
$stmt = $this->fetchStmt(); |
740
|
|
|
|
741
|
|
|
return $stmt ? $stmt->rowCount() : 0; |
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
/** |
745
|
|
|
* @return PDOStatement |
746
|
|
|
* @throws PDOException |
747
|
|
|
*/ |
748
|
|
|
public function fetchStmt() |
749
|
|
|
{ |
750
|
|
|
if ( null === $this->pdoStmt ) |
751
|
|
|
{ |
752
|
|
|
$this->execute($this->getSelectQuery(), $this->getWhereParameters()); |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
return $this->pdoStmt; |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
/** |
759
|
|
|
* @return array |
760
|
|
|
*/ |
761
|
|
|
public function fetchSqlQuery() : array |
762
|
|
|
{ |
763
|
|
|
$clone = clone $this; |
764
|
|
|
$query = $clone->getSelectQuery(); |
765
|
|
|
$params = $clone->getWhereParameters(); |
766
|
|
|
$result = [$query, $params]; |
767
|
|
|
unset($clone->handlers, $clone, $query, $params); |
768
|
|
|
|
769
|
|
|
return $result; |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
/** |
773
|
|
|
* @param string $tableName |
774
|
|
|
* @param bool $dropIfExists |
775
|
|
|
* @param array $indexes |
776
|
|
|
* @return boolean |
777
|
|
|
* @throws Exception |
778
|
|
|
*/ |
779
|
|
|
public function fetchIntoMemoryTable(string $tableName, bool $dropIfExists=true, array $indexes=[]) : bool |
780
|
|
|
{ |
781
|
|
|
$tableName = preg_replace('/[^A-Za-z0-9_]+/', '', $tableName); |
782
|
|
|
$tableName = $this->tmpTablePrefix . preg_replace('/^' . $this->tmpTablePrefix . '/', '', $tableName); |
783
|
|
|
if ( $dropIfExists ) |
784
|
|
|
{ |
785
|
|
|
$this->execute("DROP TABLE IF EXISTS {$tableName}"); |
786
|
|
|
} |
787
|
|
|
$indexSql = []; |
788
|
|
|
foreach ( $indexes as $name => $column ) |
789
|
|
|
{ |
790
|
|
|
$indexSql[] = "INDEX {$name} ({$column})"; |
791
|
|
|
} |
792
|
|
|
$indexSql = implode(", ", $indexSql); |
793
|
|
|
$indexSql = empty($indexSql) ? '' : "({$indexSql})"; |
794
|
|
|
list($sql, $params) = $this->fetchSqlQuery(); |
795
|
|
|
$sql = <<<SQL |
796
|
|
|
CREATE TEMPORARY TABLE {$tableName} {$indexSql} ENGINE=MEMORY {$sql} |
797
|
|
|
SQL; |
798
|
|
|
|
799
|
|
|
return $this->execute($sql, $params); |
800
|
|
|
} |
801
|
|
|
|
802
|
|
|
/** |
803
|
|
|
* @param string $keyedOn |
804
|
|
|
* @param int $cacheTtl |
805
|
|
|
* @return stdClass[] |
806
|
|
|
*/ |
807
|
|
|
public function fetch(string $keyedOn='', int $cacheTtl=self::CACHE_NO) : array |
808
|
|
|
{ |
809
|
|
|
$this->cacheTtl($cacheTtl); |
810
|
|
|
$fnCallback = function() use ($keyedOn) { |
811
|
|
|
|
812
|
|
|
$stmt = $this->fetchStmt(); |
813
|
|
|
$rows = []; |
814
|
|
|
while ( $record = $this->fetchRow($stmt) ) |
815
|
|
|
{ |
816
|
|
|
if ( $record === false ) continue; // For scrutinizer... |
817
|
|
|
if ( $keyedOn && property_exists($record, $keyedOn) ) |
818
|
|
|
{ |
819
|
|
|
$rows[$record->{$keyedOn}] = $record; |
820
|
|
|
continue; |
821
|
|
|
} |
822
|
|
|
$rows[] = $record; |
823
|
|
|
} |
824
|
|
|
$this->reset(); |
825
|
|
|
|
826
|
|
|
return $rows; |
827
|
|
|
}; |
828
|
|
|
if ( $this->cacheTtl === self::CACHE_NO ) |
829
|
|
|
{ |
830
|
|
|
return $fnCallback(); |
831
|
|
|
} |
832
|
|
|
$table = $this->getTableName(); |
833
|
|
|
$id = $this->parseWhereForPrimaryLookup(); |
834
|
|
|
$id = $id ? "/{$id}" : ''; |
835
|
|
|
list($sql, $params) = $this->fetchSqlQuery(); |
836
|
|
|
$sql = $this->buildQuery($sql, $params); |
837
|
|
|
$cacheKey = "/{$table}{$id}/" . md5(json_encode([ |
838
|
|
|
'sql' => $sql, |
839
|
|
|
'keyed_on' => $keyedOn, |
840
|
|
|
])); |
841
|
|
|
$data = $this->cacheData($cacheKey, $fnCallback, $this->cacheTtl); |
842
|
|
|
|
843
|
|
|
return is_array($data) ? $data : []; |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
/** |
847
|
|
|
* @return string |
848
|
|
|
*/ |
849
|
|
|
protected function parseWhereForPrimaryLookup() : string |
850
|
|
|
{ |
851
|
|
|
if ( ! ( $alias = $this->getTableAlias() ) ) |
852
|
|
|
{ |
853
|
|
|
return ''; |
854
|
|
|
} |
855
|
|
|
foreach ( $this->whereConditions as $idx => $conds ) |
856
|
|
|
{ |
857
|
|
|
if ( ! empty($conds['STATEMENT']) && $conds['STATEMENT'] === "{$alias}.id = ?" ) |
858
|
|
|
{ |
859
|
|
|
return ! empty($conds['PARAMS'][0]) ? (string)$conds['PARAMS'][0] : ''; |
860
|
|
|
} |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
return ''; |
864
|
|
|
} |
865
|
|
|
|
866
|
|
|
/** |
867
|
|
|
* @param string $cacheKey |
868
|
|
|
* @param Closure $func |
869
|
|
|
* @param int $cacheTtl - 0 for default ttl, -1 for no cache or int for custom ttl |
870
|
|
|
* @return mixed |
871
|
|
|
*/ |
872
|
|
|
protected function cacheData(string $cacheKey, Closure $func, int $cacheTtl=self::CACHE_DEFAULT) |
873
|
|
|
{ |
874
|
|
|
if ( $cacheTtl === self::CACHE_NO ) |
875
|
|
|
{ |
876
|
|
|
return $func->__invoke(); |
877
|
|
|
} |
878
|
|
|
$data = $this->getCache()->get($cacheKey); |
879
|
|
|
if ( $data && is_object($data) && property_exists($data, 'results') ) |
880
|
|
|
{ |
881
|
|
|
$this->getLogger()->debug("Cache hit on {$cacheKey}"); |
882
|
|
|
|
883
|
|
|
return $data->results; |
884
|
|
|
} |
885
|
|
|
$this->getLogger()->debug("Cache miss on {$cacheKey}"); |
886
|
|
|
$data = (object)[ |
887
|
|
|
// Watch out... invoke most likely calls reset |
888
|
|
|
// which clears the model params like _cache_ttl |
889
|
|
|
'results' => $func->__invoke(), |
890
|
|
|
]; |
891
|
|
|
try |
892
|
|
|
{ |
893
|
|
|
// The cache engine expects null for the default cache value |
894
|
|
|
$cacheTtl = $cacheTtl === self::CACHE_DEFAULT ? 0 : $cacheTtl; |
895
|
|
|
/** @noinspection PhpMethodParametersCountMismatchInspection */ |
896
|
|
|
if ( ! $this->getCache()->set($cacheKey, $data, $cacheTtl) ) |
897
|
|
|
{ |
898
|
|
|
throw new \Exception("Could not save data to cache"); |
899
|
|
|
} |
900
|
|
|
|
901
|
|
|
return $data->results; |
902
|
|
|
} |
903
|
|
|
catch (\Exception $e) |
904
|
|
|
{ |
905
|
|
|
$this->getLogger()->error($e->getMessage(), $e->getTrace()); |
906
|
|
|
|
907
|
|
|
return $data->results; |
908
|
|
|
} |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
/** |
912
|
|
|
* @param string $cacheKey |
913
|
|
|
* @return bool |
914
|
|
|
*/ |
915
|
|
|
public function clearCache(string $cacheKey) : bool |
916
|
|
|
{ |
917
|
|
|
return $this->getCache()->delete($cacheKey); |
918
|
|
|
} |
919
|
|
|
|
920
|
|
|
/** |
921
|
|
|
* @param string $table |
922
|
|
|
* @return bool |
923
|
|
|
*/ |
924
|
|
|
public function clearCacheByTable(string $table='') : bool |
925
|
|
|
{ |
926
|
|
|
$tables = $table ? [$table] : $this->getFlushCacheTables(); |
927
|
|
|
foreach ( $tables as $table ) |
928
|
|
|
{ |
929
|
|
|
$this->clearCache("/{$table}/"); |
930
|
|
|
} |
931
|
|
|
|
932
|
|
|
return true; |
933
|
|
|
} |
934
|
|
|
|
935
|
|
|
/** |
936
|
|
|
* @return string[] |
937
|
|
|
*/ |
938
|
|
|
public function getFlushCacheTables() : array |
939
|
|
|
{ |
940
|
|
|
return ! empty($this->flushCacheTables) ? $this->flushCacheTables : [$this->getTableName()]; |
941
|
|
|
} |
942
|
|
|
|
943
|
|
|
/** |
944
|
|
|
* @param Closure $fnCallback |
945
|
|
|
* @return int |
946
|
|
|
*/ |
947
|
|
|
public function fetchCallback(Closure $fnCallback) : int |
948
|
|
|
{ |
949
|
|
|
$successCnt = 0; |
950
|
|
|
$stmt = $this->fetchStmt(); |
951
|
|
|
while ( $this->tallySuccessCount($stmt, $fnCallback, $successCnt) ) |
952
|
|
|
{ |
953
|
|
|
continue; |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
return $successCnt; |
957
|
|
|
} |
958
|
|
|
|
959
|
|
|
/** |
960
|
|
|
* @param Closure $fnCallback |
961
|
|
|
* @param string $keyedOn |
962
|
|
|
* @return array |
963
|
|
|
*/ |
964
|
|
|
public function fetchObjectsByCallback(Closure $fnCallback, string $keyedOn='') : array |
965
|
|
|
{ |
966
|
|
|
$stmt = $this->fetchStmt(); |
967
|
|
|
$rows = []; |
968
|
|
|
while ( $rec = $this->fetchRow($stmt, $fnCallback) ) |
969
|
|
|
{ |
970
|
|
|
if ( $keyedOn && property_exists($rec, $keyedOn) ) |
971
|
|
|
{ |
972
|
|
|
$rows[$rec->{$keyedOn}] = $rec; |
973
|
|
|
|
974
|
|
|
continue; |
975
|
|
|
} |
976
|
|
|
$rows[] = $rec; |
977
|
|
|
} |
978
|
|
|
$this->reset(); |
979
|
|
|
|
980
|
|
|
return $rows; |
981
|
|
|
} |
982
|
|
|
|
983
|
|
|
/** |
984
|
|
|
* @param $numFailures |
985
|
|
|
* @return FluentPdoModel|$this |
986
|
|
|
*/ |
987
|
|
|
public function maxCallbackFailures(int $numFailures) : FluentPdoModel |
988
|
|
|
{ |
989
|
|
|
Assert($numFailures)->int(); |
990
|
|
|
$this->maxCallbackFails = $numFailures; |
991
|
|
|
|
992
|
|
|
return $this; |
993
|
|
|
} |
994
|
|
|
|
995
|
|
|
/** |
996
|
|
|
* @param PDOStatement $stmt |
997
|
|
|
* @param Closure $fnCallback |
998
|
|
|
* @param int $successCnt |
999
|
|
|
* @return bool|null|stdClass |
1000
|
|
|
*/ |
1001
|
|
|
protected function tallySuccessCount(PDOStatement $stmt, Closure $fnCallback, int &$successCnt) |
1002
|
|
|
{ |
1003
|
|
|
$rec = $this->fetchRow($stmt); |
1004
|
|
|
if ( $rec === false ) |
1005
|
|
|
{ |
1006
|
|
|
return false; |
1007
|
|
|
} |
1008
|
|
|
$rec = $fnCallback($rec); |
1009
|
|
|
// Callback return null then we want to exit the fetch loop |
1010
|
|
|
if ( is_null($rec) ) |
1011
|
|
|
{ |
1012
|
|
|
$this->getLogger()->warning("The callback is not returning any data which might be causing early termination of the result iteration"); |
1013
|
|
|
|
1014
|
|
|
return null; |
1015
|
|
|
} |
1016
|
|
|
// The not record then don't bump the tally |
1017
|
|
|
if ( ! $rec ) |
1018
|
|
|
{ |
1019
|
|
|
$this->numCallbackFails++; |
1020
|
|
|
if ( $this->maxCallbackFails !== -1 && $this->numCallbackFails >= $this->maxCallbackFails ) |
1021
|
|
|
{ |
1022
|
|
|
$this->getLogger()->error("The callback has failed {$this->maxCallbackFails} times... aborting..."); |
1023
|
|
|
$successCnt = null; |
1024
|
|
|
|
1025
|
|
|
return null; |
1026
|
|
|
} |
1027
|
|
|
|
1028
|
|
|
return true; |
1029
|
|
|
} |
1030
|
|
|
$successCnt++; |
1031
|
|
|
|
1032
|
|
|
return $rec; |
1033
|
|
|
} |
1034
|
|
|
|
1035
|
|
|
/** |
1036
|
|
|
* @return bool |
1037
|
|
|
*/ |
1038
|
|
|
public function canGenericUpdate() : bool |
1039
|
|
|
{ |
1040
|
|
|
return $this->canGenericUpdate; |
1041
|
|
|
} |
1042
|
|
|
|
1043
|
|
|
/** |
1044
|
|
|
* @return bool |
1045
|
|
|
*/ |
1046
|
|
|
public function canGenericCreate() : bool |
1047
|
|
|
{ |
1048
|
|
|
return $this->canGenericCreate; |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
/** |
1052
|
|
|
* @return bool |
1053
|
|
|
*/ |
1054
|
|
|
public function canGenericDelete() : bool |
1055
|
|
|
{ |
1056
|
|
|
return $this->canGenericDelete; |
1057
|
|
|
} |
1058
|
|
|
|
1059
|
|
|
/** |
1060
|
|
|
* @param string $keyedOn |
1061
|
|
|
* @param string $valueField |
1062
|
|
|
* @param int $cacheTtl |
1063
|
|
|
* @return mixed |
1064
|
|
|
*/ |
1065
|
|
|
public function fetchList(string $keyedOn='', string $valueField='', int $cacheTtl=self::CACHE_NO) : array |
1066
|
|
|
{ |
1067
|
|
|
$keyedOn = $keyedOn ?: $this->getPrimaryKeyName(); |
1068
|
|
|
$valueField = $valueField ?: $this->getDisplayColumn(); |
1069
|
|
|
$keyedOnAlias = strtolower(str_replace('.', '_', $keyedOn)); |
1070
|
|
|
$valueFieldAlias = strtolower(str_replace('.', '_', $valueField)); |
1071
|
|
|
if ( preg_match('/ as /i', $keyedOn) ) |
1072
|
|
|
{ |
1073
|
|
|
$parts = preg_split('/ as /i', $keyedOn); |
1074
|
|
|
$keyedOn = trim($parts[0]); |
1075
|
|
|
$keyedOnAlias = trim($parts[1]); |
1076
|
|
|
} |
1077
|
|
|
if ( preg_match('/ as /i', $valueField) ) |
1078
|
|
|
{ |
1079
|
|
|
$parts = preg_split('/ as /i', $valueField); |
1080
|
|
|
$valueField = trim($parts[0]); |
1081
|
|
|
$valueFieldAlias = trim($parts[1]); |
1082
|
|
|
} |
1083
|
|
|
|
1084
|
|
|
$this->cacheTtl($cacheTtl); |
1085
|
|
|
$fnCallback = function() use ($keyedOn, $keyedOnAlias, $valueField, $valueFieldAlias) { |
1086
|
|
|
|
1087
|
|
|
$rows = []; |
1088
|
|
|
$stmt = $this->select(null) |
1089
|
|
|
->select($keyedOn, $keyedOnAlias) |
1090
|
|
|
->select($valueField, $valueFieldAlias) |
1091
|
|
|
->fetchStmt(); |
1092
|
|
|
while ( $rec = $this->fetchRow($stmt) ) |
1093
|
|
|
{ |
1094
|
|
|
$rows[$rec->{$keyedOnAlias}] = $rec->{$valueFieldAlias}; |
1095
|
|
|
} |
1096
|
|
|
|
1097
|
|
|
return $rows; |
1098
|
|
|
}; |
1099
|
|
|
if ( $this->cacheTtl === self::CACHE_NO ) |
1100
|
|
|
{ |
1101
|
|
|
$result = $fnCallback(); |
1102
|
|
|
unset($cacheKey, $fnCallback); |
1103
|
|
|
|
1104
|
|
|
return $result; |
1105
|
|
|
} |
1106
|
|
|
$table = $this->getTableName(); |
1107
|
|
|
$cacheData = [ |
1108
|
|
|
'sql' => $this->fetchSqlQuery(), |
1109
|
|
|
'keyed_on' => $keyedOn, |
1110
|
|
|
'keyed_on_alias' => $keyedOnAlias, |
1111
|
|
|
'value_field' => $valueField, |
1112
|
|
|
'value_fieldAlias' => $valueFieldAlias, |
1113
|
|
|
]; |
1114
|
|
|
$cacheKey = json_encode($cacheData); |
1115
|
|
|
if ( ! $cacheKey ) |
1116
|
|
|
{ |
1117
|
|
|
$this->getLogger()->warning('Could not generate cache key from data', $cacheData); |
1118
|
|
|
$result = $fnCallback(); |
1119
|
|
|
unset($cacheKey, $fnCallback); |
1120
|
|
|
|
1121
|
|
|
return $result; |
1122
|
|
|
} |
1123
|
|
|
$cacheKey = md5($cacheKey); |
1124
|
|
|
|
1125
|
|
|
return $this->cacheData("/{$table}/list/{$cacheKey}", $fnCallback, $this->cacheTtl); |
1126
|
|
|
} |
1127
|
|
|
|
1128
|
|
|
/** |
1129
|
|
|
* @param string $column |
1130
|
|
|
* @param int $cacheTtl |
1131
|
|
|
* @param bool|true $unique |
1132
|
|
|
* @return array |
1133
|
|
|
*/ |
1134
|
|
|
public function fetchColumn(string $column, int $cacheTtl=self::CACHE_NO, bool $unique=true) : array |
1135
|
|
|
{ |
1136
|
|
|
$list = $this->select($column)->fetch('', $cacheTtl); |
1137
|
|
|
foreach ( $list as $idx => $obj ) |
1138
|
|
|
{ |
1139
|
|
|
$list[$idx] = $obj->{$column}; |
1140
|
|
|
} |
1141
|
|
|
|
1142
|
|
|
return $unique ? array_unique($list) : $list; |
1143
|
|
|
} |
1144
|
|
|
|
1145
|
|
|
/** |
1146
|
|
|
* @param string $field |
1147
|
|
|
* @param int $itemId |
1148
|
|
|
* @param int $cacheTtl |
1149
|
|
|
* @return mixed|null |
1150
|
|
|
*/ |
1151
|
|
|
public function fetchField(string $field='', int $itemId=0, int $cacheTtl=self::CACHE_NO) |
1152
|
|
|
{ |
1153
|
|
|
$field = $field ?: $this->getPrimaryKeyName(); |
1154
|
|
|
$object = $this->select(null)->select($field)->fetchOne($itemId, $cacheTtl); |
1155
|
|
|
if ( ! $object ) |
1156
|
|
|
{ |
1157
|
|
|
return null; |
1158
|
|
|
} |
1159
|
|
|
// Handle aliases |
1160
|
|
|
if ( preg_match('/ as /i', $field) ) |
1161
|
|
|
{ |
1162
|
|
|
$alias = preg_split('/ as /i', $field)[1]; |
1163
|
|
|
$field = trim($alias); |
1164
|
|
|
} |
1165
|
|
|
if ( strpos($field, '.') !== false ) |
1166
|
|
|
{ |
1167
|
|
|
$field = explode('.', $field)[1]; |
1168
|
|
|
} |
1169
|
|
|
|
1170
|
|
|
return property_exists($object, $field) ? $object->{$field} : null; |
1171
|
|
|
} |
1172
|
|
|
|
1173
|
|
|
/** |
1174
|
|
|
* @param string $field |
1175
|
|
|
* @param int $itemId |
1176
|
|
|
* @param int $cacheTtl |
1177
|
|
|
* @return string |
1178
|
|
|
*/ |
1179
|
|
|
public function fetchStr(string $field='', $itemId=0, int $cacheTtl=self::CACHE_NO) : string |
1180
|
|
|
{ |
1181
|
|
|
return (string)$this->fetchField($field, $itemId, $cacheTtl); |
1182
|
|
|
} |
1183
|
|
|
|
1184
|
|
|
/** |
1185
|
|
|
* @param int $cacheTtl |
1186
|
|
|
* @return int |
1187
|
|
|
*/ |
1188
|
|
|
public function fetchId(int $cacheTtl=self::CACHE_NO) : int |
1189
|
|
|
{ |
1190
|
|
|
return $this->fetchInt($this->getPrimaryKeyName(), 0, $cacheTtl); |
1191
|
|
|
} |
1192
|
|
|
|
1193
|
|
|
/** |
1194
|
|
|
* @param string $field |
1195
|
|
|
* @param int $itemId |
1196
|
|
|
* @param int $cacheTtl |
1197
|
|
|
* @return int |
1198
|
|
|
*/ |
1199
|
|
|
public function fetchInt(string $field='', int $itemId=0, int $cacheTtl=self::CACHE_NO) : int |
1200
|
|
|
{ |
1201
|
|
|
return (int)$this->fetchField($field, $itemId, $cacheTtl); |
1202
|
|
|
} |
1203
|
|
|
|
1204
|
|
|
/** |
1205
|
|
|
* @param string $field |
1206
|
|
|
* @param int $itemId |
1207
|
|
|
* @param int $cacheTtl |
1208
|
|
|
* @return float |
1209
|
|
|
*/ |
1210
|
|
|
public function fetchFloat(string $field='', int $itemId=0, int $cacheTtl=self::CACHE_NO) : float |
1211
|
|
|
{ |
1212
|
|
|
return (float)$this->fetchField($field, $itemId, $cacheTtl); |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
|
|
/** |
1216
|
|
|
* @param string $field |
1217
|
|
|
* @param int $itemId |
1218
|
|
|
* @param int $cacheTtl |
1219
|
|
|
* @return bool |
1220
|
|
|
*/ |
1221
|
|
|
public function fetchBool(string $field='', int $itemId=0, int $cacheTtl=self::CACHE_NO) : bool |
1222
|
|
|
{ |
1223
|
|
|
return (bool)$this->fetchField($field, $itemId, $cacheTtl); |
1224
|
|
|
} |
1225
|
|
|
|
1226
|
|
|
/** |
1227
|
|
|
* @param int|null $id |
1228
|
|
|
* @param int $cacheTtl |
1229
|
|
|
* @return stdClass|bool |
1230
|
|
|
*/ |
1231
|
|
|
public function fetchOne(int $id=0, int $cacheTtl=self::CACHE_NO) |
1232
|
|
|
{ |
1233
|
|
|
if ( $id > 0 ) |
1234
|
|
|
{ |
1235
|
|
|
$this->wherePk($id, true); |
1236
|
|
|
} |
1237
|
|
|
$fetchAll = $this |
1238
|
|
|
->limit(1) |
1239
|
|
|
->fetch('', $cacheTtl); |
1240
|
|
|
|
1241
|
|
|
return $fetchAll ? array_shift($fetchAll) : false; |
1242
|
|
|
} |
1243
|
|
|
|
1244
|
|
|
/** |
1245
|
|
|
* @param int|null $id |
1246
|
|
|
* @param int $cacheTtl |
1247
|
|
|
* @return boolean |
1248
|
|
|
*/ |
1249
|
|
|
public function fetchExists(int $id=0, int $cacheTtl=self::CACHE_NO) : bool |
1250
|
|
|
{ |
1251
|
|
|
if ( $id > 0 ) |
1252
|
|
|
{ |
1253
|
|
|
$this->wherePk($id, true); |
1254
|
|
|
} |
1255
|
|
|
$cnt = $this->count('*', $cacheTtl); |
1256
|
|
|
|
1257
|
|
|
return $cnt > 0; |
1258
|
|
|
} |
1259
|
|
|
|
1260
|
|
|
/*------------------------------------------------------------------------------ |
1261
|
|
|
Fluent Query Builder |
1262
|
|
|
*-----------------------------------------------------------------------------*/ |
1263
|
|
|
|
1264
|
|
|
/** |
1265
|
|
|
* Create the select clause |
1266
|
|
|
* |
1267
|
|
|
* @param mixed $columns - the column to select. Can be string or array of fields |
1268
|
|
|
* @param string $alias - an alias to the column |
1269
|
|
|
* @param boolean $explicitSelect |
1270
|
|
|
* @return FluentPdoModel|$this |
1271
|
|
|
*/ |
1272
|
|
|
public function select($columns='*', string $alias='', bool $explicitSelect=true) : FluentPdoModel |
1273
|
|
|
{ |
1274
|
|
|
if ( $explicitSelect ) |
1275
|
|
|
{ |
1276
|
|
|
$this->explicitSelectMode(); |
1277
|
|
|
} |
1278
|
|
|
if ( $alias && ! is_array($columns) & $columns !== $alias ) |
1279
|
|
|
{ |
1280
|
|
|
$columns .= " AS {$alias} "; |
1281
|
|
|
} |
1282
|
|
|
$schema = $this->getSchema(); |
1283
|
|
|
if ( $columns === '*' && ! empty($schema) ) |
1284
|
|
|
{ |
1285
|
|
|
$columns = array_keys($schema); |
1286
|
|
|
} |
1287
|
|
|
// Reset the select list |
1288
|
|
|
if ( is_null($columns) || $columns === '' ) |
1289
|
|
|
{ |
1290
|
|
|
$this->selectFields = []; |
1291
|
|
|
|
1292
|
|
|
return $this; |
1293
|
|
|
} |
1294
|
|
|
$columns = is_array($columns) ? $columns : [$columns]; |
1295
|
|
|
|
1296
|
|
|
// if ( empty($this->selectFields) && $addAllIfEmpty ) |
1297
|
|
|
// { |
1298
|
|
|
// $this->select('*'); |
1299
|
|
|
// } |
1300
|
|
|
if ( $this->tableAlias ) |
1301
|
|
|
{ |
1302
|
|
|
$schema = $this->getColumns(); |
1303
|
|
|
foreach ( $columns as $idx => $col ) |
1304
|
|
|
{ |
1305
|
|
|
if ( in_array($col, $schema) ) |
1306
|
|
|
{ |
1307
|
|
|
$columns[$idx] = "{$this->tableAlias}.{$col}"; |
1308
|
|
|
} |
1309
|
|
|
} |
1310
|
|
|
} |
1311
|
|
|
$this->selectFields = array_merge($this->selectFields, $columns); |
1312
|
|
|
|
1313
|
|
|
return $this; |
1314
|
|
|
} |
1315
|
|
|
|
1316
|
|
|
/** |
1317
|
|
|
* @param string $select |
1318
|
|
|
* @return FluentPdoModel|$this |
1319
|
|
|
*/ |
1320
|
|
|
public function selectRaw(string $select) : FluentPdoModel |
1321
|
|
|
{ |
1322
|
|
|
$this->selectFields[] = $select; |
1323
|
|
|
|
1324
|
|
|
return $this; |
1325
|
|
|
} |
1326
|
|
|
|
1327
|
|
|
/** |
1328
|
|
|
* @param bool $logQueries |
1329
|
|
|
* |
1330
|
|
|
* @return FluentPdoModel|$this |
1331
|
|
|
*/ |
1332
|
|
|
public function logQueries(bool $logQueries=true) : FluentPdoModel |
1333
|
|
|
{ |
1334
|
|
|
$this->logQueries = $logQueries; |
1335
|
|
|
|
1336
|
|
|
return $this; |
1337
|
|
|
} |
1338
|
|
|
|
1339
|
|
|
/** |
1340
|
|
|
* @param bool $includeCnt |
1341
|
|
|
* |
1342
|
|
|
* @return FluentPdoModel|$this |
1343
|
|
|
*/ |
1344
|
|
|
public function includeCount(bool $includeCnt=true) : FluentPdoModel |
1345
|
|
|
{ |
1346
|
|
|
$this->includeCount = $includeCnt; |
1347
|
|
|
|
1348
|
|
|
return $this; |
1349
|
|
|
} |
1350
|
|
|
|
1351
|
|
|
/** |
1352
|
|
|
* @param bool $distinct |
1353
|
|
|
* |
1354
|
|
|
* @return FluentPdoModel|$this |
1355
|
|
|
*/ |
1356
|
|
|
public function distinct(bool $distinct=true) : FluentPdoModel |
1357
|
|
|
{ |
1358
|
|
|
$this->distinct = $distinct; |
1359
|
|
|
|
1360
|
|
|
return $this; |
1361
|
|
|
} |
1362
|
|
|
|
1363
|
|
|
/** |
1364
|
|
|
* @param array $fields |
1365
|
|
|
* @return FluentPdoModel|$this |
1366
|
|
|
*/ |
1367
|
|
|
public function withBelongsTo(array $fields=[]) : FluentPdoModel |
1368
|
|
|
{ |
1369
|
|
|
if ( empty($this->associations['belongsTo']) ) |
1370
|
|
|
{ |
1371
|
|
|
return $this; |
1372
|
|
|
} |
1373
|
|
|
foreach ( $this->associations['belongsTo'] as $alias => $config ) |
1374
|
|
|
{ |
1375
|
|
|
$addFieldsForJoins = empty($fields) || in_array($config[3], $fields); |
1376
|
|
|
$this->autoJoin($alias, self::LEFT_JOIN, $addFieldsForJoins); |
1377
|
|
|
} |
1378
|
|
|
|
1379
|
|
|
return $this; |
1380
|
|
|
} |
1381
|
|
|
|
1382
|
|
|
/** |
1383
|
|
|
* @param string $alias |
1384
|
|
|
* @param bool $addSelectField |
1385
|
|
|
* @return FluentPdoModel |
1386
|
|
|
*/ |
1387
|
|
|
public function autoInnerJoin(string $alias, bool $addSelectField=true) : FluentPdoModel |
1388
|
|
|
{ |
1389
|
|
|
return $this->autoJoin($alias, self::INNER_JOIN, $addSelectField); |
1390
|
|
|
} |
1391
|
|
|
|
1392
|
|
|
/** |
1393
|
|
|
* @param string $alias |
1394
|
|
|
* @param string $type |
1395
|
|
|
* @param bool $addSelectField |
1396
|
|
|
* @return FluentPdoModel|$this |
1397
|
|
|
*/ |
1398
|
|
|
public function autoJoin(string $alias, string $type=self::LEFT_JOIN, bool $addSelectField=true) : FluentPdoModel |
1399
|
|
|
{ |
1400
|
|
|
Assert($this->associations['belongsTo'])->keyExists($alias, "Invalid join... the alias does not exists"); |
1401
|
|
|
list($table, $joinCol, $field, $fieldAlias) = $this->associations['belongsTo'][$alias]; |
1402
|
|
|
$tableJoinField = "{$this->tableAlias}.{$joinCol}"; |
1403
|
|
|
// Extra join onto another second level table |
1404
|
|
|
if ( strpos($joinCol, '.') !== false ) |
1405
|
|
|
{ |
1406
|
|
|
$tableJoinField = $joinCol; |
1407
|
|
|
if ( $addSelectField ) |
1408
|
|
|
{ |
1409
|
|
|
$this->select($joinCol, '', false); |
1410
|
|
|
} |
1411
|
|
|
} |
1412
|
|
|
$condition = "{$alias}.id = {$tableJoinField}"; |
1413
|
|
|
if ( in_array($alias, $this->joinAliases) ) |
1414
|
|
|
{ |
1415
|
|
|
return $this; |
1416
|
|
|
} |
1417
|
|
|
$this->join($table, $condition, $alias, $type); |
1418
|
|
|
if ( $addSelectField ) |
1419
|
|
|
{ |
1420
|
|
|
$this->select($field, $fieldAlias, false); |
1421
|
|
|
} |
1422
|
|
|
|
1423
|
|
|
return $this; |
1424
|
|
|
} |
1425
|
|
|
|
1426
|
|
|
/** |
1427
|
|
|
* @param array $conditions |
1428
|
|
|
* @return FluentPdoModel |
1429
|
|
|
*/ |
1430
|
|
|
public function whereArr(array $conditions) : FluentPdoModel |
1431
|
|
|
{ |
1432
|
|
|
foreach ($conditions as $key => $val) |
1433
|
|
|
{ |
1434
|
|
|
$this->where($key, $val); |
1435
|
|
|
} |
1436
|
|
|
|
1437
|
|
|
return $this; |
1438
|
|
|
} |
1439
|
|
|
/** |
1440
|
|
|
* Add where condition, more calls appends with AND |
1441
|
|
|
* |
1442
|
|
|
* @param string $condition possibly containing ? or :name |
1443
|
|
|
* @param mixed $parameters accepted by PDOStatement::execute or a scalar value |
1444
|
|
|
* @param mixed ... |
1445
|
|
|
* @return FluentPdoModel|$this |
1446
|
|
|
*/ |
1447
|
|
|
public function where($condition, $parameters=[]) : FluentPdoModel |
1448
|
|
|
{ |
1449
|
|
|
// By default the andOrOperator and wrap operator is AND, |
1450
|
|
|
if ( $this->wrapOpen || ! $this->andOrOperator ) |
1451
|
|
|
{ |
1452
|
|
|
$this->_and(); |
1453
|
|
|
} |
1454
|
|
|
|
1455
|
|
|
// where(array("column1" => 1, "column2 > ?" => 2)) |
1456
|
|
|
if ( is_array($condition) ) |
1457
|
|
|
{ |
1458
|
|
|
foreach ($condition as $key => $val) |
1459
|
|
|
{ |
1460
|
|
|
$this->where($key, $val); |
1461
|
|
|
} |
1462
|
|
|
|
1463
|
|
|
return $this; |
1464
|
|
|
} |
1465
|
|
|
|
1466
|
|
|
$args = func_num_args(); |
1467
|
|
|
if ( $args != 2 || strpbrk((string)$condition, '?:') ) |
1468
|
|
|
{ // where('column < ? OR column > ?', array(1, 2)) |
1469
|
|
|
if ( $args != 2 || !is_array($parameters) ) |
1470
|
|
|
{ // where('column < ? OR column > ?', 1, 2) |
1471
|
|
|
$parameters = func_get_args(); |
1472
|
|
|
array_shift($parameters); |
1473
|
|
|
} |
1474
|
|
|
} |
1475
|
|
|
else if ( ! is_array($parameters) ) |
1476
|
|
|
{//where(column,value) => column=value |
1477
|
|
|
$condition .= ' = ?'; |
1478
|
|
|
$parameters = [$parameters]; |
1479
|
|
|
} |
1480
|
|
|
else if ( is_array($parameters) ) |
1481
|
|
|
{ // where('column', array(1, 2)) => column IN (?,?) |
1482
|
|
|
$placeholders = $this->makePlaceholders(count($parameters)); |
1483
|
|
|
$condition = "({$condition} IN ({$placeholders}))"; |
1484
|
|
|
} |
1485
|
|
|
|
1486
|
|
|
$this->whereConditions[] = [ |
1487
|
|
|
'STATEMENT' => $condition, |
1488
|
|
|
'PARAMS' => $parameters, |
1489
|
|
|
'OPERATOR' => $this->andOrOperator |
1490
|
|
|
]; |
1491
|
|
|
// Reset the where operator to AND. To use OR, you must call _or() |
1492
|
|
|
$this->_and(); |
1493
|
|
|
|
1494
|
|
|
return $this; |
1495
|
|
|
} |
1496
|
|
|
|
1497
|
|
|
/** |
1498
|
|
|
* Create an AND operator in the where clause |
1499
|
|
|
* |
1500
|
|
|
* @return FluentPdoModel|$this |
1501
|
|
|
*/ |
1502
|
|
|
public function _and() : FluentPdoModel |
1503
|
|
|
{ |
1504
|
|
|
if ( $this->wrapOpen ) |
1505
|
|
|
{ |
1506
|
|
|
$this->whereConditions[] = self::OPERATOR_AND; |
1507
|
|
|
$this->lastWrapPosition = count($this->whereConditions); |
1508
|
|
|
$this->wrapOpen = false; |
1509
|
|
|
|
1510
|
|
|
return $this; |
1511
|
|
|
} |
1512
|
|
|
$this->andOrOperator = self::OPERATOR_AND; |
1513
|
|
|
|
1514
|
|
|
return $this; |
1515
|
|
|
} |
1516
|
|
|
|
1517
|
|
|
|
1518
|
|
|
/** |
1519
|
|
|
* Create an OR operator in the where clause |
1520
|
|
|
* |
1521
|
|
|
* @return FluentPdoModel|$this |
1522
|
|
|
*/ |
1523
|
|
|
public function _or() : FluentPdoModel |
1524
|
|
|
{ |
1525
|
|
|
if ( $this->wrapOpen ) |
1526
|
|
|
{ |
1527
|
|
|
$this->whereConditions[] = self::OPERATOR_OR; |
1528
|
|
|
$this->lastWrapPosition = count($this->whereConditions); |
1529
|
|
|
$this->wrapOpen = false; |
1530
|
|
|
|
1531
|
|
|
return $this; |
1532
|
|
|
} |
1533
|
|
|
$this->andOrOperator = self::OPERATOR_OR; |
1534
|
|
|
|
1535
|
|
|
return $this; |
1536
|
|
|
} |
1537
|
|
|
|
1538
|
|
|
/** |
1539
|
|
|
* To group multiple where clauses together. |
1540
|
|
|
* |
1541
|
|
|
* @return FluentPdoModel|$this |
1542
|
|
|
*/ |
1543
|
|
|
public function wrap() : FluentPdoModel |
1544
|
|
|
{ |
1545
|
|
|
$this->wrapOpen = true; |
1546
|
|
|
$spliced = array_splice($this->whereConditions, $this->lastWrapPosition, count($this->whereConditions), '('); |
1547
|
|
|
$this->whereConditions = array_merge($this->whereConditions, $spliced); |
1548
|
|
|
array_push($this->whereConditions,')'); |
1549
|
|
|
$this->lastWrapPosition = count($this->whereConditions); |
1550
|
|
|
|
1551
|
|
|
return $this; |
1552
|
|
|
} |
1553
|
|
|
|
1554
|
|
|
/** |
1555
|
|
|
* Where Primary key |
1556
|
|
|
* |
1557
|
|
|
* @param int $id |
1558
|
|
|
* @param bool $addAlias |
1559
|
|
|
* |
1560
|
|
|
* @return FluentPdoModel|$this |
1561
|
|
|
*/ |
1562
|
|
|
public function wherePk(int $id, bool $addAlias=true) : FluentPdoModel |
1563
|
|
|
{ |
1564
|
|
|
$alias = $addAlias && ! empty($this->tableAlias) ? "{$this->tableAlias}." : ''; |
1565
|
|
|
|
1566
|
|
|
return $this->where($alias . $this->getPrimaryKeyName(), $id); |
1567
|
|
|
} |
1568
|
|
|
|
1569
|
|
|
/** |
1570
|
|
|
* @param string $name |
1571
|
|
|
* @param bool $addAlias |
1572
|
|
|
* @return FluentPdoModel |
1573
|
|
|
*/ |
1574
|
|
|
public function whereDisplayName(string $name, bool $addAlias=true) : FluentPdoModel |
1575
|
|
|
{ |
1576
|
|
|
$alias = $addAlias && ! empty($this->tableAlias) ? "{$this->tableAlias}." : ''; |
1577
|
|
|
|
1578
|
|
|
return $this->where($alias . $this->getDisplayColumn(), $name); |
1579
|
|
|
} |
1580
|
|
|
|
1581
|
|
|
/** |
1582
|
|
|
* WHERE $columnName != $value |
1583
|
|
|
* |
1584
|
|
|
* @param string $columnName |
1585
|
|
|
* @param mixed $value |
1586
|
|
|
* @return FluentPdoModel|$this |
1587
|
|
|
*/ |
1588
|
|
|
public function whereNot(string $columnName, $value) : FluentPdoModel |
1589
|
|
|
{ |
1590
|
|
|
return $this->where("$columnName != ?", $value); |
1591
|
|
|
} |
1592
|
|
|
/** |
1593
|
|
|
* WHERE $columnName != $value |
1594
|
|
|
* |
1595
|
|
|
* @param string $columnName |
1596
|
|
|
* @param mixed $value |
1597
|
|
|
* @return FluentPdoModel|$this |
1598
|
|
|
*/ |
1599
|
|
|
public function whereCoercedNot(string $columnName, $value) : FluentPdoModel |
1600
|
|
|
{ |
1601
|
|
|
return $this->where("IFNULL({$columnName}, '') != ?", $value); |
1602
|
|
|
} |
1603
|
|
|
|
1604
|
|
|
/** |
1605
|
|
|
* WHERE $columnName LIKE $value |
1606
|
|
|
* |
1607
|
|
|
* @param string $columnName |
1608
|
|
|
* @param mixed $value |
1609
|
|
|
* @return FluentPdoModel|$this |
1610
|
|
|
*/ |
1611
|
|
|
public function whereLike(string $columnName, $value) : FluentPdoModel |
1612
|
|
|
{ |
1613
|
|
|
return $this->where("$columnName LIKE ?", $value); |
1614
|
|
|
} |
1615
|
|
|
|
1616
|
|
|
/** |
1617
|
|
|
* @param string $columnName |
1618
|
|
|
* @param mixed $value1 |
1619
|
|
|
* @param mixed $value2 |
1620
|
|
|
* @return FluentPdoModel|$this |
1621
|
|
|
*/ |
1622
|
|
|
public function whereBetween(string $columnName, $value1, $value2) : FluentPdoModel |
1623
|
|
|
{ |
1624
|
|
|
$value1 = is_string($value1) ? trim($value1) : $value1; |
1625
|
|
|
$value2 = is_string($value2) ? trim($value2) : $value2; |
1626
|
|
|
|
1627
|
|
|
return $this->where("$columnName BETWEEN ? AND ?", [$value1, $value2]); |
1628
|
|
|
} |
1629
|
|
|
|
1630
|
|
|
/** |
1631
|
|
|
* @param string $columnName |
1632
|
|
|
* @param mixed $value1 |
1633
|
|
|
* @param mixed $value2 |
1634
|
|
|
* @return FluentPdoModel|$this |
1635
|
|
|
*/ |
1636
|
|
|
public function whereNotBetween(string $columnName, $value1, $value2) : FluentPdoModel |
1637
|
|
|
{ |
1638
|
|
|
$value1 = is_string($value1) ? trim($value1) : $value1; |
1639
|
|
|
$value2 = is_string($value2) ? trim($value2) : $value2; |
1640
|
|
|
|
1641
|
|
|
return $this->where("$columnName NOT BETWEEN ? AND ?", [$value1, $value2]); |
1642
|
|
|
} |
1643
|
|
|
|
1644
|
|
|
/** |
1645
|
|
|
* @param string $columnName |
1646
|
|
|
* @param string $regex |
1647
|
|
|
* @return FluentPdoModel|$this |
1648
|
|
|
*/ |
1649
|
|
|
public function whereRegex(string $columnName, string $regex) : FluentPdoModel |
1650
|
|
|
{ |
1651
|
|
|
return $this->where("$columnName REGEXP ?", $regex); |
1652
|
|
|
} |
1653
|
|
|
|
1654
|
|
|
/** |
1655
|
|
|
* @param string $columnName |
1656
|
|
|
* @param string $regex |
1657
|
|
|
* @return FluentPdoModel|$this |
1658
|
|
|
*/ |
1659
|
|
|
public function whereNotRegex(string $columnName, string $regex) : FluentPdoModel |
1660
|
|
|
{ |
1661
|
|
|
return $this->where("$columnName NOT REGEXP ?", $regex); |
1662
|
|
|
} |
1663
|
|
|
|
1664
|
|
|
/** |
1665
|
|
|
* WHERE $columnName NOT LIKE $value |
1666
|
|
|
* |
1667
|
|
|
* @param string $columnName |
1668
|
|
|
* @param string $value |
1669
|
|
|
* @return FluentPdoModel|$this |
1670
|
|
|
*/ |
1671
|
|
|
public function whereNotLike(string $columnName, string $value) : FluentPdoModel |
1672
|
|
|
{ |
1673
|
|
|
return $this->where("$columnName NOT LIKE ?", $value); |
1674
|
|
|
} |
1675
|
|
|
|
1676
|
|
|
/** |
1677
|
|
|
* WHERE $columnName > $value |
1678
|
|
|
* |
1679
|
|
|
* @param string $columnName |
1680
|
|
|
* @param mixed $value |
1681
|
|
|
* @return FluentPdoModel|$this |
1682
|
|
|
*/ |
1683
|
|
|
public function whereGt(string $columnName, $value) : FluentPdoModel |
1684
|
|
|
{ |
1685
|
|
|
return $this->where("$columnName > ?", $value); |
1686
|
|
|
} |
1687
|
|
|
|
1688
|
|
|
/** |
1689
|
|
|
* WHERE $columnName >= $value |
1690
|
|
|
* |
1691
|
|
|
* @param string $columnName |
1692
|
|
|
* @param mixed $value |
1693
|
|
|
* @return FluentPdoModel|$this |
1694
|
|
|
*/ |
1695
|
|
|
public function whereGte(string $columnName, $value) : FluentPdoModel |
1696
|
|
|
{ |
1697
|
|
|
return $this->where("$columnName >= ?", $value); |
1698
|
|
|
} |
1699
|
|
|
|
1700
|
|
|
/** |
1701
|
|
|
* WHERE $columnName < $value |
1702
|
|
|
* |
1703
|
|
|
* @param string $columnName |
1704
|
|
|
* @param mixed $value |
1705
|
|
|
* @return FluentPdoModel|$this |
1706
|
|
|
*/ |
1707
|
|
|
public function whereLt(string $columnName, $value) : FluentPdoModel |
1708
|
|
|
{ |
1709
|
|
|
return $this->where("$columnName < ?", $value); |
1710
|
|
|
} |
1711
|
|
|
|
1712
|
|
|
/** |
1713
|
|
|
* WHERE $columnName <= $value |
1714
|
|
|
* |
1715
|
|
|
* @param string $columnName |
1716
|
|
|
* @param mixed $value |
1717
|
|
|
* @return FluentPdoModel|$this |
1718
|
|
|
*/ |
1719
|
|
|
public function whereLte(string $columnName, $value) : FluentPdoModel |
1720
|
|
|
{ |
1721
|
|
|
return $this->where("$columnName <= ?", $value); |
1722
|
|
|
} |
1723
|
|
|
|
1724
|
|
|
/** |
1725
|
|
|
* WHERE $columnName IN (?,?,?,...) |
1726
|
|
|
* |
1727
|
|
|
* @param string $columnName |
1728
|
|
|
* @param array $values |
1729
|
|
|
* @return FluentPdoModel|$this |
1730
|
|
|
*/ |
1731
|
|
|
public function whereIn(string $columnName, array $values) : FluentPdoModel |
1732
|
|
|
{ |
1733
|
|
|
return $this->where($columnName, array_values($values)); |
1734
|
|
|
} |
1735
|
|
|
|
1736
|
|
|
/** |
1737
|
|
|
* WHERE $columnName NOT IN (?,?,?,...) |
1738
|
|
|
* |
1739
|
|
|
* @param string $columnName |
1740
|
|
|
* @param array $values |
1741
|
|
|
* @return FluentPdoModel|$this |
1742
|
|
|
*/ |
1743
|
|
|
public function whereNotIn(string $columnName, array $values) : FluentPdoModel |
1744
|
|
|
{ |
1745
|
|
|
$placeholders = $this->makePlaceholders(count($values)); |
1746
|
|
|
|
1747
|
|
|
return $this->where("({$columnName} NOT IN ({$placeholders}))", $values); |
1748
|
|
|
} |
1749
|
|
|
|
1750
|
|
|
/** |
1751
|
|
|
* WHERE $columnName IS NULL |
1752
|
|
|
* |
1753
|
|
|
* @param string $columnName |
1754
|
|
|
* @return FluentPdoModel|$this |
1755
|
|
|
*/ |
1756
|
|
|
public function whereNull(string $columnName) : FluentPdoModel |
1757
|
|
|
{ |
1758
|
|
|
return $this->where("({$columnName} IS NULL)"); |
1759
|
|
|
} |
1760
|
|
|
|
1761
|
|
|
/** |
1762
|
|
|
* WHERE $columnName IS NOT NULL |
1763
|
|
|
* |
1764
|
|
|
* @param string $columnName |
1765
|
|
|
* @return FluentPdoModel|$this |
1766
|
|
|
*/ |
1767
|
|
|
public function whereNotNull(string $columnName) : FluentPdoModel |
1768
|
|
|
{ |
1769
|
|
|
return $this->where("({$columnName} IS NOT NULL)"); |
1770
|
|
|
} |
1771
|
|
|
|
1772
|
|
|
/** |
1773
|
|
|
* @param string $statement |
1774
|
|
|
* @param string $operator |
1775
|
|
|
* @return FluentPdoModel|$this |
1776
|
|
|
*/ |
1777
|
|
|
public function having(string $statement, string $operator=self::OPERATOR_AND) : FluentPdoModel |
1778
|
|
|
{ |
1779
|
|
|
$this->having[] = [ |
1780
|
|
|
'STATEMENT' => $statement, |
1781
|
|
|
'OPERATOR' => $operator |
1782
|
|
|
]; |
1783
|
|
|
|
1784
|
|
|
return $this; |
1785
|
|
|
} |
1786
|
|
|
|
1787
|
|
|
/** |
1788
|
|
|
* ORDER BY $columnName (ASC | DESC) |
1789
|
|
|
* |
1790
|
|
|
* @param string $columnName - The name of the column or an expression |
1791
|
|
|
* @param string $ordering (DESC | ASC) |
1792
|
|
|
* @return FluentPdoModel|$this |
1793
|
|
|
*/ |
1794
|
|
|
public function orderBy(string $columnName='', string $ordering='ASC') : FluentPdoModel |
1795
|
|
|
{ |
1796
|
|
|
$ordering = strtoupper($ordering); |
1797
|
|
|
Assert($ordering)->inArray(['DESC', 'ASC']); |
1798
|
|
|
if ( ! $columnName ) |
1799
|
|
|
{ |
1800
|
|
|
$this->orderBy = []; |
1801
|
|
|
|
1802
|
|
|
return $this; |
1803
|
|
|
} |
1804
|
|
|
$this->orderBy[] = trim("{$columnName} {$ordering}"); |
1805
|
|
|
|
1806
|
|
|
return $this; |
1807
|
|
|
} |
1808
|
|
|
|
1809
|
|
|
/** |
1810
|
|
|
* GROUP BY $columnName |
1811
|
|
|
* |
1812
|
|
|
* @param string $columnName |
1813
|
|
|
* @return FluentPdoModel|$this |
1814
|
|
|
*/ |
1815
|
|
|
public function groupBy(string $columnName) : FluentPdoModel |
1816
|
|
|
{ |
1817
|
|
|
$columnName = is_array($columnName) ? $columnName : [$columnName]; |
1818
|
|
|
foreach ( $columnName as $col ) |
1819
|
|
|
{ |
1820
|
|
|
$this->groupBy[] = $col; |
1821
|
|
|
} |
1822
|
|
|
|
1823
|
|
|
return $this; |
1824
|
|
|
} |
1825
|
|
|
|
1826
|
|
|
|
1827
|
|
|
/** |
1828
|
|
|
* LIMIT $limit |
1829
|
|
|
* |
1830
|
|
|
* @param int $limit |
1831
|
|
|
* @param int|null $offset |
1832
|
|
|
* @return FluentPdoModel|$this |
1833
|
|
|
*/ |
1834
|
|
|
public function limit(int $limit, int $offset=0) : FluentPdoModel |
1835
|
|
|
{ |
1836
|
|
|
$this->limit = $limit; |
1837
|
|
|
if ( $offset ) |
1838
|
|
|
{ |
1839
|
|
|
$this->offset($offset); |
1840
|
|
|
} |
1841
|
|
|
return $this; |
1842
|
|
|
} |
1843
|
|
|
|
1844
|
|
|
/** |
1845
|
|
|
* Return the limit |
1846
|
|
|
* |
1847
|
|
|
* @return integer |
1848
|
|
|
*/ |
1849
|
|
|
public function getLimit() : int |
1850
|
|
|
{ |
1851
|
|
|
return $this->limit; |
1852
|
|
|
} |
1853
|
|
|
|
1854
|
|
|
/** |
1855
|
|
|
* OFFSET $offset |
1856
|
|
|
* |
1857
|
|
|
* @param int $offset |
1858
|
|
|
* @return FluentPdoModel|$this |
1859
|
|
|
*/ |
1860
|
|
|
public function offset(int $offset) : FluentPdoModel |
1861
|
|
|
{ |
1862
|
|
|
$this->offset = (int)$offset; |
1863
|
|
|
|
1864
|
|
|
return $this; |
1865
|
|
|
} |
1866
|
|
|
|
1867
|
|
|
/** |
1868
|
|
|
* Return the offset |
1869
|
|
|
* |
1870
|
|
|
* @return integer |
1871
|
|
|
*/ |
1872
|
|
|
public function getOffset() : int |
1873
|
|
|
{ |
1874
|
|
|
return $this->offset; |
1875
|
|
|
} |
1876
|
|
|
|
1877
|
|
|
/** |
1878
|
|
|
* Build a join |
1879
|
|
|
* |
1880
|
|
|
* @param string $table - The table name |
1881
|
|
|
* @param string $constraint -> id = profile.user_id |
1882
|
|
|
* @param string $tableAlias - The alias of the table name |
1883
|
|
|
* @param string $joinOperator - LEFT | INNER | etc... |
1884
|
|
|
* @return FluentPdoModel|$this |
1885
|
|
|
*/ |
1886
|
|
|
public function join(string $table, string $constraint='', string $tableAlias='', string $joinOperator='') : FluentPdoModel |
1887
|
|
|
{ |
1888
|
|
|
if ( ! $constraint ) |
1889
|
|
|
{ |
1890
|
|
|
return $this->autoJoin($table, $joinOperator); |
1891
|
|
|
} |
1892
|
|
|
$join = [$joinOperator ? "{$joinOperator} " : '']; |
1893
|
|
|
$join[] = "JOIN {$table} "; |
1894
|
|
|
$tableAlias = $tableAlias ?: Inflector::classify($table); |
1895
|
|
|
$join[] = $tableAlias ? "AS {$tableAlias} " : ''; |
1896
|
|
|
$join[] = "ON {$constraint}"; |
1897
|
|
|
$this->joinSources[] = implode('', $join); |
1898
|
|
|
if ( $tableAlias ) |
1899
|
|
|
{ |
1900
|
|
|
$this->joinAliases[] = $tableAlias; |
1901
|
|
|
} |
1902
|
|
|
|
1903
|
|
|
return $this; |
1904
|
|
|
} |
1905
|
|
|
|
1906
|
|
|
/** |
1907
|
|
|
* Create a left join |
1908
|
|
|
* |
1909
|
|
|
* @param string $table |
1910
|
|
|
* @param string $constraint |
1911
|
|
|
* @param string $tableAlias |
1912
|
|
|
* @return FluentPdoModel|$this |
1913
|
|
|
*/ |
1914
|
|
|
public function leftJoin(string $table, string $constraint, string $tableAlias='') : FluentPdoModel |
1915
|
|
|
{ |
1916
|
|
|
return $this->join($table, $constraint, $tableAlias, self::LEFT_JOIN); |
1917
|
|
|
} |
1918
|
|
|
|
1919
|
|
|
|
1920
|
|
|
/** |
1921
|
|
|
* Return the build select query |
1922
|
|
|
* |
1923
|
|
|
* @return string |
1924
|
|
|
*/ |
1925
|
|
|
public function getSelectQuery() : string |
1926
|
|
|
{ |
1927
|
|
|
if ( $this->rawSql ) |
1928
|
|
|
{ |
1929
|
|
|
return $this->rawSql; |
1930
|
|
|
} |
1931
|
|
|
if ( empty($this->selectFields) || ! $this->explicitSelectMode ) |
1932
|
|
|
{ |
1933
|
|
|
$this->select('*', '', false); |
1934
|
|
|
} |
1935
|
|
|
foreach ( $this->selectFields as $idx => $cols ) |
1936
|
|
|
{ |
1937
|
|
|
if ( strpos(trim(strtolower($cols)), 'distinct ') === 0 ) |
1938
|
|
|
{ |
1939
|
|
|
$this->distinct = true; |
1940
|
|
|
$this->selectFields[$idx] = str_ireplace('distinct ', '', $cols); |
1941
|
|
|
} |
1942
|
|
|
} |
1943
|
|
|
if ( $this->includeCount ) |
1944
|
|
|
{ |
1945
|
|
|
$this->select('COUNT(*) as __cnt'); |
1946
|
|
|
} |
1947
|
|
|
$query = 'SELECT '; |
1948
|
|
|
$query .= $this->distinct ? 'DISTINCT ' : ''; |
1949
|
|
|
$query .= implode(', ', $this->prepareColumns($this->selectFields)); |
1950
|
|
|
$query .= " FROM {$this->tableName}" . ( $this->tableAlias ? " {$this->tableAlias}" : '' ); |
1951
|
|
|
if ( count($this->joinSources ) ) |
1952
|
|
|
{ |
1953
|
|
|
$query .= (' ').implode(' ',$this->joinSources); |
1954
|
|
|
} |
1955
|
|
|
$query .= $this->getWhereString(); // WHERE |
1956
|
|
|
if ( count($this->groupBy) ) |
1957
|
|
|
{ |
1958
|
|
|
$query .= ' GROUP BY ' . implode(', ', array_unique($this->groupBy)); |
1959
|
|
|
} |
1960
|
|
|
if ( count($this->orderBy ) ) |
1961
|
|
|
{ |
1962
|
|
|
$query .= ' ORDER BY ' . implode(', ', array_unique($this->orderBy)); |
1963
|
|
|
} |
1964
|
|
|
$query .= $this->getHavingString(); // HAVING |
1965
|
|
|
|
1966
|
|
|
return $this->connection->setLimit($query, $this->limit, $this->offset); |
1967
|
|
|
} |
1968
|
|
|
|
1969
|
|
|
/** |
1970
|
|
|
* @param string $field |
1971
|
|
|
* @param string $column |
1972
|
|
|
* @return string |
1973
|
|
|
*/ |
1974
|
|
|
public function getFieldComment(string $field, string $column) : string |
1975
|
|
|
{ |
1976
|
|
|
return $this->connection->getFieldComment($field, $column); |
1977
|
|
|
} |
1978
|
|
|
|
1979
|
|
|
/** |
1980
|
|
|
* Prepare columns to include the table alias name |
1981
|
|
|
* @param array $columns |
1982
|
|
|
* @return array |
1983
|
|
|
*/ |
1984
|
|
|
protected function prepareColumns(array $columns) : array |
1985
|
|
|
{ |
1986
|
|
|
if ( ! $this->tableAlias ) |
1987
|
|
|
{ |
1988
|
|
|
return $columns; |
1989
|
|
|
} |
1990
|
|
|
$newColumns = []; |
1991
|
|
|
foreach ($columns as $column) |
1992
|
|
|
{ |
1993
|
|
|
if ( strpos($column, ',') && ! preg_match('/^[a-zA-Z_]{2,200}\(.{1,500}\)/', trim($column)) ) |
1994
|
|
|
{ |
1995
|
|
|
$newColumns = array_merge($this->prepareColumns(explode(',', $column)), $newColumns); |
1996
|
|
|
} |
1997
|
|
|
elseif ( preg_match('/^(AVG|SUM|MAX|MIN|COUNT|CONCAT)/', $column) ) |
1998
|
|
|
{ |
1999
|
|
|
$newColumns[] = trim($column); |
2000
|
|
|
} |
2001
|
|
|
elseif (strpos($column, '.') === false && strpos(strtoupper($column), 'NULL') === false) |
2002
|
|
|
{ |
2003
|
|
|
$column = trim($column); |
2004
|
|
|
$newColumns[] = preg_match('/^[0-9]/', $column) ? trim($column) : "{$this->tableAlias}.{$column}"; |
2005
|
|
|
} |
2006
|
|
|
else |
2007
|
|
|
{ |
2008
|
|
|
$newColumns[] = trim($column); |
2009
|
|
|
} |
2010
|
|
|
} |
2011
|
|
|
|
2012
|
|
|
return $newColumns; |
2013
|
|
|
} |
2014
|
|
|
|
2015
|
|
|
/** |
2016
|
|
|
* Build the WHERE clause(s) |
2017
|
|
|
* |
2018
|
|
|
* @param bool $purgeAliases |
2019
|
|
|
* @return string |
2020
|
|
|
*/ |
2021
|
|
|
protected function getWhereString(bool $purgeAliases=false) : string |
2022
|
|
|
{ |
2023
|
|
|
// If there are no WHERE clauses, return empty string |
2024
|
|
|
if ( empty($this->whereConditions) ) |
2025
|
|
|
{ |
2026
|
|
|
return ''; |
2027
|
|
|
} |
2028
|
|
|
$whereCondition = ''; |
2029
|
|
|
$lastCondition = ''; |
2030
|
|
|
foreach ( $this->whereConditions as $condition ) |
2031
|
|
|
{ |
2032
|
|
|
if ( is_array($condition) ) |
2033
|
|
|
{ |
2034
|
|
|
if ( $whereCondition && $lastCondition != '(' && !preg_match('/\)\s+(OR|AND)\s+$/i', $whereCondition)) |
2035
|
|
|
{ |
2036
|
|
|
$whereCondition .= $condition['OPERATOR']; |
2037
|
|
|
} |
2038
|
|
|
if ( $purgeAliases && ! empty($condition['STATEMENT']) && strpos($condition['STATEMENT'], '.') !== false && ! empty($this->tableAlias) ) |
2039
|
|
|
{ |
2040
|
|
|
$condition['STATEMENT'] = preg_replace("/{$this->tableAlias}\./", '', $condition['STATEMENT']); |
2041
|
|
|
} |
2042
|
|
|
$whereCondition .= $condition['STATEMENT']; |
2043
|
|
|
$this->whereParameters = array_merge($this->whereParameters, $condition['PARAMS']); |
2044
|
|
|
} |
2045
|
|
|
else |
2046
|
|
|
{ |
2047
|
|
|
$whereCondition .= $condition; |
2048
|
|
|
} |
2049
|
|
|
$lastCondition = $condition; |
2050
|
|
|
} |
2051
|
|
|
|
2052
|
|
|
return " WHERE {$whereCondition}" ; |
2053
|
|
|
} |
2054
|
|
|
|
2055
|
|
|
/** |
2056
|
|
|
* Return the HAVING clause |
2057
|
|
|
* |
2058
|
|
|
* @return string |
2059
|
|
|
*/ |
2060
|
|
|
protected function getHavingString() : string |
2061
|
|
|
{ |
2062
|
|
|
// If there are no WHERE clauses, return empty string |
2063
|
|
|
if ( empty($this->having) ) |
2064
|
|
|
{ |
2065
|
|
|
return ''; |
2066
|
|
|
} |
2067
|
|
|
$havingCondition = ''; |
2068
|
|
|
foreach ( $this->having as $condition ) |
2069
|
|
|
{ |
2070
|
|
|
if ( $havingCondition && ! preg_match('/\)\s+(OR|AND)\s+$/i', $havingCondition) ) |
2071
|
|
|
{ |
2072
|
|
|
$havingCondition .= $condition['OPERATOR']; |
2073
|
|
|
} |
2074
|
|
|
$havingCondition .= $condition['STATEMENT']; |
2075
|
|
|
} |
2076
|
|
|
|
2077
|
|
|
return " HAVING {$havingCondition}" ; |
2078
|
|
|
} |
2079
|
|
|
|
2080
|
|
|
/** |
2081
|
|
|
* Return the values to be bound for where |
2082
|
|
|
* |
2083
|
|
|
* @param bool $purgeAliases |
2084
|
|
|
* @return array |
2085
|
|
|
*/ |
2086
|
|
|
protected function getWhereParameters(bool $purgeAliases=false) : array |
2087
|
|
|
{ |
2088
|
|
|
unset($purgeAliases); |
2089
|
|
|
|
2090
|
|
|
return $this->whereParameters; |
2091
|
|
|
} |
2092
|
|
|
|
2093
|
|
|
/** |
2094
|
|
|
* @param array $record |
2095
|
|
|
* @return stdClass |
2096
|
|
|
*/ |
2097
|
|
|
public function insertArr(array $record) : stdClass |
2098
|
|
|
{ |
2099
|
|
|
return $this->insert((object)$record); |
2100
|
|
|
} |
2101
|
|
|
|
2102
|
|
|
/** |
2103
|
|
|
* Insert new rows |
2104
|
|
|
* $records can be a stdClass or an array of stdClass to add a bulk insert |
2105
|
|
|
* If a single row is inserted, it will return it's row instance |
2106
|
|
|
* |
2107
|
|
|
* @param stdClass $rec |
2108
|
|
|
* @return stdClass |
2109
|
|
|
* @throws Exception |
2110
|
|
|
*/ |
2111
|
|
|
public function insert(stdClass $rec) : stdClass |
2112
|
|
|
{ |
2113
|
|
|
Assert((array)$rec)->notEmpty("The data passed to insert does not contain any data"); |
2114
|
|
|
Assert($rec)->isInstanceOf('stdClass', "The data to be inserted must be an object or an array of objects"); |
2115
|
|
|
|
2116
|
|
|
$rec = $this->beforeSave($rec, self::SAVE_INSERT); |
2117
|
|
|
if ( ! empty($this->errors) ) |
2118
|
|
|
{ |
2119
|
|
|
return $rec; |
2120
|
|
|
} |
2121
|
|
|
list($sql, $values) = $this->insertSqlQuery([$rec]); |
2122
|
|
|
$this->execute((string)$sql, (array)$values); |
2123
|
|
|
$rowCount = $this->rowCount(); |
2124
|
|
|
if ( $rowCount === 1 ) |
2125
|
|
|
{ |
2126
|
|
|
$primaryKeyName = $this->getPrimaryKeyName(); |
2127
|
|
|
$rec->{$primaryKeyName} = $this->getLastInsertId($primaryKeyName); |
2128
|
|
|
} |
2129
|
|
|
$rec = $this->afterSave($rec, self::SAVE_INSERT); |
2130
|
|
|
$this->destroy(); |
2131
|
|
|
|
2132
|
|
|
return $rec; |
2133
|
|
|
} |
2134
|
|
|
|
2135
|
|
|
/** |
2136
|
|
|
* @param string $name |
2137
|
|
|
* @return int |
2138
|
|
|
*/ |
2139
|
|
|
public function getLastInsertId(string $name='') : int |
2140
|
|
|
{ |
2141
|
|
|
return (int)$this->getPdo()->lastInsertId($name ?: null); |
2142
|
|
|
} |
2143
|
|
|
|
2144
|
|
|
/** |
2145
|
|
|
* @param stdClass[] $records |
2146
|
|
|
* @return stdClass[] |
2147
|
|
|
*/ |
2148
|
|
|
public function insertSqlQuery(array $records) : array |
2149
|
|
|
{ |
2150
|
|
|
Assert($records)->notEmpty("The data passed to insert does not contain any data"); |
2151
|
|
|
Assert($records)->all()->isInstanceOf('stdClass', "The data to be inserted must be an object or an array of objects"); |
2152
|
|
|
|
2153
|
|
|
$insertValues = []; |
2154
|
|
|
$questionMarks = []; |
2155
|
|
|
$properties = []; |
2156
|
|
|
foreach ( $records as $record ) |
2157
|
|
|
{ |
2158
|
|
|
$properties = !empty($properties) ? $properties : array_keys(get_object_vars($record)); |
2159
|
|
|
$questionMarks[] = '(' . $this->makePlaceholders(count($properties)) . ')'; |
2160
|
|
|
$insertValues = array_merge($insertValues, array_values((array)$record)); |
2161
|
|
|
} |
2162
|
|
|
$properties = implode(', ', $properties); |
2163
|
|
|
$questionMarks = implode(', ', $questionMarks); |
2164
|
|
|
$sql = "INSERT INTO {$this->tableName} ({$properties}) VALUES {$questionMarks}"; |
2165
|
|
|
|
2166
|
|
|
return [$sql, $insertValues]; |
2167
|
|
|
} |
2168
|
|
|
|
2169
|
|
|
/** |
2170
|
|
|
* @param $data |
2171
|
|
|
* @param array $matchOn |
2172
|
|
|
* @param bool $returnObj |
2173
|
|
|
* @return bool|int|stdClass |
2174
|
|
|
*/ |
2175
|
|
|
public function upsert($data, array $matchOn=[], $returnObj=false) |
2176
|
|
|
{ |
2177
|
|
|
if ( ! is_array($data) ) |
2178
|
|
|
{ |
2179
|
|
|
return $this->upsertOne($data, $matchOn, $returnObj); |
2180
|
|
|
} |
2181
|
|
|
Assert($data) |
2182
|
|
|
->notEmpty("The data passed to insert does not contain any data") |
2183
|
|
|
->all()->isInstanceOf('stdClass', "The data to be inserted must be an object or an array of objects"); |
2184
|
|
|
$numSuccess = 0; |
2185
|
|
|
foreach ( $data as $row ) |
2186
|
|
|
{ |
2187
|
|
|
$clone = clone $this; |
2188
|
|
|
if ( $clone->upsertOne($row, $matchOn) ) |
2189
|
|
|
{ |
2190
|
|
|
$numSuccess++; |
2191
|
|
|
} |
2192
|
|
|
unset($clone->handlers, $clone); // hhvm mem leak |
2193
|
|
|
} |
2194
|
|
|
|
2195
|
|
|
return $numSuccess; |
2196
|
|
|
} |
2197
|
|
|
|
2198
|
|
|
/** |
2199
|
|
|
* @param stdClass $object |
2200
|
|
|
* @param array $matchOn |
2201
|
|
|
* @param bool $returnObj |
2202
|
|
|
* @return bool|int|stdClass |
2203
|
|
|
*/ |
2204
|
|
|
public function upsertOne(stdClass $object, array $matchOn=[], $returnObj=false) |
2205
|
|
|
{ |
2206
|
|
|
$primaryKey = $this->getPrimaryKeyName(); |
2207
|
|
|
$matchOn = empty($matchOn) && property_exists($object, $primaryKey) ? [$primaryKey] : $matchOn; |
2208
|
|
|
foreach ( $matchOn as $column ) |
2209
|
|
|
{ |
2210
|
|
|
Assert( ! property_exists($object, $column) && $column !== $primaryKey)->false('The match on value for upserts is missing.'); |
2211
|
|
|
if ( property_exists($object, $column) ) |
2212
|
|
|
{ |
2213
|
|
|
if ( is_null($object->{$column}) ) |
2214
|
|
|
{ |
2215
|
|
|
$this->whereNull($column); |
2216
|
|
|
} |
2217
|
|
|
else |
2218
|
|
|
{ |
2219
|
|
|
$this->where($column, $object->{$column}); |
2220
|
|
|
} |
2221
|
|
|
} |
2222
|
|
|
} |
2223
|
|
|
if ( count($this->whereConditions) < 1 ) |
2224
|
|
|
{ |
2225
|
|
|
return $this->insert($object); |
2226
|
|
|
} |
2227
|
|
|
if ( ( $id = (int)$this->fetchField($primaryKey) ) ) |
2228
|
|
|
{ |
2229
|
|
|
if ( property_exists($object, $primaryKey) && empty($object->{$primaryKey}) ) |
2230
|
|
|
{ |
2231
|
|
|
$object->{$primaryKey} = $id; |
2232
|
|
|
} |
2233
|
|
|
$rowsAffected = $this->reset()->wherePk($id)->update($object); |
2234
|
|
|
if ( $rowsAffected === false ) |
2235
|
|
|
{ |
2236
|
|
|
return false; |
2237
|
|
|
} |
2238
|
|
|
|
2239
|
|
|
return $returnObj ? $this->reset()->fetchOne($id) : $id; |
2240
|
|
|
} |
2241
|
|
|
|
2242
|
|
|
return $this->insert($object); |
2243
|
|
|
} |
2244
|
|
|
|
2245
|
|
|
/** |
2246
|
|
|
* @param array $data |
2247
|
|
|
* @param array $matchOn |
2248
|
|
|
* @param bool|false $returnObj |
2249
|
|
|
* @return bool|int|stdClass |
2250
|
|
|
*/ |
2251
|
|
|
public function upsertArr(array $data, array $matchOn=[], bool $returnObj=false) |
2252
|
|
|
{ |
2253
|
|
|
return $this->upsert((object)$data, $matchOn, $returnObj); |
2254
|
|
|
} |
2255
|
|
|
|
2256
|
|
|
/** |
2257
|
|
|
* Update entries |
2258
|
|
|
* Use the query builder to create the where clause |
2259
|
|
|
* |
2260
|
|
|
* @param stdClass $record |
2261
|
|
|
* @param bool $updateAll |
2262
|
|
|
* @return int |
2263
|
|
|
* @throws Exception |
2264
|
|
|
*/ |
2265
|
|
|
public function update(stdClass $record, $updateAll=false) : int |
2266
|
|
|
{ |
2267
|
|
|
Assert($record) |
2268
|
|
|
->notEmpty("The data passed to update does not contain any data") |
2269
|
|
|
->isInstanceOf('stdClass', "The data to be updated must be an object or an array of objects"); |
2270
|
|
|
|
2271
|
|
|
if ( empty($this->whereConditions) && ! $updateAll ) |
2272
|
|
|
{ |
2273
|
|
|
throw new Exception("You cannot update an entire table without calling update with updateAll=true", 500); |
2274
|
|
|
} |
2275
|
|
|
$record = $this->beforeSave($record, self::SAVE_UPDATE); |
2276
|
|
|
if ( ! empty($this->errors) ) |
2277
|
|
|
{ |
2278
|
|
|
return 0; |
2279
|
|
|
} |
2280
|
|
|
list($sql, $values) = $this->updateSqlQuery($record); |
2281
|
|
|
$this->execute($sql, $values); |
2282
|
|
|
$this->afterSave($record, self::SAVE_UPDATE); |
2283
|
|
|
$rowCount = $this->rowCount(); |
2284
|
|
|
$this->destroy(); |
2285
|
|
|
|
2286
|
|
|
return $rowCount; |
2287
|
|
|
} |
2288
|
|
|
|
2289
|
|
|
/** |
2290
|
|
|
* @param array $record |
2291
|
|
|
* @param bool|false $updateAll |
2292
|
|
|
* @return int |
2293
|
|
|
* @throws Exception |
2294
|
|
|
*/ |
2295
|
|
|
public function updateArr(array $record, $updateAll=false) : int |
2296
|
|
|
{ |
2297
|
|
|
return $this->update((object)$record, $updateAll); |
2298
|
|
|
} |
2299
|
|
|
|
2300
|
|
|
/** |
2301
|
|
|
* @param string $field |
2302
|
|
|
* @param mixed $value |
2303
|
|
|
* @param int $id |
2304
|
|
|
* @param bool|false $updateAll |
2305
|
|
|
* @return int |
2306
|
|
|
* @throws Exception |
2307
|
|
|
*/ |
2308
|
|
|
public function updateField(string $field, $value, int $id=0, bool $updateAll=false) : int |
2309
|
|
|
{ |
2310
|
|
|
if ( $id && $id > 0 ) |
2311
|
|
|
{ |
2312
|
|
|
$this->wherePk($id); |
2313
|
|
|
} |
2314
|
|
|
$columns = $this->getColumns(); |
2315
|
|
|
if ( $columns ) |
|
|
|
|
2316
|
|
|
{ |
2317
|
|
|
Assert($field)->inArray($columns, "The field {$field} does not exist on this table {$this->tableName}"); |
2318
|
|
|
} |
2319
|
|
|
|
2320
|
|
|
return $this->update((object)[$field => $value], $updateAll); |
2321
|
|
|
} |
2322
|
|
|
|
2323
|
|
|
/** |
2324
|
|
|
* @param stdClass $record |
2325
|
|
|
* @return bool|int |
2326
|
|
|
* @throws Exception |
2327
|
|
|
*/ |
2328
|
|
|
public function updateChanged(stdClass $record) : int |
2329
|
|
|
{ |
2330
|
|
|
foreach ( $record as $field => $value ) |
|
|
|
|
2331
|
|
|
{ |
2332
|
|
|
if ( is_null($value) ) |
2333
|
|
|
{ |
2334
|
|
|
$this->whereNotNull($field); |
2335
|
|
|
continue; |
2336
|
|
|
} |
2337
|
|
|
$this->whereCoercedNot($field, $value); |
2338
|
|
|
} |
2339
|
|
|
|
2340
|
|
|
return $this->update($record); |
2341
|
|
|
} |
2342
|
|
|
|
2343
|
|
|
/** |
2344
|
|
|
* @param string $expression |
2345
|
|
|
* @param array $params |
2346
|
|
|
* @return FluentPdoModel|$this |
2347
|
|
|
*/ |
2348
|
|
|
public function updateByExpression(string $expression, array $params) : FluentPdoModel |
2349
|
|
|
{ |
2350
|
|
|
$this->updateRaw[] = [$expression, $params]; |
2351
|
|
|
|
2352
|
|
|
return $this; |
2353
|
|
|
} |
2354
|
|
|
|
2355
|
|
|
/** |
2356
|
|
|
* @param array $data |
2357
|
|
|
* @return int |
2358
|
|
|
* @throws Exception |
2359
|
|
|
*/ |
2360
|
|
|
public function rawUpdate(array $data=[]) : int |
2361
|
|
|
{ |
2362
|
|
|
list($sql, $values) = $this->updateSql($data); |
2363
|
|
|
$this->execute($sql, $values); |
2364
|
|
|
$rowCount = $this->rowCount(); |
2365
|
|
|
$this->destroy(); |
2366
|
|
|
|
2367
|
|
|
return $rowCount; |
2368
|
|
|
} |
2369
|
|
|
|
2370
|
|
|
/** |
2371
|
|
|
* @param stdClass $record |
2372
|
|
|
* @return array |
2373
|
|
|
*/ |
2374
|
|
|
public function updateSqlQuery(stdClass $record) : array |
2375
|
|
|
{ |
2376
|
|
|
Assert($record) |
2377
|
|
|
->notEmpty("The data passed to update does not contain any data") |
2378
|
|
|
->isInstanceOf('stdClass', "The data to be updated must be an object or an array of objects"); |
2379
|
|
|
// Make sure we remove the primary key |
2380
|
|
|
|
2381
|
|
|
return $this->updateSql((array)$record); |
2382
|
|
|
} |
2383
|
|
|
|
2384
|
|
|
/** |
2385
|
|
|
* @param $record |
2386
|
|
|
* @return array |
2387
|
|
|
*/ |
2388
|
|
|
protected function updateSql(array $record) : array |
2389
|
|
|
{ |
2390
|
|
|
unset($record[$this->getPrimaryKeyName()]); |
2391
|
|
|
Assert($this->limit)->eq(0, 'You cannot limit updates'); |
2392
|
|
|
|
2393
|
|
|
$fieldList = []; |
2394
|
|
|
$fieldAlias = $this->addUpdateAlias && ! empty($this->tableAlias) ? "{$this->tableAlias}." : ''; |
2395
|
|
|
foreach ( $record as $key => $value ) |
2396
|
|
|
{ |
2397
|
|
|
if ( is_numeric($key) ) |
2398
|
|
|
{ |
2399
|
|
|
$fieldList[] = $value; |
2400
|
|
|
unset($record[$key]); |
2401
|
|
|
|
2402
|
|
|
continue; |
2403
|
|
|
} |
2404
|
|
|
$fieldList[] = "{$fieldAlias}{$key} = ?"; |
2405
|
|
|
} |
2406
|
|
|
$rawParams = []; |
2407
|
|
|
foreach ( $this->updateRaw as $rawUpdate ) |
2408
|
|
|
{ |
2409
|
|
|
$fieldList[] = $rawUpdate[0]; |
2410
|
|
|
$rawParams = array_merge($rawParams, $rawUpdate[1]); |
2411
|
|
|
} |
2412
|
|
|
$fieldList = implode(', ', $fieldList); |
2413
|
|
|
$whereStr = $this->getWhereString(); |
2414
|
|
|
$joins = ! empty($this->joinSources) ? (' ').implode(' ',$this->joinSources) : ''; |
2415
|
|
|
$alias = ! empty($this->tableAlias) ? " AS {$this->tableAlias}" : ''; |
2416
|
|
|
$sql = "UPDATE {$this->tableName}{$alias}{$joins} SET {$fieldList}{$whereStr}"; |
2417
|
|
|
$values = array_merge(array_values($record), $rawParams, $this->getWhereParameters()); |
2418
|
|
|
|
2419
|
|
|
return [$sql, $values]; |
2420
|
|
|
} |
2421
|
|
|
|
2422
|
|
|
/** |
2423
|
|
|
* @param bool $deleteAll |
2424
|
|
|
* @param bool $force |
2425
|
|
|
* @return int |
2426
|
|
|
* @throws Exception |
2427
|
|
|
*/ |
2428
|
|
|
public function delete(bool $deleteAll=false, bool $force=false) : int |
2429
|
|
|
{ |
2430
|
|
|
if ( ! $force && $this->softDeletes ) |
2431
|
|
|
{ |
2432
|
|
|
return $this->updateArchived(); |
2433
|
|
|
} |
2434
|
|
|
|
2435
|
|
|
list($sql, $params) = $this->deleteSqlQuery(); |
2436
|
|
|
if ( empty($this->whereConditions) && ! $deleteAll ) |
2437
|
|
|
{ |
2438
|
|
|
throw new Exception("You cannot update an entire table without calling update with deleteAll=true"); |
2439
|
|
|
} |
2440
|
|
|
$this->execute($sql, $params); |
2441
|
|
|
|
2442
|
|
|
return $this->rowCount(); |
2443
|
|
|
} |
2444
|
|
|
|
2445
|
|
|
/** |
2446
|
|
|
* @return bool |
2447
|
|
|
*/ |
2448
|
|
|
public function isSoftDelete() : bool |
2449
|
|
|
{ |
2450
|
|
|
return $this->softDeletes; |
2451
|
|
|
} |
2452
|
|
|
|
2453
|
|
|
/** |
2454
|
|
|
* @param bool|false $force |
2455
|
|
|
* @return FluentPdoModel|$this |
2456
|
|
|
* @throws Exception |
2457
|
|
|
*/ |
2458
|
|
|
public function truncate(bool $force=false) : FluentPdoModel |
2459
|
|
|
{ |
2460
|
|
|
if ( $force ) |
2461
|
|
|
{ |
2462
|
|
|
$this->execute('SET FOREIGN_KEY_CHECKS = 0'); |
2463
|
|
|
} |
2464
|
|
|
$this->execute("TRUNCATE TABLE {$this->tableName}"); |
2465
|
|
|
if ( $force ) |
2466
|
|
|
{ |
2467
|
|
|
$this->execute('SET FOREIGN_KEY_CHECKS = 1'); |
2468
|
|
|
} |
2469
|
|
|
|
2470
|
|
|
return $this; |
2471
|
|
|
} |
2472
|
|
|
|
2473
|
|
|
/** |
2474
|
|
|
* @return array |
2475
|
|
|
*/ |
2476
|
|
|
public function deleteSqlQuery() : array |
2477
|
|
|
{ |
2478
|
|
|
$query = "DELETE FROM {$this->tableName}"; |
2479
|
|
|
if ( ! empty($this->whereConditions) ) |
2480
|
|
|
{ |
2481
|
|
|
$query .= $this->getWhereString(true); |
2482
|
|
|
|
2483
|
|
|
return [$query, $this->getWhereParameters()]; |
2484
|
|
|
} |
2485
|
|
|
|
2486
|
|
|
return [$query, []]; |
2487
|
|
|
} |
2488
|
|
|
|
2489
|
|
|
|
2490
|
|
|
/** |
2491
|
|
|
* Return the aggregate count of column |
2492
|
|
|
* |
2493
|
|
|
* @param string $column |
2494
|
|
|
* @param int $cacheTtl |
2495
|
|
|
* @return float |
2496
|
|
|
*/ |
2497
|
|
|
public function count(string $column='*', int $cacheTtl=self::CACHE_NO) : float |
2498
|
|
|
{ |
2499
|
|
|
$this->explicitSelectMode(); |
2500
|
|
|
|
2501
|
|
|
if ( empty($this->groupBy) ) |
2502
|
|
|
{ |
2503
|
|
|
return $this->fetchFloat("COUNT({$column}) AS cnt", 0, $cacheTtl); |
2504
|
|
|
} |
2505
|
|
|
$this->select("COUNT({$column}) AS cnt"); |
2506
|
|
|
$sql = $this->getSelectQuery(); |
2507
|
|
|
$params = $this->getWhereParameters(); |
2508
|
|
|
$sql = "SELECT COUNT(*) AS cnt FROM ({$sql}) t"; |
2509
|
|
|
$object = $this->query($sql, $params)->fetchOne(0, $cacheTtl); |
2510
|
|
|
if ( ! $object || empty($object->cnt) ) |
2511
|
|
|
{ |
2512
|
|
|
return 0.0; |
2513
|
|
|
} |
2514
|
|
|
|
2515
|
|
|
return (float)$object->cnt; |
2516
|
|
|
} |
2517
|
|
|
|
2518
|
|
|
|
2519
|
|
|
/** |
2520
|
|
|
* Return the aggregate max count of column |
2521
|
|
|
* |
2522
|
|
|
* @param string $column |
2523
|
|
|
* @param int $cacheTtl |
2524
|
|
|
* @return int|float|string|null |
2525
|
|
|
*/ |
2526
|
|
|
public function max(string $column, int $cacheTtl=self::CACHE_NO) |
2527
|
|
|
{ |
2528
|
|
|
return $this |
2529
|
|
|
->explicitSelectMode() |
2530
|
|
|
->fetchField("MAX({$column}) AS max", 0, $cacheTtl); |
2531
|
|
|
} |
2532
|
|
|
|
2533
|
|
|
|
2534
|
|
|
/** |
2535
|
|
|
* Return the aggregate min count of column |
2536
|
|
|
* |
2537
|
|
|
* @param string $column |
2538
|
|
|
* @param int $cacheTtl |
2539
|
|
|
* @return int|float|string|null |
2540
|
|
|
*/ |
2541
|
|
|
public function min(string $column, int $cacheTtl=self::CACHE_NO) |
2542
|
|
|
{ |
2543
|
|
|
return $this |
2544
|
|
|
->explicitSelectMode() |
2545
|
|
|
->fetchField("MIN({$column}) AS min", 0, $cacheTtl); |
2546
|
|
|
} |
2547
|
|
|
|
2548
|
|
|
/** |
2549
|
|
|
* Return the aggregate sum count of column |
2550
|
|
|
* |
2551
|
|
|
* @param string $column |
2552
|
|
|
* @param int $cacheTtl |
2553
|
|
|
* @return int|float|string|null |
2554
|
|
|
*/ |
2555
|
|
|
public function sum(string $column, int $cacheTtl=self::CACHE_NO) |
2556
|
|
|
{ |
2557
|
|
|
return $this |
2558
|
|
|
->explicitSelectMode() |
2559
|
|
|
->fetchField("SUM({$column}) AS sum", 0, $cacheTtl); |
2560
|
|
|
} |
2561
|
|
|
|
2562
|
|
|
/** |
2563
|
|
|
* Return the aggregate average count of column |
2564
|
|
|
* |
2565
|
|
|
* @param string $column |
2566
|
|
|
* @param int $cacheTtl |
2567
|
|
|
* @return int|float|string|null |
2568
|
|
|
*/ |
2569
|
|
|
public function avg(string $column, int $cacheTtl=self::CACHE_NO) |
2570
|
|
|
{ |
2571
|
|
|
return $this |
2572
|
|
|
->explicitSelectMode() |
2573
|
|
|
->fetchField("AVG({$column}) AS avg", 0, $cacheTtl); |
2574
|
|
|
} |
2575
|
|
|
|
2576
|
|
|
/*******************************************************************************/ |
2577
|
|
|
// Utilities methods |
2578
|
|
|
|
2579
|
|
|
/** |
2580
|
|
|
* Reset fields |
2581
|
|
|
* |
2582
|
|
|
* @return FluentPdoModel|$this |
2583
|
|
|
*/ |
2584
|
|
|
public function reset() : FluentPdoModel |
2585
|
|
|
{ |
2586
|
|
|
$this->whereParameters = []; |
2587
|
|
|
$this->selectFields = []; |
2588
|
|
|
$this->joinSources = []; |
2589
|
|
|
$this->joinAliases = []; |
2590
|
|
|
$this->whereConditions = []; |
2591
|
|
|
$this->limit = 0; |
2592
|
|
|
$this->offset = 0; |
2593
|
|
|
$this->orderBy = []; |
2594
|
|
|
$this->groupBy = []; |
2595
|
|
|
$this->andOrOperator = self::OPERATOR_AND; |
2596
|
|
|
$this->having = []; |
2597
|
|
|
$this->wrapOpen = false; |
2598
|
|
|
$this->lastWrapPosition = 0; |
2599
|
|
|
$this->pdoStmt = null; |
2600
|
|
|
$this->distinct = false; |
2601
|
|
|
$this->requestedFields = []; |
|
|
|
|
2602
|
|
|
$this->filterMeta = []; |
|
|
|
|
2603
|
|
|
$this->cacheTtl = -1; |
2604
|
|
|
$this->timer = []; |
2605
|
|
|
$this->builtQuery = ''; |
2606
|
|
|
$this->pagingMeta = []; |
2607
|
|
|
$this->rawSql = null; |
2608
|
|
|
$this->explicitSelectMode = false; |
2609
|
|
|
|
2610
|
|
|
return $this; |
2611
|
|
|
} |
2612
|
|
|
|
2613
|
|
|
|
2614
|
|
|
/** |
2615
|
|
|
* @return FluentPdoModel|$this |
2616
|
|
|
*/ |
2617
|
|
|
public function removeUnauthorisedFields() : FluentPdoModel |
2618
|
|
|
{ |
2619
|
|
|
return $this; |
2620
|
|
|
} |
2621
|
|
|
|
2622
|
|
|
/** |
2623
|
|
|
* @return Closure[] |
2624
|
|
|
*/ |
2625
|
|
|
protected function getFieldHandlers() : array |
2626
|
|
|
{ |
2627
|
|
|
$columns = $this->getColumns(true); |
2628
|
|
|
if ( empty($columns) ) |
2629
|
|
|
{ |
2630
|
|
|
return []; |
2631
|
|
|
} |
2632
|
|
|
|
2633
|
|
|
return [ |
2634
|
|
|
'id' => function(string $field, $value, string $type='', stdClass $record=null) { |
2635
|
|
|
|
2636
|
|
|
unset($record); |
2637
|
|
|
$value = $this->fixType($field, $value); |
2638
|
|
|
if ( $type === self::SAVE_INSERT ) |
2639
|
|
|
{ |
2640
|
|
|
Validate($value)->fieldName($field)->nullOr()->id('ID must be a valid integer id, (%s) submitted.'); |
2641
|
|
|
|
2642
|
|
|
return $value; |
2643
|
|
|
} |
2644
|
|
|
Validate($value)->fieldName($field)->id('ID must be a valid integer id, (%s) submitted.'); |
2645
|
|
|
|
2646
|
|
|
return $value; |
2647
|
|
|
}, |
2648
|
|
|
self::CREATOR_ID_FIELD => function(string $field, $value, string $type='', stdClass $record=null) { |
2649
|
|
|
|
2650
|
|
|
unset($type, $record); |
2651
|
|
|
$value = $this->fixType($field, $value); |
2652
|
|
|
// Created user id is set to current user if record is an insert or deleted if not (unless override is true) |
2653
|
|
|
$value = $this->allowMetaOverride ? $value : $this->getUserId(); |
2654
|
|
|
Validate($value)->fieldName($field)->id('Created By must be a valid integer id, (%s) submitted.'); |
2655
|
|
|
|
2656
|
|
|
return $value; |
2657
|
|
|
}, |
2658
|
|
|
self::CREATED_TS_FIELD => function(string $field, $value, string $type='', stdClass $record=null) { |
2659
|
|
|
|
2660
|
|
|
unset($type, $record); |
2661
|
|
|
$value = $this->fixType($field, $value); |
2662
|
|
|
// Created ts is set to now if record is an insert or deleted if not (unless override is true) |
2663
|
|
|
$value = static::dateTime($this->allowMetaOverride ? $value : null); |
2664
|
|
|
Validate($value)->fieldName($field)->date('Created must be a valid timestamp, (%s) submitted.'); |
2665
|
|
|
|
2666
|
|
|
return $value; |
2667
|
|
|
}, |
2668
|
|
|
self::MODIFIER_ID_FIELD => function(string $field, $value, string $type='', stdClass $record=null) { |
2669
|
|
|
|
2670
|
|
|
unset($type, $record); |
2671
|
|
|
$value = $this->fixType($field, $value); |
2672
|
|
|
// Modified user id is set to current user (unless override is true) |
2673
|
|
|
$value = $this->allowMetaOverride ? $value : $this->getUserId(); |
2674
|
|
|
Validate($value)->fieldName($field)->id('Modified By must be a valid integer id, (%s) submitted.'); |
2675
|
|
|
|
2676
|
|
|
return $value; |
2677
|
|
|
}, |
2678
|
|
|
self::MODIFIED_TS_FIELD => function(string $field, $value, string $type='', stdClass $record=null) { |
2679
|
|
|
|
2680
|
|
|
unset($type, $record); |
2681
|
|
|
$value = $this->fixType($field, $value); |
2682
|
|
|
// Modified timestamps are set to now (unless override is true) |
2683
|
|
|
$value = static::dateTime($this->allowMetaOverride ? $value : null); |
2684
|
|
|
Validate($value)->fieldName($field)->date('Modified must be a valid timestamp, (%s) submitted.'); |
2685
|
|
|
|
2686
|
|
|
return $value; |
2687
|
|
|
}, |
2688
|
|
|
self::DELETER_ID_FIELD => function(string $field, $value, string $type='', stdClass $record=null) { |
2689
|
|
|
|
2690
|
|
|
if ( $type === self::SAVE_INSERT ) |
2691
|
|
|
{ |
2692
|
|
|
return null; |
2693
|
|
|
} |
2694
|
|
|
if ( empty($record->deleted_ts) ) |
2695
|
|
|
{ |
2696
|
|
|
return null; |
2697
|
|
|
} |
2698
|
|
|
unset($type, $record); |
2699
|
|
|
$value = $this->fixType($field, $value); |
2700
|
|
|
|
2701
|
|
|
// Modified user id is set to current user (unless override is true) |
2702
|
|
|
$value = $this->allowMetaOverride ? $value : $this->getUserId(); |
2703
|
|
|
Validate($value)->fieldName($field)->nullOr()->id('Deleter must be a valid integer id, (%s) submitted.'); |
2704
|
|
|
|
2705
|
|
|
return $value; |
2706
|
|
|
}, |
2707
|
|
|
self::DELETED_TS_FIELD => function(string $field, $value, string $type='', stdClass $record=null) { |
2708
|
|
|
|
2709
|
|
|
if ( $type === self::SAVE_INSERT ) |
2710
|
|
|
{ |
2711
|
|
|
return null; |
2712
|
|
|
} |
2713
|
|
|
unset($type, $record); |
2714
|
|
|
$value = $this->fixType($field, $value); |
2715
|
|
|
if ( $value ) |
2716
|
|
|
{ |
2717
|
|
|
$value = static::dateTime($this->allowMetaOverride ? $value : null); |
2718
|
|
|
Validate($value)->fieldName($field)->date('Deleted Timestamp must be a valid timestamp, (%s) submitted.'); |
2719
|
|
|
} |
2720
|
|
|
|
2721
|
|
|
return $value; |
2722
|
|
|
}, |
2723
|
|
|
]; |
2724
|
|
|
} |
2725
|
|
|
|
2726
|
|
|
/** |
2727
|
|
|
* @return bool |
2728
|
|
|
*/ |
2729
|
|
|
public function begin() : bool |
2730
|
|
|
{ |
2731
|
|
|
$pdo = $this->getPdo(); |
2732
|
|
|
$oldDepth = $pdo->getTransactionDepth(); |
2733
|
|
|
$res = $pdo->beginTransaction(); |
2734
|
|
|
$newDepth = $pdo->getTransactionDepth(); |
2735
|
|
|
$this->getLogger()->debug("Calling db begin transaction", [ |
2736
|
|
|
'old_depth' => $oldDepth, |
2737
|
|
|
'new_depth' => $newDepth, |
2738
|
|
|
'trans_started' => $newDepth === 1 ? true : false, |
2739
|
|
|
]); |
2740
|
|
|
|
2741
|
|
|
return $res; |
2742
|
|
|
} |
2743
|
|
|
|
2744
|
|
|
/** |
2745
|
|
|
* @return bool |
2746
|
|
|
*/ |
2747
|
|
|
public function commit() : bool |
2748
|
|
|
{ |
2749
|
|
|
$pdo = $this->getPdo(); |
2750
|
|
|
$oldDepth = $pdo->getTransactionDepth(); |
2751
|
|
|
$res = $pdo->commit(); |
2752
|
|
|
$newDepth = $pdo->getTransactionDepth(); |
2753
|
|
|
$this->getLogger()->debug("Calling db commit transaction", [ |
2754
|
|
|
'old_depth' => $oldDepth, |
2755
|
|
|
'new_depth' => $newDepth, |
2756
|
|
|
'trans_ended' => $newDepth === 0 ? true : false, |
2757
|
|
|
]); |
2758
|
|
|
if ( ! $res ) |
2759
|
|
|
{ |
2760
|
|
|
return false; |
2761
|
|
|
} |
2762
|
|
|
|
2763
|
|
|
return $res === 0 ? true : $res; |
2764
|
|
|
} |
2765
|
|
|
|
2766
|
|
|
/** |
2767
|
|
|
* @return bool |
2768
|
|
|
*/ |
2769
|
|
|
public function rollback() : bool |
2770
|
|
|
{ |
2771
|
|
|
$pdo = $this->getPdo(); |
2772
|
|
|
$oldDepth = $pdo->getTransactionDepth(); |
2773
|
|
|
$res = $pdo->rollback(); |
2774
|
|
|
$newDepth = $pdo->getTransactionDepth(); |
2775
|
|
|
$this->getLogger()->debug("Calling db rollback transaction", [ |
2776
|
|
|
'old_depth' => $oldDepth, |
2777
|
|
|
'new_depth' => $newDepth, |
2778
|
|
|
'trans_ended' => $newDepth === 0 ? true : false, |
2779
|
|
|
]); |
2780
|
|
|
|
2781
|
|
|
return $res; |
2782
|
|
|
} |
2783
|
|
|
|
2784
|
|
|
/** |
2785
|
|
|
* @param stdClass $record |
2786
|
|
|
* @param string $type |
2787
|
|
|
* @return stdClass |
2788
|
|
|
*/ |
2789
|
|
|
public function applyGlobalModifiers(stdClass $record, string $type) : stdClass |
2790
|
|
|
{ |
2791
|
|
|
unset($type); |
2792
|
|
|
foreach ( $record as $field => $value ) |
|
|
|
|
2793
|
|
|
{ |
2794
|
|
|
if ( is_string($record->{$field}) ) |
2795
|
|
|
{ |
2796
|
|
|
$record->{$field} = str_replace(["\r\n", "\\r\\n", "\\n"], "\n", $value); |
2797
|
|
|
} |
2798
|
|
|
} |
2799
|
|
|
|
2800
|
|
|
return $record; |
2801
|
|
|
} |
2802
|
|
|
|
2803
|
|
|
/** |
2804
|
|
|
* @param stdClass $record |
2805
|
|
|
* @param string $type |
2806
|
|
|
* @return stdClass |
2807
|
|
|
*/ |
2808
|
|
|
public function removeUnneededFields(stdClass $record, string $type) : stdClass |
2809
|
|
|
{ |
2810
|
|
|
$creatorId = self::CREATOR_ID_FIELD; |
2811
|
|
|
$createdTs = self::CREATED_TS_FIELD; |
2812
|
|
|
|
2813
|
|
|
// remove un-needed fields |
2814
|
|
|
$columns = $this->getColumns(true); |
2815
|
|
|
if ( empty($columns) ) |
2816
|
|
|
{ |
2817
|
|
|
return $record; |
2818
|
|
|
} |
2819
|
|
|
foreach ( $record as $name => $value ) |
|
|
|
|
2820
|
|
|
{ |
2821
|
|
|
if ( ! in_array($name, $columns) || in_array($name, $this->virtualFields) ) |
2822
|
|
|
{ |
2823
|
|
|
unset($record->{$name}); |
2824
|
|
|
} |
2825
|
|
|
} |
2826
|
|
|
if ( property_exists($record, $createdTs) && $type !== 'INSERT' && ! $this->allowMetaOverride ) |
2827
|
|
|
{ |
2828
|
|
|
unset($record->{$createdTs}); |
2829
|
|
|
} |
2830
|
|
|
if ( property_exists($record, $creatorId) && $type !== 'INSERT' && ! $this->allowMetaOverride ) |
2831
|
|
|
{ |
2832
|
|
|
unset($record->{$creatorId}); |
2833
|
|
|
} |
2834
|
|
|
|
2835
|
|
|
return $record; |
2836
|
|
|
} |
2837
|
|
|
|
2838
|
|
|
|
2839
|
|
|
/** |
2840
|
|
|
* @param array $ids |
2841
|
|
|
* @param array $values |
2842
|
|
|
* @param int $batch |
2843
|
|
|
* @return bool |
2844
|
|
|
*/ |
2845
|
|
|
public function setById(array $ids, array $values, int $batch=1000) : bool |
2846
|
|
|
{ |
2847
|
|
|
$ids = array_unique($ids); |
2848
|
|
|
if ( empty($ids) ) |
2849
|
|
|
{ |
2850
|
|
|
return true; |
2851
|
|
|
} |
2852
|
|
|
if ( count($ids) <= $batch ) |
2853
|
|
|
{ |
2854
|
|
|
return (bool)$this->whereIn('id', $ids)->updateArr($values); |
2855
|
|
|
} |
2856
|
|
|
while ( ! empty($ids) ) |
2857
|
|
|
{ |
2858
|
|
|
$thisBatch = array_slice($ids, 0, $batch); |
2859
|
|
|
$ids = array_diff($ids, $thisBatch); |
2860
|
|
|
$this->reset()->whereIn('id', $thisBatch)->updateArr($values); |
2861
|
|
|
} |
2862
|
|
|
|
2863
|
|
|
return true; |
2864
|
|
|
} |
2865
|
|
|
|
2866
|
|
|
|
2867
|
|
|
/** |
2868
|
|
|
* @param string $displayColumnValue |
2869
|
|
|
* @return int |
2870
|
|
|
*/ |
2871
|
|
|
public function resolveId(string $displayColumnValue) : int |
2872
|
|
|
{ |
2873
|
|
|
$displayColumn = $this->getDisplayColumn(); |
2874
|
|
|
$className = get_class($this); |
2875
|
|
|
Assert($displayColumn)->notEmpty("Could not determine the display column for model ({$className})"); |
2876
|
|
|
|
2877
|
|
|
return $this |
2878
|
|
|
->reset() |
2879
|
|
|
->where($displayColumn, $displayColumnValue) |
2880
|
|
|
->fetchInt('id', 0, self::ONE_HOUR); |
2881
|
|
|
} |
2882
|
|
|
|
2883
|
|
|
/** |
2884
|
|
|
* @param int $resourceId |
2885
|
|
|
* @param array $query |
2886
|
|
|
* @param array $extraFields |
2887
|
|
|
* @param int $cacheTtl |
2888
|
|
|
* @return array |
2889
|
|
|
*/ |
2890
|
|
|
public function fetchApiResource(int $resourceId, array $query=[], array $extraFields=[], int $cacheTtl=self::CACHE_NO) : array |
2891
|
|
|
{ |
2892
|
|
|
Assert($resourceId)->id(); |
2893
|
|
|
|
2894
|
|
|
$query['_limit'] = 1; |
2895
|
|
|
$pagingMetaData = $this->wherePk($resourceId)->prepareApiResource($query, $extraFields); |
2896
|
|
|
if ( $pagingMetaData['total'] === 0 ) |
2897
|
|
|
{ |
2898
|
|
|
return [[], $pagingMetaData]; |
2899
|
|
|
} |
2900
|
|
|
|
2901
|
|
|
return [$this->fetchOne($resourceId, $cacheTtl), $pagingMetaData]; |
2902
|
|
|
} |
2903
|
|
|
|
2904
|
|
|
/** |
2905
|
|
|
* @param array $query |
2906
|
|
|
* @param array $extraFields |
2907
|
|
|
* @param int $cacheTtl |
2908
|
|
|
* @param string $permEntity |
2909
|
|
|
* @return array |
2910
|
|
|
*/ |
2911
|
|
|
public function fetchApiResources(array $query=[], array $extraFields=[], int $cacheTtl=self::CACHE_NO, string $permEntity='') : array |
2912
|
|
|
{ |
2913
|
|
|
$pagingMetaData = $this->prepareApiResource($query, $extraFields); |
2914
|
|
|
if ( $pagingMetaData['total'] === 0 ) |
2915
|
|
|
{ |
2916
|
|
|
return [[], $pagingMetaData]; |
2917
|
|
|
} |
2918
|
|
|
$results = $this->fetch('', $cacheTtl); |
2919
|
|
|
if ( ! $permEntity ) |
2920
|
|
|
{ |
2921
|
|
|
return [$results, $pagingMetaData]; |
2922
|
|
|
} |
2923
|
|
|
foreach ( $results as $rec ) |
2924
|
|
|
{ |
2925
|
|
|
if ( ! empty($rec->id) ) |
2926
|
|
|
{ |
2927
|
|
|
$pagingMetaData['perms'][(int)$rec->id] = $this->getMaskByResourceAndId($permEntity, $rec->id); |
2928
|
|
|
} |
2929
|
|
|
} |
2930
|
|
|
|
2931
|
|
|
return [$results, $pagingMetaData]; |
2932
|
|
|
} |
2933
|
|
|
|
2934
|
|
|
|
2935
|
|
|
/** |
2936
|
|
|
* @return array |
2937
|
|
|
*/ |
2938
|
|
|
public function getSearchableAssociations() : array |
2939
|
|
|
{ |
2940
|
|
|
$belongsTo = ! empty($this->associations['belongsTo']) ? $this->associations['belongsTo'] : []; |
2941
|
|
|
unset($belongsTo['CreatedBy'], $belongsTo['ModifiedBy']); |
2942
|
|
|
|
2943
|
|
|
return $belongsTo; |
2944
|
|
|
} |
2945
|
|
|
|
2946
|
|
|
/** |
2947
|
|
|
* @param array $fields |
2948
|
|
|
*/ |
2949
|
|
|
public function removeUnrequestedFields(array $fields) |
2950
|
|
|
{ |
2951
|
|
|
foreach ( $this->selectFields as $idx => $field ) |
2952
|
|
|
{ |
2953
|
|
|
$field = trim(static::after(' AS ', $field, true)); |
2954
|
|
|
if ( ! in_array($field, $fields) ) |
2955
|
|
|
{ |
2956
|
|
|
unset($this->selectFields[$idx]); |
2957
|
|
|
} |
2958
|
|
|
} |
2959
|
|
|
} |
2960
|
|
|
|
2961
|
|
|
/** |
2962
|
|
|
* @param array $removeFields |
2963
|
|
|
*/ |
2964
|
|
|
public function removeFields(array $removeFields=[]) |
2965
|
|
|
{ |
2966
|
|
|
$searches = []; |
2967
|
|
|
foreach ( $removeFields as $removeField ) |
2968
|
|
|
{ |
2969
|
|
|
$removeField = str_replace("{$this->tableAlias}.", '', $removeField); |
2970
|
|
|
$searches[] = "{$this->tableAlias}.{$removeField}"; |
2971
|
|
|
$searches[] = $removeField; |
2972
|
|
|
} |
2973
|
|
|
foreach ( $this->selectFields as $idx => $selected ) |
2974
|
|
|
{ |
2975
|
|
|
$selected = stripos($selected, ' AS ') !== false ? preg_split('/ as /i', $selected) : [$selected]; |
2976
|
|
|
foreach ( $selected as $haystack ) |
2977
|
|
|
{ |
2978
|
|
|
foreach ( $searches as $search ) |
2979
|
|
|
{ |
2980
|
|
|
if ( trim($haystack) === trim($search) ) |
2981
|
|
|
{ |
2982
|
|
|
unset($this->selectFields[$idx]); |
2983
|
|
|
|
2984
|
|
|
continue; |
2985
|
|
|
} |
2986
|
|
|
} |
2987
|
|
|
} |
2988
|
|
|
} |
2989
|
|
|
} |
2990
|
|
|
|
2991
|
|
|
/** |
2992
|
|
|
* @return FluentPdoModel|$this |
2993
|
|
|
*/ |
2994
|
|
|
public function defaultFilters() : FluentPdoModel |
2995
|
|
|
{ |
2996
|
|
|
return $this; |
2997
|
|
|
} |
2998
|
|
|
|
2999
|
|
|
/** |
3000
|
|
|
* @param bool $allow |
3001
|
|
|
* |
3002
|
|
|
* @return FluentPdoModel|$this |
3003
|
|
|
*/ |
3004
|
|
|
public function allowMetaColumnOverride(bool $allow=false) : FluentPdoModel |
3005
|
|
|
{ |
3006
|
|
|
$this->allowMetaOverride = $allow; |
3007
|
|
|
|
3008
|
|
|
return $this; |
3009
|
|
|
} |
3010
|
|
|
|
3011
|
|
|
/** |
3012
|
|
|
* @param bool $skip |
3013
|
|
|
* |
3014
|
|
|
* @return FluentPdoModel|$this |
3015
|
|
|
*/ |
3016
|
|
|
public function skipMetaUpdates(bool $skip=true) : FluentPdoModel |
3017
|
|
|
{ |
3018
|
|
|
$this->skipMetaUpdates = $skip; |
3019
|
|
|
|
3020
|
|
|
return $this; |
3021
|
|
|
} |
3022
|
|
|
|
3023
|
|
|
/** |
3024
|
|
|
* @param bool $add |
3025
|
|
|
* |
3026
|
|
|
* @return FluentPdoModel|$this |
3027
|
|
|
*/ |
3028
|
|
|
public function addUpdateAlias(bool $add=true) : FluentPdoModel |
3029
|
|
|
{ |
3030
|
|
|
$this->addUpdateAlias = $add; |
3031
|
|
|
|
3032
|
|
|
return $this; |
3033
|
|
|
} |
3034
|
|
|
|
3035
|
|
|
/** |
3036
|
|
|
* @param stdClass $record |
3037
|
|
|
* @return stdClass |
3038
|
|
|
*/ |
3039
|
|
|
public function onFetch(stdClass $record) : stdClass |
3040
|
|
|
{ |
3041
|
|
|
$record = $this->trimAndLowerCaseKeys($record); |
3042
|
|
|
if ( $this->filterOnFetch ) |
3043
|
|
|
{ |
3044
|
|
|
$record = $this->cleanseRecord($record); |
3045
|
|
|
} |
3046
|
|
|
|
3047
|
|
|
$record = $this->fixTypesToSentinel($record); |
3048
|
|
|
|
3049
|
|
|
return $this->fixTimestamps($record); |
3050
|
|
|
} |
3051
|
|
|
|
3052
|
|
|
/** |
3053
|
|
|
* @param $value |
3054
|
|
|
* @return string |
3055
|
|
|
*/ |
3056
|
|
|
public function gzEncodeData(string $value) : string |
3057
|
|
|
{ |
3058
|
|
|
if ( $this->hasGzipPrefix($value) ) |
3059
|
|
|
{ |
3060
|
|
|
return $value; |
3061
|
|
|
} |
3062
|
|
|
|
3063
|
|
|
return static::GZIP_PREFIX . base64_encode(gzencode($value, 9)); |
3064
|
|
|
} |
3065
|
|
|
|
3066
|
|
|
/** |
3067
|
|
|
* @param $value |
3068
|
|
|
* @return mixed|string |
3069
|
|
|
*/ |
3070
|
|
|
public function gzDecodeData(string $value) : string |
3071
|
|
|
{ |
3072
|
|
|
if ( ! $this->hasGzipPrefix($value) ) |
3073
|
|
|
{ |
3074
|
|
|
return $value; |
3075
|
|
|
} |
3076
|
|
|
$value = substr_replace($value, '', 0, strlen(static::GZIP_PREFIX)); |
3077
|
|
|
|
3078
|
|
|
return gzdecode(base64_decode($value)); |
3079
|
|
|
} |
3080
|
|
|
|
3081
|
|
|
/** |
3082
|
|
|
* @param $value |
3083
|
|
|
* @return bool |
3084
|
|
|
*/ |
3085
|
|
|
protected function hasGzipPrefix(string $value) : bool |
3086
|
|
|
{ |
3087
|
|
|
return substr($value, 0, strlen(static::GZIP_PREFIX)) === static::GZIP_PREFIX ? true : false; |
3088
|
|
|
} |
3089
|
|
|
|
3090
|
|
|
/** |
3091
|
|
|
* @param stdClass $record |
3092
|
|
|
* @return stdClass |
3093
|
|
|
*/ |
3094
|
|
|
public function fixTimestamps(stdClass $record) : stdClass |
3095
|
|
|
{ |
3096
|
|
|
foreach ( $record as $field => $value ) |
|
|
|
|
3097
|
|
|
{ |
3098
|
|
|
if ( preg_match('/_ts$/', $field) ) |
3099
|
|
|
{ |
3100
|
|
|
$record->{$field} = empty($value) ? $value : static::atom($value); |
3101
|
|
|
} |
3102
|
|
|
} |
3103
|
|
|
|
3104
|
|
|
return $record; |
3105
|
|
|
} |
3106
|
|
|
|
3107
|
|
|
/** |
3108
|
|
|
* @param int $max |
3109
|
|
|
* @return FluentPdoModel|$this |
3110
|
|
|
*/ |
3111
|
|
|
public function setMaxRecords(int $max) : FluentPdoModel |
3112
|
|
|
{ |
3113
|
|
|
Assert($max)->int(); |
3114
|
|
|
$this->defaultMax = $max; |
3115
|
|
|
|
3116
|
|
|
return $this; |
3117
|
|
|
} |
3118
|
|
|
|
3119
|
|
|
|
3120
|
|
|
/** |
3121
|
|
|
* @param stdClass $record |
3122
|
|
|
* @param string $type |
3123
|
|
|
* @return stdClass |
3124
|
|
|
*/ |
3125
|
|
|
public function afterSave(stdClass $record, string $type) : stdClass |
3126
|
|
|
{ |
3127
|
|
|
unset($type); |
3128
|
|
|
$this->clearCacheByTable(); |
3129
|
|
|
foreach ( $record as $col => $value ) |
|
|
|
|
3130
|
|
|
{ |
3131
|
|
|
if ( !empty($record->{$col}) ) |
3132
|
|
|
{ |
3133
|
|
|
if ( preg_match('/_ts$/', $col) ) |
3134
|
|
|
{ |
3135
|
|
|
$record->{$col} = static::atom($value); |
3136
|
|
|
} |
3137
|
|
|
if ( preg_match('/_am$/', $col) ) |
3138
|
|
|
{ |
3139
|
|
|
$record->{$col} = number_format($value, 2, '.', ''); |
3140
|
|
|
} |
3141
|
|
|
} |
3142
|
|
|
} |
3143
|
|
|
|
3144
|
|
|
return $record; |
3145
|
|
|
} |
3146
|
|
|
|
3147
|
|
|
/** |
3148
|
|
|
* @param stdClass $record |
3149
|
|
|
* @param string $type |
3150
|
|
|
* @return stdClass |
3151
|
|
|
*/ |
3152
|
|
|
public function addDefaultFields(stdClass $record, string $type) : stdClass |
3153
|
|
|
{ |
3154
|
|
|
$columns = $this->getColumns(true); |
3155
|
|
|
if ( empty($columns) ) |
3156
|
|
|
{ |
3157
|
|
|
return $record; |
3158
|
|
|
} |
3159
|
|
|
$defaults = [ |
3160
|
|
|
self::SAVE_UPDATE => [ |
3161
|
|
|
self::MODIFIER_ID_FIELD => null, |
3162
|
|
|
self::MODIFIED_TS_FIELD => null, |
3163
|
|
|
], |
3164
|
|
|
self::SAVE_INSERT => [ |
3165
|
|
|
self::CREATOR_ID_FIELD => null, |
3166
|
|
|
self::CREATED_TS_FIELD => null, |
3167
|
|
|
self::MODIFIER_ID_FIELD => null, |
3168
|
|
|
self::MODIFIED_TS_FIELD => null, |
3169
|
|
|
] |
3170
|
|
|
]; |
3171
|
|
|
if ( $this->skipMetaUpdates ) |
3172
|
|
|
{ |
3173
|
|
|
$defaults[self::SAVE_UPDATE] = []; |
3174
|
|
|
} |
3175
|
|
|
$columns = array_flip($this->getColumns()); |
3176
|
|
|
$defaults = array_intersect_key($defaults[$type], $columns); |
3177
|
|
|
foreach ( $defaults as $column => $def ) |
3178
|
|
|
{ |
3179
|
|
|
$record->{$column} = $record->{$column} ?? $def; |
3180
|
|
|
} |
3181
|
|
|
unset($record->active); |
3182
|
|
|
|
3183
|
|
|
return $record; |
3184
|
|
|
} |
3185
|
|
|
|
3186
|
|
|
|
3187
|
|
|
/** |
3188
|
|
|
* @return bool |
3189
|
|
|
*/ |
3190
|
|
|
public function createTable() : bool |
3191
|
|
|
{ |
3192
|
|
|
return true; |
3193
|
|
|
} |
3194
|
|
|
|
3195
|
|
|
/** |
3196
|
|
|
* @param bool|false $force |
3197
|
|
|
* @return FluentPdoModel|$this |
3198
|
|
|
* @throws Exception |
3199
|
|
|
*/ |
3200
|
|
|
public function dropTable(bool $force=false) : FluentPdoModel |
|
|
|
|
3201
|
|
|
{ |
3202
|
|
|
return $this; |
3203
|
|
|
} |
3204
|
|
|
|
3205
|
|
|
protected function compileHandlers() |
3206
|
|
|
{ |
3207
|
|
|
if ( $this->handlers ) |
|
|
|
|
3208
|
|
|
{ |
3209
|
|
|
return; |
3210
|
|
|
} |
3211
|
|
|
$parentHandlers = self::getFieldHandlers(); |
3212
|
|
|
$this->handlers = array_merge($parentHandlers, $this->getFieldHandlers()); |
3213
|
|
|
} |
3214
|
|
|
|
3215
|
|
|
/** |
3216
|
|
|
* @param string $viewName |
3217
|
|
|
* @param int $cacheTtl |
3218
|
|
|
* @return array |
3219
|
|
|
*/ |
3220
|
|
|
public function getViewColumns($viewName, $cacheTtl=self::CACHE_NO) |
3221
|
|
|
{ |
3222
|
|
|
return $this->getColumnsByTableFromDb($viewName, $cacheTtl); |
3223
|
|
|
} |
3224
|
|
|
|
3225
|
|
|
/** |
3226
|
|
|
* @param int $id |
3227
|
|
|
* @return string |
3228
|
|
|
*/ |
3229
|
|
|
public function getDisplayNameById(int $id) : string |
3230
|
|
|
{ |
3231
|
|
|
$displayColumn = $this->getDisplayColumn(); |
3232
|
|
|
$className = get_class($this); |
3233
|
|
|
Assert($displayColumn)->notEmpty("Could not determine the display column for model ({$className})"); |
3234
|
|
|
|
3235
|
|
|
return $this |
3236
|
|
|
->reset() |
3237
|
|
|
->fetchStr($displayColumn, $id, self::ONE_HOUR); |
3238
|
|
|
} |
3239
|
|
|
|
3240
|
|
|
/** |
3241
|
|
|
* @param int $id |
3242
|
|
|
* @param string $displayColumnValue |
3243
|
|
|
* @return bool |
3244
|
|
|
*/ |
3245
|
|
|
public function validIdDisplayNameCombo(int $id, $displayColumnValue) : bool |
3246
|
|
|
{ |
3247
|
|
|
return $displayColumnValue === $this->getDisplayNameById($id); |
3248
|
|
|
} |
3249
|
|
|
|
3250
|
|
|
/** |
3251
|
|
|
* @param array $toPopulate |
3252
|
|
|
* @return stdClass |
3253
|
|
|
*/ |
3254
|
|
|
protected function getEmptyObject(array $toPopulate=[]) : stdClass |
3255
|
|
|
{ |
3256
|
|
|
$toPopulate[] = 'id'; |
3257
|
|
|
|
3258
|
|
|
return (object)array_flip($toPopulate); |
3259
|
|
|
} |
3260
|
|
|
|
3261
|
|
|
/** |
3262
|
|
|
* @param array $toPopulate |
3263
|
|
|
* @return stdClass |
3264
|
|
|
*/ |
3265
|
|
|
protected static function emptyObject(array $toPopulate=[]) : stdClass |
3266
|
|
|
{ |
3267
|
|
|
$toPopulate[] = 'id'; |
3268
|
|
|
|
3269
|
|
|
return (object)array_flip($toPopulate); |
3270
|
|
|
} |
3271
|
|
|
|
3272
|
|
|
/** |
3273
|
|
|
* @param int $id |
3274
|
|
|
* @return bool |
3275
|
|
|
*/ |
3276
|
|
|
public static function isId(int $id) : bool |
3277
|
|
|
{ |
3278
|
|
|
return $id > 0; |
3279
|
|
|
} |
3280
|
|
|
|
3281
|
|
|
/** |
3282
|
|
|
* @param int $cacheTtl |
3283
|
|
|
* @return int |
3284
|
|
|
*/ |
3285
|
|
|
public function activeCount(int $cacheTtl=self::CACHE_NO) : int |
3286
|
|
|
{ |
3287
|
|
|
return (int)$this->whereActive()->count('*', $cacheTtl); |
3288
|
|
|
} |
3289
|
|
|
|
3290
|
|
|
/** |
3291
|
|
|
* @param string $tableAlias |
3292
|
|
|
* @param string $columnName |
3293
|
|
|
* @return FluentPdoModel|$this |
3294
|
|
|
*/ |
3295
|
|
|
public function whereActive(string $tableAlias='', string $columnName=self::STATUS_FIELD) : FluentPdoModel |
3296
|
|
|
{ |
3297
|
|
|
return $this->whereStatus(static::ACTIVE, $tableAlias, $columnName); |
3298
|
|
|
} |
3299
|
|
|
|
3300
|
|
|
/** |
3301
|
|
|
* @param string $tableAlias |
3302
|
|
|
* @param string $columnName |
3303
|
|
|
* @return FluentPdoModel|$this |
3304
|
|
|
*/ |
3305
|
|
|
public function whereInactive(string $tableAlias='', string $columnName=self::STATUS_FIELD) : FluentPdoModel |
3306
|
|
|
{ |
3307
|
|
|
return $this->whereStatus(static::INACTIVE, $tableAlias, $columnName); |
3308
|
|
|
} |
3309
|
|
|
|
3310
|
|
|
/** |
3311
|
|
|
* @param string $tableAlias |
3312
|
|
|
* @param string $columnName |
3313
|
|
|
* @return FluentPdoModel|$this |
3314
|
|
|
*/ |
3315
|
|
|
public function whereArchived(string $tableAlias='', string $columnName='status') : FluentPdoModel |
3316
|
|
|
{ |
3317
|
|
|
return $this->whereStatus(static::ARCHIVED, $tableAlias, $columnName); |
3318
|
|
|
} |
3319
|
|
|
|
3320
|
|
|
/** |
3321
|
|
|
* @param int $status |
3322
|
|
|
* @param string $tableAlias |
3323
|
|
|
* @param string $columnName |
3324
|
|
|
* @return FluentPdoModel|$this |
3325
|
|
|
*/ |
3326
|
|
|
public function whereStatus(int $status, string $tableAlias='', string $columnName=self::STATUS_FIELD) : FluentPdoModel |
3327
|
|
|
{ |
3328
|
|
|
Assert($status)->inArray([static::ACTIVE, static::INACTIVE, static::ARCHIVED]); |
3329
|
|
|
|
3330
|
|
|
$tableAlias = empty($tableAlias) ? $this->getTableAlias() : $tableAlias; |
3331
|
|
|
$field = empty($tableAlias) ? $columnName : "{$tableAlias}.{$columnName}"; |
3332
|
|
|
|
3333
|
|
|
return $this->where($field, $status); |
3334
|
|
|
} |
3335
|
|
|
|
3336
|
|
|
/** |
3337
|
|
|
* @param int $id |
3338
|
|
|
* @return int |
3339
|
|
|
*/ |
3340
|
|
|
public function updateActive(int $id=0) : int |
3341
|
|
|
{ |
3342
|
|
|
Assert($id)->unsignedInt(); |
3343
|
|
|
if ( $id ) |
3344
|
|
|
{ |
3345
|
|
|
$this->wherePk($id); |
3346
|
|
|
} |
3347
|
|
|
|
3348
|
|
|
return $this->updateStatus(static::ACTIVE); |
3349
|
|
|
} |
3350
|
|
|
|
3351
|
|
|
/** |
3352
|
|
|
* @param int $id |
3353
|
|
|
* @return int |
3354
|
|
|
*/ |
3355
|
|
|
public function updateInactive(int $id=0) : int |
3356
|
|
|
{ |
3357
|
|
|
Assert($id)->unsignedInt(); |
3358
|
|
|
if ( $id ) |
3359
|
|
|
{ |
3360
|
|
|
$this->wherePk($id); |
3361
|
|
|
} |
3362
|
|
|
return $this->updateStatus(static::INACTIVE); |
3363
|
|
|
} |
3364
|
|
|
|
3365
|
|
|
/** |
3366
|
|
|
* @param string $field |
3367
|
|
|
* @param int $id |
3368
|
|
|
* @return int |
3369
|
|
|
*/ |
3370
|
|
|
public function updateNow(string $field, int $id=0) : int |
3371
|
|
|
{ |
3372
|
|
|
Assert($field)->notEmpty(); |
3373
|
|
|
|
3374
|
|
|
return $this->updateField($field, date('Y-m-d H:i:s'), $id); |
3375
|
|
|
} |
3376
|
|
|
|
3377
|
|
|
/** |
3378
|
|
|
* @param string $field |
3379
|
|
|
* @param int $id |
3380
|
|
|
* @return int |
3381
|
|
|
*/ |
3382
|
|
|
public function updateToday($field, int $id=0) : int |
3383
|
|
|
{ |
3384
|
|
|
Assert($field)->notEmpty(); |
3385
|
|
|
|
3386
|
|
|
return $this->updateField($field, date('Y-m-d'), $id); |
3387
|
|
|
} |
3388
|
|
|
|
3389
|
|
|
/** |
3390
|
|
|
* @param int $id |
3391
|
|
|
* @return int |
3392
|
|
|
*/ |
3393
|
|
|
public function updateDeleted(int $id=0) : int |
3394
|
|
|
{ |
3395
|
|
|
Assert($id)->unsignedInt(); |
3396
|
|
|
if ( $id ) |
3397
|
|
|
{ |
3398
|
|
|
$this->wherePk($id); |
3399
|
|
|
} |
3400
|
|
|
|
3401
|
|
|
return $this->update((object)[ |
3402
|
|
|
self::DELETER_ID_FIELD => $this->getUserId(), |
3403
|
|
|
self::DELETED_TS_FIELD => static::dateTime(), |
3404
|
|
|
]); |
3405
|
|
|
} |
3406
|
|
|
|
3407
|
|
|
/** |
3408
|
|
|
* @param int $id |
3409
|
|
|
* @return int |
3410
|
|
|
*/ |
3411
|
|
|
public function updateArchived(int $id=0) : int |
3412
|
|
|
{ |
3413
|
|
|
Assert($id)->unsignedInt(); |
3414
|
|
|
if ( $id ) |
3415
|
|
|
{ |
3416
|
|
|
$this->wherePk($id); |
3417
|
|
|
} |
3418
|
|
|
|
3419
|
|
|
return $this->updateStatus(static::ARCHIVED); |
3420
|
|
|
} |
3421
|
|
|
|
3422
|
|
|
/** |
3423
|
|
|
* @param int $status |
3424
|
|
|
* @return int |
3425
|
|
|
* @throws \Exception |
3426
|
|
|
*/ |
3427
|
|
|
public function updateStatus(int $status) |
3428
|
|
|
{ |
3429
|
|
|
Assert($status)->inArray([static::ACTIVE, static::INACTIVE, static::ARCHIVED]); |
3430
|
|
|
|
3431
|
|
|
return $this->updateField('status', $status); |
3432
|
|
|
} |
3433
|
|
|
|
3434
|
|
|
/** |
3435
|
|
|
* Return a YYYY-MM-DD HH:II:SS date format |
3436
|
|
|
* |
3437
|
|
|
* @param string $datetime - An english textual datetime description |
3438
|
|
|
* now, yesterday, 3 days ago, +1 week |
3439
|
|
|
* http://php.net/manual/en/function.strtotime.php |
3440
|
|
|
* @return string YYYY-MM-DD HH:II:SS |
3441
|
|
|
*/ |
3442
|
|
|
public static function NOW(string $datetime='now') : string |
3443
|
|
|
{ |
3444
|
|
|
return (new DateTime($datetime ?: 'now'))->format('Y-m-d H:i:s'); |
3445
|
|
|
} |
3446
|
|
|
|
3447
|
|
|
/** |
3448
|
|
|
* Return a string containing the given number of question marks, |
3449
|
|
|
* separated by commas. Eg '?, ?, ?' |
3450
|
|
|
* |
3451
|
|
|
* @param int - total of placeholder to insert |
3452
|
|
|
* @return string |
3453
|
|
|
*/ |
3454
|
|
|
protected function makePlaceholders(int $numberOfPlaceholders=1) : string |
3455
|
|
|
{ |
3456
|
|
|
return implode(', ', array_fill(0, $numberOfPlaceholders, '?')); |
3457
|
|
|
} |
3458
|
|
|
|
3459
|
|
|
/** |
3460
|
|
|
* Format the table{Primary|Foreign}KeyName |
3461
|
|
|
* |
3462
|
|
|
* @param string $pattern |
3463
|
|
|
* @param string $tableName |
3464
|
|
|
* @return string |
3465
|
|
|
*/ |
3466
|
|
|
protected function formatKeyName(string $pattern, string $tableName) : string |
3467
|
|
|
{ |
3468
|
|
|
return sprintf($pattern, $tableName); |
3469
|
|
|
} |
3470
|
|
|
|
3471
|
|
|
/** |
3472
|
|
|
* @param array $query |
3473
|
|
|
* @param array $extraFields |
3474
|
|
|
* @return array |
3475
|
|
|
* @throws \Exception |
3476
|
|
|
*/ |
3477
|
|
|
protected function prepareApiResource(array $query=[], array $extraFields=[]) : array |
3478
|
|
|
{ |
3479
|
|
|
$this->defaultFilters()->filter($query)->paginate($query); |
3480
|
|
|
$pagingMetaData = $this->getPagingMeta(); |
3481
|
|
|
if ( $pagingMetaData['total'] === 0 ) |
3482
|
|
|
{ |
3483
|
|
|
return $pagingMetaData; |
3484
|
|
|
} |
3485
|
|
|
$this->withBelongsTo($pagingMetaData['fields']); |
3486
|
|
|
if ( ! empty($extraFields) ) |
3487
|
|
|
{ |
3488
|
|
|
$this->select($extraFields, '', false); |
3489
|
|
|
} |
3490
|
|
|
$this->removeUnauthorisedFields(); |
|
|
|
|
3491
|
|
|
if ( ! empty($pagingMetaData['fields']) ) |
3492
|
|
|
{ |
3493
|
|
|
$this->removeUnrequestedFields($pagingMetaData['fields']); |
3494
|
|
|
} |
3495
|
|
|
|
3496
|
|
|
return $pagingMetaData; |
3497
|
|
|
} |
3498
|
|
|
|
3499
|
|
|
/** |
3500
|
|
|
* @param string $query |
3501
|
|
|
* @param array $parameters |
3502
|
|
|
* |
3503
|
|
|
* @return array |
3504
|
|
|
*/ |
3505
|
|
|
protected function logQuery(string $query, array $parameters) : array |
3506
|
|
|
{ |
3507
|
|
|
$query = $this->buildQuery($query, $parameters); |
3508
|
|
|
if ( ! $this->logQueries ) |
3509
|
|
|
{ |
3510
|
|
|
return ['', '']; |
3511
|
|
|
} |
3512
|
|
|
$ident = substr(str_shuffle(md5($query)), 0, 10); |
3513
|
|
|
$this->getLogger()->debug($ident . ': ' . PHP_EOL . $query); |
3514
|
|
|
$this->timer['start'] = microtime(true); |
3515
|
|
|
|
3516
|
|
|
return [$query, $ident]; |
3517
|
|
|
} |
3518
|
|
|
|
3519
|
|
|
/** |
3520
|
|
|
* @param string $ident |
3521
|
|
|
* @param string $builtQuery |
3522
|
|
|
*/ |
3523
|
|
|
protected function logSlowQueries(string $ident, string $builtQuery) |
3524
|
|
|
{ |
3525
|
|
|
if ( ! $this->logQueries ) |
3526
|
|
|
{ |
3527
|
|
|
return ; |
3528
|
|
|
} |
3529
|
|
|
$this->timer['end'] = microtime(true); |
3530
|
|
|
$secondsTaken = round($this->timer['end'] - $this->timer['start'], 3); |
3531
|
|
|
if ( $secondsTaken > $this->slowQuerySecs ) |
3532
|
|
|
{ |
3533
|
|
|
$this->getLogger()->warning("SLOW QUERY - {$ident} - {$secondsTaken} seconds:\n{$builtQuery}"); |
3534
|
|
|
} |
3535
|
|
|
} |
3536
|
|
|
|
3537
|
|
|
/** |
3538
|
|
|
* @return float |
3539
|
|
|
*/ |
3540
|
|
|
public function getTimeTaken() : float |
3541
|
|
|
{ |
3542
|
|
|
$secondsTaken = $this->timer['end'] - $this->timer['start']; |
3543
|
|
|
|
3544
|
|
|
return (float)$secondsTaken; |
3545
|
|
|
} |
3546
|
|
|
|
3547
|
|
|
/** |
3548
|
|
|
* @param $secs |
3549
|
|
|
* @return FluentPdoModel|$this |
3550
|
|
|
*/ |
3551
|
|
|
public function slowQuerySeconds(int $secs) : FluentPdoModel |
3552
|
|
|
{ |
3553
|
|
|
Assert($secs)->notEmpty("Seconds cannot be empty.")->numeric("Seconds must be numeric."); |
3554
|
|
|
$this->slowQuerySecs = $secs; |
3555
|
|
|
|
3556
|
|
|
return $this; |
3557
|
|
|
} |
3558
|
|
|
|
3559
|
|
|
|
3560
|
|
|
/** |
3561
|
|
|
* @param $field |
3562
|
|
|
* @param array $values |
3563
|
|
|
* @param string $placeholderPrefix |
3564
|
|
|
* |
3565
|
|
|
* @return array |
3566
|
|
|
*/ |
3567
|
|
|
public function getNamedWhereIn(string $field, array $values, string $placeholderPrefix='') : array |
3568
|
|
|
{ |
3569
|
|
|
Assert($field)->string()->notEmpty(); |
3570
|
|
|
Assert($values)->isArray(); |
3571
|
|
|
|
3572
|
|
|
if ( empty($values) ) |
3573
|
|
|
{ |
3574
|
|
|
return ['', []]; |
3575
|
|
|
} |
3576
|
|
|
$placeholderPrefix = $placeholderPrefix ?: strtolower(str_replace('.', '__', $field)); |
3577
|
|
|
$params = []; |
3578
|
|
|
$placeholders = []; |
3579
|
|
|
$count = 1; |
3580
|
|
|
foreach ( $values as $val ) |
3581
|
|
|
{ |
3582
|
|
|
$name = "{$placeholderPrefix}_{$count}"; |
3583
|
|
|
$params[$name] = $val; |
3584
|
|
|
$placeholders[] = ":{$name}"; |
3585
|
|
|
$count++; |
3586
|
|
|
} |
3587
|
|
|
$placeholders = implode(',', $placeholders); |
3588
|
|
|
|
3589
|
|
|
return ["AND {$field} IN ({$placeholders})\n", $params]; |
3590
|
|
|
} |
3591
|
|
|
|
3592
|
|
|
/** |
3593
|
|
|
* @param string $field |
3594
|
|
|
* @param string $delimiter |
3595
|
|
|
* |
3596
|
|
|
* @return array |
3597
|
|
|
*/ |
3598
|
|
|
protected function getColumnAliasParts(string $field, string $delimiter=':') : array |
3599
|
|
|
{ |
3600
|
|
|
$parts = explode($delimiter, $field); |
3601
|
|
|
if ( count($parts) === 2 ) |
3602
|
|
|
{ |
3603
|
|
|
return $parts; |
3604
|
|
|
} |
3605
|
|
|
$parts = explode('.', $field); |
3606
|
|
|
if ( count($parts) === 2 ) |
3607
|
|
|
{ |
3608
|
|
|
return $parts; |
3609
|
|
|
} |
3610
|
|
|
|
3611
|
|
|
return ['', $field]; |
3612
|
|
|
} |
3613
|
|
|
|
3614
|
|
|
/** |
3615
|
|
|
* @param string $column |
3616
|
|
|
* @param string $term |
3617
|
|
|
* @return FluentPdoModel|$this |
3618
|
|
|
*/ |
3619
|
|
|
protected function addWhereClause(string $column, string $term) : FluentPdoModel |
3620
|
|
|
{ |
3621
|
|
|
/* |
3622
|
|
|
|
3623
|
|
|
whereLike i.e ?name=whereLike(%terry%) |
3624
|
|
|
whereNotLike i.e ?name=whereNotLike(%terry%) |
3625
|
|
|
whereLt i.e ?age=whereLt(18) |
3626
|
|
|
whereLte i.e ?age=whereLte(18) |
3627
|
|
|
whereGt i.e ?event_dt=whereGt(2014-10-10) |
3628
|
|
|
whereGte i.e ?event_dt=whereGte(2014-10-10) |
3629
|
|
|
whereBetween i.e ?event_dt=whereBetween(2014-10-10,2014-10-15) |
3630
|
|
|
whereNotBetween i.e ?event_dt=whereNotBetween(2014-10-10,2014-10-15) |
3631
|
|
|
|
3632
|
|
|
*/ |
3633
|
|
|
list ($func, $matches) = $this->parseWhereClause($term); |
3634
|
|
|
switch ($func) |
3635
|
|
|
{ |
3636
|
|
|
case 'whereLike': |
3637
|
|
|
|
3638
|
|
|
return $this->whereLike($column, $matches[0]); |
3639
|
|
|
|
3640
|
|
|
case 'whereNotLike': |
3641
|
|
|
|
3642
|
|
|
return $this->whereNotLike($column, $matches[0]); |
3643
|
|
|
|
3644
|
|
|
case 'whereLt': |
3645
|
|
|
|
3646
|
|
|
return $this->whereLt($column, $matches[0]); |
3647
|
|
|
|
3648
|
|
|
case 'whereLte': |
3649
|
|
|
|
3650
|
|
|
return $this->whereLte($column, $matches[0]); |
3651
|
|
|
|
3652
|
|
|
case 'whereGt': |
3653
|
|
|
|
3654
|
|
|
return $this->whereGt($column, $matches[0]); |
3655
|
|
|
|
3656
|
|
|
case 'whereGte': |
3657
|
|
|
|
3658
|
|
|
return $this->whereGte($column, $matches[0]); |
3659
|
|
|
|
3660
|
|
|
case 'whereBetween': |
3661
|
|
|
|
3662
|
|
|
return $this->whereBetween($column, $matches[0], $matches[1]); |
3663
|
|
|
|
3664
|
|
|
case 'whereNotBetween': |
3665
|
|
|
|
3666
|
|
|
return $this->whereNotBetween($column, $matches[0], $matches[1]); |
3667
|
|
|
} |
3668
|
|
|
|
3669
|
|
|
return $this->where($column, $term); |
3670
|
|
|
} |
3671
|
|
|
|
3672
|
|
|
/** |
3673
|
|
|
* @param string $term |
3674
|
|
|
* @return array |
3675
|
|
|
*/ |
3676
|
|
|
public function parseWhereClause(string $term) : array |
3677
|
|
|
{ |
3678
|
|
|
$modifiers = [ |
3679
|
|
|
'whereLike' => '/^whereLike\(([%]?[ a-z0-9:-]+[%]?)\)$/i', |
3680
|
|
|
'whereNotLike' => '/^whereNotLike\(([%]?[ a-z0-9:-]+[%]?)\)$/i', |
3681
|
|
|
'whereLt' => '/^whereLt\(([ a-z0-9:-]+)\)$/i', |
3682
|
|
|
'whereLte' => '/^whereLte\(([ a-z0-9:-]+)\)$/i', |
3683
|
|
|
'whereGt' => '/^whereGt\(([ a-z0-9:-]+)\)$/i', |
3684
|
|
|
'whereGte' => '/^whereGte\(([ a-z0-9:-]+)\)$/i', |
3685
|
|
|
'whereBetween' => '/^whereBetween\(([ a-z0-9:-]+),([ a-z0-9:-]+)\)$/i', |
3686
|
|
|
'whereNotBetween' => '/^whereNotBetween\(([ a-z0-9:-]+),([ a-z0-9:-]+)\)$/i', |
3687
|
|
|
]; |
3688
|
|
|
|
3689
|
|
|
foreach ( $modifiers as $func => $regex ) |
3690
|
|
|
{ |
3691
|
|
|
if ( preg_match($regex, $term, $matches) ) |
3692
|
|
|
{ |
3693
|
|
|
array_shift($matches); |
3694
|
|
|
|
3695
|
|
|
return [$func, $matches]; |
3696
|
|
|
} |
3697
|
|
|
} |
3698
|
|
|
|
3699
|
|
|
return ['', []]; |
3700
|
|
|
} |
3701
|
|
|
|
3702
|
|
|
public function destroy() |
3703
|
|
|
{ |
3704
|
|
|
if ( !is_null($this->pdoStmt) ) |
3705
|
|
|
{ |
3706
|
|
|
$this->pdoStmt->closeCursor(); |
3707
|
|
|
} |
3708
|
|
|
$this->pdoStmt = null; |
3709
|
|
|
$this->handlers = []; |
3710
|
|
|
} |
3711
|
|
|
|
3712
|
|
|
public function __destruct() |
3713
|
|
|
{ |
3714
|
|
|
$this->destroy(); |
3715
|
|
|
} |
3716
|
|
|
|
3717
|
|
|
/** |
3718
|
|
|
* Load a model |
3719
|
|
|
* |
3720
|
|
|
* @param string $modelName |
3721
|
|
|
* @param AbstractPdo $connection |
3722
|
|
|
* @return FluentPdoModel|$this |
3723
|
|
|
* @throws ModelNotFoundException |
3724
|
|
|
*/ |
3725
|
|
|
public static function loadModel(string $modelName, AbstractPdo $connection=null) : FluentPdoModel |
3726
|
|
|
{ |
3727
|
|
|
$modelName = static::$modelNamespace . $modelName; |
3728
|
|
|
if ( ! class_exists($modelName) ) |
3729
|
|
|
{ |
3730
|
|
|
throw new ModelNotFoundException("Failed to find model class {$modelName}."); |
3731
|
|
|
} |
3732
|
|
|
|
3733
|
|
|
return new $modelName($connection); |
3734
|
|
|
} |
3735
|
|
|
|
3736
|
|
|
/** |
3737
|
|
|
* Load a model |
3738
|
|
|
* |
3739
|
|
|
* @param string $tableName |
3740
|
|
|
* @param AbstractPdo $connection |
3741
|
|
|
* @return FluentPdoModel|$this |
3742
|
|
|
*/ |
3743
|
|
|
public static function loadTable(string $tableName, AbstractPdo $connection=null) : FluentPdoModel |
3744
|
|
|
{ |
3745
|
|
|
$modelName = Inflector::classify($tableName); |
3746
|
|
|
Assert($modelName)->notEmpty("Could not resolve model name from table name."); |
3747
|
|
|
|
3748
|
|
|
return static::loadModel($modelName, $connection); |
3749
|
|
|
} |
3750
|
|
|
|
3751
|
|
|
/** |
3752
|
|
|
* @param string $columnName |
3753
|
|
|
* @param int $cacheTtl |
3754
|
|
|
* @param bool $flushCache |
3755
|
|
|
* @return bool |
3756
|
|
|
*/ |
3757
|
|
|
public function columnExists(string $columnName, int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : bool |
3758
|
|
|
{ |
3759
|
|
|
$columns = $this->getSchemaFromDb($cacheTtl, $flushCache); |
3760
|
|
|
|
3761
|
|
|
return array_key_exists($columnName, $columns); |
3762
|
|
|
} |
3763
|
|
|
|
3764
|
|
|
/** |
3765
|
|
|
* @param string $foreignKeyName |
3766
|
|
|
* @param int $cacheTtl |
3767
|
|
|
* @param bool $flushCache |
3768
|
|
|
* @return bool |
3769
|
|
|
*/ |
3770
|
|
|
public function foreignKeyExists(string $foreignKeyName, int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : bool |
3771
|
|
|
{ |
3772
|
|
|
$columns = $this->getSchemaFromDb($cacheTtl, $flushCache); |
3773
|
|
|
|
3774
|
|
|
return array_key_exists($foreignKeyName, $columns); |
3775
|
|
|
} |
3776
|
|
|
|
3777
|
|
|
/** |
3778
|
|
|
* @param string $indexName |
3779
|
|
|
* @param int $cacheTtl |
3780
|
|
|
* @param bool $flushCache |
3781
|
|
|
* @return bool |
3782
|
|
|
*/ |
3783
|
|
|
public function indexExists(string $indexName, int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : bool |
3784
|
|
|
{ |
3785
|
|
|
Assert($indexName)->string()->notEmpty(); |
3786
|
|
|
|
3787
|
|
|
$callback = function() use ($indexName) { |
3788
|
|
|
|
3789
|
|
|
$index = $this->execute("SHOW INDEX FROM {$this->tableName} WHERE Key_name = ':indexName'", compact('indexName')); |
3790
|
|
|
|
3791
|
|
|
return $index ? true : false; |
3792
|
|
|
}; |
3793
|
|
|
if ( $cacheTtl === self::CACHE_NO ) |
3794
|
|
|
{ |
3795
|
|
|
return $callback(); |
3796
|
|
|
} |
3797
|
|
|
$cacheKey = '/column_schema/' . $this->tableName . '/index/' . $indexName; |
3798
|
|
|
if ( $flushCache === true ) |
3799
|
|
|
{ |
3800
|
|
|
$this->clearCache($cacheKey); |
3801
|
|
|
} |
3802
|
|
|
|
3803
|
|
|
return (bool)$this->cacheData($cacheKey, $callback, $cacheTtl); |
3804
|
|
|
} |
3805
|
|
|
|
3806
|
|
|
|
3807
|
|
|
|
3808
|
|
|
/** |
3809
|
|
|
* @param int $cacheTtl |
3810
|
|
|
* @param bool $flushCache |
3811
|
|
|
* @return FluentPdoModel|$this |
3812
|
|
|
*/ |
3813
|
|
|
public function loadSchemaFromDb(int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : FluentPdoModel |
3814
|
|
|
{ |
3815
|
|
|
$schema = $this->getSchemaFromDb($cacheTtl, $flushCache); |
3816
|
|
|
$this->schema($schema); |
3817
|
|
|
|
3818
|
|
|
return $this; |
3819
|
|
|
} |
3820
|
|
|
|
3821
|
|
|
/** |
3822
|
|
|
* @param int $cacheTtl |
3823
|
|
|
* @param bool $flushCache |
3824
|
|
|
* @return Column[][] |
3825
|
|
|
*/ |
3826
|
|
|
public function getSchemaFromDb(int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : array |
3827
|
|
|
{ |
3828
|
|
|
$table = $this->getTableName(); |
3829
|
|
|
Assert($table)->string()->notEmpty(); |
3830
|
|
|
$schema = []; |
3831
|
|
|
$columns = $this->getColumnsByTableFromDb($table, $cacheTtl, $flushCache); |
3832
|
|
|
foreach ( $columns[$table] as $column => $meta ) |
3833
|
|
|
{ |
3834
|
|
|
/** Column $meta */ |
3835
|
|
|
$schema[$column] = $meta->dataType; |
3836
|
|
|
} |
3837
|
|
|
|
3838
|
|
|
return $schema; |
3839
|
|
|
} |
3840
|
|
|
|
3841
|
|
|
/** |
3842
|
|
|
* @param int $cacheTtl |
3843
|
|
|
* @param bool $flushCache |
3844
|
|
|
* @return array |
3845
|
|
|
*/ |
3846
|
|
|
public function getForeignKeysFromDb(int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : array |
3847
|
|
|
{ |
3848
|
|
|
$table = $this->getTableName(); |
3849
|
|
|
Assert($table)->string()->notEmpty(); |
3850
|
|
|
$schema = []; |
3851
|
|
|
$foreignKeys = $this->getForeignKeysByTableFromDb($table, $cacheTtl, $flushCache); |
3852
|
|
|
foreach ( $foreignKeys[$table] as $key => $meta ) |
3853
|
|
|
{ |
3854
|
|
|
$schema[$key] = $meta->dataType; |
3855
|
|
|
} |
3856
|
|
|
|
3857
|
|
|
return $schema; |
3858
|
|
|
} |
3859
|
|
|
|
3860
|
|
|
/** |
3861
|
|
|
* @param string $table |
3862
|
|
|
* @param int $cacheTtl |
3863
|
|
|
* @param bool $flushCache |
3864
|
|
|
* @return Column[][] |
3865
|
|
|
*/ |
3866
|
|
|
protected function getColumnsByTableFromDb(string $table, int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : array |
3867
|
|
|
{ |
3868
|
|
|
Assert($table)->string()->notEmpty(); |
3869
|
|
|
|
3870
|
|
|
$callback = function() use ($table) { |
3871
|
|
|
|
3872
|
|
|
return $this->connection->getColumns(true, $table); |
3873
|
|
|
}; |
3874
|
|
|
$cacheKey = '/column_schema/' . $table; |
3875
|
|
|
if ( $flushCache === true ) |
3876
|
|
|
{ |
3877
|
|
|
$this->clearCache($cacheKey); |
3878
|
|
|
} |
3879
|
|
|
|
3880
|
|
|
return (array)$this->cacheData($cacheKey, $callback, $cacheTtl); |
3881
|
|
|
} |
3882
|
|
|
|
3883
|
|
|
/** |
3884
|
|
|
* @param string $table |
3885
|
|
|
* @param int $cacheTtl |
3886
|
|
|
* @param bool $flushCache |
3887
|
|
|
* @return Column[][] |
3888
|
|
|
*/ |
3889
|
|
|
protected function getForeignKeysByTableFromDb(string $table, int $cacheTtl=self::CACHE_NO, bool $flushCache=false) : array |
3890
|
|
|
{ |
3891
|
|
|
Assert($table)->string()->notEmpty(); |
3892
|
|
|
|
3893
|
|
|
$callback = function() use ($table) { |
3894
|
|
|
|
3895
|
|
|
return $this->connection->getForeignKeys($table); |
3896
|
|
|
}; |
3897
|
|
|
$cacheKey = '/foreign_keys_schema/' . $table; |
3898
|
|
|
if ( $flushCache === true ) |
3899
|
|
|
{ |
3900
|
|
|
$this->clearCache($cacheKey); |
3901
|
|
|
} |
3902
|
|
|
|
3903
|
|
|
return (array)$this->cacheData($cacheKey, $callback, $cacheTtl); |
3904
|
|
|
} |
3905
|
|
|
|
3906
|
|
|
/** |
3907
|
|
|
* @param string $table |
3908
|
|
|
* @return bool |
3909
|
|
|
*/ |
3910
|
|
|
public function clearSchemaCache(string $table) : bool |
3911
|
|
|
{ |
3912
|
|
|
return $this->clearCache('/column_schema/' . $table); |
3913
|
|
|
} |
3914
|
|
|
|
3915
|
|
|
/** |
3916
|
|
|
* @param stdClass $record |
3917
|
|
|
* @return stdClass |
3918
|
|
|
*/ |
3919
|
|
|
public function cleanseRecord(stdClass $record) : stdClass |
3920
|
|
|
{ |
3921
|
|
|
foreach ( $record as $field => $value ) |
|
|
|
|
3922
|
|
|
{ |
3923
|
|
|
if ( is_string($record->{$field}) ) |
3924
|
|
|
{ |
3925
|
|
|
$sanitised = filter_var($record->{$field}, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); |
3926
|
|
|
$record->{$field} = str_replace(["\r\n", "\\r\\n", "\\n"], "\n", $sanitised); |
3927
|
|
|
if ( $this->logFilterChanges && $value !== $record->{$field} ) |
3928
|
|
|
{ |
3929
|
|
|
$table = $this->tableName ?: ''; |
3930
|
|
|
$this->getLogger()->debug("Field {$table}.{$field} has been cleansed", ['old' => $value, 'new' => $record->{$field}]); |
3931
|
|
|
} |
3932
|
|
|
} |
3933
|
|
|
} |
3934
|
|
|
|
3935
|
|
|
return $record; |
3936
|
|
|
} |
3937
|
|
|
|
3938
|
|
|
/** |
3939
|
|
|
* @param stdClass $record |
3940
|
|
|
* @param string $type |
3941
|
|
|
* @return stdClass |
3942
|
|
|
*/ |
3943
|
|
|
public function beforeSave(stdClass $record, string $type) : stdClass |
3944
|
|
|
{ |
3945
|
|
|
$record = $this->addDefaultFields($record, $type); |
3946
|
|
|
$record = $this->applyGlobalModifiers($record, $type); |
3947
|
|
|
$record = $this->applyHandlers($record, $type); |
3948
|
|
|
$record = $this->removeUnneededFields($record, $type); |
3949
|
|
|
|
3950
|
|
|
return $record; |
3951
|
|
|
} |
3952
|
|
|
|
3953
|
|
|
/** |
3954
|
|
|
* @param array $data |
3955
|
|
|
* @param string $saveType |
3956
|
|
|
* @return array |
3957
|
|
|
*/ |
3958
|
|
|
public function cleanseWebData(array $data, string $saveType) : array |
3959
|
|
|
{ |
3960
|
|
|
Assert($saveType)->inArray([self::SAVE_UPDATE, self::SAVE_INSERT]); |
3961
|
|
|
$columns = $this->getColumns(false); |
3962
|
|
|
if ( empty($columns) ) |
3963
|
|
|
{ |
3964
|
|
|
return $data; |
3965
|
|
|
} |
3966
|
|
|
foreach ( $data as $field => $val ) |
3967
|
|
|
{ |
3968
|
|
|
$data[$field] = empty($val) && $val !== 0 ? null : $val; |
3969
|
|
|
} |
3970
|
|
|
|
3971
|
|
|
return array_intersect_key($data, $columns); |
3972
|
|
|
} |
3973
|
|
|
|
3974
|
|
|
/** |
3975
|
|
|
* @return array |
3976
|
|
|
*/ |
3977
|
|
|
public function skeleton() : array |
3978
|
|
|
{ |
3979
|
|
|
$skel = []; |
3980
|
|
|
$columns = $this->getColumns(false); |
3981
|
|
|
foreach ( $columns as $column => $type ) |
3982
|
|
|
{ |
3983
|
|
|
$skel[$column] = null; |
3984
|
|
|
} |
3985
|
|
|
|
3986
|
|
|
return $skel; |
3987
|
|
|
} |
3988
|
|
|
|
3989
|
|
|
/** |
3990
|
|
|
* @param bool $toString |
3991
|
|
|
* @return array |
3992
|
|
|
*/ |
3993
|
|
|
public function getErrors(bool $toString=false) : array |
3994
|
|
|
{ |
3995
|
|
|
if ( ! $toString ) |
3996
|
|
|
{ |
3997
|
|
|
return $this->errors; |
3998
|
|
|
} |
3999
|
|
|
$errors = []; |
4000
|
|
|
foreach ( $this->errors as $field => $error ) |
4001
|
|
|
{ |
4002
|
|
|
$errors[] = implode("\n", $error); |
4003
|
|
|
} |
4004
|
|
|
|
4005
|
|
|
return implode("\n", $errors); |
4006
|
|
|
} |
4007
|
|
|
|
4008
|
|
|
/** |
4009
|
|
|
* @param bool $throw |
4010
|
|
|
* @return FluentPdoModel|$this |
4011
|
|
|
*/ |
4012
|
|
|
public function validationExceptions(bool $throw=true) : FluentPdoModel |
4013
|
|
|
{ |
4014
|
|
|
$this->validationExceptions = $throw; |
4015
|
|
|
|
4016
|
|
|
return $this; |
4017
|
|
|
} |
4018
|
|
|
|
4019
|
|
|
/** |
4020
|
|
|
* @param array $query array('_limit' => int, '_offset' => int, '_order' => string, '_fields' => string, _search) |
4021
|
|
|
* |
4022
|
|
|
* @return FluentPdoModel|$this |
4023
|
|
|
* @throws Exception |
4024
|
|
|
*/ |
4025
|
|
|
public function paginate(array $query=[]) : FluentPdoModel |
4026
|
|
|
{ |
4027
|
|
|
$_fields = null; |
4028
|
|
|
$_order = null; |
4029
|
|
|
$_limit = null; |
4030
|
|
|
$_offset = null; |
4031
|
|
|
extract($query); |
4032
|
|
|
$this->setLimit((int)$_limit, (int)$_offset); |
4033
|
|
|
$this->setOrderBy((string)$_order); |
4034
|
|
|
$_fields = is_array($_fields) ? $_fields : (string)$_fields; |
4035
|
|
|
$_fields = empty($_fields) ? [] : $_fields; |
4036
|
|
|
$_fields = is_string($_fields) ? explode('|', $_fields) : $_fields; |
4037
|
|
|
$_fields = empty($_fields) ? [] : $_fields; |
4038
|
|
|
$this->setFields(is_array($_fields) ? $_fields : explode('|', (string)$_fields)); |
4039
|
|
|
|
4040
|
|
|
return $this; |
4041
|
|
|
} |
4042
|
|
|
|
4043
|
|
|
/** |
4044
|
|
|
* @param int $limit |
4045
|
|
|
* @param int $offset |
4046
|
|
|
* @return FluentPdoModel|$this |
4047
|
|
|
*/ |
4048
|
|
|
protected function setLimit(int $limit=0, int $offset=0) : FluentPdoModel |
4049
|
|
|
{ |
4050
|
|
|
$limit = ! $limit || (int)$limit > (int)$this->defaultMax ? (int)$this->defaultMax : (int)$limit; |
4051
|
|
|
if ( ! is_numeric($limit) ) |
4052
|
|
|
{ |
4053
|
|
|
return $this; |
4054
|
|
|
} |
4055
|
|
|
$this->limit((int)$limit); |
4056
|
|
|
if ( $offset && is_numeric($offset) ) |
4057
|
|
|
{ |
4058
|
|
|
$this->offset((int)$offset); |
4059
|
|
|
} |
4060
|
|
|
|
4061
|
|
|
return $this; |
4062
|
|
|
} |
4063
|
|
|
|
4064
|
|
|
/** |
4065
|
|
|
* @param array $fields |
4066
|
|
|
* @return FluentPdoModel|$this |
4067
|
|
|
* @throws Exception |
4068
|
|
|
*/ |
4069
|
|
|
protected function setFields(array $fields=[]) : FluentPdoModel |
4070
|
|
|
{ |
4071
|
|
|
if ( ! $fields ) |
|
|
|
|
4072
|
|
|
{ |
4073
|
|
|
return $this; |
4074
|
|
|
} |
4075
|
|
|
$this->explicitSelectMode(); |
4076
|
|
|
$columns = $this->getColumns(); |
4077
|
|
|
|
4078
|
|
|
foreach ( $fields as $idx => $field ) |
4079
|
|
|
{ |
4080
|
|
|
list($alias, $field) = $this->getColumnAliasParts($field); |
4081
|
|
|
$field = $field === '_display_field' ? $this->displayColumn : $field; |
4082
|
|
|
// Regular primary table field |
4083
|
|
|
if ( ( empty($alias) || $alias === $this->tableAlias ) && in_array($field, $columns) ) |
4084
|
|
|
{ |
4085
|
|
|
$this->select("{$this->tableAlias}.{$field}"); |
4086
|
|
|
$this->requestedFields[] = "{$this->tableAlias}.{$field}"; |
4087
|
|
|
|
4088
|
|
|
continue; |
4089
|
|
|
} |
4090
|
|
|
// Reference table field with alias |
4091
|
|
|
if ( ! empty($alias) ) |
4092
|
|
|
{ |
4093
|
|
|
Assert($this->associations['belongsTo'])->keyExists($alias, "Invalid table alias ({$alias}) specified for the field query"); |
4094
|
|
|
Assert($field)->eq($this->associations['belongsTo'][$alias][3], "Invalid field ({$alias}.{$field}) specified for the field query"); |
4095
|
|
|
list(, , $joinField, $fieldAlias) = $this->associations['belongsTo'][$alias]; |
4096
|
|
|
$this->autoJoin($alias, static::LEFT_JOIN, false); |
4097
|
|
|
$this->select($joinField, $fieldAlias); |
4098
|
|
|
$this->requestedFields[] = $fieldAlias; |
4099
|
|
|
|
4100
|
|
|
continue; |
4101
|
|
|
} |
4102
|
|
|
// Reference table select field without alias |
4103
|
|
|
$belongsTo = array_key_exists('belongsTo', $this->associations) ? $this->associations['belongsTo'] : []; |
4104
|
|
|
foreach ( $belongsTo as $joinAlias => $config ) |
4105
|
|
|
{ |
4106
|
|
|
list(, , $joinField, $fieldAlias) = $config; |
4107
|
|
|
if ( $field === $fieldAlias ) |
4108
|
|
|
{ |
4109
|
|
|
$this->autoJoin($joinAlias, static::LEFT_JOIN, false); |
4110
|
|
|
$this->select($joinField, $fieldAlias); |
4111
|
|
|
$this->requestedFields[] = $fieldAlias; |
4112
|
|
|
|
4113
|
|
|
continue; |
4114
|
|
|
} |
4115
|
|
|
} |
4116
|
|
|
} |
4117
|
|
|
|
4118
|
|
|
return $this; |
4119
|
|
|
} |
4120
|
|
|
|
4121
|
|
|
/** |
4122
|
|
|
* @param string $orderBy |
4123
|
|
|
* @return FluentPdoModel|$this|FluentPdoModel |
4124
|
|
|
*/ |
4125
|
|
|
protected function setOrderBy(string $orderBy='') : FluentPdoModel |
4126
|
|
|
{ |
4127
|
|
|
if ( ! $orderBy ) |
4128
|
|
|
{ |
4129
|
|
|
return $this; |
4130
|
|
|
} |
4131
|
|
|
$columns = $this->getColumns(); |
4132
|
|
|
list($order, $direction)= strpos($orderBy, ',') !== false ? explode(',', $orderBy) : [$orderBy, 'ASC']; |
4133
|
|
|
list($alias, $field) = $this->getColumnAliasParts(trim($order), '.'); |
4134
|
|
|
$field = explode(' ', $field); |
4135
|
|
|
$field = trim($field[0]); |
4136
|
|
|
$direction = ! in_array(strtoupper(trim($direction)), ['ASC', 'DESC']) ? 'ASC' : strtoupper(trim($direction)); |
4137
|
|
|
$belongsTo = array_key_exists('belongsTo', $this->associations) ? $this->associations['belongsTo'] : []; |
4138
|
|
|
// Regular primary table order by |
4139
|
|
|
if ( ( empty($alias) || $alias === $this->tableAlias ) && in_array($field, $columns) ) |
4140
|
|
|
{ |
4141
|
|
|
return $this->orderBy("{$this->tableAlias}.{$field}", $direction); |
4142
|
|
|
} |
4143
|
|
|
// Reference table order by with alias |
4144
|
|
|
if ( ! empty($alias) ) |
4145
|
|
|
{ |
4146
|
|
|
Assert($belongsTo)->keyExists($alias, "Invalid table alias ({$alias}) specified for the order query"); |
4147
|
|
|
Assert($field)->eq($belongsTo[$alias][3], "Invalid field ({$alias}.{$field}) specified for the order query"); |
4148
|
|
|
|
4149
|
|
|
return $this->autoJoin($alias)->orderBy("{$alias}.{$field}", $direction); |
4150
|
|
|
} |
4151
|
|
|
// Reference table order by without alias |
4152
|
|
|
foreach ( $belongsTo as $joinAlias => $config ) |
4153
|
|
|
{ |
4154
|
|
|
if ( $field === $config[3] ) |
4155
|
|
|
{ |
4156
|
|
|
return $this->autoJoin($joinAlias)->orderBy($config[2], $direction); |
4157
|
|
|
} |
4158
|
|
|
} |
4159
|
|
|
|
4160
|
|
|
return $this; |
4161
|
|
|
} |
4162
|
|
|
|
4163
|
|
|
/** |
4164
|
|
|
* @return array |
4165
|
|
|
*/ |
4166
|
|
|
public function getPagingMeta() |
4167
|
|
|
{ |
4168
|
|
|
if ( empty($this->pagingMeta) ) |
4169
|
|
|
{ |
4170
|
|
|
$this->setPagingMeta(); |
4171
|
|
|
} |
4172
|
|
|
|
4173
|
|
|
return $this->pagingMeta; |
4174
|
|
|
} |
4175
|
|
|
|
4176
|
|
|
/** |
4177
|
|
|
* @return FluentPdoModel|$this |
4178
|
|
|
*/ |
4179
|
|
|
public function setPagingMeta() : FluentPdoModel |
4180
|
|
|
{ |
4181
|
|
|
$model = clone $this; |
4182
|
|
|
$limit = intval($this->getLimit()); |
4183
|
|
|
$offset = intval($this->getOffset()); |
4184
|
|
|
$total = intval($model->withBelongsTo()->select('')->offset(0)->limit(0)->orderBy()->count()); |
4185
|
|
|
unset($model->handlers, $model); //hhmv mem leak |
4186
|
|
|
$orderBys = ! is_array($this->orderBy) ? [] : $this->orderBy; |
4187
|
|
|
$this->pagingMeta = [ |
4188
|
|
|
'limit' => $limit, |
4189
|
|
|
'offset' => $offset, |
4190
|
|
|
'page' => $offset === 0 ? 1 : intval( $offset / $limit ) + 1, |
4191
|
|
|
'pages' => $limit === 0 ? 1 : intval(ceil($total / $limit)), |
4192
|
|
|
'order' => $orderBys, |
4193
|
|
|
'total' => $total = $limit === 1 && $total > 1 ? 1 : $total, |
4194
|
|
|
'filters' => $this->filterMeta, |
4195
|
|
|
'fields' => $this->requestedFields, |
4196
|
|
|
'perms' => [], |
4197
|
|
|
]; |
4198
|
|
|
|
4199
|
|
|
return $this; |
4200
|
|
|
} |
4201
|
|
|
|
4202
|
|
|
/** |
4203
|
|
|
* Take a web request and format a query |
4204
|
|
|
* |
4205
|
|
|
* @param array $query |
4206
|
|
|
* |
4207
|
|
|
* @return FluentPdoModel|$this |
4208
|
|
|
* @throws Exception |
4209
|
|
|
*/ |
4210
|
|
|
public function filter(array $query=[]) : FluentPdoModel |
4211
|
|
|
{ |
4212
|
|
|
$columns = $this->getColumns(false); |
4213
|
|
|
$alias = ''; |
4214
|
|
|
foreach ( $query as $column => $value ) |
4215
|
|
|
{ |
4216
|
|
|
if ( in_array($column, $this->paginationAttribs) ) |
4217
|
|
|
{ |
4218
|
|
|
continue; |
4219
|
|
|
} |
4220
|
|
|
$field = $this->findFieldByQuery($column, $this->displayColumn); |
4221
|
|
|
if ( is_null($field) ) |
4222
|
|
|
{ |
4223
|
|
|
continue; |
4224
|
|
|
} |
4225
|
|
|
$this->filterMeta[$field] = $value; |
4226
|
|
|
$where = ! is_array($value) && mb_stripos((string)$value, '|') !== false ? explode('|', $value) : $value; |
4227
|
|
|
if ( is_array($where) ) |
4228
|
|
|
{ |
4229
|
|
|
$this->whereIn($field, $where); |
4230
|
|
|
} |
4231
|
|
|
else |
4232
|
|
|
{ |
4233
|
|
|
$this->addWhereClause($field, (string)$where); |
4234
|
|
|
} |
4235
|
|
|
} |
4236
|
|
|
if ( empty($query['_search']) ) |
4237
|
|
|
{ |
4238
|
|
|
return $this; |
4239
|
|
|
} |
4240
|
|
|
$alias = ! empty($alias) ? $alias : $this->tableAlias; |
4241
|
|
|
$stringCols = array_filter($columns, function($type) { |
4242
|
|
|
|
4243
|
|
|
return in_array($type, ['varchar', 'text', 'enum']); |
4244
|
|
|
}); |
4245
|
|
|
$terms = explode('|', $query['_search']); |
4246
|
|
|
$whereLikes = []; |
4247
|
|
|
foreach ( $stringCols as $column => $type ) |
4248
|
|
|
{ |
4249
|
|
|
if ( in_array($column, $this->excludedSearchCols) ) |
4250
|
|
|
{ |
4251
|
|
|
continue; |
4252
|
|
|
} |
4253
|
|
|
foreach ( $terms as $term ) |
4254
|
|
|
{ |
4255
|
|
|
$whereLikes["{$alias}.{$column}"] = "%{$term}%"; |
4256
|
|
|
} |
4257
|
|
|
} |
4258
|
|
|
// Reference fields... |
4259
|
|
|
$belongsTo = $this->getSearchableAssociations(); |
4260
|
|
|
foreach ( $belongsTo as $alias => $config ) |
4261
|
|
|
{ |
4262
|
|
|
foreach ( $terms as $term ) |
4263
|
|
|
{ |
4264
|
|
|
$whereLikes[$config[2]] = "%{$term}%"; |
4265
|
|
|
} |
4266
|
|
|
} |
4267
|
|
|
if ( empty($whereLikes) ) |
4268
|
|
|
{ |
4269
|
|
|
return $this; |
4270
|
|
|
} |
4271
|
|
|
$this->where('1', '1')->wrap()->_and(); |
4272
|
|
|
foreach ( $whereLikes as $column => $term ) |
4273
|
|
|
{ |
4274
|
|
|
$this->_or()->whereLike($column, $term); |
4275
|
|
|
} |
4276
|
|
|
$this->wrap(); |
4277
|
|
|
|
4278
|
|
|
return $this; |
4279
|
|
|
} |
4280
|
|
|
|
4281
|
|
|
/** |
4282
|
|
|
* @param string $column |
4283
|
|
|
* @param string $displayCol |
4284
|
|
|
* @return string|null |
4285
|
|
|
*/ |
4286
|
|
|
protected function findFieldByQuery(string $column, string $displayCol) |
4287
|
|
|
{ |
4288
|
|
|
list($alias, $field) = $this->getColumnAliasParts($column); |
4289
|
|
|
$field = $field === '_display_field' ? $displayCol : $field; |
4290
|
|
|
$columns = $this->getColumns(); |
4291
|
|
|
$tableAlias = $this->getTableAlias(); |
4292
|
|
|
if ( ! empty($alias) && $alias === $tableAlias ) |
4293
|
|
|
{ |
4294
|
|
|
// Alias is set but the field isn't correct |
4295
|
|
|
if ( ! in_array($field, $columns) ) |
4296
|
|
|
{ |
4297
|
|
|
return null; |
4298
|
|
|
} |
4299
|
|
|
return "{$alias}.{$field}"; |
4300
|
|
|
} |
4301
|
|
|
// Alias isn't passed in but the field is ok |
4302
|
|
|
if ( empty($alias) && in_array($field, $columns) ) |
4303
|
|
|
{ |
4304
|
|
|
return "{$tableAlias}.{$field}"; |
4305
|
|
|
} |
4306
|
|
|
// // Alias is passed but not this table in but there is a matching field on this table |
4307
|
|
|
// if ( empty($alias) ) //&& in_array($field, $columns) ) |
4308
|
|
|
// { |
4309
|
|
|
// return null; |
4310
|
|
|
// } |
4311
|
|
|
// Now search the associations for the field |
4312
|
|
|
$associations = $this->getSearchableAssociations(); |
4313
|
|
|
if ( ! empty($alias) ) |
4314
|
|
|
{ |
4315
|
|
|
if ( array_key_exists($alias, $associations) && $associations[$alias][3] === $field ) |
4316
|
|
|
{ |
4317
|
|
|
return "{$alias}.{$field}"; |
4318
|
|
|
} |
4319
|
|
|
|
4320
|
|
|
return null; |
4321
|
|
|
} |
4322
|
|
|
foreach ( $associations as $assocAlias => $config ) |
4323
|
|
|
{ |
4324
|
|
|
list(, , $assocField, $fieldAlias) = $config; |
4325
|
|
|
if ( $fieldAlias === $field ) |
4326
|
|
|
{ |
4327
|
|
|
return $assocField; |
4328
|
|
|
} |
4329
|
|
|
} |
4330
|
|
|
|
4331
|
|
|
return null; |
4332
|
|
|
} |
4333
|
|
|
|
4334
|
|
|
/** |
4335
|
|
|
* @param string $field |
4336
|
|
|
* @param mixed $value |
4337
|
|
|
* @param array $pdoMetaData |
4338
|
|
|
* @return float|int |
4339
|
|
|
* @throws Exception |
4340
|
|
|
*/ |
4341
|
|
|
protected function fixTypeToSentinel(string $field, $value, array $pdoMetaData=[]) |
4342
|
|
|
{ |
4343
|
|
|
Assert($value)->nullOr()->scalar("var is type of " . gettype($value)); |
4344
|
|
|
|
4345
|
|
|
$fieldType = strtolower($pdoMetaData['native_type'] ??''?: ''); |
4346
|
|
|
if ( ! $fieldType ) |
4347
|
|
|
{ |
4348
|
|
|
$schema = $this->getColumns(); |
4349
|
|
|
if ( empty($schema) ) |
4350
|
|
|
{ |
4351
|
|
|
return $value; |
4352
|
|
|
} |
4353
|
|
|
$columns = $this->getColumns(false); |
4354
|
|
|
Assert($columns)->keyExists($field, "The property {$field} does not exist."); |
4355
|
|
|
|
4356
|
|
|
$fieldType = $columns[$field] ?: null; |
4357
|
|
|
} |
4358
|
|
|
|
4359
|
|
|
|
4360
|
|
|
// Don't cast invalid values... only those that can be cast cleanly |
4361
|
|
|
switch ( $fieldType ) |
4362
|
|
|
{ |
4363
|
|
|
case 'varchar': |
4364
|
|
|
case 'var_string': |
4365
|
|
|
case 'string': |
4366
|
|
|
case 'text'; |
4367
|
|
|
case 'date': |
4368
|
|
|
case 'datetime': |
4369
|
|
|
case 'timestamp': |
4370
|
|
|
case 'blob': |
4371
|
|
|
|
4372
|
|
|
return (string)$value; |
4373
|
|
|
|
4374
|
|
|
case 'int': |
4375
|
|
|
case 'integer': |
4376
|
|
|
case 'tinyint': |
4377
|
|
|
case 'tiny': |
4378
|
|
|
case 'long': |
4379
|
|
|
case 'longlong': |
4380
|
|
|
|
4381
|
|
|
return (int)$value; |
4382
|
|
|
|
4383
|
|
|
case 'decimal': |
4384
|
|
|
case 'float': |
4385
|
|
|
case 'double': |
4386
|
|
|
case 'newdecimal': |
4387
|
|
|
|
4388
|
|
|
return (float)$value; |
4389
|
|
|
|
4390
|
|
|
default: |
4391
|
|
|
|
4392
|
|
|
Assert(false)->true('Unknown type: ' . $fieldType); |
4393
|
|
|
|
4394
|
|
|
return $value; |
4395
|
|
|
} |
4396
|
|
|
} |
4397
|
|
|
|
4398
|
|
|
/** |
4399
|
|
|
* @param string $field |
4400
|
|
|
* @param mixed $value |
4401
|
|
|
* @param bool|false $permissive |
4402
|
|
|
* @return float|int|null|string |
4403
|
|
|
*/ |
4404
|
|
|
protected function fixType(string $field, $value, bool $permissive=false) |
4405
|
|
|
{ |
4406
|
|
|
Assert($value)->nullOr()->scalar("var is type of " . gettype($value)); |
4407
|
|
|
$schema = $this->getColumns(); |
4408
|
|
|
if ( empty($schema) || ( ! array_key_exists($field, $schema) && $permissive ) ) |
4409
|
|
|
{ |
4410
|
|
|
return $value; |
4411
|
|
|
} |
4412
|
|
|
$columns = $this->getColumns(false); |
4413
|
|
|
Assert($columns)->keyExists($field, "The property {$field} does not exist."); |
4414
|
|
|
|
4415
|
|
|
$fieldType = ! empty($columns[$field]) ? $columns[$field] : null; |
4416
|
|
|
|
4417
|
|
|
if ( is_null($value) ) |
4418
|
|
|
{ |
4419
|
|
|
return null; |
4420
|
|
|
} |
4421
|
|
|
// return on null, '' but not 0 |
4422
|
|
|
if ( ! is_numeric($value) && empty($value) ) |
4423
|
|
|
{ |
4424
|
|
|
return null; |
4425
|
|
|
} |
4426
|
|
|
// Don't cast invalid values... only those that can be cast cleanly |
4427
|
|
|
switch ( $fieldType ) |
4428
|
|
|
{ |
4429
|
|
|
case 'varchar': |
4430
|
|
|
case 'text'; |
4431
|
|
|
case 'date': |
4432
|
|
|
case 'datetime': |
4433
|
|
|
case 'timestamp': |
4434
|
|
|
|
4435
|
|
|
// return on null, '' but not 0 |
4436
|
|
|
return ! is_numeric($value) && empty($value) ? null : (string)$value; |
4437
|
|
|
|
4438
|
|
|
case 'int': |
4439
|
|
|
|
4440
|
|
|
if ( $field === 'id' || substr($field, -3) === '_id' ) |
4441
|
|
|
{ |
4442
|
|
|
return $value ? (int)$value : null; |
4443
|
|
|
} |
4444
|
|
|
|
4445
|
|
|
return ! is_numeric($value) ? null : (int)$value; |
4446
|
|
|
|
4447
|
|
|
case 'decimal': |
4448
|
|
|
|
4449
|
|
|
return ! is_numeric($value) ? null : (float)$value; |
4450
|
|
|
|
4451
|
|
|
default: |
4452
|
|
|
|
4453
|
|
|
return $value; |
|
|
|
|
4454
|
|
|
} |
4455
|
|
|
} |
4456
|
|
|
|
4457
|
|
|
/** |
4458
|
|
|
* @param stdClass $record |
4459
|
|
|
* @param string $type |
4460
|
|
|
* @return stdClass |
4461
|
|
|
*/ |
4462
|
|
|
public function fixTypesToSentinel(stdClass $record, string $type='') : stdClass |
4463
|
|
|
{ |
4464
|
|
|
foreach ( $this->rowMetaData as $column => $pdoMetaData ) |
4465
|
|
|
{ |
4466
|
|
|
if ( ! property_exists($record, $column) ) |
4467
|
|
|
{ |
4468
|
|
|
continue; |
4469
|
|
|
} |
4470
|
|
|
$record->{$column} = $this->fixTypeToSentinel($column, $record->{$column}, $pdoMetaData); |
4471
|
|
|
} |
4472
|
|
|
// PDO might not be able to generate the meta data to sniff types |
4473
|
|
|
if ( ! $this->rowMetaData ) |
|
|
|
|
4474
|
|
|
{ |
4475
|
|
|
foreach ( $this->getColumns(false) as $column => $fieldType ) |
4476
|
|
|
{ |
4477
|
|
|
if ( ! property_exists($record, $column) ) |
4478
|
|
|
{ |
4479
|
|
|
continue; |
4480
|
|
|
} |
4481
|
|
|
$record->{$column} = $this->fixTypeToSentinel($column, $record->{$column}); |
4482
|
|
|
} |
4483
|
|
|
} |
4484
|
|
|
|
4485
|
|
|
unset($type); |
4486
|
|
|
|
4487
|
|
|
return $record; |
4488
|
|
|
} |
4489
|
|
|
|
4490
|
|
|
/** |
4491
|
|
|
* @param stdClass $record |
4492
|
|
|
* @param string $type |
4493
|
|
|
* @return stdClass |
4494
|
|
|
* @throws Exception |
4495
|
|
|
*/ |
4496
|
|
|
public function applyHandlers(stdClass $record, string $type='INSERT') : stdClass |
4497
|
|
|
{ |
4498
|
|
|
$this->compileHandlers(); |
4499
|
|
|
$this->errors = []; |
4500
|
|
|
// Disable per field exceptions so we can capture all errors for the record |
4501
|
|
|
$tmpExceptions = $this->validationExceptions; |
4502
|
|
|
$this->validationExceptions = false; |
4503
|
|
|
foreach ( $this->handlers as $field => $fnValidator ) |
4504
|
|
|
{ |
4505
|
|
|
if ( ! property_exists($record, $field) ) |
4506
|
|
|
{ |
4507
|
|
|
// If the operation is an update it can be a partial update |
4508
|
|
|
if ( $type === self::SAVE_UPDATE ) |
4509
|
|
|
{ |
4510
|
|
|
continue; |
4511
|
|
|
} |
4512
|
|
|
$record->{$field} = null; |
4513
|
|
|
} |
4514
|
|
|
$record->{$field} = $this->applyHandler($field, $record->{$field}, $type, $record); |
|
|
|
|
4515
|
|
|
} |
4516
|
|
|
$this->validationExceptions = $tmpExceptions; |
4517
|
|
|
if ( $this->validationExceptions && ! empty($this->errors) ) |
4518
|
|
|
{ |
4519
|
|
|
throw new ModelFailedValidationException("Validation of data failed", $this->getErrors(), 422); |
4520
|
|
|
} |
4521
|
|
|
|
4522
|
|
|
return $record; |
4523
|
|
|
} |
4524
|
|
|
|
4525
|
|
|
|
4526
|
|
|
/** |
4527
|
|
|
* @param stdClass $record |
4528
|
|
|
* @param array $fields |
4529
|
|
|
* @param string $type |
4530
|
|
|
* @return bool |
4531
|
|
|
*/ |
4532
|
|
|
protected function uniqueCheck(stdClass $record, array $fields, string $type) : bool |
4533
|
|
|
{ |
4534
|
|
|
if ( $type === self::SAVE_UPDATE ) |
4535
|
|
|
{ |
4536
|
|
|
$this->whereNot($this->primaryKey, $record->{$this->primaryKey}); |
4537
|
|
|
} |
4538
|
|
|
foreach ( $fields as $field ) |
4539
|
|
|
{ |
4540
|
|
|
$this->where($field, $record->{$field}); |
4541
|
|
|
} |
4542
|
|
|
|
4543
|
|
|
return (int)$this->count() > 0; |
4544
|
|
|
} |
4545
|
|
|
|
4546
|
|
|
/** |
4547
|
|
|
* @param string $field |
4548
|
|
|
* @param mixed $value |
4549
|
|
|
* @param string $type |
4550
|
|
|
* @param stdClass $record |
4551
|
|
|
* @return null |
4552
|
|
|
* @throws Exception |
4553
|
|
|
*/ |
4554
|
|
|
protected function applyHandler(string $field, $value, string $type='', stdClass $record=null) |
4555
|
|
|
{ |
4556
|
|
|
$this->compileHandlers(); |
4557
|
|
|
$fnHandler = ! empty($this->handlers[$field]) ? $this->handlers[$field] : null; |
4558
|
|
|
if ( is_callable($fnHandler) ) |
4559
|
|
|
{ |
4560
|
|
|
try |
4561
|
|
|
{ |
4562
|
|
|
$value = $fnHandler($field, $value, $type, $record); |
4563
|
|
|
} |
4564
|
|
|
catch( Exception $e ) |
4565
|
|
|
{ |
4566
|
|
|
$this->errors[$field][] = $e->getMessage(); |
4567
|
|
|
if ( $this->validationExceptions && ! empty($this->errors) ) |
4568
|
|
|
{ |
4569
|
|
|
throw new ModelFailedValidationException("Validation of data failed", $this->getErrors(), 422); |
4570
|
|
|
} |
4571
|
|
|
|
4572
|
|
|
return null; |
4573
|
|
|
} |
4574
|
|
|
} |
4575
|
|
|
|
4576
|
|
|
return $value; |
4577
|
|
|
} |
4578
|
|
|
|
4579
|
|
|
/** |
4580
|
|
|
* @param string $start |
4581
|
|
|
* @param string $end |
4582
|
|
|
* @param string $hayStack |
4583
|
|
|
* @return mixed |
4584
|
|
|
*/ |
4585
|
|
|
public static function between(string $start, string $end, string $hayStack) : string |
4586
|
|
|
{ |
4587
|
|
|
return static::before($end, static::after($start, $hayStack)); |
4588
|
|
|
} |
4589
|
|
|
|
4590
|
|
|
/** |
4591
|
|
|
* @param string $needle |
4592
|
|
|
* @param string $hayStack |
4593
|
|
|
* @param bool $returnOrigIfNeedleNotExists |
4594
|
|
|
* @return mixed |
4595
|
|
|
*/ |
4596
|
|
|
public static function before(string $needle, string $hayStack, bool $returnOrigIfNeedleNotExists=false) : string |
4597
|
|
|
{ |
4598
|
|
|
$result = mb_substr($hayStack, 0, mb_strpos($hayStack, $needle)); |
4599
|
|
|
if ( !$result && $returnOrigIfNeedleNotExists ) |
4600
|
|
|
{ |
4601
|
|
|
return $hayStack; |
4602
|
|
|
} |
4603
|
|
|
|
4604
|
|
|
return $result; |
4605
|
|
|
} |
4606
|
|
|
|
4607
|
|
|
/** |
4608
|
|
|
* @param string $needle |
4609
|
|
|
* @param string $hayStack |
4610
|
|
|
* @param bool $returnOrigIfNeedleNotExists |
4611
|
|
|
* @return string |
4612
|
|
|
*/ |
4613
|
|
|
public static function after(string $needle, string $hayStack, bool $returnOrigIfNeedleNotExists=false) : string |
4614
|
|
|
{ |
4615
|
|
|
if ( ! is_bool(mb_strpos($hayStack, $needle)) ) |
4616
|
|
|
{ |
4617
|
|
|
return mb_substr($hayStack, mb_strpos($hayStack, $needle) + mb_strlen($needle)); |
4618
|
|
|
} |
4619
|
|
|
|
4620
|
|
|
return $returnOrigIfNeedleNotExists ? $hayStack : ''; |
4621
|
|
|
} |
4622
|
|
|
|
4623
|
|
|
/** |
4624
|
|
|
* @return int |
4625
|
|
|
*/ |
4626
|
|
|
public function getUserId() |
4627
|
|
|
{ |
4628
|
|
|
return 0; |
4629
|
|
|
} |
4630
|
|
|
|
4631
|
|
|
/** |
4632
|
|
|
* @param string $entity |
4633
|
|
|
* @param int $id |
4634
|
|
|
* @return int |
4635
|
|
|
*/ |
4636
|
|
|
public function getMaskByResourceAndId(string $entity, int $id) : int |
|
|
|
|
4637
|
|
|
{ |
4638
|
|
|
return 31; |
4639
|
|
|
} |
4640
|
|
|
|
4641
|
|
|
/** |
4642
|
|
|
* @param string|int|null $time |
4643
|
|
|
* @return string |
4644
|
|
|
*/ |
4645
|
|
|
public static function date($time=null) : string |
4646
|
|
|
{ |
4647
|
|
|
return date('Y-m-d', static::getTime($time)); |
4648
|
|
|
} |
4649
|
|
|
|
4650
|
|
|
/** |
4651
|
|
|
* @param string|int|null $time |
4652
|
|
|
* @return string |
4653
|
|
|
*/ |
4654
|
|
|
public static function dateTime($time=null) : string |
4655
|
|
|
{ |
4656
|
|
|
return date('Y-m-d H:i:s', static::getTime($time)); |
4657
|
|
|
} |
4658
|
|
|
|
4659
|
|
|
/** |
4660
|
|
|
* @param string|int|null $time |
4661
|
|
|
* @return string |
4662
|
|
|
*/ |
4663
|
|
|
public static function atom($time=null) : string |
4664
|
|
|
{ |
4665
|
|
|
return date('Y-m-d\TH:i:sP', static::getTime($time)); |
4666
|
|
|
} |
4667
|
|
|
|
4668
|
|
|
/** |
4669
|
|
|
* @param string|int|null $time |
4670
|
|
|
* @return int |
4671
|
|
|
*/ |
4672
|
|
|
public static function getTime($time=null) : int |
4673
|
|
|
{ |
4674
|
|
|
if ( ! $time ) |
4675
|
|
|
{ |
4676
|
|
|
return time(); |
4677
|
|
|
} |
4678
|
|
|
if ( is_int($time) ) |
4679
|
|
|
{ |
4680
|
|
|
return $time; |
4681
|
|
|
} |
4682
|
|
|
|
4683
|
|
|
return strtotime($time); |
4684
|
|
|
} |
4685
|
|
|
|
4686
|
|
|
/** |
4687
|
|
|
* @param int $id |
4688
|
|
|
* @param int $cacheTtl |
4689
|
|
|
* @return string |
4690
|
|
|
*/ |
4691
|
|
|
public function getCodeById(int $id, int $cacheTtl=self::ONE_DAY) : string |
4692
|
|
|
{ |
4693
|
|
|
Assert($id)->id(); |
4694
|
|
|
$code = $this->defaultFilters()->fetchStr($this->getDisplayColumn(), $id, $cacheTtl); |
4695
|
|
|
Assert($code)->notEmpty(); |
4696
|
|
|
|
4697
|
|
|
return $code; |
4698
|
|
|
} |
4699
|
|
|
|
4700
|
|
|
/** |
4701
|
|
|
* @param array $authUserRoles |
4702
|
|
|
* @param int $authUserId |
4703
|
|
|
* @return FluentPdoModel |
4704
|
|
|
*/ |
4705
|
|
|
public function applyRoleFilter(array $authUserRoles, int $authUserId) : FluentPdoModel |
|
|
|
|
4706
|
|
|
{ |
4707
|
|
|
return $this; |
4708
|
|
|
} |
4709
|
|
|
|
4710
|
|
|
/** |
4711
|
|
|
* @param int $id |
4712
|
|
|
* @param string[] $authUserRoles |
4713
|
|
|
* @param int $authUserId |
4714
|
|
|
* @return bool |
4715
|
|
|
*/ |
4716
|
|
|
public function canAccessIdWithRole(int $id, array $authUserRoles, int $authUserId) : bool |
|
|
|
|
4717
|
|
|
{ |
4718
|
|
|
return true; |
4719
|
|
|
} |
4720
|
|
|
} |
4721
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.