|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* Handles DB operations on row level |
|
5
|
|
|
* |
|
6
|
|
|
* Class screens ordinary CRUD tasks providing high-level IDBRow interface. |
|
7
|
|
|
* - advanced DB data parsing to class properties with type conversion; |
|
8
|
|
|
* - safe storing with string escape function; |
|
9
|
|
|
* - smart class self-storing procedures; |
|
10
|
|
|
* - smart DB row partial update; |
|
11
|
|
|
* - delta updates for numeric DB fields on demand; |
|
12
|
|
|
* - managed external access to properties - READ/WRITE, READ-ONLY, READ-PROHIBITED; |
|
13
|
|
|
* - virtual properties - with only setter/getter and no corresponding real property; |
|
14
|
|
|
* - dual external access to protected data via property name or via getter/setter - including access to virtual properties; |
|
15
|
|
|
* |
|
16
|
|
|
* |
|
17
|
|
|
* No properties should be directly exposed to public |
|
18
|
|
|
* All property modifications should pass through __call() method to made partial update feature work; |
|
19
|
|
|
* All declared internal properties should start with '_' following by one of the indexes from $_properties static |
|
20
|
|
|
* |
|
21
|
|
|
* method int getDbId() |
|
22
|
|
|
* @property int dbId |
|
23
|
|
|
*/ |
|
24
|
|
|
abstract class DBRow implements IDbRow { |
|
25
|
|
|
// TODO |
|
26
|
|
|
/** |
|
27
|
|
|
* Should be this object - (!) not class - cached |
|
28
|
|
|
* There exists tables that didn't need to cache rows - logs as example |
|
29
|
|
|
* And there can be special needs to not cache some class instances when stream-reading many rows i.e. fleets in stat calculation |
|
30
|
|
|
* |
|
31
|
|
|
* @var bool $_cacheable |
|
32
|
|
|
*/ |
|
33
|
|
|
public $_cacheable = true; // |
|
34
|
|
|
// TODO |
|
35
|
|
|
/** |
|
36
|
|
|
* БД для доступа к данным |
|
37
|
|
|
* |
|
38
|
|
|
* @var db_mysql $db |
|
39
|
|
|
*/ |
|
40
|
|
|
protected static $db = null; |
|
41
|
|
|
/** |
|
42
|
|
|
* Table name in DB |
|
43
|
|
|
* |
|
44
|
|
|
* @var string |
|
45
|
|
|
*/ |
|
46
|
|
|
protected static $_table = ''; |
|
47
|
|
|
/** |
|
48
|
|
|
* Name of ID field in DB |
|
49
|
|
|
* |
|
50
|
|
|
* @var string |
|
51
|
|
|
*/ |
|
52
|
|
|
protected static $_dbIdFieldName = 'id'; |
|
53
|
|
|
/** |
|
54
|
|
|
* DB_ROW to Class translation scheme |
|
55
|
|
|
* |
|
56
|
|
|
* @var array |
|
57
|
|
|
*/ |
|
58
|
|
|
protected static $_properties = array( |
|
59
|
|
|
'dbId' => array( |
|
60
|
|
|
P_DB_FIELD => 'id', |
|
61
|
|
|
), |
|
62
|
|
|
); |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* Object list that should mimic object DB operations - i.e. units on fleet |
|
66
|
|
|
* |
|
67
|
|
|
* @var IDbRow[] |
|
68
|
|
|
*/ |
|
69
|
|
|
protected $triggerDbOperationOn = array(); // Not a static - because it's an object array |
|
70
|
|
|
/** |
|
71
|
|
|
* List of property names that was changed since last DB operation |
|
72
|
|
|
* |
|
73
|
|
|
* @var string[] |
|
74
|
|
|
*/ |
|
75
|
|
|
protected $propertiesChanged = array(); |
|
76
|
|
|
/** |
|
77
|
|
|
* List of property names->$delta that was adjusted since last DB operation - and then need to be processed as Deltas |
|
78
|
|
|
* |
|
79
|
|
|
* @var string[] |
|
80
|
|
|
*/ |
|
81
|
|
|
protected $propertiesAdjusted = array(); |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* @var int |
|
85
|
|
|
*/ |
|
86
|
|
|
protected $_dbId = 0; |
|
87
|
|
|
|
|
88
|
|
|
/** |
|
89
|
|
|
* Flag to skip lock on current Load operation |
|
90
|
|
|
* |
|
91
|
|
|
* @var bool |
|
92
|
|
|
*/ |
|
93
|
|
|
protected $lockSkip = false; |
|
94
|
|
|
|
|
95
|
|
|
|
|
96
|
|
|
// Some magic ******************************************************************************************************** |
|
97
|
|
|
|
|
98
|
|
|
public function __construct() { |
|
99
|
|
|
static::$db = classSupernova::$db; |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* Getter with support of protected methods |
|
104
|
|
|
* |
|
105
|
|
|
* @param $name |
|
106
|
|
|
* |
|
107
|
|
|
* @return mixed |
|
108
|
|
|
*/ |
|
109
|
|
|
public function __get($name) { |
|
110
|
|
|
// Redirecting inaccessible get to __call which will handle the rest |
|
111
|
|
|
return $this->__call('get' . ucfirst($name), array()); |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* Setter with support of protected properties/methods |
|
116
|
|
|
* |
|
117
|
|
|
* @param $name |
|
118
|
|
|
* @param $value |
|
119
|
|
|
*/ |
|
120
|
|
|
// TODO - сеттер должен параллельно изменять значение db_row - for now... |
|
121
|
|
|
public function __set($name, $value) { |
|
122
|
|
|
// Redirecting inaccessible set to __call which will handle the rest |
|
123
|
|
|
$this->__call('set' . ucfirst($name), array($value)); |
|
124
|
|
|
} |
|
125
|
|
|
|
|
126
|
|
|
/** |
|
127
|
|
|
* Handles getters and setters |
|
128
|
|
|
* |
|
129
|
|
|
* @param string $name |
|
130
|
|
|
* @param array $arguments |
|
131
|
|
|
* |
|
132
|
|
|
* @return mixed |
|
133
|
|
|
* @throws ExceptionPropertyNotExists |
|
134
|
|
|
*/ |
|
135
|
|
|
public function __call($name, $arguments) { |
|
136
|
|
|
$left3 = substr($name, 0, 3); |
|
137
|
|
|
$propertyName = lcfirst(substr($name, 3)); |
|
138
|
|
|
|
|
139
|
|
|
// If method is not getter or setter OR property name not exists in $_properties - raising exception |
|
140
|
|
|
// Descendants can catch this Exception to make own __call magic |
|
141
|
|
|
if(($left3 != 'get' && $left3 != 'set') || empty(static::$_properties[$propertyName])) { |
|
142
|
|
|
throw new ExceptionPropertyNotExists('Property ' . $propertyName . ' not exists when calling getter/setter ' . get_called_class() . '::' . $name, ERR_ERROR); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
// TODO check for read-only |
|
146
|
|
|
|
|
147
|
|
|
if($left3 == 'set') { |
|
148
|
|
|
if(!empty($this->propertiesAdjusted[$propertyName])) { |
|
149
|
|
|
throw new PropertyAccessException('Property ' . $propertyName . ' already was adjusted so no SET is possible until dbSave in ' . get_called_class() . '::' . $name, ERR_ERROR); |
|
150
|
|
|
} |
|
151
|
|
|
$this->propertiesChanged[$propertyName] = 1; |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
// Now deciding - will we call a protected setter or will we work with protected property |
|
155
|
|
|
|
|
156
|
|
|
// If method exists - just calling it |
|
157
|
|
|
if(method_exists($this, $name)) { |
|
158
|
|
|
return call_user_func_array(array($this, $name), $arguments); |
|
159
|
|
|
} |
|
160
|
|
|
// No getter/setter exists - works directly with protected property |
|
161
|
|
|
|
|
162
|
|
|
// Is it getter? |
|
163
|
|
|
if($left3 === 'get') { |
|
164
|
|
|
return $this->{'_' . $propertyName}; |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
// Not getter? Then it's setter |
|
168
|
|
|
$this->{'_' . $propertyName} = $arguments[0]; |
|
169
|
|
|
|
|
170
|
|
|
return null; |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
// IDBrow Implementation ********************************************************************************************* |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Loading object from DB by primary ID |
|
177
|
|
|
* |
|
178
|
|
|
* @param int $dbId |
|
179
|
|
|
* @param bool $lockSkip |
|
180
|
|
|
* |
|
181
|
|
|
* @return |
|
182
|
|
|
*/ |
|
183
|
|
|
public function dbLoad($dbId, $lockSkip = false) { |
|
184
|
|
|
$dbId = idval($dbId); |
|
185
|
|
|
if($dbId <= 0) { |
|
186
|
|
|
classSupernova::$debug->error(get_called_class() . '::dbLoad $dbId not positive = ' . $dbId); |
|
187
|
|
|
|
|
188
|
|
|
return; |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
|
|
$this->_dbId = $dbId; |
|
|
|
|
|
|
192
|
|
|
$this->lockSkip = $lockSkip; |
|
193
|
|
|
// TODO - Use classSupernova::$db_records_locked |
|
194
|
|
|
if(false && !$lockSkip && sn_db_transaction_check(false)) { |
|
195
|
|
|
$this->dbGetLockById($this->_dbId); |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
$db_row = doquery("SELECT * FROM `{{" . static::$_table . "}}` WHERE `" . static::$_dbIdFieldName . "` = " . $this->_dbId . " LIMIT 1 FOR UPDATE;", true); |
|
|
|
|
|
|
199
|
|
|
if(empty($db_row)) { |
|
200
|
|
|
return; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
$this->dbRowParse($db_row); |
|
204
|
|
|
$this->lockSkip = false; |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* Lock all fields that belongs to operation |
|
209
|
|
|
* |
|
210
|
|
|
* @param int $dbId |
|
211
|
|
|
* |
|
212
|
|
|
* @return |
|
213
|
|
|
* param DBLock $dbRow - Object that accumulates locks |
|
214
|
|
|
* |
|
215
|
|
|
*/ |
|
216
|
|
|
abstract public function dbGetLockById($dbId); |
|
217
|
|
|
|
|
218
|
|
|
/** |
|
219
|
|
|
* Saving object to DB |
|
220
|
|
|
* This is meta-method: |
|
221
|
|
|
* - if object is new - then it inserted to DB; |
|
222
|
|
|
* - if object is empty - it deleted from DB; |
|
223
|
|
|
* - otherwise object is updated in DB; |
|
224
|
|
|
*/ |
|
225
|
|
|
// TODO - perform operations only if properties was changed |
|
226
|
|
|
public function dbSave() { |
|
227
|
|
|
if($this->isNew()) { |
|
228
|
|
|
// No DB_ID - new unit |
|
229
|
|
|
if($this->isEmpty()) { |
|
230
|
|
|
classSupernova::$debug->error(__FILE__ . ':' . __LINE__ . ' - object is empty on ' . get_called_class() . '::dbSave'); |
|
231
|
|
|
} |
|
232
|
|
|
$this->dbInsert(); |
|
233
|
|
|
} else { |
|
234
|
|
|
// DB_ID is present |
|
235
|
|
|
if($this->isEmpty()) { |
|
236
|
|
|
$this->dbDelete(); |
|
237
|
|
|
} else { |
|
238
|
|
|
if(!sn_db_transaction_check(false)) { |
|
239
|
|
|
classSupernova::$debug->error(__FILE__ . ':' . __LINE__ . ' - transaction should always be started on ' . get_called_class() . '::dbUpdate'); |
|
240
|
|
|
} |
|
241
|
|
|
$this->dbUpdate(); |
|
242
|
|
|
} |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
if(!empty($this->triggerDbOperationOn)) { |
|
246
|
|
|
foreach($this->triggerDbOperationOn as $item) { |
|
247
|
|
|
$item->dbSave(); |
|
248
|
|
|
} |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
$this->propertiesChanged = array(); |
|
252
|
|
|
$this->propertiesAdjusted = array(); |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
|
|
256
|
|
|
|
|
257
|
|
|
// CRUD ************************************************************************************************************** |
|
258
|
|
|
|
|
259
|
|
|
/** |
|
260
|
|
|
* Inserts record to DB |
|
261
|
|
|
* |
|
262
|
|
|
* @return int|string |
|
263
|
|
|
*/ |
|
264
|
|
|
// TODO - protected |
|
265
|
|
|
public function dbInsert() { |
|
266
|
|
|
if(!$this->isNew()) { |
|
267
|
|
|
classSupernova::$debug->error(__FILE__ . ':' . __LINE__ . ' - record db_id is not empty on ' . get_called_class() . '::dbInsert'); |
|
268
|
|
|
} |
|
269
|
|
|
$this->_dbId = $this->db_field_set_create($this->dbMakeFieldSet()); |
|
270
|
|
|
|
|
271
|
|
|
if(empty($this->_dbId)) { |
|
272
|
|
|
classSupernova::$debug->error(__FILE__ . ':' . __LINE__ . ' - error saving record ' . get_called_class() . '::dbInsert'); |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
return $this->_dbId; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
/** |
|
279
|
|
|
* Updates record in DB |
|
280
|
|
|
*/ |
|
281
|
|
|
// TODO - protected |
|
282
|
|
|
public function dbUpdate() { |
|
283
|
|
|
// TODO - Update |
|
284
|
|
|
if($this->isNew()) { |
|
285
|
|
|
classSupernova::$debug->error(__FILE__ . ':' . __LINE__ . ' - unit db_id is empty on dbUpdate'); |
|
286
|
|
|
} |
|
287
|
|
|
$this->db_field_update($this->dbMakeFieldSet(true)); |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
/** |
|
291
|
|
|
* Deletes record from DB |
|
292
|
|
|
*/ |
|
293
|
|
|
// TODO - protected |
|
294
|
|
|
public function dbDelete() { |
|
295
|
|
|
if($this->isNew()) { |
|
296
|
|
|
classSupernova::$debug->error(__FILE__ . ':' . __LINE__ . ' - unit db_id is empty on dbDelete'); |
|
297
|
|
|
} |
|
298
|
|
|
doquery("DELETE FROM {{" . static::$_table . "}} WHERE `" . static::$_dbIdFieldName . "` = " . $this->_dbId); |
|
299
|
|
|
$this->_dbId = 0; |
|
300
|
|
|
// Обо всём остальном должен позаботиться контейнер |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
/** |
|
304
|
|
|
* Является ли запись новой - т.е. не имеет своей записи в БД |
|
305
|
|
|
* |
|
306
|
|
|
* @return bool |
|
307
|
|
|
*/ |
|
308
|
|
|
public function isNew() { |
|
309
|
|
|
return $this->_dbId == 0; |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
/** |
|
313
|
|
|
* Является ли запись пустой - т.е. при исполнении _dbSave должен быть удалён |
|
314
|
|
|
* |
|
315
|
|
|
* @return bool |
|
316
|
|
|
*/ |
|
317
|
|
|
abstract public function isEmpty(); |
|
318
|
|
|
|
|
319
|
|
|
// Other Methods ***************************************************************************************************** |
|
320
|
|
|
|
|
321
|
|
|
// /** |
|
322
|
|
|
// * Resets object to zero state |
|
323
|
|
|
// * @see DBRow::dbLoad() |
|
324
|
|
|
// * |
|
325
|
|
|
// * @return void |
|
326
|
|
|
// */ |
|
327
|
|
|
// protected function _reset() { |
|
328
|
|
|
// $this->dbRowParse(array()); |
|
329
|
|
|
// } |
|
330
|
|
|
|
|
331
|
|
|
/** |
|
332
|
|
|
* Парсит запись из БД в поля объекта |
|
333
|
|
|
* |
|
334
|
|
|
* @param array $db_row |
|
335
|
|
|
*/ |
|
336
|
|
|
public function dbRowParse(array $db_row) { |
|
337
|
|
|
foreach(static::$_properties as $property_name => &$property_data) { |
|
338
|
|
|
// Advanced values extraction procedure. Should be used when at least one of following rules is matched: |
|
339
|
|
|
// - one field should translate to several properties; |
|
340
|
|
|
// - one property should be filled according to several fields; |
|
341
|
|
|
// - property filling requires some lookup in object values; |
|
342
|
|
View Code Duplication |
if(!empty($property_data[P_METHOD_EXTRACT]) && is_callable(array($this, $property_data[P_METHOD_EXTRACT]))) { |
|
|
|
|
|
|
343
|
|
|
call_user_func_array(array($this, $property_data[P_METHOD_EXTRACT]), array(&$db_row)); |
|
344
|
|
|
continue; |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
// If property is read-only - doing nothing |
|
348
|
|
|
if(!empty($property_data[P_READ_ONLY])) { |
|
349
|
|
|
continue; |
|
350
|
|
|
} |
|
351
|
|
|
|
|
352
|
|
|
// Getting field value as base only if $_properties has 1-to-1 relation to object property |
|
353
|
|
|
$value = !empty($property_data[P_DB_FIELD]) && isset($db_row[$property_data[P_DB_FIELD]]) ? $db_row[$property_data[P_DB_FIELD]] : null; |
|
354
|
|
|
|
|
355
|
|
|
// Making format conversion from string ($db_row default type) to property type |
|
356
|
|
|
!empty($property_data[P_FUNC_INPUT]) && is_callable($property_data[P_FUNC_INPUT]) ? $value = call_user_func($property_data[P_FUNC_INPUT], $value) : false; |
|
357
|
|
|
|
|
358
|
|
|
// If there is setter for this field - using it. Setters is always a methods of $THIS |
|
359
|
|
|
if(!empty($property_data[P_METHOD_SET]) && is_callable(array($this, $property_data[P_METHOD_SET]))) { |
|
360
|
|
|
call_user_func(array($this, $property_data[P_METHOD_SET]), $value); |
|
361
|
|
|
} else { |
|
362
|
|
|
$this->{$property_name} = $value; |
|
363
|
|
|
} |
|
364
|
|
|
} |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
/** |
|
368
|
|
|
* Делает из свойств класса массив db_field_name => db_field_value |
|
369
|
|
|
* |
|
370
|
|
|
* @return array |
|
371
|
|
|
*/ |
|
372
|
|
|
protected function dbMakeFieldSet($isUpdate = false) { |
|
373
|
|
|
$array = array(); |
|
374
|
|
|
|
|
375
|
|
|
foreach(static::$_properties as $property_name => &$property_data) { |
|
376
|
|
|
// TODO - on isUpdate add only changed/adjusted properties |
|
377
|
|
|
|
|
378
|
|
View Code Duplication |
if(!empty($property_data[P_METHOD_INJECT]) && is_callable(array($this, $property_data[P_METHOD_INJECT]))) { |
|
|
|
|
|
|
379
|
|
|
call_user_func_array(array($this, $property_data[P_METHOD_INJECT]), array(&$array)); |
|
380
|
|
|
continue; |
|
381
|
|
|
} |
|
382
|
|
|
|
|
383
|
|
|
// Skipping properties which have no corresponding field in DB |
|
384
|
|
|
if(empty($property_data[P_DB_FIELD])) { |
|
385
|
|
|
continue; |
|
386
|
|
|
} |
|
387
|
|
|
|
|
388
|
|
|
// Checking - is property was adjusted or changed |
|
389
|
|
|
if($isUpdate && array_key_exists($property_name, $this->propertiesAdjusted)) { |
|
390
|
|
|
// For adjusted property - take value from propertiesAdjusted array |
|
391
|
|
|
// TODO - differ how treated conversion to string for changed and adjusted properties |
|
392
|
|
|
$value = $this->propertiesAdjusted[$property_name]; |
|
393
|
|
|
} else { |
|
394
|
|
|
// Getting property value. Optionally getter is invoked by __get() |
|
395
|
|
|
$value = $this->{$property_name}; |
|
396
|
|
|
} |
|
397
|
|
|
|
|
398
|
|
|
// If need some conversion to DB format - doing it |
|
399
|
|
|
!empty($property_data[P_FUNC_OUTPUT]) && is_callable($property_data[P_FUNC_OUTPUT]) |
|
400
|
|
|
? $value = call_user_func($property_data[P_FUNC_OUTPUT], $value) : false; |
|
401
|
|
|
!empty($property_data[P_METHOD_OUTPUT]) && is_callable(array($this, $property_data[P_METHOD_OUTPUT])) |
|
402
|
|
|
? $value = call_user_func(array($this, $property_data[P_METHOD_OUTPUT]), $value) : false; |
|
403
|
|
|
|
|
404
|
|
|
$array[$property_data[P_DB_FIELD]] = $value; |
|
405
|
|
|
} |
|
406
|
|
|
|
|
407
|
|
|
return $array; |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
/** |
|
411
|
|
|
* Check if DB field changed on property change and if it changed - returns name of property which triggered change |
|
412
|
|
|
* |
|
413
|
|
|
* @param string $fieldName |
|
414
|
|
|
* |
|
415
|
|
|
* @return string|false |
|
416
|
|
|
*/ |
|
417
|
|
|
protected function isFieldChanged($fieldName) { |
|
418
|
|
|
$isFieldChanged = false; |
|
419
|
|
|
foreach($this->propertiesChanged as $propertyName => $cork) { |
|
420
|
|
|
$propertyScheme = static::$_properties[$propertyName]; |
|
421
|
|
|
if(!empty($propertyScheme[P_DB_FIELDS_LINKED])) { |
|
422
|
|
|
foreach($propertyScheme[P_DB_FIELDS_LINKED] as $linkedFieldName) { |
|
423
|
|
|
if($linkedFieldName == $fieldName) { |
|
424
|
|
|
$isFieldChanged = $propertyName; |
|
425
|
|
|
break 2; |
|
426
|
|
|
} |
|
427
|
|
|
} |
|
428
|
|
|
} |
|
429
|
|
|
if(!empty($propertyScheme[P_DB_FIELD]) && $propertyScheme[P_DB_FIELD] == $fieldName) { |
|
430
|
|
|
$isFieldChanged = $propertyName; |
|
431
|
|
|
break; |
|
432
|
|
|
} |
|
433
|
|
|
} |
|
434
|
|
|
|
|
435
|
|
|
return $isFieldChanged; |
|
436
|
|
|
} |
|
437
|
|
|
|
|
438
|
|
|
/** |
|
439
|
|
|
* @param array $field_set |
|
440
|
|
|
* |
|
441
|
|
|
* @return int|string |
|
442
|
|
|
*/ |
|
443
|
|
|
protected function db_field_set_create(array $field_set) { |
|
444
|
|
|
!sn_db_field_set_is_safe($field_set) ? $field_set = sn_db_field_set_make_safe($field_set) : false; |
|
445
|
|
|
sn_db_field_set_safe_flag_clear($field_set); |
|
446
|
|
|
|
|
447
|
|
|
$values = implode(',', $field_set); |
|
448
|
|
|
$fields = implode(',', array_keys($field_set)); |
|
449
|
|
|
|
|
450
|
|
|
$result = 0; |
|
451
|
|
|
if(classSupernova::db_query("INSERT INTO `{{" . static::$_table . "}}` ({$fields}) VALUES ({$values});")) { |
|
452
|
|
|
$result = db_insert_id(); |
|
453
|
|
|
} |
|
454
|
|
|
|
|
455
|
|
|
return $result; |
|
456
|
|
|
} |
|
457
|
|
|
|
|
458
|
|
|
/** |
|
459
|
|
|
* @param array $field_set |
|
460
|
|
|
* |
|
461
|
|
|
* @return array|bool|mysqli_result|null |
|
462
|
|
|
*/ |
|
463
|
|
|
// TODO - UPDATE ONLY CHANGED FIELDS |
|
464
|
|
|
protected function db_field_update(array $field_set) { |
|
465
|
|
|
!sn_db_field_set_is_safe($field_set) ? $field_set = sn_db_field_set_make_safe($field_set) : false; |
|
466
|
|
|
sn_db_field_set_safe_flag_clear($field_set); |
|
467
|
|
|
|
|
468
|
|
|
$set = array(); |
|
469
|
|
|
foreach($field_set as $fieldName => $value) { |
|
470
|
|
|
if(!($changedProperty = $this->isFieldChanged($fieldName))) { |
|
471
|
|
|
continue; |
|
472
|
|
|
} |
|
473
|
|
|
|
|
474
|
|
|
// TODO - separate sets from adjusts |
|
475
|
|
|
if(array_key_exists($changedProperty, $this->propertiesAdjusted)) { |
|
476
|
|
|
$value = "`{$fieldName}` + ($value)"; // braces for negative values |
|
477
|
|
|
} |
|
478
|
|
|
|
|
479
|
|
|
$set[] = "`{$fieldName}` = $value"; |
|
480
|
|
|
} |
|
481
|
|
|
$set_string = implode(',', $set); |
|
482
|
|
|
|
|
483
|
|
|
//pdump($set_string, get_called_class()); |
|
|
|
|
|
|
484
|
|
|
|
|
485
|
|
|
return empty($set_string) |
|
486
|
|
|
? true |
|
487
|
|
|
: classSupernova::db_query("UPDATE `{{" . static::$_table . "}}` SET {$set_string} WHERE `" . static::$_dbIdFieldName . "` = " . $this->_dbId); |
|
488
|
|
|
} |
|
489
|
|
|
|
|
490
|
|
|
} |
|
491
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.