Passed
Push — develop ( 23d850...73e75c )
by Kenneth
01:44
created

Statement::placeholderGetName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace GeekLab\GLPDO2;
4
5
// Make EA inspection stop complaining.
6
use \Exception;
7
use GeekLab\GLPDO2\Bindings\BindingsInterface;
8
use \PDO;
9
use \PDOStatement;
10
11
class Statement
12
{
13
    /** @var BindingsInterface $bindings */
14
    private $bindings;
15
16
    /** @var int $bindPos Position for SQL binds. */
17
    private $bindPos = 0;
18
19
    /** @var array $named Named binding values. */
20
    private $named = [];
21
22
    /** @var array $SQL SQL Statement. */
23
    private $SQL = [];
24
25
    /** @var int Position holder for statement processing. */
26
    private $sqlPos = 0;
27
28
    /** @var array Raw named placeholders. */
29
    private $rawNamed = [];
30
31
    /** @var int $rawPos Position holder for raw statement processing. */
32
    private $rawPos = 0;
33
34
    /** @var array $rawSql SQL Statement. */
35
    private $rawSql = [];
36
37
    public function __construct(BindingsInterface $bindings)
38
    {
39
        $this->bindings = $bindings;
40
    }
41
42
    /**
43
     * Bind a value to a named parameter.
44
     *
45
     * @param string $name
46
     * @param string|int|float|bool|null $value
47
     * @param int $type
48
     *
49
     * @return Statement
50
     */
51
    public function bind(string $name, $value, int $type = PDO::PARAM_STR): self
52
    {
53
        $this->named[$name] = array(
54
            'type' => $type,
55
            'value' => $value
56
        );
57
58
        return $this;
59
    }
60
61
    /**
62
     * Bind a raw value to a named parameter.
63
     *
64
     * @param string $name
65
     * @param string|int|float|bool $value
66
     * @return Statement
67
     */
68
    public function rawBind(string $name, $value): self
69
    {
70
        $this->rawNamed[$name] = $value;
71
72
        return $this;
73
    }
74
75
    // Bind types
76
77
    /**
78
     * Bind a boolean value as bool, with NULL option or with integer option.
79
     *
80
     * @param string|int|bool|null $value
81
     * @param bool $null
82
     * @param bool $int
83
     *
84
     * @return Statement
85
     * @throws Exception
86
     */
87
    public function bBool($value = null, bool $null = false, bool $int = false): self
88
    {
89
        $this->bind($this->getNextName(), ...$this->bindings->bBool($value, $null, $int));
0 ignored issues
show
Bug introduced by
$this->bindings->bBool($value, $null, $int) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::bind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

89
        $this->bind($this->getNextName(), /** @scrutinizer ignore-type */ ...$this->bindings->bBool($value, $null, $int));
Loading history...
90
        return $this;
91
    }
92
93
    /**
94
     * Bind a date value as date or optional NULL.
95
     * YYYY-MM-DD is the proper date format.
96
     *
97
     * @param string|null $value
98
     * @param bool $null
99
     *
100
     * @return Statement
101
     * @throws Exception
102
     */
103
    public function bDate($value = null, bool $null = false): self
104
    {
105
        $this->bind($this->getNextName(), ...$this->bindings->bDate($value, $null));
0 ignored issues
show
Bug introduced by
$this->bindings->bDate($value, $null) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::bind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
        $this->bind($this->getNextName(), /** @scrutinizer ignore-type */ ...$this->bindings->bDate($value, $null));
Loading history...
106
        return $this;
107
    }
108
109
    /**
110
     * Bind a date value as date time or optional NULL.
111
     * YYYY-MM-DD HH:MM:SS is the proper date format.
112
     *
113
     * @param string|null $value
114
     * @param bool $null
115
     *
116
     * @return Statement
117
     * @throws Exception
118
     */
119
    public function bDateTime($value = null, bool $null = false): self
120
    {
121
        $this->bind($this->getNextName(), ...$this->bindings->bDateTime($value, $null));
0 ignored issues
show
Bug introduced by
$this->bindings->bDateTime($value, $null) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::bind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

121
        $this->bind($this->getNextName(), /** @scrutinizer ignore-type */ ...$this->bindings->bDateTime($value, $null));
Loading history...
122
        return $this;
123
    }
124
125
    /**
126
     * Bind a float.
127
     *
128
     * @param string|int|float|null $value
129
     * @param int $decimals
130
     * @param bool $null
131
     *
132
     * @return Statement
133
     * @throws Exception
134
     */
135
    public function bFloat($value = null, $decimals = 3, $null = false): self
136
    {
137
        $this->rawBind($this->getNextName('raw'), ...$this->bindings->bFloat($value, $decimals, $null));
0 ignored issues
show
Bug introduced by
$this->bindings->bFloat($value, $decimals, $null) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::rawBind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

137
        $this->rawBind($this->getNextName('raw'), /** @scrutinizer ignore-type */ ...$this->bindings->bFloat($value, $decimals, $null));
Loading history...
138
        return $this;
139
    }
140
141
142
    /**
143
     * Bind an integer with optional NULL.
144
     *
145
     * @param string|int|float|bool|null $value
146
     * @param bool $null
147
     *
148
     * @return Statement
149
     * @throws Exception
150
     */
151
    public function bInt($value = null, bool $null = false): self
152
    {
153
        $this->bind($this->getNextName(), ...$this->bindings->bInt($value, $null));
0 ignored issues
show
Bug introduced by
$this->bindings->bInt($value, $null) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::bind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

153
        $this->bind($this->getNextName(), /** @scrutinizer ignore-type */ ...$this->bindings->bInt($value, $null));
Loading history...
154
        return $this;
155
    }
156
157
    /**
158
     * Convert array of integers to comma separated values. Uses %%
159
     * Great for IN() statements.
160
     *
161
     * @param array $data
162
     * @param int $default
163
     *
164
     * @return Statement
165
     * @throws Exception
166
     */
167
    public function bIntArray(array $data, int $default = 0): self
168
    {
169
        $this->rawBind($this->getNextName('raw'), ...$this->bindings->bIntArray($data, $default));
0 ignored issues
show
Bug introduced by
$this->bindings->bIntArray($data, $default) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::rawBind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
        $this->rawBind($this->getNextName('raw'), /** @scrutinizer ignore-type */ ...$this->bindings->bIntArray($data, $default));
Loading history...
170
        return $this;
171
    }
172
173
    /**
174
     * Bind a object or JSON string to a string
175
     *
176
     * @param string|object|null $value
177
     * @param bool $null
178
     *
179
     * @return Statement
180
     * @throws Exception
181
     */
182
    public function bJSON($value, bool $null = false): self
183
    {
184
        $this->bind($this->getNextName(), ...$this->bindings->bJSON($value, $null));
0 ignored issues
show
Bug introduced by
$this->bindings->bJSON($value, $null) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::bind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

184
        $this->bind($this->getNextName(), /** @scrutinizer ignore-type */ ...$this->bindings->bJSON($value, $null));
Loading history...
185
        return $this;
186
    }
187
188
    /**
189
     * Create and bind string for LIKE() statements.
190
     *
191
     * @param string $value
192
     * @param bool $ends Ends with?
193
     * @param bool $starts Starts with?
194
     *
195
     * @return Statement
196
     */
197
    public function bLike(string $value, bool $ends = false, bool $starts = false): self
198
    {
199
        $this->bind($this->getNextName(), ...$this->bindings->bLike($value, $ends, $starts));
0 ignored issues
show
Bug introduced by
$this->bindings->bLike($value, $ends, $starts) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::bind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
        $this->bind($this->getNextName(), /** @scrutinizer ignore-type */ ...$this->bindings->bLike($value, $ends, $starts));
Loading history...
200
        return $this;
201
    }
202
203
    /**
204
     * !!!DANGER!!!
205
     * Bind a raw value.
206
     *
207
     * @param string|int|float|bool $value
208
     *
209
     * @return Statement
210
     */
211
    public function bRaw($value): self
212
    {
213
        $this->rawBind($this->getNextName('raw'), ...$this->bindings->bRaw($value));
0 ignored issues
show
Bug introduced by
$this->bindings->bRaw($value) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::rawBind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
        $this->rawBind($this->getNextName('raw'), /** @scrutinizer ignore-type */ ...$this->bindings->bRaw($value));
Loading history...
214
        return $this;
215
    }
216
217
    /**
218
     * Bind a string value.
219
     *
220
     * @param string|int|float|bool|null $value
221
     * @param bool $null
222
     * @param int $type
223
     *
224
     * @return Statement
225
     * @throws Exception
226
     */
227
    public function bStr($value, bool $null = false, int $type = PDO::PARAM_STR): self
228
    {
229
        $this->bind($this->getNextName(), ...$this->bindings->bStr($value, $null, $type));
0 ignored issues
show
Bug introduced by
$this->bindings->bStr($value, $null, $type) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::bind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

229
        $this->bind($this->getNextName(), /** @scrutinizer ignore-type */ ...$this->bindings->bStr($value, $null, $type));
Loading history...
230
        return $this;
231
    }
232
233
    /**
234
     * Convert an array into a string and bind it.
235
     * Great for IN() statements.
236
     *
237
     * @param array $values
238
     * @param string|int|float|bool $default
239
     *
240
     * @return Statement
241
     */
242
    public function bStrArr(array $values, $default = ''): self
243
    {
244
        $this->rawBind($this->getNextName('raw'), ...$this->bindings->bStrArr($values, $default));
0 ignored issues
show
Bug introduced by
$this->bindings->bStrArr($values, $default) is expanded, but the parameter $value of GeekLab\GLPDO2\Statement::rawBind() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

244
        $this->rawBind($this->getNextName('raw'), /** @scrutinizer ignore-type */ ...$this->bindings->bStrArr($values, $default));
Loading history...
245
        return $this;
246
    }
247
248
249
    // The rest of the helpers
250
251
    /**
252
     * Name the positions for binding in PDO.
253
     *
254
     * @param string $type
255
     *
256
     * @return string
257
     */
258
    private function getNextName(string $type = 'bind'): string
259
    {
260
        switch ($type) {
261
            case 'sql':
262
                // sql statement syntax
263
                $ret = sprintf(':pos%d', $this->sqlPos++);
264
265
                return $ret;
266
267
            case 'rawSql':
268
                //$ret = sprintf(':raw%d', $this->_rawSql++);
269
                $ret = sprintf(':raw%d', $this->rawPos);
270
271
                return $ret;
272
273
            case 'raw':
274
                // raw statement syntax
275
                $ret = sprintf(':raw%d', $this->rawPos++);
276
277
                return $ret;
278
279
            case 'bind':
280
            default:
281
                // bind/filling values
282
                $ret = sprintf(':pos%d', $this->bindPos++);
283
284
                return $ret;
285
        }
286
    }
287
288
    /**
289
     * Prepare and Execute the SQL statement.
290
     *
291
     * @param PDO $PDO
292
     *
293
     * @return PDOStatement
294
     * @throws Exception
295
     */
296
    public function execute(PDO $PDO): PDOStatement
297
    {
298
        // Prepare the SQL, force to string in case of null.
299
        $sql = (string) implode(' ', $this->SQL);
300
301
        // Replace raw placements with raw values.
302
        foreach ($this->rawNamed as $name => $rVal) {
303
            $sql = (string) preg_replace('/' . $name . '\b/', $rVal, $sql);
304
        }
305
306
        /** @var PDOStatement $stmt */
307
        $stmt = $PDO->prepare($sql);
308
309
        // Bind named parameters.
310
        foreach ($this->named as $name => $sVal) {
311
            switch ($sVal['type']) {
312
                case PDO::PARAM_BOOL:
313
                    $stmt->bindValue($name, (bool) $sVal['value'], $sVal['type']);
314
                    break;
315
316
                case PDO::PARAM_NULL:
317
                    $stmt->bindValue($name, null);
318
                    break;
319
320
                case PDO::PARAM_INT:
321
                    $stmt->bindValue($name, (int) $sVal['value'], $sVal['type']);
322
                    break;
323
324
                case PDO::PARAM_STR:
325
                default:
326
                    $stmt->bindValue($name, (string) $sVal['value'], $sVal['type']);
327
                    break;
328
            }
329
        }
330
331
        $stmt->execute();
332
        return $stmt;
333
    }
334
335
    /**
336
     * Use for building out what a might look like when it's pass to the DB.
337
     * Used by Statement::getComputed()
338
     *
339
     * @param array $matches
340
     *
341
     * @return mixed
342
     * @throws Exception
343
     */
344
    private function placeholderFill(array $matches)
345
    {
346
        $key = $matches[0];
347
348
        // Can't fill this param.
349
        if (!isset($this->named[$key]) && !isset($this->rawNamed[$key])) {
350
            return $key;
351
        }
352
353
        if (isset($this->named[$key])) {
354
            // here is the param
355
            $sVal = $this->named[$key];
356
357
            switch ($sVal['type']) {
358
                case PDO::PARAM_BOOL:
359
                    return $sVal['value'] ? 'TRUE' : 'FALSE';
360
361
                case PDO::PARAM_NULL:
362
                    return 'NULL';
363
364
                case PDO::PARAM_INT:
365
                    return (int) $sVal['value'];
366
367
                case PDO::PARAM_STR:
368
                default:
369
                    return "'" . $sVal['value'] . "'";
370
            }
371
        }
372
373
        // Since it's not named, it must be raw.
374
        return $this->rawNamed[$key];
375
    }
376
377
    /**
378
     * Get name of the placeholder.
379
     *
380
     * @return string
381
     */
382
    private function placeholderGetName(): string
383
    {
384
        return $this->getNextName('sql');
385
    }
386
387
    /**
388
     * Get name of the raw placeholder.
389
     *
390
     * @return string
391
     */
392
    private function rawPlaceHolderGetName(): string
393
    {
394
        return $this->getNextName('rawSql');
395
    }
396
397
    /**
398
     * Builds up the SQL parameterized statement.
399
     *
400
     * @param string $text
401
     *
402
     * @return Statement
403
     */
404
    public function sql(string $text): self
405
    {
406
        // Replace positioned placeholders with named placeholders (first value).
407
        // Force to string, in the case of null.
408
        $text = (string) preg_replace_callback('/\?/m', function () {
409
            return $this->placeholderGetName();
410
        }, $text);
411
412
        $text = (string) preg_replace_callback('/%%/m', function () {
413
            return $this->rawPlaceholderGetName();
414
        }, $text);
415
416
        $this->SQL[] = $text;
417
418
        return $this;
419
    }
420
421
    /**
422
     * Reset / Clear out properties.
423
     *
424
     * @return Statement
425
     */
426
    public function reset(): self
427
    {
428
        $this->bindPos = 0;
429
        $this->named = [];
430
        $this->SQL = [];
431
        $this->sqlPos = 0;
432
        $this->rawNamed = array();
433
        $this->rawPos = 0;
434
        $this->rawSql = array();
435
436
        return $this;
437
    }
438
439
    /**
440
     * Create what the SQL query string might look like.
441
     * Great for debugging. YMMV though.
442
     *
443
     * @return string
444
     */
445
    public function getComputed(): string
446
    {
447
        // Merge SQL together
448
        $sql = implode("\n", $this->SQL);
449
450
        // Replace positioned placeholders with named placeholders (first value).
451
        // Force to string, in the case of null.
452
        $sql = (string) preg_replace_callback('/:[a-z0-9_]+/m', array($this, 'placeholderFill'), $sql);
453
454
        return $sql;
455
    }
456
457
    /**
458
     * Return the SQL as a string.
459
     *
460
     * @return string
461
     */
462
    public function __toString(): string
463
    {
464
        return $this->getComputed();
465
    }
466
467
    /**
468
     * Magic Method for debugging.
469
     *
470
     * @return array
471
     */
472
    public function __debugInfo(): array
473
    {
474
        return [
475
            'Named Positions' => $this->named,
476
            'Unbound SQL' => $this->SQL,
477
            'Bound SQL' => $this->getComputed()
478
        ];
479
    }
480
}
481