Passed
Push — master ( 702dee...34a29f )
by Kenneth
02:41 queued 48s
created

Statement   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 504
Duplicated Lines 0 %

Importance

Changes 22
Bugs 1 Features 0
Metric Value
eloc 130
c 22
b 1
f 0
dl 0
loc 504
rs 8.8798
wmc 44

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A rawPlaceholderFill() 0 7 2
A bFloat() 0 5 1
A bStrArr() 0 5 1
A bRaw() 0 5 1
A __debugInfo() 0 6 1
A rawBind() 0 5 1
B placeholderFill() 0 31 9
A bIntArray() 0 5 1
A __toString() 0 3 1
A placeholderGetName() 0 3 1
A bLike() 0 5 1
A reset() 0 11 1
A getComputed() 0 10 1
A bDateTime() 0 5 1
A bBoolInt() 0 5 1
A rawPlaceHolderGetName() 0 3 1
A bJSON() 0 5 1
A execute() 0 33 6
A bInt() 0 5 1
A bStr() 0 5 1
A bDate() 0 5 1
A bind() 0 8 1
A sql() 0 15 1
A getNextName() 0 27 5
A bBool() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Statement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Statement, and based on these observations, apply Extract Interface, too.

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