Completed
Push — master ( 9fa01f...c95cc6 )
by Dmytro
03:42
created

DBPreparedQuery::prepareLimit()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 18
Code Lines 14

Duplication

Lines 5
Ratio 27.78 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
eloc 14
c 1
b 0
f 1
nc 5
nop 0
dl 5
loc 18
rs 8.2222
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
    /* Service variables */
42
43
    /**
44
     * Creates and initialize DBPreparedQuery object.
45
     *
46
     * @param string $query DB SQL query template.
47
     * @param string $types Parameters SQL types string ("idsb").
48
     * @param array $params List of the DB SQL query parameters.
49
     */
50
    public function __construct($query = "", $types = "", $params = []) {
51
        $this->query = $query;
52
        $this->types = $types;
53
        $this->params = $params;
54
55
        if (!empty($this->query)) {
56
            $this->type = $this->detectType();
57
        }
58
        parent::__construct($this->type);
59
    }
60
61
    /**
62
     * Verify if current DBPreparedQuery is have parameters for binding.
63
     *
64
     * @return bool
65
     */
66
    public function isBindable() {
67
        return ($this->params != null && count($this->params) > 0);
68
    }
69
70
    /**
71
     * Verify if current DBPreparedQuery is valid for the execution.
72
     *
73
     * @return bool
74
     */
75
    public function isValid() {
76
        self::checkParameterTypes($this->params, $this->types);
77
78
        return true;
79
    }
80
81
    /**
82
     * Adds conditions WHERE SQL string to the SQL query.
83
     */
84
    public function prepareConditions() {
85
        if (!empty($this->conditions)) {
86
            $this->query.= " WHERE ";
87
            $this->sqlPushValues($this->conditions, " AND ");
88
        }
89
    }
90
91
    /**
92
     * Adds ORDER SQL string to the SQL query.
93
     */
94
    public function prepareOrder() {
95
        if (!empty($this->order)) {
96
            $this->query.= " ORDER BY";
97
            if (is_array($this->order)) {
98 View Code Duplication
                foreach ($this->order as $fieldName => $ord) {
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...
99
                    $this->query.= " " . $fieldName . " " . $ord . ",";
100
                }
101
                $this->query = substr($this->query, 0, strlen($this->query) - 1);
102
            } elseif (is_string($this->order)) {
103
                $this->query.= " " . $this->order;
104
            }
105
        }
106
    }
107
108
    /**
109
     * Adds LIMIT SQL string to the SQL query.
110
     *
111
     * @throws DBCoreException If some error occurred.
112
     */
113
    public function prepareLimit() {
114
        if (!is_null($this->limit)) {
115
            if (Tools::isInteger($this->limit)) {
116
                $this->query.= " LIMIT " . $this->limit;
117
                $count = $this->limit;
0 ignored issues
show
Unused Code introduced by
$count is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
118
            } elseif (is_array($this->limit) && count($this->limit) == 2) {
119
                $offset = $this->limit[0];
120
                $count = $this->limit[1];
121 View Code Duplication
                if (Tools::isInteger($offset) && Tools::isInteger($count)) {
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...
122
                    $this->query.= " LIMIT " . $offset . ", " . $count;
123
                } else {
124
                    throw new DBCoreException("Invalid LIMIT param in select() method.");
125
                }
126
            } else {
127
                throw new DBCoreException("Invalid LIMIT param in select() method.");
128
            }
129
        }
130
    }
131
132
    /**
133
     * Prepares SQL query to the execution.
134
     *
135
     * @param string $query Initial SQL query string.
136
     * @param array $conditions Conditions list.
137
     * @param array $order List of order conditions (fieldName => order),
138
     *           order may be 'ASC' OR 'DESC'.
139
     * @param int $offset Limit offset value (or count if this is single
140
     *           parameter).
141
     * @param int $count Number of records to select.
142
     *
143
     * @return DBPreparedQuery Oneself after modifications.
144
     * @throws DBCoreException If some error occurred.
145
     */
146
    public function prepare($query, $conditions = null, $order = null, $offset = null, $count = null) {
147
        if (empty($query)) {
148
            throw new DBCoreException("Nothing to run, SQL query is not initialized");
149
        }
150
        $this->query = $query;
151
152
        if (!is_null($conditions)) {
153
            if (!is_array($conditions)) {
154
                throw new DBCoreException("Invalid conditions array");
155
            }
156
            $this->conditions = $conditions;
157
        }
158
        $this->prepareConditions();
159
160
        if (!is_null($order)) {
161
            $this->order = $order;
162
        }
163
        $this->prepareOrder();
164
165 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...
166
            if (is_null($count)) {
167
                $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...
168
            } else {
169
                $this->dbQuery->limit = [$offset, $count];
170
            }
171
        }
172
        $this->prepareLimit();
173
174
        return $this;
175
    }
176
177
    /**
178
     * Executes SQL query.
179
     *
180
     * @param bool $debug Debug mode flag.
181
     *
182
     * @return mixed Statement object or FALSE if an error occurred if SELECT
183
     *           query executed or number of affected rows on success if other
184
     *           type of query executed.
185
     */
186
    public function go($debug = false) {
187
        if ($debug) {
188
            $this->debug();
189
        } else {
190
            if ($this->isSelector()) {
191
                return DBCore::doSelectQuery($this);
192
            }
193
194
            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...
195
        }
196
    }
197
198
    /**
199
     * Shows debug information for the SQL query without execution.
200
     */
201
    public function debug() {
202
        self::showQueryDebugInfo($this->query, $this->types, $this->params);
203
    }
204
205
    /**
206
     * Checks query parameters types correspondence.
207
     *
208
     * @param array $params Parameters of the query.
209
     * @param string $types Types of the parameters ("idsb").
210
     *
211
     * @throws DBCoreException
212
     */
213
    private static function checkParameterTypes($params, $types) {
214
        if (count($params) != strlen($types)) {
215
            throw new DBCoreException(
216
                "Number of types is not equal parameters number"
217
            );
218
        }
219
220
        foreach ($params as $key => $value) {
221
            $type = $types[$key];
222
223
            if (!in_array($type, ['i', 'd', 's', 'b'])) {
224
                throw new DBCoreException(
225
                    "Invalid query parameters types string (type '" . $type .
226
                    "' is undefined, only 'i', 'd', 's' and 'b' types are acceptable)"
227
                );
228
            }
229
230
            $typeByValue = DBField::getType($value);
231
            if ($typeByValue != 's') {
232
                if ($type != $typeByValue && !(
233
                       ($type == 'd' && $typeByValue == 'i') || // We can put integer as double
234
                       ($type == 's' && $typeByValue == 'i') // We can put integer as string
235
                   )
236
                ) {
237
                    throw new DBCoreException(
238
                        "Invalid query parameters types string ('" . $value .
239
                        "' is not '" . $type . "' type but '" . $typeByValue . "' detected)"
240
                    );
241
                }
242
            } else { // in case if we try send non-string parameters as a string value
243
                switch ($type) {
244
                    case 'i':
245
                        if (!(Tools::isNumeric($value) && ((string)(int)$value === $value))) {
246
                            throw new DBCoreException(
247
                                "Invalid query parameters types string ('" . $value . "' is not '" . $type . ")"
248
                            );
249
                        }
250
                        break;
251
                    case 'd':
252
                        if (!Tools::isDoubleString($value)) {
253
                            throw new DBCoreException(
254
                                "Invalid query parameters types string ('" . $value . "' is not '" . $type . ")"
255
                            );
256
                        }
257
                        break;
258
                    case 'b':
259
                        if (!in_array(strtolower($value), ['true', 'false'])) {
260
                            throw new DBCoreException(
261
                                "Invalid query parameters types string ('" . $value . "' is not '" . $type . ")"
262
                            );
263
                        }
264
                        break;
265
                }
266
            }
267
        }
268
    }
269
270
    /**
271
     * Return qwestion marks string for IN(...) SQL construction.
272
     *
273
     * @param int $length Length of the result string.
274
     *
275
     * @return string
276
     */
277
    public static function sqlQMString($length) {
278
        return implode(",", array_fill(0, $length, "?"));
279
    }
280
281
    /**
282
     * Return fields and qwestion marks string for SET field1=?, ... SQL construction.
283
     *
284
     * @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue)
285
     * @param string $idFieldName Name of the primary key field.
286
     *
287
     * @return string
288
     */
289
    public static function sqlQMValuesString($fieldsList, $idFieldName = "") {
290
        $chunks = [];
291
        foreach (array_keys($fieldsList) as $fieldName) {
292
            if ($fieldName != $idFieldName) {
293
                $chunks[] = "`" . $fieldName . "` = ?";
294
            }
295
        }
296
297
        return implode(", ", $chunks);
298
    }
299
300
    /**
301
     * Return fields and values string for SET field1=value1, ... SQL construction.
302
     *
303
     * @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue)
304
     * @param string $idFieldName Name of the primary key field.
305
     *
306
     * @return string
307
     */
308
    public static function sqlValuesString($fieldsList, $idFieldName) {
309
        $chunks = [];
310
        foreach ($fieldsList as $fieldName => $fieldValue) {
311
            if ($fieldName != $idFieldName) {
312
                $chunks[]= "`" . $fieldName . "` = '" . $fieldValue . "'";
313
            }
314
        }
315
316
        return implode(", ", $chunks);
317
    }
318
319
    /**
320
     * Returns SQL types string.
321
     * Type specification chars:
322
     *    i - corresponding variable has type integer
323
     *    d - corresponding variable has type double
324
     *    s - corresponding variable has type string
325
     *    b - corresponding variable is a blob and will be sent in packets.
326
     *
327
     * @param array<mixed> $fieldsList List of the table fields (syntax: array[fieldName] = fieldValue)
328
     * @param string $idFieldName Name of the primary key field.
329
     * @return string
330
     */
331
    public static function sqlTypesString($fieldsList, $idFieldName = "") {
332
        $typesString = "";
333
        foreach ($fieldsList as $fieldName => $fieldValue) {
334
            if ($fieldName != $idFieldName) {
335
                if (Tools::isDouble($fieldValue)) {
336
                    $typesString.= "d";
337
                } elseif (Tools::isInteger($fieldValue)) {
338
                    $typesString.= "i";
339
                } else {
340
                    $typesString.= "s";
341
                }
342
            }
343
        }
344
345
        return $typesString;
346
    }
347
348
    /**
349
     * Returns SQL types string of single type.
350
     *
351
     * @param string $type SQL type.
352
     * @param int $length Length of the SQL types string.
353
     *
354
     * @return string
355
     * @throws DBFieldTypeException If invalid type passed.
356
     */
357
    public static function sqlSingleTypeString($type, $length) {
358
        $type = DBField::castType($type);
359
        $typesString = "";
360
        while ($length > 0) {
361
            $typesString.= $type;
362
            $length--;
363
        }
364
365
        return $typesString;
366
    }
367
368
    /**
369
     * Push values to the DBPreparedQuery SQL query field end.
370
     *
371
     * @param array $values List of pairs key => value or SQL query parts with
372
     *           parameters.
373
     * @param string $separator Join separator.
374
     */
375
    public function sqlPushValues($values, $separator = ", ") {
376
        $chunks = [];
377
        foreach ($values as $fieldName => $fieldValue) {
378
            if (!is_array($fieldValue)) {
379
                if (!is_null($fieldValue)) {
380
                    $chunks[] = $fieldName . " = ?";
381
                    $this->types.= DBField::getType($fieldValue);
382
                    $this->params[] = $fieldValue;
383
                } else {
384
                    $chunks[] = $fieldName;
385
                }
386
            } else {
387
                $condition = $fieldName;
388
                $localParams = $fieldValue;
389
390
                $chunks[] = $condition;
391
                foreach ($localParams as $param) {
392
                    $this->types.= DBField::getType($param);
393
                    $this->params[] = $param;
394
                }
395
            }
396
        }
397
        $this->query.= implode($separator, $chunks);
398
    }
399
400
}
401