1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Asymptix\db; |
4
|
|
|
|
5
|
|
|
use Asymptix\core\Tools; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Complex DB query object for Prepared Statement. |
9
|
|
|
* |
10
|
|
|
* @category Asymptix PHP Framework |
11
|
|
|
* @author Dmytro Zarezenko <[email protected]> |
12
|
|
|
* @copyright (c) 2015 - 2016, Dmytro Zarezenko |
13
|
|
|
* |
14
|
|
|
* @git https://github.com/Asymptix/Framework |
15
|
|
|
* @license http://opensource.org/licenses/MIT |
16
|
|
|
*/ |
17
|
|
|
class DBPreparedQuery extends DBQuery { |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* DB query template. |
21
|
|
|
* |
22
|
|
|
* @var string |
23
|
|
|
*/ |
24
|
|
|
public $query = ""; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Parameters SQL types string ("idsb"). |
28
|
|
|
* |
29
|
|
|
* @var string |
30
|
|
|
*/ |
31
|
|
|
public $types = ""; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* List of the DB SQL query parameters. |
35
|
|
|
* |
36
|
|
|
* @var array |
37
|
|
|
*/ |
38
|
|
|
public $params = []; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Creates and initialize DBPreparedQuery object. |
42
|
|
|
* |
43
|
|
|
* @param string $query DB SQL query template. |
44
|
|
|
* @param string $types Parameters SQL types string ("idsb"). |
45
|
|
|
* @param array $params List of the DB SQL query parameters. |
46
|
|
|
*/ |
47
|
|
|
public function __construct($query = "", $types = "", $params = []) { |
48
|
|
|
$this->query = $query; |
49
|
|
|
$this->types = str_replace(["|"], "", $types); |
50
|
|
|
$this->params = $params; |
51
|
|
|
|
52
|
|
|
if (!empty($this->query)) { |
53
|
|
|
$this->type = $this->detectType(); |
54
|
|
|
} |
55
|
|
|
parent::__construct($this->type); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Verify if current DBPreparedQuery is have parameters for binding. |
60
|
|
|
* |
61
|
|
|
* @return bool |
62
|
|
|
*/ |
63
|
|
|
public function isBindable() { |
64
|
|
|
return ($this->params != null && count($this->params) > 0); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Verify if current DBPreparedQuery is valid for the execution. |
69
|
|
|
* |
70
|
|
|
* @return bool |
71
|
|
|
*/ |
72
|
|
|
public function isValid() { |
73
|
|
|
self::checkParameterTypes($this->params, $this->types); |
74
|
|
|
|
75
|
|
|
return true; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Adds conditions WHERE SQL string to the SQL query. |
80
|
|
|
*/ |
81
|
|
|
public function prepareConditions() { |
82
|
|
|
if (!empty($this->conditions)) { |
83
|
|
|
$this->query.= " WHERE "; |
84
|
|
|
$this->sqlPushValues($this->conditions, " AND "); |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Adds ORDER SQL string to the SQL query. |
90
|
|
|
*/ |
91
|
|
|
public function prepareOrder() { |
92
|
|
|
if (!empty($this->order)) { |
93
|
|
|
$this->query.= " ORDER BY"; |
94
|
|
|
if (is_array($this->order)) { |
95
|
|
|
foreach ($this->order as $fieldName => $ord) { |
96
|
|
|
$this->query.= " " . $fieldName . " " . $ord . ","; |
97
|
|
|
} |
98
|
|
|
$this->query = substr($this->query, 0, strlen($this->query) - 1); |
99
|
|
|
} elseif (is_string($this->order)) { |
100
|
|
|
$this->query.= " " . $this->order; |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Adds LIMIT SQL string to the SQL query. |
107
|
|
|
* |
108
|
|
|
* @return mixed Number of records will be selected or null. |
109
|
|
|
* @throws DBCoreException If some error occurred. |
110
|
|
|
*/ |
111
|
|
|
public function prepareLimit() { |
112
|
|
|
$count = null; |
113
|
|
|
if (!is_null($this->limit)) { |
114
|
|
|
if (Tools::isInteger($this->limit)) { |
115
|
|
|
$this->query.= " LIMIT " . $this->limit; |
116
|
|
|
$count = $this->limit; |
117
|
|
|
} elseif (is_array($this->limit) && count($this->limit) == 2) { |
118
|
|
|
$offset = $this->limit[0]; |
119
|
|
|
$count = $this->limit[1]; |
120
|
|
|
if (Tools::isInteger($offset) && Tools::isInteger($count)) { |
121
|
|
|
$this->query.= " LIMIT " . $offset . ", " . $count; |
122
|
|
|
} else { |
123
|
|
|
throw new DBCoreException("Invalid LIMIT param in select() method."); |
124
|
|
|
} |
125
|
|
|
} else { |
126
|
|
|
throw new DBCoreException("Invalid LIMIT param in select() method."); |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
return $count; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Prepares SQL query to the execution. |
135
|
|
|
* |
136
|
|
|
* @param string $query Initial SQL query string. |
137
|
|
|
* @param array $conditions Conditions list. |
138
|
|
|
* @param array $order List of order conditions (fieldName => order), |
139
|
|
|
* order may be 'ASC' OR 'DESC'. |
140
|
|
|
* @param int $offset Limit offset value (or count if this is single |
141
|
|
|
* parameter). |
142
|
|
|
* @param int $count Number of records to select. |
143
|
|
|
* |
144
|
|
|
* @return DBPreparedQuery Oneself after modifications. |
145
|
|
|
* @throws DBCoreException If some error occurred. |
146
|
|
|
*/ |
147
|
|
|
public function prepare($query, $conditions = null, $order = null, $offset = null, $count = null) { |
148
|
|
|
if (empty($query)) { |
149
|
|
|
throw new DBCoreException("Nothing to run, SQL query is not initialized"); |
150
|
|
|
} |
151
|
|
|
$this->query = $query; |
152
|
|
|
|
153
|
|
|
if (!is_null($conditions)) { |
154
|
|
|
if (!is_array($conditions)) { |
155
|
|
|
throw new DBCoreException("Invalid conditions array"); |
156
|
|
|
} |
157
|
|
|
$this->conditions = $conditions; |
158
|
|
|
} |
159
|
|
|
$this->prepareConditions(); |
160
|
|
|
|
161
|
|
|
if (!is_null($order)) { |
162
|
|
|
$this->order = $order; |
163
|
|
|
} |
164
|
|
|
$this->prepareOrder(); |
165
|
|
|
|
166
|
|
View Code Duplication |
if (!is_null($offset)) { |
|
|
|
|
167
|
|
|
if (is_null($count)) { |
168
|
|
|
$this->dbQuery->limit = $offset; |
|
|
|
|
169
|
|
|
} else { |
170
|
|
|
$this->dbQuery->limit = [$offset, $count]; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
$this->prepareLimit(); |
174
|
|
|
|
175
|
|
|
return $this; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Executes SQL query. |
180
|
|
|
* |
181
|
|
|
* @param bool $debug Debug mode flag. |
182
|
|
|
* |
183
|
|
|
* @return mixed Statement object or FALSE if an error occurred if SELECT |
184
|
|
|
* query executed or number of affected rows on success if other |
185
|
|
|
* type of query executed. |
186
|
|
|
*/ |
187
|
|
|
public function go($debug = false) { |
188
|
|
|
if ($debug) { |
189
|
|
|
$this->debug(); |
190
|
|
|
} else { |
191
|
|
|
if ($this->isSelector()) { |
192
|
|
|
return DBCore::doSelectQuery($this); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
return DBCore::doUpdateQuery($this); |
|
|
|
|
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Shows debug information for the SQL query without execution. |
201
|
|
|
*/ |
202
|
|
|
public function debug() { |
203
|
|
|
self::showQueryDebugInfo($this->query, $this->types, $this->params); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Checks query parameters types correspondence. |
208
|
|
|
* |
209
|
|
|
* @param array $params Parameters of the query. |
210
|
|
|
* @param string $types Types of the parameters ("idsb"). |
211
|
|
|
* |
212
|
|
|
* @throws DBCoreException |
213
|
|
|
*/ |
214
|
|
|
private static function checkParameterTypes($params, $types) { |
215
|
|
|
if (count($params) != strlen($types)) { |
216
|
|
|
throw new DBCoreException( |
217
|
|
|
"Number of types is not equal parameters number" |
218
|
|
|
); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
foreach ($params as $key => $value) { |
222
|
|
|
$type = $types[$key]; |
223
|
|
|
|
224
|
|
|
if (!in_array($type, ['i', 'd', 's', 'b'])) { |
225
|
|
|
throw new DBCoreException( |
226
|
|
|
"Invalid query parameters types string (type '" . $type . |
227
|
|
|
"' is undefined, only 'i', 'd', 's' and 'b' types are acceptable)" |
228
|
|
|
); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$typeByValue = DBField::getType($value); |
232
|
|
|
if ($typeByValue != 's') { |
233
|
|
|
if ($type != $typeByValue && !( |
234
|
|
|
($type == 'd' && $typeByValue == 'i') || // We can put integer as double |
235
|
|
|
($type == 's' && $typeByValue == 'i') // We can put integer as string |
236
|
|
|
) |
237
|
|
|
) { |
238
|
|
|
throw new DBCoreException( |
239
|
|
|
"Invalid query parameters types string ('" . $value . |
240
|
|
|
"' is not '" . $type . "' type but '" . $typeByValue . "' detected)" |
241
|
|
|
); |
242
|
|
|
} |
243
|
|
|
} else { // in case if we try send non-string parameters as a string value |
244
|
|
|
switch ($type) { |
245
|
|
|
case 'i': |
246
|
|
|
if (!(Tools::isNumeric($value) && ((string)(int)$value === $value))) { |
247
|
|
|
throw new DBCoreException( |
248
|
|
|
"Invalid query parameters types string ('" . $value . "' is not '" . $type . ")" |
249
|
|
|
); |
250
|
|
|
} |
251
|
|
|
break; |
252
|
|
|
case 'd': |
253
|
|
|
if (!Tools::isDoubleString($value)) { |
254
|
|
|
throw new DBCoreException( |
255
|
|
|
"Invalid query parameters types string ('" . $value . "' is not '" . $type . ")" |
256
|
|
|
); |
257
|
|
|
} |
258
|
|
|
break; |
259
|
|
|
case 'b': |
260
|
|
|
if (!in_array(strtolower($value), ['true', 'false'])) { |
261
|
|
|
throw new DBCoreException( |
262
|
|
|
"Invalid query parameters types string ('" . $value . "' is not '" . $type . ")" |
263
|
|
|
); |
264
|
|
|
} |
265
|
|
|
break; |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Return qwestion marks string for IN(...) SQL construction. |
273
|
|
|
* |
274
|
|
|
* @param int $length Length of the result string. |
275
|
|
|
* |
276
|
|
|
* @return string |
277
|
|
|
*/ |
278
|
|
|
public static function sqlQMString($length) { |
279
|
|
|
return implode(",", array_fill(0, $length, "?")); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Return fields and qwestion marks string for SET field1=?, ... SQL construction. |
284
|
|
|
* |
285
|
|
|
* @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue) |
286
|
|
|
* @param string $idFieldName Name of the primary key field. |
287
|
|
|
* |
288
|
|
|
* @return string |
289
|
|
|
*/ |
290
|
|
|
public static function sqlQMValuesString($fieldsList, $idFieldName = "") { |
291
|
|
|
$chunks = []; |
292
|
|
|
foreach (array_keys($fieldsList) as $fieldName) { |
293
|
|
|
if ($fieldName != $idFieldName) { |
294
|
|
|
$chunks[] = "`" . $fieldName . "` = ?"; |
295
|
|
|
} |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
return implode(", ", $chunks); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Return fields and values string for SET field1=value1, ... SQL construction. |
303
|
|
|
* |
304
|
|
|
* @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue) |
305
|
|
|
* @param string $idFieldName Name of the primary key field. |
306
|
|
|
* |
307
|
|
|
* @return string |
308
|
|
|
*/ |
309
|
|
|
public static function sqlValuesString($fieldsList, $idFieldName) { |
310
|
|
|
$chunks = []; |
311
|
|
|
foreach ($fieldsList as $fieldName => $fieldValue) { |
312
|
|
|
if ($fieldName != $idFieldName) { |
313
|
|
|
$chunks[]= "`" . $fieldName . "` = '" . $fieldValue . "'"; |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
return implode(", ", $chunks); |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Returns SQL types string. |
322
|
|
|
* Type specification chars: |
323
|
|
|
* i - corresponding variable has type integer |
324
|
|
|
* d - corresponding variable has type double |
325
|
|
|
* s - corresponding variable has type string |
326
|
|
|
* b - corresponding variable is a blob and will be sent in packets. |
327
|
|
|
* |
328
|
|
|
* @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue) |
329
|
|
|
* @param string $idFieldName Name of the primary key field. |
330
|
|
|
* @return string |
331
|
|
|
*/ |
332
|
|
|
public static function sqlTypesString($fieldsList, $idFieldName = "") { |
333
|
|
|
$typesString = ""; |
334
|
|
|
foreach ($fieldsList as $fieldName => $fieldValue) { |
335
|
|
|
if ($fieldName != $idFieldName) { |
336
|
|
|
if (Tools::isDouble($fieldValue)) { |
337
|
|
|
$typesString.= "d"; |
338
|
|
|
} elseif (Tools::isInteger($fieldValue)) { |
339
|
|
|
$typesString.= "i"; |
340
|
|
|
} else { |
341
|
|
|
$typesString.= "s"; |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
return $typesString; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Returns SQL types string of single type. |
351
|
|
|
* |
352
|
|
|
* @param string $type SQL type. |
353
|
|
|
* @param int $length Length of the SQL types string. |
354
|
|
|
* |
355
|
|
|
* @return string |
356
|
|
|
* @throws DBFieldTypeException If invalid type passed. |
357
|
|
|
*/ |
358
|
|
|
public static function sqlSingleTypeString($type, $length) { |
359
|
|
|
$type = DBField::castType($type); |
360
|
|
|
$typesString = ""; |
361
|
|
|
while ($length > 0) { |
362
|
|
|
$typesString.= $type; |
363
|
|
|
$length--; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
return $typesString; |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Push values to the DBPreparedQuery SQL query field end. |
371
|
|
|
* |
372
|
|
|
* @param array $values List of pairs key => value or SQL query parts with |
373
|
|
|
* parameters. |
374
|
|
|
* @param string $separator Join separator. |
375
|
|
|
*/ |
376
|
|
|
public function sqlPushValues($values, $separator = ", ") { |
377
|
|
|
$chunks = []; |
378
|
|
|
foreach ($values as $fieldName => $fieldValue) { |
379
|
|
|
if (!is_array($fieldValue)) { |
380
|
|
|
if (!is_null($fieldValue)) { |
381
|
|
|
$chunks[] = $fieldName . " = ?"; |
382
|
|
|
$this->types.= DBField::getType($fieldValue); |
383
|
|
|
$this->params[] = $fieldValue; |
384
|
|
|
} else { |
385
|
|
|
$chunks[] = $fieldName; |
386
|
|
|
} |
387
|
|
|
} else { |
388
|
|
|
$condition = $fieldName; |
389
|
|
|
$localParams = $fieldValue; |
390
|
|
|
|
391
|
|
|
$chunks[] = $condition; |
392
|
|
|
foreach ($localParams as $param) { |
393
|
|
|
$this->types.= DBField::getType($param); |
394
|
|
|
$this->params[] = $param; |
395
|
|
|
} |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
$this->query.= implode($separator, $chunks); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
} |
402
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.