Completed
Pull Request — 3.x (#120)
by
unknown
01:47
created

AbstractQuery::indentCsv()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/bsd-license.php BSD
7
 *
8
 */
9
namespace Aura\SqlQuery;
10
11
use Aura\SqlQuery\Common\SubselectInterface;
12
13
/**
14
 *
15
 * Abstract query object.
16
 *
17
 * @package Aura.SqlQuery
18
 *
19
 */
20
abstract class AbstractQuery
21
{
22
    /**
23
     *
24
     * Data to be bound to the query.
25
     *
26
     * @var array
27
     *
28
     */
29
    protected $bind_values = array();
30
31
    /**
32
     *
33
     * The list of WHERE conditions.
34
     *
35
     * @var array
36
     *
37
     */
38
    protected $where = array();
39
40
    /**
41
     *
42
     * ORDER BY these columns.
43
     *
44
     * @var array
45
     *
46
     */
47
    protected $order_by = array();
48
49
    /**
50
     *
51
     * The list of flags.
52
     *
53
     * @var array
54
     *
55
     */
56
    protected $flags = array();
57
58
    /**
59
     *
60
     * A helper for quoting identifier names.
61
     *
62
     * @var Quoter
63
     *
64
     */
65
    protected $quoter;
66
67
    /**
68
     *
69
     * Prefix to use on placeholders for "sequential" bound values; used for
70
     * deconfliction when merging bound values from sub-selects, etc.
71
     *
72
     * @var mixed
73
     *
74
     */
75
    protected $seq_bind_prefix = '';
76
77
    /**
78
     *
79
     * Constructor.
80
     *
81
     * @param Quoter $quoter A helper for quoting identifier names.
82
     *
83
     * @param string $seq_bind_prefix A prefix for rewritten sequential-binding
84
     * placeholders (@see getSeqPlaceholder()).
85
     *
86
     */
87 397
    public function __construct(Quoter $quoter, $seq_bind_prefix = '')
88
    {
89 397
        $this->quoter = $quoter;
90 397
        $this->seq_bind_prefix = $seq_bind_prefix;
91 397
    }
92
93
    /**
94
     *
95
     * Returns the prefix for rewritten sequential-binding placeholders
96
     * (@see getSeqPlaceholder()).
97
     *
98
     * @return string
99
     *
100
     */
101 1
    public function getSeqBindPrefix()
102
    {
103 1
        return $this->seq_bind_prefix;
104
    }
105
106
    /**
107
     *
108
     * Returns this query object as an SQL statement string.
109
     *
110
     * @return string
111
     *
112
     */
113 242
    public function __toString()
114
    {
115 242
        return $this->getStatement();
116
    }
117
118
    /**
119
     *
120
     * Returns this query object as an SQL statement string.
121
     *
122
     * @return string
123
     *
124
     */
125 68
    public function getStatement()
126
    {
127 68
        return $this->build();
128
    }
129
130
    /**
131
     *
132
     * Builds this query object into a string.
133
     *
134
     * @return string
135
     *
136
     */
137
    abstract protected function build();
138
139
    /**
140
     *
141
     * Returns the prefix to use when quoting identifier names.
142
     *
143
     * @return string
144
     *
145
     */
146 251
    public function getQuoteNamePrefix()
147
    {
148 251
        return $this->quoter->getQuoteNamePrefix();
149
    }
150
151
    /**
152
     *
153
     * Returns the suffix to use when quoting identifier names.
154
     *
155
     * @return string
156
     *
157
     */
158 251
    public function getQuoteNameSuffix()
159
    {
160 251
        return $this->quoter->getQuoteNameSuffix();
161
    }
162
163
    /**
164
     *
165
     * Returns an array as an indented comma-separated values string.
166
     *
167
     * @param array $list The values to convert.
168
     *
169
     * @return string
170
     *
171
     */
172 219
    protected function indentCsv(array $list)
173
    {
174 219
        return PHP_EOL . '    '
175 219
             . implode(',' . PHP_EOL . '    ', $list);
176
    }
177
178
    /**
179
     *
180
     * Returns an array as an indented string.
181
     *
182
     * @param array $list The values to convert.
183
     *
184
     * @return string
185
     *
186
     */
187 71
    protected function indent(array $list)
188
    {
189 71
        return PHP_EOL . '    '
190 71
             . implode(PHP_EOL . '    ', $list);
191
    }
192
193
    /**
194
     *
195
     * Binds multiple values to placeholders; merges with existing values.
196
     *
197
     * @param array $bind_values Values to bind to placeholders.
198
     *
199
     * @return $this
200
     *
201
     */
202 31
    public function bindValues(array $bind_values)
203
    {
204
        // array_merge() renumbers integer keys, which is bad for
205
        // question-mark placeholders
206 31
        foreach ($bind_values as $key => $val) {
207 31
            $this->bindValue($key, $val);
208
        }
209 31
        return $this;
210
    }
211
212
    /**
213
     *
214
     * Binds a single value to the query.
215
     *
216
     * @param string $name The placeholder name or number.
217
     *
218
     * @param mixed $value The value to bind to the placeholder.
219
     *
220
     * @return $this
221
     *
222
     */
223 72
    public function bindValue($name, $value)
224
    {
225 72
        $this->bind_values[$name] = $value;
226 72
        return $this;
227
    }
228
229
    /**
230
     *
231
     * Gets the values to bind to placeholders.
232
     *
233
     * @return array
234
     *
235
     */
236 126
    public function getBindValues()
237
    {
238 126
        return $this->bind_values;
239
    }
240
241
    /**
242
     *
243
     * Reset all values bound to named placeholders.
244
     *
245
     * @return $this
246
     *
247
     */
248
    public function resetBindValues()
249
    {
250
        $this->bind_values = array();
251
        return $this;
252
    }
253
254
    /**
255
     *
256
     * Builds the flags as a space-separated string.
257
     *
258
     * @return string
259
     *
260
     */
261 252
    protected function buildFlags()
262
    {
263 252
        if (empty($this->flags)) {
264 214
            return ''; // not applicable
265
        }
266
267 38
        return ' ' . implode(' ', array_keys($this->flags));
268
    }
269
270
    /**
271
     *
272
     * Sets or unsets specified flag.
273
     *
274
     * @param string $flag Flag to set or unset
275
     *
276
     * @param bool $enable Flag status - enabled or not (default true)
277
     *
278
     * @return null
279
     *
280
     */
281 43
    protected function setFlag($flag, $enable = true)
282
    {
283 43
        if ($enable) {
284 43
            $this->flags[$flag] = true;
285
        } else {
286 5
            unset($this->flags[$flag]);
287
        }
288 43
    }
289
290
    /**
291
     *
292
     * Reset all query flags.
293
     *
294
     * @return $this
295
     *
296
     */
297 15
    public function resetFlags()
298
    {
299 15
        $this->flags = array();
300 15
        return $this;
301
    }
302
303
    /**
304
     *
305
     * Adds a WHERE condition to the query by AND or OR. If the condition has
306
     * ?-placeholders, additional arguments to the method will be bound to
307
     * those placeholders sequentially.
308
     *
309
     * @param string $andor Add the condition using this operator, typically
310
     * 'AND' or 'OR'.
311
     *
312
     * @param string $cond The WHERE condition.
313
     *
314
     * @param array ...$bind arguments to bind to placeholders
315
     *
316
     * @return $this
317
     *
318
     */
319 60
    protected function addWhere($andor, $cond, ...$bind)
320
    {
321 60
        $this->addClauseCondWithBind('where', $andor, $cond, $bind);
322 60
        return $this;
323
    }
324
325
    /**
326
     *
327
     * Adds conditions and binds values to a clause.
328
     *
329
     * @param string $clause The clause to work with, typically 'where' or
330
     * 'having'.
331
     *
332
     * @param string $andor Add the condition using this operator, typically
333
     * 'AND' or 'OR'.
334
     *
335
     * @param string $cond The WHERE condition.
336
337
     * @param array $bind arguments to bind to placeholders
338
     *
339
     * @return null
340
     *
341
     */
342 70
    protected function addClauseCondWithBind($clause, $andor, $cond, $bind)
343
    {
344 70
        $cond = $this->rebuildCondAndBindValues($cond, $bind);
345
346
        // add condition to clause; eg $this->where or $this->having
347 70
        $clause =& $this->$clause;
348 70
        if ($clause) {
349 49
            $clause[] = "$andor $cond";
350
        } else {
351 70
            $clause[] = $cond;
352
        }
353 70
    }
354
355
    /**
356
     *
357
     * Rebuilds a condition string, replacing sequential placeholders with
358
     * named placeholders, and binding the sequential values to the named
359
     * placeholders.
360
     *
361
     * @param string $cond The condition with sequential placeholders.
362
     *
363
     * @param array $bind_values The values to bind to the sequential
364
     * placeholders under their named versions.
365
     *
366
     * @return string The rebuilt condition string.
367
     *
368
     */
369 120
    protected function rebuildCondAndBindValues($cond, array $bind_values)
370
    {
371 120
        $cond = $this->quoter->quoteNamesIn($cond);
372
373
        // bind values against ?-mark placeholders, but because PDO is finicky
374
        // about the numbering of sequential placeholders, convert each ?-mark
375
        // to a named placeholder
376 120
        $parts = preg_split('/(\?)/', $cond, null, PREG_SPLIT_DELIM_CAPTURE);
377 120
        foreach ($parts as $key => $val) {
378 120
            if ($val != '?') {
379 120
                continue;
380
            }
381
382 80
            $bind_value = array_shift($bind_values);
383 80
            if ($bind_value instanceof SubselectInterface) {
384 10
                $parts[$key] = $bind_value->getStatement();
385 10
                $this->bind_values = array_merge(
386 10
                    $this->bind_values,
387 10
                    $bind_value->getBindValues()
388
                );
389 10
                continue;
390
            }
391
392 75
            $placeholder = $this->getSeqPlaceholder();
393 75
            $parts[$key] = ':' . $placeholder;
394 75
            $this->bind_values[$placeholder] = $bind_value;
395
        }
396
397 120
        $cond = implode('', $parts);
398 120
        return $cond;
399
    }
400
401
    /**
402
     *
403
     * Gets the current sequential placeholder name.
404
     *
405
     * @return string
406
     *
407
     */
408 75
    protected function getSeqPlaceholder()
409
    {
410 75
        $i = count($this->bind_values) + 1;
411 75
        return $this->seq_bind_prefix . "_{$i}_";
412
    }
413
414
    /**
415
     *
416
     * Builds the `WHERE` clause of the statement.
417
     *
418
     * @return string
419
     *
420
     */
421 206
    protected function buildWhere()
422
    {
423 206
        if (empty($this->where)) {
424 151
            return ''; // not applicable
425
        }
426
427 60
        return PHP_EOL . 'WHERE' . $this->indent($this->where);
428
    }
429
430
    /**
431
     *
432
     * Adds a column order to the query.
433
     *
434
     * @param array $spec The columns and direction to order by.
435
     *
436
     * @return $this
437
     *
438
     */
439 9
    protected function addOrderBy(array $spec)
440
    {
441 9
        foreach ($spec as $col) {
442 9
            $this->order_by[] = $this->quoter->quoteNamesIn($col);
443
        }
444 9
        return $this;
445
    }
446
447
    /**
448
     *
449
     * Builds the `ORDER BY ...` clause of the statement.
450
     *
451
     * @return string
452
     *
453
     */
454 206
    protected function buildOrderBy()
455
    {
456 206
        if (empty($this->order_by)) {
457 197
            return ''; // not applicable
458
        }
459
460 9
        return PHP_EOL . 'ORDER BY' . $this->indentCsv($this->order_by);
461
    }
462
463
    /**
464
     *
465
     * Template method overridden for queries that allow LIMIT and OFFSET.
466
     *
467
     * Builds the `LIMIT ... OFFSET` clause of the statement.
468
     *
469
     * Note that this will allow OFFSET values with a LIMIT.
470
     *
471
     * @return string
472
     *
473
     */
474 8
    protected function buildLimit()
475
    {
476 8
        return '';
477
    }
478
}
479