1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the Divergence package. |
4
|
|
|
* |
5
|
|
|
* (c) Henry Paradiz <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Divergence\Models; |
12
|
|
|
|
13
|
|
|
use Exception; |
14
|
|
|
use ReflectionClass; |
15
|
|
|
use JsonSerializable; |
16
|
|
|
use Divergence\IO\Database\SQL; |
17
|
|
|
use Divergence\Models\Mapping\Column; |
18
|
|
|
use Divergence\Models\RecordValidator; |
19
|
|
|
use Divergence\IO\Database\MySQL as DB; |
20
|
|
|
use Divergence\Models\Mapping\Relation; |
21
|
|
|
use Divergence\IO\Database\Query\Delete; |
22
|
|
|
use Divergence\IO\Database\Query\Insert; |
23
|
|
|
use Divergence\IO\Database\Query\Update; |
24
|
|
|
use Divergence\Models\Mapping\DefaultGetMapper; |
25
|
|
|
use Divergence\Models\Mapping\DefaultSetMapper; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* ActiveRecord |
29
|
|
|
* |
30
|
|
|
* @package Divergence |
31
|
|
|
* @author Henry Paradiz <[email protected]> |
32
|
|
|
* @author Chris Alfano <[email protected]> |
33
|
|
|
* |
34
|
|
|
* @property-read bool $isDirty False by default. Set to true only when an object has had any field change from it's state when it was instantiated. |
35
|
|
|
* @property-read bool $isPhantom True if this object was instantiated as a brand new object and isn't yet saved. |
36
|
|
|
* @property-read bool $wasPhantom True if this object was originally instantiated as a brand new object. Will stay true even if saved during that PHP runtime. |
37
|
|
|
* @property-read bool $isValid True if this object is valid. This value is true by default and will only be set to false if the validator is executed first and finds a validation problem. |
38
|
|
|
* @property-read bool $isNew False by default. Set to true only when an object that isPhantom is saved. |
39
|
|
|
* @property-read bool $isUpdated False by default. Set to true when an object that already existed in the data store is saved. |
40
|
|
|
* |
41
|
|
|
* @property int $ID Default primary key field. Part of Divergence\Models\Model but used in this file as a default. |
42
|
|
|
* @property string $Class Name of this fully qualified PHP class for use with subclassing to explicitly specify which class to instantiate a record as when pulling from datastore. Part of Divergence\Models\Model but used in this file as a default. |
43
|
|
|
* @property mixed $Created Timestamp of when this record was created. Supports Unix timestamp as well as any format accepted by PHP's strtotime as well as MySQL standard. Part of Divergence\Models\Model but used in this file as a default. |
44
|
|
|
* @property int $CreatorID A standard user ID field for use by your login & authentication system. Part of Divergence\Models\Model but used in this file as a default. |
45
|
|
|
* |
46
|
|
|
* @property-read array $validationErrors An empty string by default. Returns validation errors as an array. |
47
|
|
|
* @property-read array $data A plain PHP array of the fields and values for this model object. |
48
|
|
|
* @property-read array $originalValues A plain PHP array of the fields and values for this model object when it was instantiated. |
49
|
|
|
* |
50
|
|
|
* @property array $versioningFields |
51
|
|
|
* @property-read array $_relatedObjects Relationship cache |
52
|
|
|
* |
53
|
|
|
* @method static void _defineRelationships() |
54
|
|
|
* @method static void _initRelationships() |
55
|
|
|
* @method static bool _relationshipExists(string $value) |
56
|
|
|
* @method static array<ActiveRecord>|ActiveRecord|null _getRelationshipValue(string $value) |
57
|
|
|
* @method void beforeVersionedSave() |
58
|
|
|
* @method void afterVersionedSave() |
59
|
|
|
* @method static string getHistoryTable() |
60
|
|
|
*/ |
61
|
|
|
class ActiveRecord implements JsonSerializable |
62
|
|
|
{ |
63
|
|
|
/** |
64
|
|
|
* @var bool $autoCreateTables Set this to true if you want the table(s) to automatically be created when not found. |
65
|
|
|
*/ |
66
|
|
|
public static $autoCreateTables = true; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @var string $tableName Name of table |
70
|
|
|
*/ |
71
|
|
|
public static $tableName = 'records'; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* |
75
|
|
|
* @var string $singularNoun Noun to describe singular object |
76
|
|
|
*/ |
77
|
|
|
public static $singularNoun = 'record'; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* |
81
|
|
|
* @var string $pluralNoun Noun to describe a plurality of objects |
82
|
|
|
*/ |
83
|
|
|
public static $pluralNoun = 'records'; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* |
87
|
|
|
* @var array $fieldDefaults Defaults values for field definitions |
88
|
|
|
*/ |
89
|
|
|
public static $fieldDefaults = [ |
90
|
|
|
'type' => 'string', |
91
|
|
|
'notnull' => true, |
92
|
|
|
]; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* @var array $fields Field definitions |
96
|
|
|
*/ |
97
|
|
|
public static $fields = []; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @var array $indexes Index definitions |
101
|
|
|
*/ |
102
|
|
|
public static $indexes = []; |
103
|
|
|
|
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @var array $validators Validation checks |
107
|
|
|
*/ |
108
|
|
|
public static $validators = []; |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @var array $relationships Relationship definitions |
112
|
|
|
*/ |
113
|
|
|
public static $relationships = []; |
114
|
|
|
|
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Class names of possible contexts |
118
|
|
|
* @var array |
119
|
|
|
*/ |
120
|
|
|
public static $contextClasses; |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @var string|null $primaryKey The primary key for this model. Optional. Defaults to ID |
124
|
|
|
*/ |
125
|
|
|
public static $primaryKey = null; |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @var string $handleField Field which should be treated as unique but generated automatically. |
129
|
|
|
*/ |
130
|
|
|
public static $handleField = 'Handle'; |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @var null|string $rootClass The root class. |
134
|
|
|
*/ |
135
|
|
|
public static $rootClass = null; |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @var null|string $defaultClass The default class to be used when creating a new object. |
139
|
|
|
*/ |
140
|
|
|
public static $defaultClass = null; |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @var array $subClasses Array of class names providing valid classes you can use for this model. |
144
|
|
|
*/ |
145
|
|
|
public static $subClasses = []; |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* @var callable $beforeSave Runs inside the save() method before save actually happens. |
149
|
|
|
*/ |
150
|
|
|
public static $beforeSave; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @var callable $afterSave Runs inside the save() method after save if no exception was thrown. |
154
|
|
|
*/ |
155
|
|
|
public static $afterSave; |
156
|
|
|
|
157
|
|
|
|
158
|
|
|
// versioning |
159
|
|
|
public static $historyTable; |
160
|
|
|
public static $createRevisionOnDestroy = true; |
161
|
|
|
public static $createRevisionOnSave = true; |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Internal registry of fields that comprise this class. The setting of this variable of every parent derived from a child model will get merged. |
165
|
|
|
* |
166
|
|
|
* @var array $_classFields |
167
|
|
|
*/ |
168
|
|
|
protected static $_classFields = []; |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Internal registry of relationships that are part of this class. The setting of this variable of every parent derived from a child model will get merged. |
172
|
|
|
* |
173
|
|
|
* @var array $_classFields |
174
|
|
|
*/ |
175
|
|
|
protected static $_classRelationships = []; |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Internal registry of before save PHP callables that are part of this class. The setting of this variable of every parent derived from a child model will get merged. |
179
|
|
|
* |
180
|
|
|
* @var array $_classBeforeSave |
181
|
|
|
*/ |
182
|
|
|
protected static $_classBeforeSave = []; |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Internal registry of after save PHP callables that are part of this class. The setting of this variable of every parent derived from a child model will get merged. |
186
|
|
|
* |
187
|
|
|
* @var array $_classAfterSave |
188
|
|
|
*/ |
189
|
|
|
protected static $_classAfterSave = []; |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Global registry of booleans that check if a given model has had it's fields defined in a static context. The key is a class name and the value is a simple true / false. |
193
|
|
|
* |
194
|
|
|
* @used-by ActiveRecord::init() |
195
|
|
|
* |
196
|
|
|
* @var array $_fieldsDefined |
197
|
|
|
*/ |
198
|
|
|
protected static $_fieldsDefined = []; |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Global registry of booleans that check if a given model has had it's relationships defined in a static context. The key is a class name and the value is a simple true / false. |
202
|
|
|
* |
203
|
|
|
* @used-by ActiveRecord::init() |
204
|
|
|
* |
205
|
|
|
* @var array $_relationshipsDefined |
206
|
|
|
*/ |
207
|
|
|
protected static $_relationshipsDefined = []; |
208
|
|
|
protected static $_eventsDefined = []; |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* @var array $_record Raw array data for this model. |
212
|
|
|
*/ |
213
|
|
|
protected $_record; |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @var array $_convertedValues Raw array data for this model of data normalized for it's field type. |
217
|
|
|
*/ |
218
|
|
|
protected $_convertedValues; |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* @var RecordValidator $_validator Instance of a RecordValidator object. |
222
|
|
|
*/ |
223
|
|
|
protected $_validator; |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* @var array $_validationErrors Array of validation errors if there are any. |
227
|
|
|
*/ |
228
|
|
|
protected $_validationErrors; |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @var array $_originalValues If any values have been changed the initial value is stored here. |
232
|
|
|
*/ |
233
|
|
|
protected $_originalValues; |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* False by default. Set to true only when an object has had any field change from it's state when it was instantiated. |
238
|
|
|
* |
239
|
|
|
* @var bool $_isDirty |
240
|
|
|
* |
241
|
|
|
* @used-by $this->save() |
242
|
|
|
* @used-by $this->__get() |
243
|
|
|
*/ |
244
|
|
|
protected $_isDirty; |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* True if this object was instantiated as a brand new object and isn't yet saved. |
248
|
|
|
* |
249
|
|
|
* @var bool $_isPhantom |
250
|
|
|
* |
251
|
|
|
* @used-by $this->save() |
252
|
|
|
* @used-by $this->__get() |
253
|
|
|
*/ |
254
|
|
|
protected $_isPhantom; |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* True if this object was originally instantiated as a brand new object. Will stay true even if saved during that PHP runtime. |
258
|
|
|
* |
259
|
|
|
* @var bool $_wasPhantom |
260
|
|
|
* |
261
|
|
|
* @used-by $this->__get() |
262
|
|
|
*/ |
263
|
|
|
protected $_wasPhantom; |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* True if this object is valid. This value is true by default and will only be set to false if the validator is executed first and finds a validation problem. |
267
|
|
|
* |
268
|
|
|
* @var bool $_isValid |
269
|
|
|
* |
270
|
|
|
* @used-by $this->__get() |
271
|
|
|
*/ |
272
|
|
|
protected $_isValid; |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* False by default. Set to true only when an object that isPhantom is saved. |
276
|
|
|
* |
277
|
|
|
* @var bool $_isNew |
278
|
|
|
* |
279
|
|
|
* @used-by $this->save() |
280
|
|
|
* @used-by $this->__get() |
281
|
|
|
*/ |
282
|
|
|
protected $_isNew; |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* False by default. Set to true when an object that already existed in the data store is saved. |
286
|
|
|
* |
287
|
|
|
* @var bool $_isUpdated |
288
|
|
|
* |
289
|
|
|
* @used-by $this->__get() |
290
|
|
|
*/ |
291
|
|
|
protected $_isUpdated; |
292
|
|
|
|
293
|
|
|
|
294
|
|
|
public const defaultSetMapper = DefaultSetMapper::class; |
295
|
|
|
public const defaultGetMapper = DefaultGetMapper::class; |
296
|
|
|
/** |
297
|
|
|
* __construct Instantiates a Model and returns. |
298
|
|
|
* |
299
|
|
|
* @param array $record Raw array data to start off the model. |
300
|
|
|
* @param boolean $isDirty Whether or not to treat this object as if it was modified from the start. |
301
|
|
|
* @param boolean $isPhantom Whether or not to treat this object as a brand new record not yet in the database. |
302
|
|
|
* |
303
|
|
|
* @uses static::init |
304
|
|
|
* |
305
|
|
|
* @return static Instance of the value of $this->Class |
306
|
|
|
*/ |
307
|
109 |
|
public function __construct($record = [], $isDirty = false, $isPhantom = null) |
308
|
|
|
{ |
309
|
109 |
|
$this->_record = $record; |
310
|
109 |
|
$this->_isPhantom = isset($isPhantom) ? $isPhantom : empty($record); |
311
|
109 |
|
$this->_wasPhantom = $this->_isPhantom; |
312
|
109 |
|
$this->_isDirty = $this->_isPhantom || $isDirty; |
313
|
109 |
|
$this->_isNew = false; |
314
|
109 |
|
$this->_isUpdated = false; |
315
|
|
|
|
316
|
109 |
|
$this->_isValid = true; |
317
|
109 |
|
$this->_validationErrors = []; |
318
|
109 |
|
$this->_originalValues = []; |
319
|
|
|
|
320
|
109 |
|
static::init(); |
321
|
|
|
|
322
|
|
|
// set Class |
323
|
109 |
|
if (static::fieldExists('Class') && !$this->Class) { |
324
|
109 |
|
$this->_setFieldValue('Class', get_class($this)); |
325
|
|
|
} |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* __get Passthru to getValue($name) |
330
|
|
|
* |
331
|
|
|
* @param string $name Name of the magic field you want. |
332
|
|
|
* |
333
|
|
|
* @return mixed The return of $this->getValue($name) |
334
|
|
|
*/ |
335
|
82 |
|
public function __get($name) |
336
|
|
|
{ |
337
|
82 |
|
return $this->getValue($name); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Passthru to setValue($name,$value) |
342
|
|
|
* |
343
|
|
|
* @param string $name Name of the magic field to set. |
344
|
|
|
* @param mixed $value Value to set. |
345
|
|
|
* |
346
|
|
|
* @return mixed The return of $this->setValue($name,$value) |
347
|
|
|
*/ |
348
|
4 |
|
public function __set($name, $value) |
349
|
|
|
{ |
350
|
4 |
|
return $this->setValue($name, $value); |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Tests if a magic class attribute is set or not. |
355
|
|
|
* |
356
|
|
|
* @param string $name Name of the magic field to set. |
357
|
|
|
* |
358
|
|
|
* @return bool Returns true if a value was returned by $this->getValue($name), false otherwise. |
359
|
|
|
*/ |
360
|
1 |
|
public function __isset($name) |
361
|
|
|
{ |
362
|
1 |
|
$value = $this->getValue($name); |
363
|
1 |
|
return isset($value); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Gets the primary key field for his model. |
368
|
|
|
* |
369
|
|
|
* @return string ID by default or static::$primaryKey if it's set. |
370
|
|
|
*/ |
371
|
28 |
|
public static function getPrimaryKey() |
372
|
|
|
{ |
373
|
28 |
|
return isset(static::$primaryKey) ? static::$primaryKey : 'ID'; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* Gets the primary key value for his model. |
378
|
|
|
* |
379
|
|
|
* @return mixed The primary key value for this object. |
380
|
|
|
*/ |
381
|
17 |
|
public function getPrimaryKeyValue() |
382
|
|
|
{ |
383
|
17 |
|
if (isset(static::$primaryKey)) { |
384
|
2 |
|
return $this->{static::$primaryKey} ?? $this->_getFieldValue(static::$primaryKey); |
385
|
|
|
} else { |
386
|
17 |
|
return $this->_getFieldValue('ID'); |
387
|
|
|
} |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* init Initializes the model by checking the ancestor tree for the existence of various config fields and merges them. |
392
|
|
|
* |
393
|
|
|
* @uses static::$_fieldsDefined Sets static::$_fieldsDefined[get_called_class()] to true after running. |
394
|
|
|
* @uses static::$_relationshipsDefined Sets static::$_relationshipsDefined[get_called_class()] to true after running. |
395
|
|
|
* @uses static::$_eventsDefined Sets static::$_eventsDefined[get_called_class()] to true after running. |
396
|
|
|
* |
397
|
|
|
* @used-by static::__construct() |
398
|
|
|
* @used-by static::fieldExists() |
399
|
|
|
* @used-by static::getClassFields() |
400
|
|
|
* @used-by static::getColumnName() |
401
|
|
|
* |
402
|
|
|
* @return void |
403
|
|
|
*/ |
404
|
125 |
|
public static function init() |
405
|
|
|
{ |
406
|
125 |
|
$className = get_called_class(); |
407
|
|
|
|
408
|
125 |
|
$className::$rootClass = $className::$rootClass ?? $className; |
|
|
|
|
409
|
125 |
|
$className::$defaultClass = $className::$defaultClass ?? $className; |
|
|
|
|
410
|
125 |
|
$className::$subClasses = $className::$subClasses ?? [$className]; |
|
|
|
|
411
|
|
|
|
412
|
125 |
|
if (empty(static::$_fieldsDefined[$className])) { |
413
|
7 |
|
static::_defineFields(); |
414
|
7 |
|
static::_initFields(); |
415
|
|
|
|
416
|
7 |
|
static::$_fieldsDefined[$className] = true; |
417
|
|
|
} |
418
|
125 |
|
if (empty(static::$_relationshipsDefined[$className]) && static::isRelational()) { |
419
|
3 |
|
static::_defineRelationships(); |
420
|
3 |
|
static::_initRelationships(); |
421
|
|
|
|
422
|
3 |
|
static::$_relationshipsDefined[$className] = true; |
423
|
|
|
} |
424
|
|
|
|
425
|
125 |
|
if (empty(static::$_eventsDefined[$className])) { |
426
|
7 |
|
static::_defineEvents(); |
427
|
|
|
|
428
|
7 |
|
static::$_eventsDefined[$className] = true; |
429
|
|
|
} |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* getValue Pass thru for __get |
434
|
|
|
* |
435
|
|
|
* @param string $name The name of the field you want to get. |
436
|
|
|
* |
437
|
|
|
* @return mixed Value of the field you wanted if it exists or null otherwise. |
438
|
|
|
*/ |
439
|
83 |
|
public function getValue($name) |
440
|
|
|
{ |
441
|
|
|
switch ($name) { |
442
|
83 |
|
case 'isDirty': |
443
|
23 |
|
return $this->_isDirty; |
444
|
|
|
|
445
|
79 |
|
case 'isPhantom': |
446
|
24 |
|
return $this->_isPhantom; |
447
|
|
|
|
448
|
77 |
|
case 'wasPhantom': |
449
|
3 |
|
return $this->_wasPhantom; |
450
|
|
|
|
451
|
77 |
|
case 'isValid': |
452
|
3 |
|
return $this->_isValid; |
453
|
|
|
|
454
|
77 |
|
case 'isNew': |
455
|
3 |
|
return $this->_isNew; |
456
|
|
|
|
457
|
77 |
|
case 'isUpdated': |
458
|
3 |
|
return $this->_isUpdated; |
459
|
|
|
|
460
|
77 |
|
case 'validationErrors': |
461
|
38 |
|
return array_filter($this->_validationErrors); |
462
|
|
|
|
463
|
71 |
|
case 'data': |
464
|
25 |
|
return $this->getData(); |
465
|
|
|
|
466
|
56 |
|
case 'originalValues': |
467
|
3 |
|
return $this->_originalValues; |
468
|
|
|
|
469
|
|
|
default: |
470
|
54 |
|
{ |
471
|
|
|
// handle field |
472
|
54 |
|
if (static::fieldExists($name)) { |
473
|
54 |
|
return $this->_getFieldValue($name); |
474
|
54 |
|
} |
475
|
|
|
// handle relationship |
476
|
12 |
|
elseif (static::isRelational()) { |
477
|
10 |
|
if (static::_relationshipExists($name)) { |
478
|
54 |
|
return $this->_getRelationshipValue($name); |
479
|
54 |
|
} |
480
|
54 |
|
} |
481
|
|
|
// default Handle to ID if not caught by fieldExists |
482
|
2 |
|
elseif ($name == static::$handleField) { |
483
|
54 |
|
return $this->_getFieldValue('ID'); |
484
|
54 |
|
} |
485
|
54 |
|
} |
486
|
|
|
} |
487
|
|
|
// undefined |
488
|
2 |
|
return null; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Sets a value on this model. |
493
|
|
|
* |
494
|
|
|
* @param string $name |
495
|
|
|
* @param mixed $value |
496
|
|
|
* @return void|false False if the field does not exist. Void otherwise. |
497
|
|
|
*/ |
498
|
7 |
|
public function setValue($name, $value) |
499
|
|
|
{ |
500
|
|
|
// handle field |
501
|
7 |
|
if (static::fieldExists($name)) { |
502
|
7 |
|
$this->_setFieldValue($name, $value); |
503
|
|
|
} |
504
|
|
|
// undefined |
505
|
|
|
else { |
506
|
1 |
|
return false; |
507
|
|
|
} |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* Checks if this model is versioned. |
512
|
|
|
* |
513
|
|
|
* @return boolean Returns true if this class is defined with Divergence\Models\Versioning as a trait. |
514
|
|
|
*/ |
515
|
112 |
|
public static function isVersioned() |
516
|
|
|
{ |
517
|
112 |
|
return in_array('Divergence\\Models\\Versioning', class_uses(get_called_class())); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* Checks if this model is ready for relationships. |
522
|
|
|
* |
523
|
|
|
* @return boolean Returns true if this class is defined with Divergence\Models\Relations as a trait. |
524
|
|
|
*/ |
525
|
125 |
|
public static function isRelational() |
526
|
|
|
{ |
527
|
125 |
|
return in_array('Divergence\\Models\\Relations', class_uses(get_called_class())); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Create a new object from this model. |
532
|
|
|
* |
533
|
|
|
* @param array $values Array of keys as fields and values. |
534
|
|
|
* @param boolean $save If the object should be immediately saved to database before being returned. |
535
|
|
|
* @return static An object of this model. |
536
|
|
|
*/ |
537
|
26 |
|
public static function create($values = [], $save = false) |
538
|
|
|
{ |
539
|
26 |
|
$className = get_called_class(); |
540
|
|
|
|
541
|
|
|
// create class |
542
|
|
|
/** @var ActiveRecord */ |
543
|
26 |
|
$ActiveRecord = new $className(); |
544
|
26 |
|
$ActiveRecord->setFields($values); |
545
|
|
|
|
546
|
26 |
|
if ($save) { |
547
|
4 |
|
$ActiveRecord->save(); |
548
|
|
|
} |
549
|
|
|
|
550
|
26 |
|
return $ActiveRecord; |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* Checks if a model is a certain class. |
555
|
|
|
* @param string $class Check if the model matches this class. |
556
|
|
|
* @return boolean True if model matches the class provided. False otherwise. |
557
|
|
|
*/ |
558
|
1 |
|
public function isA($class): bool |
559
|
|
|
{ |
560
|
1 |
|
return is_a($this, $class); |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* Used to instantiate a new model of a different class with this model's field's. Useful when you have similar classes or subclasses with the same parent. |
565
|
|
|
* |
566
|
|
|
* @param string $className If you leave this blank the return will be $this |
567
|
|
|
* @param array $fieldValues Optional. Any field values you want to override. |
568
|
|
|
* @return static A new model of a different class with this model's field's. Useful when you have similar classes or subclasses with the same parent. |
569
|
|
|
*/ |
570
|
2 |
|
public function changeClass($className = false, $fieldValues = false) |
571
|
|
|
{ |
572
|
2 |
|
if (!$className) { |
573
|
1 |
|
return $this; |
574
|
|
|
} |
575
|
|
|
|
576
|
2 |
|
$this->_record[static::_cn('Class')] = $className; |
577
|
2 |
|
$ActiveRecord = new $className($this->_record, true, $this->isPhantom); |
578
|
|
|
|
579
|
2 |
|
if ($fieldValues) { |
580
|
1 |
|
$ActiveRecord->setFields($fieldValues); |
581
|
|
|
} |
582
|
|
|
|
583
|
2 |
|
if (!$this->isPhantom) { |
584
|
|
|
$ActiveRecord->save(); |
585
|
|
|
} |
586
|
|
|
|
587
|
2 |
|
return $ActiveRecord; |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
/** |
591
|
|
|
* Change multiple fields in the model with an array. |
592
|
|
|
* |
593
|
|
|
* @param array $values Field/values array to change multiple fields in this model. |
594
|
|
|
* @return void |
595
|
|
|
*/ |
596
|
37 |
|
public function setFields($values) |
597
|
|
|
{ |
598
|
37 |
|
foreach ($values as $field => $value) { |
599
|
34 |
|
$this->_setFieldValue($field, $value); |
600
|
|
|
} |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
/** |
604
|
|
|
* Change one field in the model. |
605
|
|
|
* |
606
|
|
|
* @param string $field |
607
|
|
|
* @param mixed $value |
608
|
|
|
* @return void |
609
|
|
|
*/ |
610
|
1 |
|
public function setField($field, $value) |
611
|
|
|
{ |
612
|
1 |
|
$this->_setFieldValue($field, $value); |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Implements JsonSerializable for this class. |
617
|
|
|
* |
618
|
|
|
* @return array Return for extension JsonSerializable |
619
|
|
|
*/ |
620
|
6 |
|
public function jsonSerialize(): array |
621
|
|
|
{ |
622
|
6 |
|
return $this->getData(); |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Gets normalized object data. |
627
|
|
|
* |
628
|
|
|
* @return array The model's data as a normal array with any validation errors included. |
629
|
|
|
*/ |
630
|
38 |
|
public function getData(): array |
631
|
|
|
{ |
632
|
38 |
|
$data = []; |
633
|
|
|
|
634
|
38 |
|
foreach (static::$_classFields[get_called_class()] as $field => $options) { |
635
|
38 |
|
$data[$field] = $this->_getFieldValue($field); |
636
|
|
|
} |
637
|
|
|
|
638
|
38 |
|
if ($this->validationErrors) { |
639
|
1 |
|
$data['validationErrors'] = $this->validationErrors; |
640
|
|
|
} |
641
|
|
|
|
642
|
38 |
|
return $data; |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
/** |
646
|
|
|
* Checks if a field has been changed from it's value when this object was created. |
647
|
|
|
* |
648
|
|
|
* @param string $field |
649
|
|
|
* @return boolean |
650
|
|
|
*/ |
651
|
1 |
|
public function isFieldDirty($field): bool |
652
|
|
|
{ |
653
|
1 |
|
return $this->isPhantom || array_key_exists($field, $this->_originalValues); |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
/** |
657
|
|
|
* Gets values that this model was instantiated with for a given field. |
658
|
|
|
* |
659
|
|
|
* @param string $field Field name |
660
|
|
|
* @return mixed |
661
|
|
|
*/ |
662
|
1 |
|
public function getOriginalValue($field) |
663
|
|
|
{ |
664
|
1 |
|
return $this->_originalValues[$field]; |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* Fires a DB::clearCachedRecord a key static::$tableName.'/'.static::getPrimaryKey() |
669
|
|
|
* |
670
|
|
|
* @return void |
671
|
|
|
*/ |
672
|
20 |
|
public function clearCaches() |
673
|
|
|
{ |
674
|
20 |
|
foreach ($this->getClassFields() as $field => $options) { |
675
|
20 |
|
if (!empty($options['unique']) || !empty($options['primary'])) { |
676
|
20 |
|
$key = sprintf('%s/%s', static::$tableName, $field); |
677
|
20 |
|
DB::clearCachedRecord($key); |
678
|
|
|
} |
679
|
|
|
} |
680
|
|
|
} |
681
|
|
|
|
682
|
|
|
/** |
683
|
|
|
* Runs the before save event function one at a time for any class that had $beforeSave configured in the ancestor tree. |
684
|
|
|
*/ |
685
|
21 |
|
public function beforeSave() |
686
|
|
|
{ |
687
|
21 |
|
foreach (static::$_classBeforeSave as $beforeSave) { |
688
|
1 |
|
if (is_callable($beforeSave)) { |
689
|
1 |
|
$beforeSave($this); |
690
|
|
|
} |
691
|
|
|
} |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
/** |
695
|
|
|
* Runs the after save event function one at a time for any class that had $beforeSave configured in the ancestor tree. Will only fire if save was successful. |
696
|
|
|
*/ |
697
|
19 |
|
public function afterSave() |
698
|
|
|
{ |
699
|
19 |
|
foreach (static::$_classAfterSave as $afterSave) { |
700
|
1 |
|
if (is_callable($afterSave)) { |
701
|
1 |
|
$afterSave($this); |
702
|
|
|
} |
703
|
|
|
} |
704
|
|
|
} |
705
|
|
|
|
706
|
|
|
/** |
707
|
|
|
* Saves this object to the database currently in use. |
708
|
|
|
* |
709
|
|
|
* @param bool $deep Default is true. When true will try to save any dirty models in any defined and initialized relationships. |
710
|
|
|
* |
711
|
|
|
* @uses $this->_isPhantom |
712
|
|
|
* @uses $this->_isDirty |
713
|
|
|
*/ |
714
|
20 |
|
public function save($deep = true) |
715
|
|
|
{ |
716
|
|
|
// run before save |
717
|
20 |
|
$this->beforeSave(); |
718
|
|
|
|
719
|
20 |
|
if (static::isVersioned()) { |
720
|
13 |
|
$this->beforeVersionedSave(); |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
// set created |
724
|
20 |
|
if (static::fieldExists('Created') && (!$this->Created || ($this->Created == 'CURRENT_TIMESTAMP'))) { |
725
|
8 |
|
$this->Created = $this->_record['Created'] = time(); |
726
|
8 |
|
unset($this->_convertedValues['Created']); |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
// validate |
730
|
20 |
|
if (!$this->validate($deep)) { |
731
|
|
|
throw new Exception('Cannot save invalid record'); |
732
|
|
|
} |
733
|
|
|
|
734
|
20 |
|
$this->clearCaches(); |
735
|
|
|
|
736
|
20 |
|
if ($this->isDirty) { |
737
|
|
|
// prepare record values |
738
|
19 |
|
$recordValues = $this->_prepareRecordValues(); |
739
|
|
|
|
740
|
|
|
// transform record to set array |
741
|
19 |
|
$set = static::_mapValuesToSet($recordValues); |
742
|
|
|
|
743
|
|
|
// create new or update existing |
744
|
19 |
|
if ($this->_isPhantom) { |
745
|
15 |
|
DB::nonQuery((new Insert())->setTable(static::$tableName)->set($set), null, [static::class,'handleException']); |
746
|
14 |
|
$primaryKey = $this->getPrimaryKey(); |
747
|
14 |
|
$insertID = DB::insertID(); |
748
|
14 |
|
$fields = static::getClassFields(); |
749
|
14 |
|
if (($fields[$primaryKey]['type'] ?? false) === 'integer') { |
750
|
14 |
|
$insertID = intval($insertID); |
751
|
|
|
} |
752
|
14 |
|
$this->_record[$primaryKey] = $insertID; |
753
|
14 |
|
$this->$primaryKey = $insertID; |
754
|
14 |
|
$this->_isPhantom = false; |
755
|
14 |
|
$this->_isNew = true; |
756
|
5 |
|
} elseif (count($set)) { |
757
|
5 |
|
DB::nonQuery((new Update())->setTable(static::$tableName)->set($set)->where( |
758
|
5 |
|
sprintf('`%s` = %u', static::_cn($this->getPrimaryKey()), (string)$this->getPrimaryKeyValue()) |
759
|
5 |
|
), null, [static::class,'handleException']); |
760
|
|
|
|
761
|
4 |
|
$this->_isUpdated = true; |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
// update state |
765
|
17 |
|
$this->_isDirty = false; |
766
|
17 |
|
if (static::isVersioned()) { |
767
|
10 |
|
$this->afterVersionedSave(); |
768
|
|
|
} |
769
|
|
|
} |
770
|
18 |
|
$this->afterSave(); |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
|
774
|
|
|
/** |
775
|
|
|
* Deletes this object. |
776
|
|
|
* |
777
|
|
|
* @return bool True if database returns number of affected rows above 0. False otherwise. |
778
|
|
|
*/ |
779
|
9 |
|
public function destroy(): bool |
780
|
|
|
{ |
781
|
9 |
|
if (static::isVersioned()) { |
782
|
5 |
|
if (static::$createRevisionOnDestroy) { |
783
|
|
|
// save a copy to history table |
784
|
5 |
|
if ($this->fieldExists('Created')) { |
785
|
5 |
|
$this->Created = time(); |
786
|
|
|
} |
787
|
|
|
|
788
|
5 |
|
$recordValues = $this->_prepareRecordValues(); |
789
|
5 |
|
$set = static::_mapValuesToSet($recordValues); |
790
|
|
|
|
791
|
5 |
|
DB::nonQuery((new Insert())->setTable(static::getHistoryTable())->set($set), null, [static::class,'handleException']); |
792
|
|
|
} |
793
|
|
|
} |
794
|
|
|
|
795
|
8 |
|
return static::delete((string)$this->getPrimaryKeyValue()); |
796
|
|
|
} |
797
|
|
|
|
798
|
|
|
/** |
799
|
|
|
* Delete by ID |
800
|
|
|
* |
801
|
|
|
* @param int|string $id |
802
|
|
|
* @return bool True if database returns number of affected rows above 0. False otherwise. |
803
|
|
|
*/ |
804
|
8 |
|
public static function delete($id): bool |
805
|
|
|
{ |
806
|
8 |
|
DB::nonQuery((new Delete())->setTable(static::$tableName)->where(sprintf('`%s` = %u', static::_cn(static::$primaryKey ? static::$primaryKey : 'ID'), $id)), null, [static::class,'handleException']); |
807
|
|
|
|
808
|
8 |
|
return DB::affectedRows() > 0; |
809
|
|
|
} |
810
|
|
|
|
811
|
|
|
/** |
812
|
|
|
* Checks of a field exists for this model in the fields config. |
813
|
|
|
* |
814
|
|
|
* @param string $field Name of the field |
815
|
|
|
* @return bool True if the field exists. False otherwise. |
816
|
|
|
*/ |
817
|
125 |
|
public static function fieldExists($field): bool |
818
|
|
|
{ |
819
|
125 |
|
static::init(); |
820
|
125 |
|
return array_key_exists($field, static::$_classFields[get_called_class()]); |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
/** |
824
|
|
|
* Returns the current configuration of class fields for the called class. |
825
|
|
|
* |
826
|
|
|
* @return array Current configuration of class fields for the called class. |
827
|
|
|
*/ |
828
|
21 |
|
public static function getClassFields(): array |
829
|
|
|
{ |
830
|
21 |
|
static::init(); |
831
|
21 |
|
return static::$_classFields[get_called_class()]; |
832
|
|
|
} |
833
|
|
|
|
834
|
|
|
/** |
835
|
|
|
* Returns either a field option or an array of all the field options. |
836
|
|
|
* |
837
|
|
|
* @param string $field Name of the field. |
838
|
|
|
* @param boolean $optionKey |
839
|
|
|
* @return array|mixed |
840
|
|
|
*/ |
841
|
1 |
|
public static function getFieldOptions($field, $optionKey = false) |
842
|
|
|
{ |
843
|
1 |
|
if ($optionKey) { |
844
|
1 |
|
return static::$_classFields[get_called_class()][$field][$optionKey]; |
845
|
|
|
} else { |
846
|
1 |
|
return static::$_classFields[get_called_class()][$field]; |
847
|
|
|
} |
848
|
|
|
} |
849
|
|
|
|
850
|
|
|
/** |
851
|
|
|
* Returns columnName for given field |
852
|
|
|
* @param string $field name of field |
853
|
|
|
* @return string column name |
854
|
|
|
*/ |
855
|
122 |
|
public static function getColumnName($field) |
856
|
|
|
{ |
857
|
122 |
|
static::init(); |
858
|
122 |
|
if (!static::fieldExists($field)) { |
859
|
2 |
|
throw new Exception('getColumnName called on nonexisting column: ' . get_called_class().'->'.$field); |
860
|
|
|
} |
861
|
|
|
|
862
|
121 |
|
return static::$_classFields[get_called_class()][$field]['columnName']; |
863
|
|
|
} |
864
|
|
|
|
865
|
2 |
|
public static function mapFieldOrder($order) |
866
|
|
|
{ |
867
|
2 |
|
return static::_mapFieldOrder($order); |
868
|
|
|
} |
869
|
|
|
|
870
|
1 |
|
public static function mapConditions($conditions) |
871
|
|
|
{ |
872
|
1 |
|
return static::_mapConditions($conditions); |
873
|
|
|
} |
874
|
|
|
|
875
|
|
|
/** |
876
|
|
|
* Returns static::$rootClass for the called class. |
877
|
|
|
* |
878
|
|
|
* @return string static::$rootClass for the called class. |
879
|
|
|
*/ |
880
|
1 |
|
public function getRootClass(): string |
881
|
|
|
{ |
882
|
1 |
|
return static::$rootClass; |
|
|
|
|
883
|
|
|
} |
884
|
|
|
|
885
|
|
|
/** |
886
|
|
|
* Sets an array of validation errors for this object. |
887
|
|
|
* |
888
|
|
|
* @param array $array Validation errors in the form Field Name => error message |
889
|
|
|
* @return void |
890
|
|
|
*/ |
891
|
2 |
|
public function addValidationErrors($array) |
892
|
|
|
{ |
893
|
2 |
|
foreach ($array as $field => $errorMessage) { |
894
|
2 |
|
$this->addValidationError($field, $errorMessage); |
895
|
|
|
} |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
/** |
899
|
|
|
* Sets a validation error for this object. Sets $this->_isValid to false. |
900
|
|
|
* |
901
|
|
|
* @param string $field |
902
|
|
|
* @param string $errorMessage |
903
|
|
|
* @return void |
904
|
|
|
*/ |
905
|
2 |
|
public function addValidationError($field, $errorMessage) |
906
|
|
|
{ |
907
|
2 |
|
$this->_isValid = false; |
908
|
2 |
|
$this->_validationErrors[$field] = $errorMessage; |
909
|
|
|
} |
910
|
|
|
|
911
|
|
|
/** |
912
|
|
|
* Get a validation error for a given field. |
913
|
|
|
* |
914
|
|
|
* @param string $field Name of the field. |
915
|
|
|
* @return string|null A validation error for the field. Null is no validation error found. |
916
|
|
|
*/ |
917
|
1 |
|
public function getValidationError($field) |
918
|
|
|
{ |
919
|
|
|
// break apart path |
920
|
1 |
|
$crumbs = explode('.', $field); |
921
|
|
|
|
922
|
|
|
// resolve path recursively |
923
|
1 |
|
$cur = &$this->_validationErrors; |
924
|
1 |
|
while ($crumb = array_shift($crumbs)) { |
925
|
1 |
|
if (array_key_exists($crumb, $cur)) { |
926
|
1 |
|
$cur = &$cur[$crumb]; |
927
|
|
|
} else { |
928
|
1 |
|
return null; |
929
|
|
|
} |
930
|
|
|
} |
931
|
|
|
|
932
|
|
|
// return current value |
933
|
1 |
|
return $cur; |
934
|
|
|
} |
935
|
|
|
|
936
|
|
|
/** |
937
|
|
|
* Validates the model. Instantiates a new RecordValidator object and sets it to $this->_validator. |
938
|
|
|
* Then validates against the set validators in this model. Returns $this->_isValid |
939
|
|
|
* |
940
|
|
|
* @param boolean $deep If true will attempt to validate any already loaded relationship members. |
941
|
|
|
* @return bool $this->_isValid which could be set to true or false depending on what happens with the RecordValidator. |
942
|
|
|
*/ |
943
|
20 |
|
public function validate($deep = true) |
944
|
|
|
{ |
945
|
20 |
|
$this->_isValid = true; |
946
|
20 |
|
$this->_validationErrors = []; |
947
|
|
|
|
948
|
20 |
|
if (!isset($this->_validator)) { |
949
|
20 |
|
$this->_validator = new RecordValidator($this->_record); |
950
|
|
|
} else { |
951
|
6 |
|
$this->_validator->resetErrors(); |
952
|
|
|
} |
953
|
|
|
|
954
|
20 |
|
foreach (static::$validators as $validator) { |
955
|
|
|
$this->_validator->validate($validator); |
956
|
|
|
} |
957
|
|
|
|
958
|
20 |
|
$this->finishValidation(); |
959
|
|
|
|
960
|
20 |
|
if ($deep) { |
961
|
|
|
// validate relationship objects |
962
|
20 |
|
if (!empty(static::$_classRelationships[get_called_class()])) { |
963
|
1 |
|
foreach (static::$_classRelationships[get_called_class()] as $relationship => $options) { |
964
|
1 |
|
if (empty($this->_relatedObjects[$relationship])) { |
965
|
1 |
|
continue; |
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
|
969
|
1 |
|
if ($options['type'] == 'one-one') { |
970
|
|
|
if ($this->_relatedObjects[$relationship]->isDirty) { |
971
|
|
|
$this->_relatedObjects[$relationship]->validate(); |
972
|
|
|
$this->_isValid = $this->_isValid && $this->_relatedObjects[$relationship]->isValid; |
973
|
|
|
$this->_validationErrors[$relationship] = $this->_relatedObjects[$relationship]->validationErrors; |
974
|
|
|
} |
975
|
1 |
|
} elseif ($options['type'] == 'one-many') { |
976
|
1 |
|
foreach ($this->_relatedObjects[$relationship] as $i => $object) { |
977
|
1 |
|
if ($object->isDirty) { |
978
|
|
|
$object->validate(); |
979
|
|
|
$this->_isValid = $this->_isValid && $object->isValid; |
980
|
|
|
$this->_validationErrors[$relationship][$i] = $object->validationErrors; |
981
|
|
|
} |
982
|
|
|
} |
983
|
|
|
} |
984
|
|
|
} // foreach |
985
|
|
|
} // if |
986
|
|
|
} // if ($deep) |
987
|
|
|
|
988
|
20 |
|
return $this->_isValid; |
989
|
|
|
} |
990
|
|
|
|
991
|
|
|
/** |
992
|
|
|
* Handle any errors that come from the database client in the process of running a query. |
993
|
|
|
* If the error code from MySQL 42S02 (table not found) is thrown this method will attempt to create the table before running the original query and returning. |
994
|
|
|
* Other errors will be routed through to DB::handleException |
995
|
|
|
* |
996
|
|
|
* @param Exception $exception |
997
|
|
|
* @param string $query |
998
|
|
|
* @param array $queryLog |
999
|
|
|
* @param array|string $parameters |
1000
|
|
|
* @return mixed Retried query result or the return from DB::handleException |
1001
|
|
|
*/ |
1002
|
7 |
|
public static function handleException(\Exception $e, $query = null, $queryLog = null, $parameters = null) |
1003
|
|
|
{ |
1004
|
7 |
|
$Connection = DB::getConnection(); |
1005
|
7 |
|
if ($Connection->errorCode() == '42S02' && static::$autoCreateTables) { |
1006
|
2 |
|
$CreateTable = SQL::getCreateTable(static::$rootClass); |
1007
|
|
|
|
1008
|
|
|
// history versions table |
1009
|
2 |
|
if (static::isVersioned()) { |
1010
|
1 |
|
$CreateTable .= SQL::getCreateTable(static::$rootClass, true); |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
2 |
|
$Statement = $Connection->query($CreateTable); |
1014
|
|
|
|
1015
|
|
|
// check for errors |
1016
|
2 |
|
$ErrorInfo = $Statement->errorInfo(); |
1017
|
|
|
|
1018
|
|
|
// handle query error |
1019
|
2 |
|
if ($ErrorInfo[0] != '00000') { |
1020
|
|
|
self::handleException($query, $queryLog); |
|
|
|
|
1021
|
|
|
} |
1022
|
|
|
|
1023
|
|
|
// clear buffer (required for the next query to work without running fetchAll first |
1024
|
2 |
|
$Statement->closeCursor(); |
1025
|
|
|
|
1026
|
2 |
|
return $Connection->query((string)$query); // now the query should finish with no error |
1027
|
|
|
} else { |
1028
|
5 |
|
return DB::handleException($e, $query, $queryLog); |
1029
|
|
|
} |
1030
|
|
|
} |
1031
|
|
|
|
1032
|
|
|
/** |
1033
|
|
|
* Iterates through all static::$beforeSave and static::$afterSave in this class and any of it's parent classes. |
1034
|
|
|
* Checks if they are callables and if they are adds them to static::$_classBeforeSave[] and static::$_classAfterSave[] |
1035
|
|
|
* |
1036
|
|
|
* @return void |
1037
|
|
|
* |
1038
|
|
|
* @uses static::$beforeSave |
1039
|
|
|
* @uses static::$afterSave |
1040
|
|
|
* @uses static::$_classBeforeSave |
1041
|
|
|
* @uses static::$_classAfterSave |
1042
|
|
|
*/ |
1043
|
7 |
|
protected static function _defineEvents() |
1044
|
|
|
{ |
1045
|
|
|
// run before save |
1046
|
7 |
|
$className = get_called_class(); |
1047
|
|
|
|
1048
|
|
|
// merge fields from first ancestor up |
1049
|
7 |
|
$classes = class_parents($className); |
1050
|
7 |
|
array_unshift($classes, $className); |
1051
|
|
|
|
1052
|
7 |
|
while ($class = array_pop($classes)) { |
1053
|
7 |
|
if (is_callable($class::$beforeSave)) { |
1054
|
|
|
if (!empty($class::$beforeSave)) { |
1055
|
|
|
if (!in_array($class::$beforeSave, static::$_classBeforeSave)) { |
1056
|
|
|
static::$_classBeforeSave[] = $class::$beforeSave; |
1057
|
|
|
} |
1058
|
|
|
} |
1059
|
|
|
} |
1060
|
|
|
|
1061
|
7 |
|
if (is_callable($class::$afterSave)) { |
1062
|
|
|
if (!empty($class::$afterSave)) { |
1063
|
|
|
if (!in_array($class::$afterSave, static::$_classAfterSave)) { |
1064
|
|
|
static::$_classAfterSave[] = $class::$afterSave; |
1065
|
|
|
} |
1066
|
|
|
} |
1067
|
|
|
} |
1068
|
|
|
} |
1069
|
|
|
} |
1070
|
|
|
|
1071
|
|
|
/** |
1072
|
|
|
* Merges all static::$_classFields in this class and any of it's parent classes. |
1073
|
|
|
* Sets the merged value to static::$_classFields[get_called_class()] |
1074
|
|
|
* |
1075
|
|
|
* @return void |
1076
|
|
|
* |
1077
|
|
|
* @uses static::$_classFields |
1078
|
|
|
* @uses static::$_classFields |
1079
|
|
|
*/ |
1080
|
7 |
|
protected static function _defineFields() |
1081
|
|
|
{ |
1082
|
7 |
|
$className = get_called_class(); |
1083
|
|
|
|
1084
|
|
|
// skip if fields already defined |
1085
|
7 |
|
if (isset(static::$_classFields[$className])) { |
1086
|
|
|
return; |
1087
|
|
|
} |
1088
|
|
|
|
1089
|
|
|
// merge fields from first ancestor up |
1090
|
7 |
|
$classes = class_parents($className); |
1091
|
7 |
|
array_unshift($classes, $className); |
1092
|
|
|
|
1093
|
7 |
|
static::$_classFields[$className] = []; |
1094
|
7 |
|
while ($class = array_pop($classes)) { |
1095
|
7 |
|
if (!empty($class::$fields)) { |
1096
|
|
|
static::$_classFields[$className] = array_merge(static::$_classFields[$className], $class::$fields); |
1097
|
|
|
} |
1098
|
7 |
|
$attributeFields = $class::_definedAttributeFields(); |
1099
|
7 |
|
if (!empty($attributeFields['fields'])) { |
1100
|
6 |
|
static::$_classFields[$className] = array_merge(static::$_classFields[$className], $attributeFields['fields']); |
1101
|
|
|
} |
1102
|
7 |
|
if (!empty($attributeFields['relations'])) { |
1103
|
1 |
|
$class::$relationships = $attributeFields['relations']; |
1104
|
|
|
} |
1105
|
|
|
} |
1106
|
|
|
} |
1107
|
|
|
|
1108
|
|
|
/** |
1109
|
|
|
* This function grabs all protected fields on the model and uses that as the basis for what constitutes a mapped field |
1110
|
|
|
* It skips a certain list of protected fields that are built in for ORM operation |
1111
|
|
|
* |
1112
|
|
|
* @return array |
1113
|
|
|
*/ |
1114
|
7 |
|
public static function _definedAttributeFields(): array |
1115
|
|
|
{ |
1116
|
7 |
|
$fields = []; |
1117
|
7 |
|
$relations = []; |
1118
|
7 |
|
$properties = (new ReflectionClass(static::class))->getProperties(); |
1119
|
7 |
|
if (!empty($properties)) { |
1120
|
7 |
|
foreach ($properties as $property) { |
1121
|
7 |
|
if ($property->isProtected()) { |
1122
|
|
|
|
1123
|
|
|
// skip these because they are built in |
1124
|
7 |
|
if (in_array($property->getName(), [ |
1125
|
7 |
|
'_classFields','_classRelationships','_classBeforeSave','_classAfterSave','_fieldsDefined','_relationshipsDefined','_eventsDefined','_record','_validator' |
1126
|
7 |
|
,'_validationErrors','_isDirty','_isValid','_convertedValues','_originalValues','_isPhantom','_wasPhantom','_isNew','_isUpdated','_relatedObjects' |
1127
|
7 |
|
])) { |
1128
|
7 |
|
continue; |
1129
|
|
|
} |
1130
|
|
|
|
1131
|
6 |
|
$isRelationship = false; |
1132
|
|
|
|
1133
|
6 |
|
if ($attributes = $property->getAttributes()) { |
1134
|
6 |
|
foreach ($attributes as $attribute) { |
1135
|
6 |
|
$attributeName = $attribute->getName(); |
1136
|
6 |
|
if ($attributeName === Column::class) { |
1137
|
6 |
|
$fields[$property->getName()] = array_merge($attribute->getArguments(), ['attributeField'=>true]); |
1138
|
|
|
} |
1139
|
|
|
|
1140
|
6 |
|
if ($attributeName === Relation::class) { |
1141
|
1 |
|
$isRelationship = true; |
1142
|
1 |
|
$relations[$property->getName()] = $attribute->getArguments(); |
1143
|
|
|
} |
1144
|
|
|
} |
1145
|
|
|
} else { |
1146
|
|
|
// default |
1147
|
4 |
|
if (!$isRelationship) { |
1148
|
4 |
|
$fields[$property->getName()] = []; |
1149
|
|
|
} |
1150
|
|
|
} |
1151
|
|
|
} |
1152
|
|
|
} |
1153
|
|
|
} |
1154
|
7 |
|
return [ |
1155
|
7 |
|
'fields' => $fields, |
1156
|
7 |
|
'relations' => $relations |
1157
|
7 |
|
]; |
1158
|
|
|
} |
1159
|
|
|
|
1160
|
|
|
|
1161
|
|
|
/** |
1162
|
|
|
* Called after _defineFields to initialize and apply defaults to the fields property |
1163
|
|
|
* Must be idempotent as it may be applied multiple times up the inheritance chain |
1164
|
|
|
* @return void |
1165
|
|
|
* |
1166
|
|
|
* @uses static::$_classFields |
1167
|
|
|
*/ |
1168
|
7 |
|
protected static function _initFields() |
1169
|
|
|
{ |
1170
|
7 |
|
$className = get_called_class(); |
1171
|
7 |
|
$optionsMask = [ |
1172
|
7 |
|
'type' => null, |
1173
|
7 |
|
'length' => null, |
1174
|
7 |
|
'primary' => null, |
1175
|
7 |
|
'unique' => null, |
1176
|
7 |
|
'autoincrement' => null, |
1177
|
7 |
|
'notnull' => null, |
1178
|
7 |
|
'unsigned' => null, |
1179
|
7 |
|
'default' => null, |
1180
|
7 |
|
'values' => null, |
1181
|
7 |
|
]; |
1182
|
|
|
|
1183
|
|
|
// apply default values to field definitions |
1184
|
7 |
|
if (!empty(static::$_classFields[$className])) { |
1185
|
6 |
|
$fields = []; |
1186
|
|
|
|
1187
|
6 |
|
foreach (static::$_classFields[$className] as $field => $options) { |
1188
|
6 |
|
if (is_string($field)) { |
1189
|
6 |
|
if (is_array($options)) { |
1190
|
6 |
|
$fields[$field] = array_merge($optionsMask, static::$fieldDefaults, ['columnName' => $field], $options); |
1191
|
|
|
} elseif (is_string($options)) { |
1192
|
|
|
$fields[$field] = array_merge($optionsMask, static::$fieldDefaults, ['columnName' => $field, 'type' => $options]); |
1193
|
|
|
} elseif ($options == null) { |
1194
|
6 |
|
continue; |
1195
|
|
|
} |
1196
|
|
|
} elseif (is_string($options)) { |
1197
|
|
|
$field = $options; |
1198
|
|
|
$fields[$field] = array_merge($optionsMask, static::$fieldDefaults, ['columnName' => $field]); |
1199
|
|
|
} |
1200
|
|
|
|
1201
|
6 |
|
if ($field == 'Class') { |
1202
|
|
|
// apply Class enum values |
1203
|
6 |
|
$fields[$field]['values'] = static::$subClasses; |
1204
|
|
|
} |
1205
|
|
|
|
1206
|
6 |
|
if (!isset($fields[$field]['blankisnull']) && empty($fields[$field]['notnull'])) { |
1207
|
6 |
|
$fields[$field]['blankisnull'] = true; |
1208
|
|
|
} |
1209
|
|
|
|
1210
|
6 |
|
if ($fields[$field]['autoincrement']) { |
1211
|
6 |
|
$fields[$field]['primary'] = true; |
1212
|
|
|
} |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
6 |
|
static::$_classFields[$className] = $fields; |
1216
|
|
|
} |
1217
|
|
|
} |
1218
|
|
|
|
1219
|
|
|
|
1220
|
|
|
/** |
1221
|
|
|
* Returns class name for instantiating given record |
1222
|
|
|
* @param array $record record |
1223
|
|
|
* @return string class name |
1224
|
|
|
*/ |
1225
|
98 |
|
protected static function _getRecordClass($record) |
1226
|
|
|
{ |
1227
|
98 |
|
$static = get_called_class(); |
1228
|
|
|
|
1229
|
98 |
|
if (!static::fieldExists('Class')) { |
1230
|
|
|
return $static; |
1231
|
|
|
} |
1232
|
|
|
|
1233
|
98 |
|
$columnName = static::_cn('Class'); |
1234
|
|
|
|
1235
|
98 |
|
if (!empty($record[$columnName]) && is_subclass_of($record[$columnName], $static)) { |
1236
|
19 |
|
return $record[$columnName]; |
1237
|
|
|
} else { |
1238
|
79 |
|
return $static; |
1239
|
|
|
} |
1240
|
|
|
} |
1241
|
|
|
|
1242
|
|
|
/** |
1243
|
|
|
* Shorthand alias for _getColumnName |
1244
|
|
|
* @param string $field name of field |
1245
|
|
|
* @return string column name |
1246
|
|
|
*/ |
1247
|
121 |
|
protected static function _cn($field) |
1248
|
|
|
{ |
1249
|
121 |
|
return static::getColumnName($field); |
1250
|
|
|
} |
1251
|
|
|
|
1252
|
|
|
|
1253
|
65 |
|
private function applyNewValue($type, $field, $value) |
1254
|
|
|
{ |
1255
|
65 |
|
if (!isset($this->_convertedValues[$field])) { |
1256
|
65 |
|
if (is_null($value) && !in_array($type, ['set','list'])) { |
1257
|
|
|
unset($this->_convertedValues[$field]); |
1258
|
|
|
return null; |
1259
|
|
|
} |
1260
|
65 |
|
$this->_convertedValues[$field] = $value; |
1261
|
|
|
} |
1262
|
65 |
|
return $this->_convertedValues[$field]; |
1263
|
|
|
} |
1264
|
|
|
|
1265
|
|
|
/** |
1266
|
|
|
* Applies type-dependent transformations to the value in $this->_record[$fieldOptions['columnName']] |
1267
|
|
|
* Caches to $this->_convertedValues[$field] and returns the value in there. |
1268
|
|
|
* @param string $field Name of field |
1269
|
|
|
* @return mixed value |
1270
|
|
|
*/ |
1271
|
81 |
|
protected function _getFieldValue($field, $useDefault = true) |
1272
|
|
|
{ |
1273
|
81 |
|
$fieldOptions = static::$_classFields[get_called_class()][$field]; |
1274
|
|
|
|
1275
|
81 |
|
if (isset($this->_record[$fieldOptions['columnName']])) { |
1276
|
81 |
|
$value = $this->_record[$fieldOptions['columnName']]; |
1277
|
|
|
|
1278
|
81 |
|
$defaultGetMapper = static::defaultGetMapper; |
1279
|
|
|
|
1280
|
|
|
// apply type-dependent transformations |
1281
|
81 |
|
switch ($fieldOptions['type']) { |
1282
|
81 |
|
case 'timestamp': |
1283
|
33 |
|
return $this->applyNewValue($fieldOptions['type'], $field, $defaultGetMapper::getTimestampValue($value)); |
1284
|
|
|
|
1285
|
81 |
|
case 'serialized': |
1286
|
16 |
|
return $this->applyNewValue($fieldOptions['type'], $field, $defaultGetMapper::getSerializedValue($value)); |
1287
|
|
|
|
1288
|
81 |
|
case 'set': |
1289
|
81 |
|
case 'list': |
1290
|
16 |
|
return $this->applyNewValue($fieldOptions['type'], $field, $defaultGetMapper::getListValue($value, $fieldOptions['delimiter'])); |
1291
|
|
|
|
1292
|
81 |
|
case 'int': |
1293
|
81 |
|
case 'integer': |
1294
|
66 |
|
case 'uint': |
1295
|
64 |
|
return $this->applyNewValue($fieldOptions['type'], $field, $defaultGetMapper::getIntegerValue($value)); |
1296
|
|
|
|
1297
|
66 |
|
case 'boolean': |
1298
|
16 |
|
return $this->applyNewValue($fieldOptions['type'], $field, $defaultGetMapper::getBooleanValue($value)); |
1299
|
|
|
|
1300
|
66 |
|
case 'decimal': |
1301
|
19 |
|
return $this->applyNewValue($fieldOptions['type'], $field, $defaultGetMapper::getDecimalValue($value)); |
1302
|
|
|
|
1303
|
66 |
|
case 'password': |
1304
|
|
|
default: |
1305
|
66 |
|
return $value; |
1306
|
|
|
} |
1307
|
41 |
|
} elseif ($useDefault && isset($fieldOptions['default'])) { |
1308
|
|
|
// return default |
1309
|
16 |
|
return $fieldOptions['default']; |
1310
|
|
|
} else { |
1311
|
41 |
|
switch ($fieldOptions['type']) { |
1312
|
41 |
|
case 'set': |
1313
|
41 |
|
case 'list': |
1314
|
|
|
return []; |
1315
|
|
|
default: |
1316
|
41 |
|
return null; |
1317
|
|
|
} |
1318
|
|
|
} |
1319
|
|
|
} |
1320
|
|
|
|
1321
|
|
|
/** |
1322
|
|
|
* Sets given field's value |
1323
|
|
|
* @param string $field Name of field |
1324
|
|
|
* @param mixed $value New value |
1325
|
|
|
* @return mixed value |
1326
|
|
|
*/ |
1327
|
109 |
|
protected function _setFieldValue($field, $value) |
1328
|
|
|
{ |
1329
|
|
|
// ignore setting versioning fields |
1330
|
109 |
|
if (static::isVersioned()) { |
1331
|
53 |
|
if ($field === 'RevisionID') { |
1332
|
1 |
|
return false; |
1333
|
|
|
} |
1334
|
|
|
} |
1335
|
|
|
|
1336
|
109 |
|
if (!static::fieldExists($field)) { |
1337
|
|
|
return false; |
1338
|
|
|
} |
1339
|
109 |
|
$fieldOptions = static::$_classFields[get_called_class()][$field]; |
1340
|
|
|
|
1341
|
|
|
// no overriding autoincrements |
1342
|
109 |
|
if ($fieldOptions['autoincrement']) { |
1343
|
2 |
|
return false; |
1344
|
|
|
} |
1345
|
|
|
|
1346
|
109 |
|
$setMapper = static::defaultSetMapper; |
1347
|
|
|
|
1348
|
|
|
// pre-process value |
1349
|
109 |
|
$forceDirty = false; |
1350
|
109 |
|
switch ($fieldOptions['type']) { |
1351
|
109 |
|
case 'clob': |
1352
|
109 |
|
case 'string': |
1353
|
36 |
|
{ |
1354
|
36 |
|
$value = $setMapper::setStringValue($value); |
1355
|
36 |
|
if (!$fieldOptions['notnull'] && $fieldOptions['blankisnull'] && ($value === '' || $value === null)) { |
1356
|
36 |
|
$value = null; |
1357
|
36 |
|
} |
1358
|
36 |
|
break; |
1359
|
36 |
|
} |
1360
|
|
|
|
1361
|
109 |
|
case 'boolean': |
1362
|
12 |
|
{ |
1363
|
12 |
|
$value = $setMapper::setBooleanValue($value); |
1364
|
12 |
|
break; |
1365
|
12 |
|
} |
1366
|
|
|
|
1367
|
109 |
|
case 'decimal': |
1368
|
15 |
|
{ |
1369
|
15 |
|
$value = $setMapper::setDecimalValue($value); |
1370
|
15 |
|
break; |
1371
|
15 |
|
} |
1372
|
|
|
|
1373
|
109 |
|
case 'int': |
1374
|
109 |
|
case 'uint': |
1375
|
109 |
|
case 'integer': |
1376
|
15 |
|
{ |
1377
|
15 |
|
$value = $setMapper::setIntegerValue($value); |
1378
|
15 |
|
if (!$fieldOptions['notnull'] && ($value === '' || is_null($value))) { |
1379
|
15 |
|
$value = null; |
1380
|
15 |
|
} |
1381
|
15 |
|
break; |
1382
|
15 |
|
} |
1383
|
|
|
|
1384
|
109 |
|
case 'timestamp': |
1385
|
14 |
|
{ |
1386
|
14 |
|
unset($this->_convertedValues[$field]); |
1387
|
14 |
|
$value = $setMapper::setTimestampValue($value); |
1388
|
14 |
|
break; |
1389
|
14 |
|
} |
1390
|
|
|
|
1391
|
109 |
|
case 'date': |
1392
|
13 |
|
{ |
1393
|
13 |
|
unset($this->_convertedValues[$field]); |
1394
|
13 |
|
$value = $setMapper::setDateValue($value); |
1395
|
13 |
|
break; |
1396
|
13 |
|
} |
1397
|
|
|
|
1398
|
109 |
|
case 'serialized': |
1399
|
12 |
|
{ |
1400
|
12 |
|
if (!is_string($value)) { |
1401
|
12 |
|
$value = $setMapper::setSerializedValue($value); |
1402
|
12 |
|
} |
1403
|
12 |
|
break; |
1404
|
12 |
|
} |
1405
|
109 |
|
case 'enum': |
1406
|
109 |
|
{ |
1407
|
109 |
|
$value = $setMapper::setEnumValue($fieldOptions['values'], $value); |
1408
|
109 |
|
break; |
1409
|
109 |
|
} |
1410
|
12 |
|
case 'set': |
1411
|
12 |
|
case 'list': |
1412
|
12 |
|
{ |
1413
|
12 |
|
$value = $setMapper::setListValue($value, isset($fieldOptions['delimiter']) ? $fieldOptions['delimiter'] : null); |
1414
|
12 |
|
$this->_convertedValues[$field] = $value; |
1415
|
12 |
|
$forceDirty = true; |
1416
|
12 |
|
break; |
1417
|
12 |
|
} |
1418
|
|
|
} |
1419
|
|
|
|
1420
|
109 |
|
if ($forceDirty || (empty($this->_record[$field]) && isset($value)) || ($this->_record[$field] !== $value)) { |
1421
|
47 |
|
$this->_setValueAndMarkDirty($field, $value, $fieldOptions); |
1422
|
47 |
|
return true; |
1423
|
|
|
} else { |
1424
|
77 |
|
return false; |
1425
|
|
|
} |
1426
|
|
|
} |
1427
|
|
|
|
1428
|
47 |
|
protected function _setValueAndMarkDirty($field, $value, $fieldOptions) |
1429
|
|
|
{ |
1430
|
47 |
|
$columnName = static::_cn($field); |
1431
|
47 |
|
if (isset($this->_record[$columnName])) { |
1432
|
16 |
|
$this->_originalValues[$field] = $this->_record[$columnName]; |
1433
|
|
|
} |
1434
|
47 |
|
$this->_record[$columnName] = $value; |
1435
|
|
|
// only set value if this is an attribute mapped field |
1436
|
47 |
|
if (isset(static::$_classFields[get_called_class()][$columnName]['attributeField'])) { |
1437
|
47 |
|
$this->$columnName = $value; |
1438
|
|
|
} |
1439
|
47 |
|
$this->_isDirty = true; |
1440
|
|
|
|
1441
|
|
|
// If a model has been modified we should clear the relationship cache |
1442
|
|
|
// TODO: this can be smarter by only looking at fields that are used in the relationship configuration |
1443
|
47 |
|
if (!empty($fieldOptions['relationships']) && static::isRelational()) { |
1444
|
|
|
foreach ($fieldOptions['relationships'] as $relationship => $isCached) { |
1445
|
|
|
if ($isCached) { |
1446
|
|
|
unset($this->_relatedObjects[$relationship]); |
1447
|
|
|
} |
1448
|
|
|
} |
1449
|
|
|
} |
1450
|
|
|
} |
1451
|
|
|
|
1452
|
23 |
|
protected function _prepareRecordValues() |
1453
|
|
|
{ |
1454
|
23 |
|
$record = []; |
1455
|
|
|
|
1456
|
23 |
|
foreach (static::$_classFields[get_called_class()] as $field => $options) { |
1457
|
23 |
|
$columnName = static::_cn($field); |
1458
|
|
|
|
1459
|
23 |
|
if (array_key_exists($columnName, $this->_record) || isset($this->$columnName)) { |
1460
|
23 |
|
$value = $this->_record[$columnName] ?? $this->$columnName; |
1461
|
|
|
|
1462
|
23 |
|
if (!$value && !empty($options['blankisnull'])) { |
1463
|
23 |
|
$value = null; |
1464
|
|
|
} |
1465
|
23 |
|
} elseif (isset($options['default'])) { |
1466
|
8 |
|
$value = $options['default']; |
1467
|
|
|
} else { |
1468
|
23 |
|
continue; |
1469
|
|
|
} |
1470
|
|
|
|
1471
|
23 |
|
if (($options['type'] == 'date') && ($value == '0000-00-00') && !empty($options['blankisnull'])) { |
1472
|
|
|
$value = null; |
1473
|
|
|
} |
1474
|
23 |
|
if (($options['type'] == 'timestamp')) { |
1475
|
23 |
|
if (is_numeric($value)) { |
1476
|
14 |
|
$value = date('Y-m-d H:i:s', $value); |
1477
|
16 |
|
} elseif ($value == null && !$options['notnull']) { |
1478
|
|
|
$value = null; |
1479
|
|
|
} |
1480
|
|
|
} |
1481
|
|
|
|
1482
|
23 |
|
if (($options['type'] == 'serialized') && !is_string($value)) { |
1483
|
|
|
$value = serialize($value); |
1484
|
|
|
} |
1485
|
|
|
|
1486
|
23 |
|
if (($options['type'] == 'list') && is_array($value)) { |
1487
|
11 |
|
$delim = empty($options['delimiter']) ? ',' : $options['delimiter']; |
1488
|
11 |
|
$value = implode($delim, $value); |
1489
|
|
|
} |
1490
|
|
|
|
1491
|
23 |
|
$record[$field] = $value; |
1492
|
|
|
} |
1493
|
|
|
|
1494
|
23 |
|
return $record; |
1495
|
|
|
} |
1496
|
|
|
|
1497
|
23 |
|
protected static function _mapValuesToSet($recordValues) |
1498
|
|
|
{ |
1499
|
23 |
|
$set = []; |
1500
|
|
|
|
1501
|
23 |
|
foreach ($recordValues as $field => $value) { |
1502
|
23 |
|
$fieldConfig = static::$_classFields[get_called_class()][$field]; |
1503
|
|
|
|
1504
|
23 |
|
if ($value === null) { |
1505
|
10 |
|
$set[] = sprintf('`%s` = NULL', $fieldConfig['columnName']); |
1506
|
23 |
|
} elseif ($fieldConfig['type'] == 'timestamp' && $value == 'CURRENT_TIMESTAMP') { |
1507
|
|
|
$set[] = sprintf('`%s` = CURRENT_TIMESTAMP', $fieldConfig['columnName']); |
1508
|
23 |
|
} elseif ($fieldConfig['type'] == 'set' && is_array($value)) { |
1509
|
11 |
|
$set[] = sprintf('`%s` = "%s"', $fieldConfig['columnName'], DB::escape(join(',', $value))); |
1510
|
23 |
|
} elseif ($fieldConfig['type'] == 'boolean') { |
1511
|
16 |
|
$set[] = sprintf('`%s` = %u', $fieldConfig['columnName'], $value ? 1 : 0); |
1512
|
|
|
} else { |
1513
|
23 |
|
$set[] = sprintf('`%s` = "%s"', $fieldConfig['columnName'], DB::escape($value)); |
|
|
|
|
1514
|
|
|
} |
1515
|
|
|
} |
1516
|
|
|
|
1517
|
23 |
|
return $set; |
1518
|
|
|
} |
1519
|
|
|
|
1520
|
16 |
|
protected static function _mapFieldOrder($order) |
1521
|
|
|
{ |
1522
|
16 |
|
if (is_string($order)) { |
1523
|
4 |
|
return [$order]; |
1524
|
14 |
|
} elseif (is_array($order)) { |
1525
|
14 |
|
$r = []; |
1526
|
|
|
|
1527
|
14 |
|
foreach ($order as $key => $value) { |
1528
|
14 |
|
if (is_string($key)) { |
1529
|
13 |
|
$columnName = static::_cn($key); |
1530
|
13 |
|
$direction = strtoupper($value)=='DESC' ? 'DESC' : 'ASC'; |
1531
|
|
|
} else { |
1532
|
2 |
|
$columnName = static::_cn($value); |
1533
|
1 |
|
$direction = 'ASC'; |
1534
|
|
|
} |
1535
|
|
|
|
1536
|
13 |
|
$r[] = sprintf('`%s` %s', $columnName, $direction); |
1537
|
|
|
} |
1538
|
|
|
|
1539
|
13 |
|
return $r; |
1540
|
|
|
} |
1541
|
|
|
} |
1542
|
|
|
|
1543
|
|
|
/** |
1544
|
|
|
* @param array<string,null|string|array{'operator': string, 'value': string}> $conditions |
1545
|
|
|
* @return array |
1546
|
|
|
*/ |
1547
|
85 |
|
protected static function _mapConditions($conditions) |
1548
|
|
|
{ |
1549
|
85 |
|
foreach ($conditions as $field => &$condition) { |
1550
|
85 |
|
if (is_string($field)) { |
1551
|
83 |
|
if (isset(static::$_classFields[get_called_class()][$field])) { |
1552
|
82 |
|
$fieldOptions = static::$_classFields[get_called_class()][$field]; |
1553
|
|
|
} |
1554
|
|
|
|
1555
|
83 |
|
if ($condition === null || ($condition == '' && $fieldOptions['blankisnull'])) { |
|
|
|
|
1556
|
1 |
|
$condition = sprintf('`%s` IS NULL', static::_cn($field)); |
1557
|
83 |
|
} elseif (is_array($condition)) { |
1558
|
1 |
|
$condition = sprintf('`%s` %s "%s"', static::_cn($field), $condition['operator'], DB::escape($condition['value'])); |
|
|
|
|
1559
|
|
|
} else { |
1560
|
83 |
|
$condition = sprintf('`%s` = "%s"', static::_cn($field), DB::escape($condition)); |
1561
|
|
|
} |
1562
|
|
|
} |
1563
|
|
|
} |
1564
|
|
|
|
1565
|
85 |
|
return $conditions; |
1566
|
|
|
} |
1567
|
|
|
|
1568
|
20 |
|
protected function finishValidation() |
1569
|
|
|
{ |
1570
|
20 |
|
$this->_isValid = $this->_isValid && !$this->_validator->hasErrors(); |
1571
|
|
|
|
1572
|
20 |
|
if (!$this->_isValid) { |
1573
|
|
|
$this->_validationErrors = array_merge($this->_validationErrors, $this->_validator->getErrors()); |
1574
|
|
|
} |
1575
|
|
|
|
1576
|
20 |
|
return $this->_isValid; |
1577
|
|
|
} |
1578
|
|
|
} |
1579
|
|
|
|