DBPreparedQuery::isBindable()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
167
            if (is_null($count)) {
168
                $this->dbQuery->limit = $offset;
0 ignored issues
show
Bug introduced by
The property dbQuery does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
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);
0 ignored issues
show
Documentation introduced by
$this is of type this<Asymptix\db\DBPreparedQuery>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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