Test Failed
Push — develop ( eabf9f )
by Kenneth
03:54
created

Statement::bDateNullable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GeekLab\GLPDO2;
4
5
// Make EA inspection stop complaining.
6
use \PDO;
7
use \PDOStatement;
8
use \Exception;
9
use \TypeError;
10
use GeekLab\GLPDO2\Bindings\Bindings;
11
12
class Statement
13
{
14
    /** @var Bindings $bindings */
15
    private $bindings;
16
17
    /** @var int $bindPos Position for SQL binds. */
18
    private $bindPos = 0;
19
20
    /** @var array $named Named binding values. */
21
    private $named = [];
22
23
    /** @var array $SQL SQL Statement. */
24
    private $SQL = [];
25
26
    /** @var int Position holder for statement processing. */
27
    private $sqlPos = 0;
28
29
    /** @var array Raw named placeholders. */
30
    private $rawNamed = [];
31
32
    /** @var int $rawPos Position holder for raw statement processing. */
33
    private $rawPos = 0;
34
35
    /** @var array $rawSql SQL Statement. */
36
    private $rawSql = [];
37
38
    public function __construct(Bindings $bindings)
39
    {
40
        $this->bindings = $bindings;
41
    }
42
43
    /**
44
     * Replace the raw placeholder with raw values.
45
     *
46
     * @param string $sql
47
     *
48
     * @return string
49
     */
50
    private function rawPlaceholderFill(string $sql): string
51
    {
52
        foreach ($this->rawNamed as $name => $rVal) {
53
            $sql = (string) preg_replace('/' . $name . '\b/', $rVal, $sql);
54
        }
55
56
        return $sql;
57
    }
58
59
    /**
60
     * Bind a value to a named parameter.
61
     *
62
     * @param string $name
63
     * @param string|int|float|bool|null $value
64
     * @param int $type
65
     *
66
     * @return Statement
67
     */
68
    public function bind(string $name, $value, int $type = PDO::PARAM_STR): self
69
    {
70
        $this->named[$name] = array(
71
            'type' => $type,
72
            'value' => $value
73
        );
74
75
        return $this;
76
    }
77
78
    /**
79
     * Bind a raw value to a named parameter.
80
     *
81
     * @param string $name
82
     * @param string|int|float|bool $value
83
     * @return Statement
84
     */
85
    public function rawBind(string $name, $value): self
86
    {
87
        $this->rawNamed[$name] = $value;
88
89
        return $this;
90
    }
91
92
    // Bind types
93
94
    /**
95
     * Bind a boolean value as bool or null.
96
     * Knock, knock. Who's there? Tri-state.
97
     *
98
     * @param int|bool|null $value
99
     *
100
     * @return Statement
101
     * @throws Exception
102
     */
103
    public function bBoolNullable($value = null): self
104
    {
105
        $binding = $this->bindings->bBoolNullable($value);
106
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
107
        return $this;
108
    }
109
110
    /**
111
     * Bind a boolean value as bool.
112
     *
113
     * @param int|bool $value
114
     *
115
     * @return Statement
116
     * @throws Exception
117
     */
118
    public function bBool($value): self
119
    {
120
        $binding = $this->bindings->bBool($value);
121
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
122
        return $this;
123
    }
124
125
    /**
126
     * Bind a boolean value as int or null.
127
     * Tri-state who? Tri-state Boolean...
128
     *
129
     * @param int|bool|null $value
130
     *
131
     * @return Statement
132
     * @throws Exception
133
     */
134
    public function bBoolIntNullable($value = null): self
135
    {
136
        $binding = $this->bindings->bBoolIntNullable($value);
137
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
138
        return $this;
139
    }
140
141
    /**
142
     * Bind a boolean value as int.
143
     *
144
     * @param int|bool $value
145
     *
146
     * @return Statement
147
     * @throws Exception
148
     */
149
    public function bBoolInt($value): self
150
    {
151
        $binding = $this->bindings->bBoolInt($value);
152
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
153
        return $this;
154
    }
155
156
    /**
157
     * Bind a date value as date or null.
158
     * YYYY-MM-DD is the proper date format.
159
     *
160
     * @param string|null $value
161
     *
162
     * @return Statement
163
     */
164
    public function bDateNullable(?string $value = null): self
165
    {
166
        $binding = $this->bindings->bDateNullable($value);
167
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
168
        return $this;
169
    }
170
171
    /**
172
     * Bind a date value as date.
173
     * YYYY-MM-DD is the proper date format.
174
     *
175
     * @param string $value
176
     *
177
     * @return Statement
178
     */
179
    public function bDate(string $value): self
180
    {
181
        $binding = $this->bindings->bDate($value);
182
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
183
        return $this;
184
    }
185
186
    /**
187
     * Bind a date time value as date time or null.
188
     * YYYY-MM-DD HH:MM:SS is the proper date format.
189
     *
190
     * @param string|null $value
191
     *
192
     * @return Statement
193
     * @throws Exception
194
     */
195
    public function bDateTimeNullable(?string $value = null): self
196
    {
197
        $binding = $this->bindings->bDateTimeNullable($value);
198
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
199
        return $this;
200
    }
201
202
    /**
203
     * Bind a date time value as date time.
204
     * YYYY-MM-DD HH:MM:SS is the proper date format.
205
     *
206
     * @param string $value
207
     *
208
     * @return Statement
209
     * @throws Exception
210
     */
211
    public function bDateTime(string $value): self
212
    {
213
        $binding = $this->bindings->bDateTime($value);
214
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
215
        return $this;
216
    }
217
218
219
    /**
220
     * Bind a float.
221
     *
222
     * @param string|int|float|null $value
223
     * @param int $decimals
224
     *
225
     * @return Statement
226
     * @throws Exception
227
     */
228
    public function bFloatNullable($value = null, $decimals = 3): self
229
    {
230
        $binding = $this->bindings->bFloatNullable($value, $decimals);
231
        $this->rawBind($this->getNextName('raw'), $binding[0]);
232
        return $this;
233
    }
234
235
    /**
236
     * Bind a float.
237
     *
238
     * @param string|int|float| $value
239
     * @param int $decimals
240
     *
241
     * @return Statement
242
     * @throws Exception
243
     */
244
    public function bFloat($value = null, $decimals = 3): self
245
    {
246
        $binding = $this->bindings->bFloat($value, $decimals);
247
        $this->rawBind($this->getNextName('raw'), $binding[0]);
248
        return $this;
249
    }
250
251
    /**
252
     * Bind an integer or null.
253
     *
254
     * @param string|int|float|bool|null $value
255
     *
256
     * @return Statement
257
     * @throws Exception
258
     */
259
    public function bIntNullable($value = null): self
260
    {
261
        $binding = $this->bindings->bIntNullable($value);
262
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
263
        return $this;
264
    }
265
266
    /**
267
     * Bind an integer.
268
     *
269
     * @param string|int|float|bool $value
270
     *
271
     * @return Statement
272
     * @throws Exception
273
     */
274
    public function bInt($value = null): self
275
    {
276
        $binding = $this->bindings->bInt($value);
277
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
278
        return $this;
279
    }
280
281
    /**
282
     * Convert array of integers to comma separated values. Uses %%
283
     * Great for IN() statements.
284
     *
285
     * @param array $data
286
     * @param int $default
287
     *
288
     * @return Statement
289
     * @throws Exception
290
     */
291
    public function bIntArray(array $data, int $default = 0): self
292
    {
293
        $binding = $this->bindings->bIntArray($data, $default);
294
        $this->rawBind($this->getNextName('raw'), $binding[0]);
295
        return $this;
296
    }
297
298
    /**
299
     * Bind a object or JSON string to a string
300
     *
301
     * @param string|object|null $value
302
     * @param bool $null
303
     *
304
     * @return Statement
305
     * @throws Exception
306
     */
307
    public function bJSON($value, bool $null = false): self
308
    {
309
        $binding = $this->bindings->bJSON($value, $null);
310
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
311
        return $this;
312
    }
313
314
    /**
315
     * Create and bind string for LIKE() statements.
316
     *
317
     * @param string $value
318
     * @param bool $ends Ends with?
319
     * @param bool $starts Starts with?
320
     *
321
     * @return Statement
322
     */
323
    public function bLike(string $value, bool $ends = false, bool $starts = false): self
324
    {
325
        $binding = $this->bindings->bLike($value, $ends, $starts);
326
        $this->bind($this->getNextName(), $binding[0]);
327
        return $this;
328
    }
329
330
    /**
331
     * !!!DANGER!!!
332
     * Bind a raw value.
333
     *
334
     * @param string|int|float|bool $value
335
     *
336
     * @return Statement
337
     */
338
    public function bRaw($value): self
339
    {
340
        $binding = $this->bindings->bRaw($value);
341
        $this->rawBind($this->getNextName('raw'), $binding[0]);
342
        return $this;
343
    }
344
345
    /**
346
     * Bind a string value.
347
     *
348
     * @param string|int|float|bool|null $value
349
     * @param bool $null
350
     * @param int $type
351
     *
352
     * @return Statement
353
     * @throws Exception
354
     */
355
    public function bStr($value, bool $null = false, int $type = PDO::PARAM_STR): self
356
    {
357
        $binding = $this->bindings->bStr($value, $null, $type);
358
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
359
        return $this;
360
    }
361
362
    /**
363
     * Convert an array into a string and bind it.
364
     * Great for IN() statements.
365
     *
366
     * @param array $values
367
     * @param string|int|float|bool $default
368
     *
369
     * @return Statement
370
     */
371
    public function bStrArr(array $values, $default = ''): self
372
    {
373
        $binding = $this->bindings->bStrArr($values, $default);
374
        $this->rawBind($this->getNextName('raw'), $binding[0]);
375
        return $this;
376
    }
377
378
    // The rest of the helpers
379
380
    /**
381
     * Name the positions for binding in PDO.
382
     *
383
     * @param string $type
384
     *
385
     * @return string
386
     */
387
    private function getNextName(string $type = 'bind'): string
388
    {
389
        switch ($type) {
390
            case 'sql':
391
                // sql statement syntax
392
                $ret = sprintf(':pos%d', $this->sqlPos++);
393
394
                return $ret;
395
396
            case 'rawSql':
397
                //$ret = sprintf(':raw%d', $this->_rawSql++);
398
                $ret = sprintf(':raw%d', $this->rawPos);
399
400
                return $ret;
401
402
            case 'raw':
403
                // raw statement syntax
404
                $ret = sprintf(':raw%d', $this->rawPos++);
405
406
                return $ret;
407
408
            case 'bind':
409
            default:
410
                // bind/filling values
411
                $ret = sprintf(':pos%d', $this->bindPos++);
412
413
                return $ret;
414
        }
415
    }
416
417
    /**
418
     * Prepare and Execute the SQL statement.
419
     *
420
     * @param PDO $PDO
421
     *
422
     * @return PDOStatement
423
     * @throws Exception
424
     */
425
    public function execute(PDO $PDO): PDOStatement
426
    {
427
        // Prepare the SQL, force to string in case of null.
428
        // Then replace raw placements with raw values.
429
        $sql = $this->rawPlaceholderFill((string) implode(' ', $this->SQL));
430
431
        /** @var PDOStatement $stmt */
432
        $stmt = $PDO->prepare($sql);
433
434
        // Bind named parameters.
435
        foreach ($this->named as $name => $sVal) {
436
            switch ($sVal['type']) {
437
                case PDO::PARAM_BOOL:
438
                    $stmt->bindValue($name, (bool) $sVal['value'], $sVal['type']);
439
                    break;
440
441
                case PDO::PARAM_NULL:
442
                    $stmt->bindValue($name, null);
443
                    break;
444
445
                case PDO::PARAM_INT:
446
                    $stmt->bindValue($name, (int) $sVal['value'], $sVal['type']);
447
                    break;
448
449
                case PDO::PARAM_STR:
450
                default:
451
                    $stmt->bindValue($name, (string) $sVal['value'], $sVal['type']);
452
                    break;
453
            }
454
        }
455
456
        $stmt->execute();
457
        return $stmt;
458
    }
459
460
    /**
461
     * Use for building out what a might look like when it's pass to the DB.
462
     * Used by Statement::getComputed()
463
     *
464
     * @param array $matches
465
     *
466
     * @return mixed
467
     * @throws Exception
468
     */
469
    private function placeholderFill(array $matches)
470
    {
471
        $key = $matches[0];
472
473
        // Can't fill this param.
474
        if (!isset($this->named[$key]) && !isset($this->rawNamed[$key])) {
475
            return $key;
476
        }
477
478
        if (isset($this->named[$key])) {
479
            // here is the param
480
            $sVal = $this->named[$key];
481
482
            switch ($sVal['type']) {
483
                case PDO::PARAM_BOOL:
484
                    return $sVal['value'] ? 'TRUE' : 'FALSE';
485
486
                case PDO::PARAM_NULL:
487
                    return 'NULL';
488
489
                case PDO::PARAM_INT:
490
                    return (int) $sVal['value'];
491
492
                case PDO::PARAM_STR:
493
                default:
494
                    return "'" . $sVal['value'] . "'";
495
            }
496
        }
497
498
        // Since it's not named, it must be raw.
499
        return $this->rawNamed[$key];
500
    }
501
502
    /**
503
     * Get name of the placeholder.
504
     *
505
     * @return string
506
     */
507
    private function placeholderGetName(): string
508
    {
509
        return $this->getNextName('sql');
510
    }
511
512
    /**
513
     * Get name of the raw placeholder.
514
     *
515
     * @return string
516
     */
517
    private function rawPlaceHolderGetName(): string
518
    {
519
        return $this->getNextName('rawSql');
520
    }
521
522
    /**
523
     * Builds up the SQL parameterized statement.
524
     *
525
     * @param string $text
526
     *
527
     * @return Statement
528
     */
529
    public function sql(string $text): self
530
    {
531
        // Replace positioned placeholders with named placeholders (first value).
532
        // Force to string, in the case of null.
533
        $text = (string) preg_replace_callback('/\?/m', function () {
534
            return $this->placeholderGetName();
535
        }, $text);
536
537
        $text = (string) preg_replace_callback('/%%/m', function () {
538
            return $this->rawPlaceholderGetName();
539
        }, $text);
540
541
        $this->SQL[] = $text;
542
543
        return $this;
544
    }
545
546
    /**
547
     * Reset / Clear out properties.
548
     *
549
     * @return Statement
550
     */
551
    public function reset(): self
552
    {
553
        $this->bindPos = 0;
554
        $this->named = [];
555
        $this->SQL = [];
556
        $this->sqlPos = 0;
557
        $this->rawNamed = array();
558
        $this->rawPos = 0;
559
        $this->rawSql = array();
560
561
        return $this;
562
    }
563
564
    /**
565
     * Create what the SQL query string might look like.
566
     * Great for debugging. YMMV though.
567
     *
568
     * @return string
569
     */
570
    public function getComputed(): string
571
    {
572
        // Merge SQL together
573
        $sql = implode("\n", $this->SQL);
574
575
        // Replace positioned placeholders with named placeholders (first value).
576
        // Force to string, in the case of null.
577
        $sql = (string) preg_replace_callback('/:[a-z0-9_]+/m', function ($matches) {
578
            return $this->placeholderFill($matches);
579
        }, $sql);
580
581
        return $sql;
582
    }
583
584
    /**
585
     * Return the SQL as a string.
586
     *
587
     * @return string
588
     */
589
    public function __toString(): string
590
    {
591
        return $this->getComputed();
592
    }
593
594
    /**
595
     * Magic Method for debugging.
596
     *
597
     * @return array
598
     */
599
    public function __debugInfo(): array
600
    {
601
        return [
602
            'Named Positions' => $this->named,
603
            'Unbound SQL' => $this->SQL,
604
            'Bound SQL' => $this->getComputed()
605
        ];
606
    }
607
}
608