Passed
Pull Request — 3.x (#198)
by Akihito
01:28
created

AbstractQuery::getBindValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
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 Common\Quoter
65
     *
66
     */
67
    protected $quoter;
68
69
    /**
70
     *
71
     * A builder for the query.
72
     *
73
     * @var Common\AbstractBuilder
74
     *
75
     */
76
    protected $builder;
77
78
    /**
79
     * @var int
80
     */
81
    protected $inlineCount = 0;
82
83
    /**
84
     *
85
     * Constructor.
86
     *
87 417
     * @param Common\Quoter $quoter A helper for quoting identifier names.
88
     *
89 417
     * @param Common\AbstractBuilder $builder A builder for the query.
90 417
     *
91 417
     */
92
    public function __construct(QuoterInterface $quoter, $builder)
93
    {
94
        $this->quoter = $quoter;
0 ignored issues
show
Documentation Bug introduced by
$quoter is of type Aura\SqlQuery\Common\QuoterInterface, but the property $quoter was declared to be of type Aura\SqlQuery\Common\Quoter. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
95
        $this->builder = $builder;
96
    }
97
98
    /**
99
     *
100 243
     * Returns this query object as an SQL statement string.
101
     *
102 243
     * @return string
103
     *
104
     */
105
    public function __toString()
106
    {
107
        return $this->getStatement();
108
    }
109
110
    /**
111
     *
112 69
     * Returns this query object as an SQL statement string.
113
     *
114 69
     * @return string
115
     *
116
     */
117
    public function getStatement()
118
    {
119
        return $this->build();
120
    }
121
122
    /**
123
     *
124
     * Builds this query object into a string.
125
     *
126
     * @return string
127
     *
128
     */
129
    abstract protected function build();
130
131
    /**
132
     *
133 267
     * Returns the prefix to use when quoting identifier names.
134
     *
135 267
     * @return string
136
     *
137
     */
138
    public function getQuoteNamePrefix()
139
    {
140
        return $this->quoter->getQuoteNamePrefix();
141
    }
142
143
    /**
144
     *
145 267
     * Returns the suffix to use when quoting identifier names.
146
     *
147 267
     * @return string
148
     *
149
     */
150
    public function getQuoteNameSuffix()
151
    {
152
        return $this->quoter->getQuoteNameSuffix();
153
    }
154
155
    /**
156
     *
157
     * Binds multiple values to placeholders; merges with existing values.
158
     *
159 41
     * @param array $bind_values Values to bind to placeholders.
160
     *
161
     * @return $this
162
     *
163 41
     */
164 31
    public function bindValues(array $bind_values)
165
    {
166 41
        // array_merge() renumbers integer keys, which is bad for
167
        // question-mark placeholders
168
        foreach ($bind_values as $key => $val) {
169
            $this->bindValue($key, $val);
170
        }
171
        return $this;
172
    }
173
174
    /**
175
     *
176
     * Binds a single value to the query.
177
     *
178
     * @param string $name The placeholder name or number.
179
     *
180 136
     * @param mixed $value The value to bind to the placeholder.
181
     *
182 136
     * @return $this
183 136
     *
184
     */
185
    public function bindValue($name, $value)
186
    {
187
        $this->bind_values[$name] = $value;
188
        return $this;
189
    }
190
191
    /**
192
     *
193 126
     * Gets the values to bind to placeholders.
194
     *
195 126
     * @return array
196
     *
197
     */
198
    public function getBindValues()
199
    {
200
        return $this->bind_values;
201
    }
202
203
    /**
204
     *
205 15
     * Reset all values bound to named placeholders.
206
     *
207 15
     * @return $this
208 15
     *
209
     */
210
    public function resetBindValues()
211
    {
212
        $this->bind_values = array();
213
        return $this;
214
    }
215
216
    /**
217
     *
218
     * Sets or unsets specified flag.
219
     *
220
     * @param string $flag Flag to set or unset
221
     *
222 43
     * @param bool $enable Flag status - enabled or not (default true)
223
     *
224 43
     * @return null
225 43
     *
226
     */
227 5
    protected function setFlag($flag, $enable = true)
228
    {
229 43
        if ($enable) {
230
            $this->flags[$flag] = true;
231
        } else {
232
            unset($this->flags[$flag]);
233
        }
234
    }
235
236
    /**
237
     *
238
     * Returns true if the specified flag was enabled by setFlag().
239
     *
240 10
     * @param string $flag Flag to check
241
     *
242 10
     * @return bool
243
     *
244
     */
245
    protected function hasFlag($flag)
246
    {
247
        return isset($this->flags[$flag]);
248
    }
249
250
    /**
251
     *
252 20
     * Reset all query flags.
253
     *
254 20
     * @return $this
255 20
     *
256
     */
257
    public function resetFlags()
258
    {
259
        $this->flags = array();
260
        return $this;
261
    }
262
263
    /**
264
     *
265
     * Adds conditions and binds values to a clause.
266
     *
267
     * @param string $clause The clause to work with, typically 'where' or
268
     * 'having'.
269
     *
270
     * @param string $andor Add the condition using this operator, typically
271
     * 'AND' or 'OR'.
272
     *
273
     * @param string $cond The WHERE condition.
274
     *
275 80
     * @param array $bind arguments to bind to placeholders
276
     *
277 80
     * @return null
278 10
     *
279 10
     */
280 10
    protected function addClauseCondWithBind($clause, $andor, $cond, $bind)
281
    {
282
        if ($cond instanceof Closure) {
0 ignored issues
show
introduced by
$cond is never a sub-type of Closure.
Loading history...
283 80
            $this->addClauseCondClosure($clause, $andor, $cond);
284 80
            $this->bindValues($bind);
285
            return;
286 80
        }
287 80
288 59
        $cond = $this->quoter->quoteNamesIn($cond);
289
        $cond = $this->rebuildCondAndBindValues($cond, $bind);
290 80
291
        $clause =& $this->$clause;
292 80
        if ($clause) {
293
            $clause[] = "$andor $cond";
294
        } else {
295
            $clause[] = $cond;
296
        }
297
    }
298
299
    /**
300
     *
301
     * Adds to a clause through a closure, enclosing within parentheses.
302
     *
303
     * @param string $clause The clause to work with, typically 'where' or
304
     * 'having'.
305
     *
306
     * @param string $andor Add the condition using this operator, typically
307
     * 'AND' or 'OR'.
308
     *
309 10
     * @param callable $closure The closure that adds to the clause.
310
     *
311
     * @return null
312
     *
313
     */
314 10
    protected function addClauseCondClosure($clause, $andor, $closure)
315 10
    {
316
        // retain the prior set of conditions, and temporarily reset the clause
317
        // for the closure to work with (otherwise there will be an extraneous
318 10
        // opening AND/OR keyword)
319
        $set = $this->$clause;
320
        $this->$clause = [];
321 10
322
        // invoke the closure, which will re-populate the $this->$clause
323 10
        $closure($this);
324 10
325
        // are there new clause elements?
326
        if (! $this->$clause) {
327
            // no: restore the old ones, and done
328
            $this->$clause = $set;
329 10
            return;
330 10
        }
331
332 10
        // append an opening parenthesis to the prior set of conditions,
333
        // with AND/OR as needed ...
334
        if ($set) {
335
            $set[] = "{$andor} (";
336 10
        } else {
337 10
            $set[] = "(";
338
        }
339 10
340
        // append the new conditions to the set, with indenting
341
        foreach ($this->$clause as $cond) {
342 10
            $set[] = "    {$cond}";
343 10
        }
344
        $set[] = ")";
345
346
        // ... then put the full set of conditions back into $this->$clause
347
        $this->$clause = $set;
348
    }
349
350
    /**
351
     *
352
     * Rebuilds a condition string, replacing sequential placeholders with
353
     * named placeholders, and binding the sequential values to the named
354
     * placeholders.
355
     *
356
     * @param string $cond The condition with sequential placeholders.
357
     *
358
     * @param array $bind_values The values to bind to the sequential
359 130
     * placeholders under their named versions.
360
     *
361 130
     * @return string The rebuilt condition string.
362
     *
363 130
     */
364 80
    protected function rebuildCondAndBindValues($cond, array $bind_values)
365 10
    {
366
        $index = 0;
367 80
        $selects = [];
368
369
        foreach ($bind_values as $key => $val) {
370
            if ($val instanceof SelectInterface) {
371 130
                $selects[":{$key}"] = $val;
372 10
            } elseif (is_array($val)) {
373 10
                $cond = $this->getCond($key, $cond, $val, $index);
374 10
            } else {
375 10
                $this->bindValue($key, $val);
376
            }
377
            $index++;
378
        }
379 130
380 130
        foreach ($selects as $key => $select) {
381
            $selects[$key] = $select->getStatement();
382
            $this->bind_values = array_merge(
383
                $this->bind_values,
384
                $select->getBindValues()
385
            );
386
        }
387
388
        $cond = strtr($cond, $selects);
389
        return $cond;
390
    }
391
392 9
    protected function inlineArray(array $array)
393
    {
394 9
        $keys = [];
395 9
        foreach ($array as $val) {
396
            $this->inlineCount++;
397 9
            $key = "__{$this->inlineCount}__";
398
            $this->bindValue($key, $val);
399
            $keys[] = ":{$key}";
400
        }
401
        return implode(', ', $keys);
402
    }
403
404
    /**
405
     *
406
     * Adds a column order to the query.
407
     *
408
     * @param array $spec The columns and direction to order by.
409
     *
410
     * @return $this
411
     *
412
     */
413
    protected function addOrderBy(array $spec)
414
    {
415
        foreach ($spec as $col) {
416
            $this->order_by[] = $this->quoter->quoteNamesIn($col);
417
        }
418
        return $this;
419
    }
420
421
    /**
422
     * @param int|string $key
423
     * @param string     $cond
424
     * @param array      $val
425
     * @param int        $index
426
     *
427
     * @return string
428
     */
429
    private function getCond($key, $cond, array $val, $index)
430
    {
431
        if (is_string($key)) {
432
            return str_replace(':' . $key, $this->inlineArray($val), $cond);
433
        }
434
        assert(is_int($key));
435
436
        if (preg_match_all('/\?/', $cond, $matches, PREG_OFFSET_CAPTURE) !== false) {
437
            return substr_replace($cond, $this->inlineArray($val), $matches[0][$index][1], 1);
0 ignored issues
show
Bug Best Practice introduced by
The expression return substr_replace($c...tches[0][$index][1], 1) also could return the type array which is incompatible with the documented return type string.
Loading history...
438
        }
439
440
        return $cond;
441
    }
442
}
443