Passed
Push — v2 ( 0821e6...8b5ae0 )
by Berend
03:14
created

Query::addLateBindings()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 6
c 0
b 0
f 0
nc 6
nop 0
dl 0
loc 13
ccs 5
cts 5
cp 1
crap 5
rs 9.6111
1
<?php
2
3
/**
4
 * This file is part of the miBadger package.
5
 *
6
 * @author Michael Webbers <[email protected]>
7
 * @license http://opensource.org/licenses/Apache-2.0 Apache v2 License
8
 */
9
10
namespace miBadger\Query;
11
12
/**
13
 * The query class.
14
 *
15
 * @since 1.0.0
16
 */
17
class Query implements QueryInterface
18
{
19
	/* @var \PDO The PDO. */
20
	private $pdo;
21
22
	/* @var array The bindings. */
23
	private $bindings;
24
25
	/* @var QueryBuilder The query builder. */
26
	private $queryBuilder;
27
28
	/* @var QueryExpression The expression for the where clause */
29
	private $whereExpression;
30
31
	/* @var QueryExpression The expression for the having clause */
32
	private $havingExpression;
33
34 21
	/**
35
	 * Construct a query object with the given pdo and table.
36 21
	 *
37 21
	 * @param \PDO $pdo
38 21
	 * @param string $table
39 21
	 */
40
	public function __construct(\PDO $pdo, $table)
41
	{
42
		$this->pdo = $pdo;
43
		$this->bindings = [];
44
		$this->whereExpression = null;
45
		$this->havingExpression = null;
46 21
		$this->queryBuilder = new QueryBuilder($table);
47
	}
48 21
49
	/**
50
	 * Returns a string representation of the query object.
51
	 *
52
	 * @return string a string representation of the query object.
53
	 */
54 17
	public function __toString()
55
	{
56 17
		return $this->toPreparedString();
57
	}
58 17
59
	/**
60
	 * Binds the registered Where and Having expression clauses, 
61
	 * 	before being executed (or printed)
62
	 */
63
	private function addLateBindings()
64 1
	{
65
		// Late binding of where statement
66 1
		if ($this->whereExpression !== null) {
67 1
			foreach ($this->whereExpression->getFlattenedConditions() as $cond) {
68
				$cond->bind($this, 'where');
69 1
			}
70
		}
71
72
		// Late binding of having statement
73
		if ($this->havingExpression !== null) {
74
			foreach ($this->havingExpression->getFlattenedConditions() as $cond) {
75 1
				$cond->bind($this, 'having');
76
			}
77 1
		}		
78
	}
79 1
80
	/**
81
	 * Removes the late-bound bindings for the Where/Having expression clauses,
82
	 * 	used to make debug strings possible
83
	 */
84
	private function clearLateBindings()
85 1
	{
86
		// Remove the binding from the QueryConditions and remove the bound values.
87 1
		if ($this->whereExpression !== null) {
88
			foreach ($this->whereExpression->getFlattenedConditions() as $cond) {
89 1
				$cond->clearBinding();
90
			}
91
		}
92
		$this->removeBindings('where');
93
94
		if ($this->havingExpression !== null) {
95 1
			foreach ($this->havingExpression->getFlattenedConditions() as $cond) {
96
				$cond->clearBinding();
97 1
			}
98
		}
99 1
		$this->removeBindings('having');
100
	}
101
102
	/**
103
	 * Returns the prepared SQL string, where binding substitutions have been applied
104
	 * Example: SELECT * FROM table WHERE name = :where1
105 1
	 */
106
	public function toPreparedString()
107 1
	{
108
		$this->addLateBindings();
109 1
		return $this->queryBuilder->__toString();
110
	}
111
112
	/**
113
	 * Returns the SQL string without prepared statements, useful for debugging or logging purposes
114
	 * Example: SELECT * FROM table WHERE name = John Doe
115 1
	 */
116
	public function toDebugString()
117 1
	{
118
		$this->clearLateBindings();
119 1
		return $this->queryBuilder->__toString();
120
	}
121
122
	/**
123
	 * {@inheritdoc}
124
	 */
125 1
	public function select(Array $columns = ['*'], bool $quote = true)
126
	{
127 1
		if ($quote === true && $columns !== ['*']) {
128
			$quotedColumns = [];
129 1
			foreach ($columns as $column) {
130
				$quotedColumns[] = '`' . $column . '`';
131
			}
132
			$this->queryBuilder->select($quotedColumns);
133
		} else {
134
			$this->queryBuilder->select($columns);
135 7
		}
136
137 7
		return $this;
138
	}
139 7
140 7
	/**
141
	 * {@inheritdoc}
142
	 */
143 7
	public function insert(array $values)
144 7
	{
145
		$this->bindings['insert'] = [];
146
		$this->queryBuilder->insert($this->setBindings('insert', $values));
147
148
		return $this;
149
	}
150 1
151
	/**
152 1
	 * {@inheritdoc}
153
	 */
154 1
	public function update(array $values)
155
	{
156
		$this->queryBuilder->update($this->setBindings('update', $values));
157
158
		return $this;
159
	}
160 1
161
	/**
162 1
	 * {@inheritdoc}
163
	 */
164 1
	public function delete()
165
	{
166
		$this->queryBuilder->delete();
167
168
		return $this;
169
	}
170 2
171
	/**
172 2
	 * {@inheritdoc}
173
	 */
174 2
	public function join($table, $primary, $operator, $secondary)
175
	{
176
		$this->queryBuilder->join($table, $primary, $operator, $secondary);
177
178
		return $this;
179
	}
180 1
181
	/**
182 1
	 * {@inheritdoc}
183
	 */
184 1
	public function leftJoin($table, $primary, $operator, $secondary)
185
	{
186
		$this->queryBuilder->leftJoin($table, $primary, $operator, $secondary);
187
188
		return $this;
189
	}
190
191
	/**
192 4
	 * {@inheritdoc}
193
	 */
194 4
	public function rightJoin($table, $primary, $operator, $secondary)
195
	{
196 4
		$this->queryBuilder->rightJoin($table, $primary, $operator, $secondary);
197 1
198 1
		return $this;
199
	}
200
201
	/**
202 4
	 * {@inheritdoc}
203
	 */
204 4
	public function crossJoin($table, $primary, $operator, $secondary)
205
	{
206
		$this->queryBuilder->crossJoin($table, $primary, $operator, $secondary);
207
208
		return $this;
209
	}
210
211
	/**
212
	 * {@inheritdoc}
213 1
	 */
214
	public function where(QueryExpression $exp)
215 1
	{
216
		$this->whereExpression = $exp;
217 1
218 1
		if ($this->queryBuilder->where !== null) {
219 1
			throw new QueryException('Can only call where on query once.');
220 1
		}
221 1
222 1
		$this->queryBuilder->where($exp);
223
		return $this;
224
	}
225 1
226
	public function having(QueryExpression $exp)
227
	{
228
		$this->havingExpression = $exp;
229
230
		if ($this->queryBuilder->having !== null) {
231
			throw new QueryException('Can only call having on query once.');
232
		}
233
234
		$this->queryBuilder->having($exp);
235 10
		return $this;
236
	}
237 10
238
	/**
239 10
	 * {@inheritdoc}
240
	 */
241
	public function groupBy($column)
242
	{
243
		$this->queryBuilder->groupBy($column);
244
245
		return $this;
246
	}
247
248
	/**
249 3
	 * {@inheritdoc}
250
	 */
251 3
	public function orderBy($column, $order = null)
252
	{
253 3
		$this->queryBuilder->orderBy($column, $order);
254 3
255
		return $this;
256
	}
257 3
258
	/**
259
	 * {@inheritdoc}
260
	 */
261
	public function limit($limit)
262
	{
263
		$this->queryBuilder->limit($this->setBinding('limit', (int) $limit));
264
265
		return $this;
266
	}
267 2
268
	/**
269 2
	 * {@inheritdoc}
270
	 */
271
	public function offset($offset)
272
	{
273
		$this->queryBuilder->offset($this->setBinding('offset', (int) $offset));
274
275
		return $this;
276
	}
277
278
	/**
279 2
	 * Returns the result of the executed prepared query.
280
	 *
281 2
	 * @return QueryResult the result of the executed prepared query.
282
	 */
283
	public function execute()
284
	{
285
		$pdoStatement = $this->pdo->prepare((string) $this);
286
287
		foreach ($this->bindings as $clause => $predicate) {
288
			foreach ($predicate as $key => $value) {
289
				$pdoStatement->bindValue(sprintf(':%s%d', $clause, $key + 1), $value, $this->getPdoDataType($value));
290 4
			}
291
		}
292 4
293
		$pdoStatement->execute();
294 4
295
		$this->clearLateBindings();
296
297
		return new QueryResult($pdoStatement);
0 ignored issues
show
Bug introduced by
It seems like $pdoStatement can also be of type boolean; however, parameter $pdoStatement of miBadger\Query\QueryResult::__construct() does only seem to accept PDOStatement, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

297
		return new QueryResult(/** @scrutinizer ignore-type */ $pdoStatement);
Loading history...
298
	}
299
300
	/**
301
	 * Returns the data type of the given value.
302
	 *
303
	 * @param mixed $value
304
	 * @return int the data type of the given value.
305
	 */
306
	protected function getPdoDataType($value)
307
	{
308
		$result = \PDO::PARAM_STR;
309
310
		if (is_bool($value)) {
311
			$result = \PDO::PARAM_BOOL;
312
		} elseif (is_null($value)) {
313
			$result = \PDO::PARAM_NULL;
314
		} elseif (is_int($value)) {
315
			$result = \PDO::PARAM_INT;
316
		}
317
318
		return $result;
319
	}
320
321
	/**
322
	 * Returns a binding for the given clause and value.
323
	 *
324
	 * @param string $clause
325
	 * @param string $value
326
	 * @return string a binding for the given clause and value.
327
	 */
328
	public function addBinding($clause, $value)
329
	{
330
		$this->bindings[$clause][] = $value;
331
332
		return sprintf(':%s%d', $clause, count($this->bindings[$clause]));
333
	}
334
335
	/**
336
	 * Returns bindings for the given clause and values.
337
	 *
338
	 * @param string $clause
339
	 * @param array $values
340
	 * @return array bindings for the given clause and values.
341
	 */
342
	public function addBindings($clause, array $values)
343
	{
344
		$result = [];
345
346
		foreach ($values as $key => $value) {
347 7
			$result[$key] = $this->addBinding($clause, $value);
348
		}
349 7
350
		return $result;
351
	}
352
353
	/**
354
	 * Returns a binding for the given clause and value.
355
	 *
356
	 * @param string $clause
357
	 * @param string $value
358
	 * @return string a binding for the given clause and value.
359
	 */
360
	private function setBinding($clause, $value)
361
	{
362
		return $this->removeBindings($clause)->addBinding($clause, $value);
363
	}
364
365
	/**
366
	 * Returns bindings for the given clause and values.
367
	 *
368
	 * @param string $clause
369 1
	 * @param array $values
370
	 * @return array bindings for the given clause and values.
371 1
	 */
372
	private function setBindings($clause, array $values)
373
	{
374
		return $this->removeBindings($clause)->addBindings($clause, $values);
375
	}
376
377
	/**
378
	 * Remove the bindings that are associated with the given clause.
379
	 *
380 8
	 * @param string $clause
381
	 * @return $this
382 8
	 */
383
	private function removeBindings($clause)
384
	{
385
		$this->bindings[$clause] = [];
386
387
		return $this;
388
	}
389
390
	/**
391
	 * Creates a Greater than Query condition, equivalent to mysql > operator
392
	 * @param string $left the lhs of the condition. Unescaped
393
	 * @param mixed $right the rhs of the condition. Escaped
394
	 * @return QueryCondition the query condition
395
	 */
396
	public static function Greater($left, $right): QueryCondition
397
	{
398
		return new QueryCondition($left, '>', $right);
399
	}
400
401
	/**
402 1
	 * Creates a "Greater than or equal to" Query condition, equivalent to mysql >= operator
403
	 * @param string $left the lhs of the condition. Unescaped
404 1
	 * @param mixed $right the rhs of the condition. Escaped
405
	 * @return QueryCondition the query condition
406
	 */
407
	public static function GreaterOrEqual($left, $right): QueryCondition
408
	{
409
		return new QueryCondition($left, '>=', $right);
410
	}
411
412
	/**
413
	 * Creates a "Less than" Query condition, equivalent to mysql < operator
414
	 * @param string $left the lhs of the condition. Unescaped
415
	 * @param mixed $right the rhs of the condition. Escaped
416
	 * @return QueryCondition the query condition
417
	 */
418
	public static function Less($left, $right): QueryCondition
419
	{
420
		return new QueryCondition($left, '<', $right);
421
	}
422
423
	/**
424 3
	 * Creates a "Less than or equal to" Query condition, equivalent to mysql <= operator
425
	 * @param string $left the lhs of the condition. Unescaped
426 3
	 * @param mixed $right the rhs of the condition. Escaped
427
	 * @return QueryCondition the query condition
428
	 */
429
	public static function LessOrEqual($left, $right): QueryCondition
430
	{
431
		return new QueryCondition($left, '<=', $right);
432
	}
433 3
434
	/**
435 3
	 * Creates an "equal to" Query condition, equivalent to mysql = operator
436
	 * @param string $left the lhs of the condition. Unescaped
437
	 * @param mixed $right the rhs of the condition. Escaped
438
	 * @return QueryCondition the query condition
439
	 */
440
	public static function Equal($left, $right): QueryCondition
441
	{
442 1
		return new QueryCondition($left, '=', $right);
443
	}
444 1
445
	/**
446
	 * Creates a "Not equal to" Query condition, equivalent to mysql <> or != operators
447
	 * @param string $left the lhs of the condition. Unescaped
448
	 * @param mixed $right the rhs of the condition. Escaped
449
	 * @return QueryCondition the query condition
450
	 */
451
	public static function NotEqual($left, $right): QueryCondition
452 1
	{
453
		return new QueryCondition($left, '<>', $right);
454 1
	}
455
456
	/**
457
	 * Creates a "Not Like" Query condition, equivalent to mysql NOT LIKE operator
458
	 * @param string $left the lhs of the condition. Unescaped
459
	 * @param mixed $right the rhs of the condition. Escaped
460
	 * @return QueryCondition the query condition
461
	 */
462
	public static function NotLike($left, $right): QueryCondition
463
	{
464
		return new QueryCondition($left, 'NOT LIKE', $right);
465
	}
466
467
	/**
468
	 * Creates a "Like" Query condition, equivalent to mysql LIKE operator
469
	 * @param string $left the lhs of the condition. Unescaped
470
	 * @param mixed $right the rhs of the condition. Escaped
471
	 * @return QueryCondition the query condition
472
	 */
473
	public static function Like($left, $right): QueryCondition
474
	{
475
		return new QueryCondition($left, 'LIKE', $right);
476
	}
477
478
	/**
479
	 * Creates an "Is" Query condition, equivalent to mysql IS operator
480
	 * @param string $left the lhs of the condition. Unescaped
481
	 * @param mixed $right the rhs of the condition. Escaped
482
	 * @return QueryCondition the query condition
483
	 */
484
	public static function Is($left, $right): QueryCondition
485
	{
486
		return new QueryCondition($left, 'IS', $right);
487
	}
488
489
	/**
490
	 * Creates an "Is not" Query condition, equivalent to mysql IS NOT operator
491
	 * @param string $left the lhs of the condition. Unescaped
492
	 * @param string|array $right the rhs of the condition. Escaped
493
	 * @return QueryCondition the query condition
494
	 */
495
	public static function IsNot($left, $right): QueryCondition
496
	{
497
		return new QueryCondition($left, 'IS NOT', $right);
498
	}
499
500
	/**
501
	 * Creates a "Not in" Query condition, equivalent to mysql NOT IN operator
502
	 * @param string $needle the parameter that cannot be present in the haystack. Unescaped
503
	 * @param string|Array $haystack the values that can be searched through. Escaped
504
	 * @return QueryCondition the query condition
505
	 */
506
	public static function NotIn($needle, $haystack): QueryCondition
507
	{
508
		return new QueryCondition($needle, 'NOT IN', $haystack);
509
	}
510
511
	/**
512
	 * Creates a "In" Query condition, equivalent to mysql IN operator
513
	 * @param string $needle the parameter that has to be found. Unescaped
514
	 * @param string|Array $haystack the values that can be searched through. Escaped
515
	 * @return QueryCondition the query condition
516
	 */
517
	public static function In($needle, $haystack): QueryCondition
518
	{
519
		return new QueryCondition($needle, 'IN', $haystack);
520
	}
521
522
	/**
523
	 * Creates an "AND" predicate from a variable number of expressions
524
	 * @return QueryPredicate the predicate expression
525
	 */
526
	public static function And(QueryExpression $left, QueryExpression ...$others): ?QueryPredicate
527
	{
528
		return new QueryPredicate('AND', $left, ...$others);
529
	}
530
531
	/**
532
	 * Combines an array of QueryExpression clauses into an AND predicate
533
	 * @return miBadger\Query\QueryExpression|null Either null (if array contains no clauses), 
0 ignored issues
show
Bug introduced by
The type miBadger\Query\miBadger\Query\QueryExpression was not found. Did you mean miBadger\Query\QueryExpression? If so, make sure to prefix the type with \.
Loading history...
534
	 * 				the single clause in the input array, or a QueryPredicate combining the clauses
535
	 */
536
	public static function AndArray(Array $clauses): ?QueryPredicate
537
	{
538
		if (count($clauses) == 0) {
539
			return null;
540
		} else if (count($clauses) == 1) 
541
		{
542
			return $clauses[0];
543
		} else {
544
			return new QueryPredicate('AND', $clauses[0], ...array_slice($clauses, 1));
545
		}
546
	}
547
548
	/**
549
	 * Creates an "OR" predicate from a variable number of expressions
550
	 * @return QueryPredicate the predicate expression
551
	 */
552
	public static function Or(QueryExpression $left, QueryExpression ...$others): QueryPredicate
553
	{
554
		return new QueryPredicate('OR', $left, ...$others);
555
	}
556
557
	/**
558
	 * Combines an array of QueryExpression clauses into an OR predicate
559
	 * @return miBadger\Query\QueryExpression|null Either null (if array contains no clauses), 
560
	 * 				the single clause in the input array, or a QueryPredicate combining the clauses
561
	 */
562
	public static function OrArray(Array $clauses): ?QueryPredicate
563
	{
564
		if (count($clauses) == 0) {
565
			return null;
566
		} else if (count($clauses) == 1)
567
		{
568
			return $clauses[0];
569
		} else {
570
			return new QueryPredicate('OR', $clauses[0], ...array_slice($clauses, 1));
571
		}
572
	}
573
574
	/**
575
	 * Creates a "NOT" predicate negating an expression
576
	 * @param QueryExpression The condition to be negated
0 ignored issues
show
Bug introduced by
The type miBadger\Query\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
577
	 * @return QueryPredicate the predicate expression
578
	 */
579
	public static function Not(QueryExpression $exp): QueryPredicate
580
	{
581
		return new QueryPredicate('NOT', $exp);
582
	}
583
}
584