Completed
Push — intermixed-snippets ( 1a61e3 )
by Paul
01:52
created

AbstractQuery::build()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
c 0
b 0
f 0
ccs 0
cts 0
cp 0
nc 1
1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/mit-license.php MIT
7
 *
8
 */
9
namespace Aura\SqlQuery;
10
11
use Aura\SqlQuery\Common\SelectInterface;
12
use Aura\SqlQuery\Common\QuoterInterface;
13
use Closure;
14
15
/**
16
 *
17
 * Abstract query object.
18
 *
19
 * @package Aura.SqlQuery
20
 *
21
 */
22
abstract class AbstractQuery
23
{
24
    /**
25
     *
26
     * Data to be bound to the query.
27
     *
28
     * @var array
29
     *
30
     */
31
    protected $bind_values = array();
32
33
    /**
34
     *
35
     * The list of WHERE conditions.
36
     *
37
     * @var array
38
     *
39
     */
40
    protected $where = array();
41
42
    /**
43
     *
44
     * ORDER BY these columns.
45
     *
46
     * @var array
47
     *
48
     */
49
    protected $order_by = array();
50
51
    /**
52
     *
53
     * The list of flags.
54
     *
55
     * @var array
56
     *
57
     */
58
    protected $flags = array();
59
60
    /**
61
     *
62
     * A helper for quoting identifier names.
63
     *
64
     * @var Quoter
65
     *
66
     */
67
    protected $quoter;
68
69
    /**
70
     *
71
     * A builder for the query.
72
     *
73
     * @var AbstractBuilder
74
     *
75
     */
76
    protected $builder;
77
78
    protected $seq_bind_prefix = '';
79
80
    protected $seq_bind_number = 0;
81
82
    /**
83
     *
84
     * Constructor.
85
     *
86
     * @param Quoter $quoter A helper for quoting identifier names.
87
     *
88
     * @param AbstractBuilder $builder A builder for the query.
89
     *
90
     */
91 417
    public function __construct(
92
        QuoterInterface $quoter,
93
        $builder,
94
        $seq_bind_prefix = ''
95
    ) {
96 417
        $this->quoter = $quoter;
0 ignored issues
show
Documentation Bug introduced by
It seems like $quoter of type object<Aura\SqlQuery\Common\QuoterInterface> is incompatible with the declared type object<Aura\SqlQuery\Quoter> of property $quoter.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
97 417
        $this->builder = $builder;
98 417
        $this->seq_bind_prefix = $seq_bind_prefix;
99 417
    }
100
101
    /**
102
     *
103
     * Returns this query object as an SQL statement string.
104
     *
105
     * @return string
106
     *
107
     */
108 243
    public function __toString()
109
    {
110 243
        return $this->getStatement();
111
    }
112
113
    /**
114
     *
115
     * Returns this query object as an SQL statement string.
116
     *
117
     * @return string
118
     *
119
     */
120 69
    public function getStatement()
121
    {
122 69
        return $this->build();
123
    }
124
125
    /**
126
     *
127
     * Builds this query object into a string.
128
     *
129
     * @return string
130
     *
131
     */
132
    abstract protected function build();
133
134
    /**
135
     *
136
     * Returns the prefix to use when quoting identifier names.
137
     *
138
     * @return string
139
     *
140
     */
141 267
    public function getQuoteNamePrefix()
142
    {
143 267
        return $this->quoter->getQuoteNamePrefix();
144
    }
145
146
    /**
147
     *
148
     * Returns the suffix to use when quoting identifier names.
149
     *
150
     * @return string
151
     *
152
     */
153 267
    public function getQuoteNameSuffix()
154
    {
155 267
        return $this->quoter->getQuoteNameSuffix();
156
    }
157
158
    /**
159
     *
160
     * Binds multiple values to placeholders; merges with existing values.
161
     *
162
     * @param array $bind_values Values to bind to placeholders.
163
     *
164
     * @return $this
165
     *
166
     */
167 31
    public function bindValues(array $bind_values)
168
    {
169
        // array_merge() renumbers integer keys, which is bad for
170
        // question-mark placeholders
171 31
        foreach ($bind_values as $key => $val) {
172 31
            $this->bindValue($key, $val);
173 31
        }
174 31
        return $this;
175
    }
176
177
    /**
178
     *
179
     * Binds a single value to the query.
180
     *
181
     * @param string $name The placeholder name or number.
182
     *
183
     * @param mixed $value The value to bind to the placeholder.
184
     *
185
     * @return $this
186
     *
187
     */
188 136
    public function bindValue($name, $value)
189
    {
190 136
        $this->bind_values[$name] = $value;
191 136
        return $this;
192
    }
193
194
    /**
195
     *
196
     * Gets the values to bind to placeholders.
197
     *
198
     * @return array
199
     *
200
     */
201 126
    public function getBindValues()
202
    {
203 126
        return $this->bind_values;
204
    }
205
206
    /**
207
     *
208
     * Reset all values bound to named placeholders.
209
     *
210
     * @return $this
211
     *
212
     */
213 15
    public function resetBindValues()
214
    {
215 15
        $this->bind_values = array();
216 15
        return $this;
217
    }
218
219
    /**
220
     *
221
     * Sets or unsets specified flag.
222
     *
223
     * @param string $flag Flag to set or unset
224
     *
225
     * @param bool $enable Flag status - enabled or not (default true)
226
     *
227
     * @return null
228
     *
229
     */
230 43
    protected function setFlag($flag, $enable = true)
231
    {
232 43
        if ($enable) {
233 43
            $this->flags[$flag] = true;
234 43
        } else {
235 5
            unset($this->flags[$flag]);
236
        }
237 43
    }
238
239
    /**
240
     *
241
     * Returns true if the specified flag was enabled by setFlag().
242
     *
243
     * @param string $flag Flag to check
244
     *
245
     * @return bool
246
     *
247
     */
248 10
    protected function hasFlag($flag)
249
    {
250 10
        return isset($this->flags[$flag]);
251
    }
252
253
    /**
254
     *
255
     * Reset all query flags.
256
     *
257
     * @return $this
258
     *
259
     */
260 20
    public function resetFlags()
261
    {
262 20
        $this->flags = array();
263 20
        return $this;
264
    }
265
266
    /**
267
     *
268
     * Adds conditions to a clause, and binds values.
269
     *
270
     * @param string $clause The clause to work with, typically 'where' or
271
     * 'having'.
272
     *
273
     * @param string $andor Add the condition using this operator, typically
274
     * 'AND' or 'OR'.
275
     *
276
     * @param array $cond The intermixed WHERE snippets and bind values.
277
     *
278
     * @return null
279
     *
280
     */
281 80
    protected function addClauseConditions($clause, $andor, array $cond)
282
    {
283 80
        if (empty($cond)) {
284
            return;
285
        }
286
287 80
        if ($cond[0] instanceof Closure) {
288 10
            $this->addClauseClosure($clause, $andor, $cond[0]);
289 10
            return;
290
        }
291
292 80
        $cond = $this->fixConditions($cond);
293 80
        $clause =& $this->$clause;
294 80
        if ($clause) {
295 59
            $clause[] = "{$andor} {$cond}";
296 59
        } else {
297 80
            $clause[] = $cond;
298
        }
299 80
    }
300
301
    /**
302
     *
303
     * Rebuilds intermixed condition snippets and bind values into a single
304
     * string, binding along the way.
305
     *
306
     * @param array $cond The intermixed condition snippets and bind values.
307
     *
308
     * @return string The rebuilt condition string.
309
     *
310
     */
311 130
    protected function fixConditions(array $cond)
312
    {
313 130
        $fixed = '';
314 130
        while (! empty($cond)) {
315 130
            $fixed .= $this->fixCondition($cond);
316 130
        }
317 130
        return $fixed;
318
    }
319
320
    /**
321
     *
322
     * Rebuilds the next condition snippets and its bind value.
323
     *
324
     * @param array $cond The intermixed condition snippets and bind values.
325
     *
326
     * @return string The rebuilt condition string.
327
     *
328
     */
329 130
    protected function fixCondition(&$cond)
330
    {
331 130
        $fixed = $this->quoter->quoteNamesIn(array_shift($cond));
332 130
        if (! $cond) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cond of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
333 114
            return $fixed;
334
        }
335
336 80
        $value = array_shift($cond);
337
338 80
        if ($value instanceof SelectInterface) {
339 10
            $fixed .= $value->getStatement();
340 10
            $this->bind_values = array_merge(
341 10
                $this->bind_values,
342 10
                $value->getBindValues()
343 10
            );
344 10
            return $fixed;
345
        }
346
347 75
        $place = $this->getSeqPlaceholder();
348 75
        $fixed .= ":{$place}";
349 75
        $this->bindValue($place, $value);
350 75
        return $fixed;
351
    }
352
353
    /**
354
     *
355
     * Adds to a clause through a closure, enclosing within parentheses.
356
     *
357
     * @param string $clause The clause to work with, typically 'where' or
358
     * 'having'.
359
     *
360
     * @param string $andor Add the condition using this operator, typically
361
     * 'AND' or 'OR'.
362
     *
363
     * @param callable $closure The closure that adds to the clause.
364
     *
365
     * @return null
366
     *
367
     */
368 10
    protected function addClauseClosure($clause, $andor, $closure)
369
    {
370
        // retain the prior set of conditions, and temporarily reset the clause
371
        // for the closure to work with (otherwise there will be an extraneous
372
        // opening AND/OR keyword)
373 10
        $set = $this->$clause;
374 10
        $this->$clause = [];
375
376
        // invoke the closure, which will re-populate the $this->$clause
377 10
        $closure($this);
378
379
        // are there new clause elements?
380 10
        if (! $this->$clause) {
381
            // no: restore the old ones, and done
382 10
            $this->$clause = $set;
383 10
            return;
384
        }
385
386
        // append an opening parenthesis to the prior set of conditions,
387
        // with AND/OR as needed ...
388 10
        if ($set) {
389 10
            $set[] = "{$andor} (";
390 10
        } else {
391 10
            $set[] = "(";
392
        }
393
394
        // append the new conditions to the set, with indenting
395 10
        foreach ($this->$clause as $cond) {
396 10
            $set[] = "    {$cond}";
397 10
        }
398 10
        $set[] = ")";
399
400
        // ... then put the full set of conditions back into $this->$clause
401 10
        $this->$clause = $set;
402 10
    }
403
404
405
    /**
406
     *
407
     * Adds a column order to the query.
408
     *
409
     * @param array $spec The columns and direction to order by.
410
     *
411
     * @return $this
412
     *
413
     */
414 9
    protected function addOrderBy(array $spec)
415
    {
416 9
        foreach ($spec as $col) {
417 9
            $this->order_by[] = $this->quoter->quoteNamesIn($col);
418 9
        }
419 9
        return $this;
420
    }
421
422 75
    protected function getSeqPlaceholder()
423
    {
424 75
        $this->seq_bind_number ++;
425 75
        return $this->seq_bind_prefix . "_{$this->seq_bind_number}_";
426
    }
427
428
}
429