Passed
Push — master ( 2ecf44...c26111 )
by Kenneth
03:39 queued 02:05
created

Statement::bDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 4
b 0
f 0
nc 1
nop 2
dl 0
loc 5
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
        $binding = $this->bindings->bBool($value, $null, $int);
90
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
91
        return $this;
92
    }
93
94
    /**
95
     * Bind a date value as date or optional NULL.
96
     * YYYY-MM-DD is the proper date format.
97
     *
98
     * @param string|null $value
99
     * @param bool $null
100
     *
101
     * @return Statement
102
     * @throws Exception
103
     */
104
    public function bDate($value = null, bool $null = false): self
105
    {
106
        $binding = $this->bindings->bDate($value, $null);
107
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
108
        return $this;
109
    }
110
111
    /**
112
     * Bind a date value as date time or optional NULL.
113
     * YYYY-MM-DD HH:MM:SS is the proper date format.
114
     *
115
     * @param string|null $value
116
     * @param bool $null
117
     *
118
     * @return Statement
119
     * @throws Exception
120
     */
121
    public function bDateTime($value = null, bool $null = false): self
122
    {
123
        $binding = $this->bindings->bDateTime($value, $null);
124
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
125
        return $this;
126
    }
127
128
    /**
129
     * Bind a float.
130
     *
131
     * @param string|int|float|null $value
132
     * @param int $decimals
133
     * @param bool $null
134
     *
135
     * @return Statement
136
     * @throws Exception
137
     */
138
    public function bFloat($value = null, $decimals = 3, $null = false): self
139
    {
140
        $binding = $this->bindings->bFloat($value, $decimals, $null);
141
        $this->rawBind($this->getNextName('raw'), $binding[0]);
142
        return $this;
143
    }
144
145
146
    /**
147
     * Bind an integer with optional NULL.
148
     *
149
     * @param string|int|float|bool|null $value
150
     * @param bool $null
151
     *
152
     * @return Statement
153
     * @throws Exception
154
     */
155
    public function bInt($value = null, bool $null = false): self
156
    {
157
        $binding = $this->bindings->bInt($value, $null);
158
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
159
        return $this;
160
    }
161
162
    /**
163
     * Convert array of integers to comma separated values. Uses %%
164
     * Great for IN() statements.
165
     *
166
     * @param array $data
167
     * @param int $default
168
     *
169
     * @return Statement
170
     * @throws Exception
171
     */
172
    public function bIntArray(array $data, int $default = 0): self
173
    {
174
        $binding = $this->bindings->bIntArray($data, $default);
175
        $this->rawBind($this->getNextName('raw'), $binding[0]);
176
        return $this;
177
    }
178
179
    /**
180
     * Bind a object or JSON string to a string
181
     *
182
     * @param string|object|null $value
183
     * @param bool $null
184
     *
185
     * @return Statement
186
     * @throws Exception
187
     */
188
    public function bJSON($value, bool $null = false): self
189
    {
190
        $binding = $this->bindings->bJSON($value, $null);
191
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
192
        return $this;
193
    }
194
195
    /**
196
     * Create and bind string for LIKE() statements.
197
     *
198
     * @param string $value
199
     * @param bool $ends Ends with?
200
     * @param bool $starts Starts with?
201
     *
202
     * @return Statement
203
     */
204
    public function bLike(string $value, bool $ends = false, bool $starts = false): self
205
    {
206
        $binding = $this->bindings->bLike($value, $ends, $starts);
207
        $this->bind($this->getNextName(), $binding[0]);
208
        return $this;
209
    }
210
211
    /**
212
     * !!!DANGER!!!
213
     * Bind a raw value.
214
     *
215
     * @param string|int|float|bool $value
216
     *
217
     * @return Statement
218
     */
219
    public function bRaw($value): self
220
    {
221
        $binding = $this->bindings->bRaw($value);
222
        $this->rawBind($this->getNextName('raw'), $binding[0]);
223
        return $this;
224
    }
225
226
    /**
227
     * Bind a string value.
228
     *
229
     * @param string|int|float|bool|null $value
230
     * @param bool $null
231
     * @param int $type
232
     *
233
     * @return Statement
234
     * @throws Exception
235
     */
236
    public function bStr($value, bool $null = false, int $type = PDO::PARAM_STR): self
237
    {
238
        $binding = $this->bindings->bStr($value, $null, $type);
239
        $this->bind($this->getNextName(), $binding[0], $binding[1]);
240
        return $this;
241
    }
242
243
    /**
244
     * Convert an array into a string and bind it.
245
     * Great for IN() statements.
246
     *
247
     * @param array $values
248
     * @param string|int|float|bool $default
249
     *
250
     * @return Statement
251
     */
252
    public function bStrArr(array $values, $default = ''): self
253
    {
254
        $binding = $this->bindings->bStrArr($values, $default);
255
        $this->rawBind($this->getNextName('raw'), $binding[0]);
256
        return $this;
257
    }
258
259
    // The rest of the helpers
260
261
    /**
262
     * Name the positions for binding in PDO.
263
     *
264
     * @param string $type
265
     *
266
     * @return string
267
     */
268
    private function getNextName(string $type = 'bind'): string
269
    {
270
        switch ($type) {
271
            case 'sql':
272
                // sql statement syntax
273
                $ret = sprintf(':pos%d', $this->sqlPos++);
274
275
                return $ret;
276
277
            case 'rawSql':
278
                //$ret = sprintf(':raw%d', $this->_rawSql++);
279
                $ret = sprintf(':raw%d', $this->rawPos);
280
281
                return $ret;
282
283
            case 'raw':
284
                // raw statement syntax
285
                $ret = sprintf(':raw%d', $this->rawPos++);
286
287
                return $ret;
288
289
            case 'bind':
290
            default:
291
                // bind/filling values
292
                $ret = sprintf(':pos%d', $this->bindPos++);
293
294
                return $ret;
295
        }
296
    }
297
298
    /**
299
     * Prepare and Execute the SQL statement.
300
     *
301
     * @param PDO $PDO
302
     *
303
     * @return PDOStatement
304
     * @throws Exception
305
     */
306
    public function execute(PDO $PDO): PDOStatement
307
    {
308
        // Prepare the SQL, force to string in case of null.
309
        $sql = (string) implode(' ', $this->SQL);
310
311
        // Replace raw placements with raw values.
312
        foreach ($this->rawNamed as $name => $rVal) {
313
            $sql = (string) preg_replace('/' . $name . '\b/', $rVal, $sql);
314
        }
315
316
        /** @var PDOStatement $stmt */
317
        $stmt = $PDO->prepare($sql);
318
319
        // Bind named parameters.
320
        foreach ($this->named as $name => $sVal) {
321
            switch ($sVal['type']) {
322
                case PDO::PARAM_BOOL:
323
                    $stmt->bindValue($name, (bool) $sVal['value'], $sVal['type']);
324
                    break;
325
326
                case PDO::PARAM_NULL:
327
                    $stmt->bindValue($name, null);
328
                    break;
329
330
                case PDO::PARAM_INT:
331
                    $stmt->bindValue($name, (int) $sVal['value'], $sVal['type']);
332
                    break;
333
334
                case PDO::PARAM_STR:
335
                default:
336
                    $stmt->bindValue($name, (string) $sVal['value'], $sVal['type']);
337
                    break;
338
            }
339
        }
340
341
        $stmt->execute();
342
        return $stmt;
343
    }
344
345
    /**
346
     * Use for building out what a might look like when it's pass to the DB.
347
     * Used by Statement::getComputed()
348
     *
349
     * @param array $matches
350
     *
351
     * @return mixed
352
     * @throws Exception
353
     */
354
    private function placeholderFill(array $matches)
355
    {
356
        $key = $matches[0];
357
358
        // Can't fill this param.
359
        if (!isset($this->named[$key]) && !isset($this->rawNamed[$key])) {
360
            return $key;
361
        }
362
363
        if (isset($this->named[$key])) {
364
            // here is the param
365
            $sVal = $this->named[$key];
366
367
            switch ($sVal['type']) {
368
                case PDO::PARAM_BOOL:
369
                    return $sVal['value'] ? 'TRUE' : 'FALSE';
370
371
                case PDO::PARAM_NULL:
372
                    return 'NULL';
373
374
                case PDO::PARAM_INT:
375
                    return (int) $sVal['value'];
376
377
                case PDO::PARAM_STR:
378
                default:
379
                    return "'" . $sVal['value'] . "'";
380
            }
381
        }
382
383
        // Since it's not named, it must be raw.
384
        return $this->rawNamed[$key];
385
    }
386
387
    /**
388
     * Get name of the placeholder.
389
     *
390
     * @return string
391
     */
392
    private function placeholderGetName(): string
393
    {
394
        return $this->getNextName('sql');
395
    }
396
397
    /**
398
     * Get name of the raw placeholder.
399
     *
400
     * @return string
401
     */
402
    private function rawPlaceHolderGetName(): string
403
    {
404
        return $this->getNextName('rawSql');
405
    }
406
407
    /**
408
     * Builds up the SQL parameterized statement.
409
     *
410
     * @param string $text
411
     *
412
     * @return Statement
413
     */
414
    public function sql(string $text): self
415
    {
416
        // Replace positioned placeholders with named placeholders (first value).
417
        // Force to string, in the case of null.
418
        $text = (string) preg_replace_callback('/\?/m', function () {
419
            return $this->placeholderGetName();
420
        }, $text);
421
422
        $text = (string) preg_replace_callback('/%%/m', function () {
423
            return $this->rawPlaceholderGetName();
424
        }, $text);
425
426
        $this->SQL[] = $text;
427
428
        return $this;
429
    }
430
431
    /**
432
     * Reset / Clear out properties.
433
     *
434
     * @return Statement
435
     */
436
    public function reset(): self
437
    {
438
        $this->bindPos = 0;
439
        $this->named = [];
440
        $this->SQL = [];
441
        $this->sqlPos = 0;
442
        $this->rawNamed = array();
443
        $this->rawPos = 0;
444
        $this->rawSql = array();
445
446
        return $this;
447
    }
448
449
    /**
450
     * Create what the SQL query string might look like.
451
     * Great for debugging. YMMV though.
452
     *
453
     * @return string
454
     */
455
    public function getComputed(): string
456
    {
457
        // Merge SQL together
458
        $sql = implode("\n", $this->SQL);
459
460
        // Replace positioned placeholders with named placeholders (first value).
461
        // Force to string, in the case of null.
462
        $sql = (string) preg_replace_callback('/:[a-z0-9_]+/m', array($this, 'placeholderFill'), $sql);
463
464
        return $sql;
465
    }
466
467
    /**
468
     * Return the SQL as a string.
469
     *
470
     * @return string
471
     */
472
    public function __toString(): string
473
    {
474
        return $this->getComputed();
475
    }
476
477
    /**
478
     * Magic Method for debugging.
479
     *
480
     * @return array
481
     */
482
    public function __debugInfo(): array
483
    {
484
        return [
485
            'Named Positions' => $this->named,
486
            'Unbound SQL' => $this->SQL,
487
            'Bound SQL' => $this->getComputed()
488
        ];
489
    }
490
}
491