QueryBuilder::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 1
eloc 6
nc 1
nop 2
1
<?php
2
/**
3
 * Query
4
 *
5
 * Free Query Builder / Database Abstraction Layer
6
 *
7
 * @package		Query
8
 * @author		Timothy J. Warren
9
 * @copyright	Copyright (c) 2012 - 2015
10
 * @link 		https://github.com/aviat4ion/Query
11
 * @license		http://philsturgeon.co.uk/code/dbad-license
12
 */
13
14
// --------------------------------------------------------------------------
15
16
namespace Query;
17
18
// --------------------------------------------------------------------------
19
20
/**
21
 * Convenience class for creating sql queries - also the class that
22
 * instantiates the specific db driver
23
 *
24
 * @package Query
25
 * @subpackage Query_Builder
26
 */
27
class QueryBuilder extends AbstractQueryBuilder implements QueryBuilderInterface {
28
29
	/**
30
	 * String class values to be reset
31
	 *
32
	 * @var array
33
	 */
34
	private $string_vars = array(
35
		'select_string',
36
		'from_string',
37
		'set_string',
38
		'order_string',
39
		'group_string',
40
		'limit',
41
		'offset',
42
		'explain',
43
	);
44
45
	/**
46
	 * Array class variables to be reset
47
	 *
48
	 * @var array
49
	 */
50
	private $array_vars = array(
51
		'set_array_keys',
52
		'order_array',
53
		'group_array',
54
		'values',
55
		'where_values',
56
		'query_map',
57
		'having_map'
58
	);
59
60
	// --------------------------------------------------------------------------
61
	// ! Methods
62
	// --------------------------------------------------------------------------
63
64
	/**
65
	 * Constructor
66
	 *
67
	 * @param DriverInterface $db
68
	 * @param QueryParser $parser
69
	 */
70
	public function __construct(DriverInterface $db, QueryParser $parser)
71
	{
72
		// Inject driver and parser
73
		$this->db = $db;
74
		$this->parser = $parser;
75
76
		$this->queries['total_time'] = 0;
77
78
		// Alias driver sql and util classes
79
		$this->sql = $this->db->get_sql();
80
		$this->util = $this->db->get_util();
81
	}
82
83
	// --------------------------------------------------------------------------
84
85
	/**
86
	 * Destructor
87
	 * @codeCoverageIgnore
88
	 */
89
	public function __destruct()
90
	{
91
		$this->db = NULL;
92
	}
93
94
	// --------------------------------------------------------------------------
95
96
	/**
97
	 * Calls a function further down the inheritence chain
98
	 *
99
	 * @param string $name
100
	 * @param array $params
101
	 * @return mixed
102
	 * @throws \BadMethodCallException
103
	 */
104
	public function __call($name, $params)
105
	{
106
		// Allow camel-case method calls
107
		$snake_name = \from_camel_case($name);
108
109
		foreach(array($this, $this->db) as $object)
110
		{
111
			foreach(array($name, $snake_name) as $method_name)
112
			{
113
				if (method_exists($object, $method_name))
114
				{
115
					return call_user_func_array(array($object, $method_name), $params);
116
				}
117
			}
118
119
		}
120
121
		throw new \BadMethodCallException("Method does not exist");
122
	}
123
124
	// --------------------------------------------------------------------------
125
	// ! Select Queries
126
	// --------------------------------------------------------------------------
127
128
	/**
129
	 * Specifies rows to select in a query
130
	 *
131
	 * @param string $fields
132
	 * @return QueryBuilder
133
	 */
134
	public function select($fields)
135
	{
136
		// Split fields by comma
137
		$fields_array = explode(",", $fields);
138
		$fields_array = array_map('mb_trim', $fields_array);
139
140
		// Split on 'As'
141
		foreach ($fields_array as $key => $field)
142
		{
143
			if (stripos($field, 'as') !== FALSE)
144
			{
145
				$fields_array[$key] = preg_split('` as `i', $field);
146
				$fields_array[$key] = array_map('mb_trim', $fields_array[$key]);
147
			}
148
		}
149
150
		// Quote the identifiers
151
		$safe_array = $this->db->quote_ident($fields_array);
152
153
		unset($fields_array);
154
155
		// Join the strings back together
156
		for($i = 0, $c = count($safe_array); $i < $c; $i++)
157
		{
158
			if (is_array($safe_array[$i]))
159
			{
160
				$safe_array[$i] = implode(' AS ', $safe_array[$i]);
161
			}
162
		}
163
164
		$this->select_string .= implode(', ', $safe_array);
165
166
		unset($safe_array);
167
168
		return $this;
169
	}
170
171
	// --------------------------------------------------------------------------
172
173
	/**
174
	 * Selects the maximum value of a field from a query
175
	 *
176
	 * @param string $field
177
	 * @param string|FALSE $as
178
	 * @return QueryBuilder
179
	 */
180
	public function select_max($field, $as=FALSE)
181
	{
182
		// Create the select string
183
		$this->select_string .= ' MAX'.$this->_select($field, $as);
184
		return $this;
185
	}
186
187
	// --------------------------------------------------------------------------
188
189
	/**
190
	 * Selects the minimum value of a field from a query
191
	 *
192
	 * @param string $field
193
	 * @param string|bool $as
194
	 * @return QueryBuilder
195
	 */
196
	public function select_min($field, $as=FALSE)
197
	{
198
		// Create the select string
199
		$this->select_string .= ' MIN'.$this->_select($field, $as);
200
		return $this;
201
	}
202
203
	// --------------------------------------------------------------------------
204
205
	/**
206
	 * Selects the average value of a field from a query
207
	 *
208
	 * @param string $field
209
	 * @param string|bool $as
210
	 * @return QueryBuilder
211
	 */
212
	public function select_avg($field, $as=FALSE)
213
	{
214
		// Create the select string
215
		$this->select_string .= ' AVG'.$this->_select($field, $as);
216
		return $this;
217
	}
218
219
	// --------------------------------------------------------------------------
220
221
	/**
222
	 * Selects the sum of a field from a query
223
	 *
224
	 * @param string $field
225
	 * @param string|bool $as
226
	 * @return QueryBuilder
227
	 */
228
	public function select_sum($field, $as=FALSE)
229
	{
230
		// Create the select string
231
		$this->select_string .= ' SUM'.$this->_select($field, $as);
232
		return $this;
233
	}
234
235
	// --------------------------------------------------------------------------
236
237
	/**
238
	 * Adds the 'distinct' keyword to a query
239
	 *
240
	 * @return QueryBuilder
241
	 */
242
	public function distinct()
243
	{
244
		// Prepend the keyword to the select string
245
		$this->select_string = ' DISTINCT '.$this->select_string;
246
		return $this;
247
	}
248
249
	// --------------------------------------------------------------------------
250
251
	/**
252
	 * Tell the database to give you the query plan instead of result set
253
	 *
254
	 * @return QueryBuilder
255
	 */
256
	public function explain()
257
	{
258
		$this->explain = TRUE;
259
		return $this;
260
	}
261
262
	// --------------------------------------------------------------------------
263
264
	/**
265
	 * Specify the database table to select from
266
	 *
267
	 * @param string $tblname
268
	 * @return QueryBuilder
269
	 */
270
	public function from($tblname)
271
	{
272
		// Split identifiers on spaces
273
		$ident_array = explode(' ', \mb_trim($tblname));
274
		$ident_array = array_map('\\mb_trim', $ident_array);
275
276
		// Quote the identifiers
277
		$ident_array[0] = $this->db->quote_table($ident_array[0]);
278
		$ident_array = $this->db->quote_ident($ident_array);
279
280
		// Paste it back together
281
		$this->from_string = implode(' ', $ident_array);
282
283
		return $this;
284
	}
285
286
	// --------------------------------------------------------------------------
287
	// ! 'Like' methods
288
	// --------------------------------------------------------------------------
289
290
	/**
291
	 * Creates a Like clause in the sql statement
292
	 *
293
	 * @param string $field
294
	 * @param mixed $val
295
	 * @param string $pos
296
	 * @return QueryBuilder
297
	 */
298
	public function like($field, $val, $pos='both')
299
	{
300
		return $this->_like($field, $val, $pos, 'LIKE', 'AND');
301
	}
302
303
	// --------------------------------------------------------------------------
304
305
	/**
306
	 * Generates an OR Like clause
307
	 *
308
	 * @param string $field
309
	 * @param mixed $val
310
	 * @param string $pos
311
	 * @return QueryBuilder
312
	 */
313
	public function or_like($field, $val, $pos='both')
314
	{
315
		return $this->_like($field, $val, $pos, 'LIKE', 'OR');
316
	}
317
318
	// --------------------------------------------------------------------------
319
320
	/**
321
	 * Generates a NOT LIKE clause
322
	 *
323
	 * @param string $field
324
	 * @param mixed $val
325
	 * @param string $pos
326
	 * @return QueryBuilder
327
	 */
328
	public function not_like($field, $val, $pos='both')
329
	{
330
		return $this->_like($field, $val, $pos, 'NOT LIKE', 'AND');
331
	}
332
333
	// --------------------------------------------------------------------------
334
335
	/**
336
	 * Generates a OR NOT LIKE clause
337
	 *
338
	 * @param string $field
339
	 * @param mixed $val
340
	 * @param string $pos
341
	 * @return QueryBuilder
342
	 */
343
	public function or_not_like($field, $val, $pos='both')
344
	{
345
		return $this->_like($field, $val, $pos, 'NOT LIKE', 'OR');
346
	}
347
348
	// --------------------------------------------------------------------------
349
	// ! Having methods
350
	// --------------------------------------------------------------------------
351
352
	/**
353
	 * Generates a 'Having' clause
354
	 *
355
	 * @param mixed $key
356
	 * @param mixed $val
357
	 * @return QueryBuilder
358
	 */
359
	public function having($key, $val=array())
360
	{
361
		return $this->_having($key, $val, 'AND');
362
	}
363
364
	// --------------------------------------------------------------------------
365
366
	/**
367
	 * Generates a 'Having' clause prefixed with 'OR'
368
	 *
369
	 * @param mixed $key
370
	 * @param mixed $val
371
	 * @return QueryBuilder
372
	 */
373
	public function or_having($key, $val=array())
374
	{
375
		return $this->_having($key, $val, 'OR');
376
	}
377
378
	// --------------------------------------------------------------------------
379
	// ! 'Where' methods
380
	// --------------------------------------------------------------------------
381
382
	/**
383
	 * Specify condition(s) in the where clause of a query
384
	 * Note: this function works with key / value, or a
385
	 * passed array with key / value pairs
386
	 *
387
	 * @param mixed $key
388
	 * @param mixed $val
389
	 * @param mixed $escape
390
	 * @return QueryBuilder
391
	 */
392
	public function where($key, $val=array(), $escape=NULL)
393
	{
394
		return $this->_where_string($key, $val, 'AND');
395
	}
396
397
	// --------------------------------------------------------------------------
398
399
	/**
400
	 * Where clause prefixed with "OR"
401
	 *
402
	 * @param string $key
403
	 * @param mixed $val
404
	 * @return QueryBuilder
405
	 */
406
	public function or_where($key, $val=array())
407
	{
408
		return $this->_where_string($key, $val, 'OR');
409
	}
410
411
	// --------------------------------------------------------------------------
412
413
	/**
414
	 * Where clause with 'IN' statement
415
	 *
416
	 * @param mixed $field
417
	 * @param mixed $val
418
	 * @return QueryBuilder
419
	 */
420
	public function where_in($field, $val=array())
421
	{
422
		return $this->_where_in($field, $val);
423
	}
424
425
	// --------------------------------------------------------------------------
426
427
	/**
428
	 * Where in statement prefixed with "or"
429
	 *
430
	 * @param string $field
431
	 * @param mixed $val
432
	 * @return QueryBuilder
433
	 */
434
	public function or_where_in($field, $val=array())
435
	{
436
		return $this->_where_in($field, $val, 'IN', 'OR');
437
	}
438
439
	// --------------------------------------------------------------------------
440
441
	/**
442
	 * WHERE NOT IN (FOO) clause
443
	 *
444
	 * @param string $field
445
	 * @param mixed $val
446
	 * @return QueryBuilder
447
	 */
448
	public function where_not_in($field, $val=array())
449
	{
450
		return $this->_where_in($field, $val, 'NOT IN', 'AND');
451
	}
452
453
	// --------------------------------------------------------------------------
454
455
	/**
456
	 * OR WHERE NOT IN (FOO) clause
457
	 *
458
	 * @param string $field
459
	 * @param mixed $val
460
	 * @return QueryBuilder
461
	 */
462
	public function or_where_not_in($field, $val=array())
463
	{
464
		return $this->_where_in($field, $val, 'NOT IN', 'OR');
465
	}
466
467
	// --------------------------------------------------------------------------
468
	// ! Other Query Modifier methods
469
	// --------------------------------------------------------------------------
470
471
	/**
472
	 * Sets values for inserts / updates / deletes
473
	 *
474
	 * @param mixed $key
475
	 * @param mixed $val
476
	 * @return QueryBuilder
477
	 */
478
	public function set($key, $val = NULL)
479
	{
480
		$this->_mixed_set($this->set_array_keys, $key, $val, self::KEY);
481
		$this->_mixed_set($this->values, $key, $val, self::VALUE);
482
483
		// Use the keys of the array to make the insert/update string
484
		// Escape the field names
485
		$this->set_array_keys = array_map(array($this->db, '_quote'), $this->set_array_keys);
486
487
		// Generate the "set" string
488
		$this->set_string = implode('=?,', $this->set_array_keys);
489
		$this->set_string .= '=?';
490
491
		return $this;
492
	}
493
494
	// --------------------------------------------------------------------------
495
496
	/**
497
	 * Creates a join phrase in a compiled query
498
	 *
499
	 * @param string $table
500
	 * @param string $condition
501
	 * @param string $type
502
	 * @return QueryBuilder
503
	 */
504
	public function join($table, $condition, $type='')
505
	{
506
		// Prefix and quote table name
507
		$table = explode(' ', mb_trim($table));
508
		$table[0] = $this->db->quote_table($table[0]);
509
		$table = $this->db->quote_ident($table);
510
		$table = implode(' ', $table);
511
512
		// Parse out the join condition
513
		$parsed_condition = $this->parser->compile_join($condition);
514
		$condition = $table . ' ON ' . $parsed_condition;
515
516
		$this->_append_map("\n" . strtoupper($type) . ' JOIN ', $condition, 'join');
517
518
		return $this;
519
	}
520
521
	// --------------------------------------------------------------------------
522
523
	/**
524
	 * Group the results by the selected field(s)
525
	 *
526
	 * @param mixed $field
527
	 * @return QueryBuilder
528
	 */
529
	public function group_by($field)
530
	{
531
		if ( ! is_scalar($field))
532
		{
533
			$new_group_array = array_map(array($this->db, 'quote_ident'), $field);
534
			$this->group_array = array_merge($this->group_array, $new_group_array);
535
		}
536
		else
537
		{
538
			$this->group_array[] = $this->db->quote_ident($field);
539
		}
540
541
		$this->group_string = ' GROUP BY ' . implode(',', $this->group_array);
542
543
		return $this;
544
	}
545
546
	// --------------------------------------------------------------------------
547
548
	/**
549
	 * Order the results by the selected field(s)
550
	 *
551
	 * @param string $field
552
	 * @param string $type
553
	 * @return QueryBuilder
554
	 */
555
	public function order_by($field, $type="")
556
	{
557
		// When ordering by random, do an ascending order if the driver
558
		// doesn't support random ordering
559
		if (stripos($type, 'rand') !== FALSE)
560
		{
561
			$rand = $this->sql->random();
562
			$type = ($rand !== FALSE) ? $rand : 'ASC';
563
		}
564
565
		// Set fields for later manipulation
566
		$field = $this->db->quote_ident($field);
567
		$this->order_array[$field] = $type;
568
569
		$order_clauses = array();
570
571
		// Flatten key/val pairs into an array of space-separated pairs
572
		foreach($this->order_array as $k => $v)
573
		{
574
			$order_clauses[] = $k . ' ' . strtoupper($v);
575
		}
576
577
		// Set the final string
578
		$this->order_string = ( ! isset($rand))
579
			? "\nORDER BY ".implode(', ', $order_clauses)
580
			: "\nORDER BY".$rand;
581
582
		return $this;
583
	}
584
585
	// --------------------------------------------------------------------------
586
587
	/**
588
	 * Set a limit on the current sql statement
589
	 *
590
	 * @param int $limit
591
	 * @param int|bool $offset
592
	 * @return QueryBuilder
593
	 */
594
	public function limit($limit, $offset=FALSE)
595
	{
596
		$this->limit = (int) $limit;
597
		$this->offset = $offset;
598
599
		return $this;
600
	}
601
602
	// --------------------------------------------------------------------------
603
	// ! Query Grouping Methods
604
	// --------------------------------------------------------------------------
605
606
	/**
607
	 * Adds a paren to the current query for query grouping
608
	 *
609
	 * @return QueryBuilder
610
	 */
611
	public function group_start()
612
	{
613
		$conj = (empty($this->query_map)) ? ' WHERE ' : ' ';
614
615
		$this->_append_map($conj, '(', 'group_start');
616
617
		return $this;
618
	}
619
620
	// --------------------------------------------------------------------------
621
622
	/**
623
	 * Adds a paren to the current query for query grouping,
624
	 * prefixed with 'OR'
625
	 *
626
	 * @return QueryBuilder
627
	 */
628
	public function or_group_start()
629
	{
630
		$this->_append_map('', ' OR (', 'group_start');
631
632
		return $this;
633
	}
634
635
	// --------------------------------------------------------------------------
636
637
	/**
638
	 * Adds a paren to the current query for query grouping,
639
	 * prefixed with 'OR NOT'
640
	 *
641
	 * @return QueryBuilder
642
	 */
643
	public function or_not_group_start()
644
	{
645
		$this->_append_map('', ' OR NOT (', 'group_start');
646
647
		return $this;
648
	}
649
650
	// --------------------------------------------------------------------------
651
652
	/**
653
	 * Ends a query group
654
	 *
655
	 * @return QueryBuilder
656
	 */
657
	public function group_end()
658
	{
659
		$this->_append_map('', ')', 'group_end');
660
661
		return $this;
662
	}
663
664
	// --------------------------------------------------------------------------
665
	// ! Query execution methods
666
	// --------------------------------------------------------------------------
667
668
	/**
669
	 * Select and retrieve all records from the current table, and/or
670
	 * execute current compiled query
671
	 *
672
	 * @param $table
673
	 * @param int|bool $limit
674
	 * @param int|bool $offset
675
	 * @return \PDOStatement
676
	 */
677
	public function get($table='', $limit=FALSE, $offset=FALSE)
678
	{
679
		// Set the table
680
		if ( ! empty($table))
681
		{
682
			$this->from($table);
683
		}
684
685
		// Set the limit, if it exists
686
		if (is_int($limit))
687
		{
688
			$this->limit($limit, $offset);
689
		}
690
691
		return $this->_run("get", $table);
692
	}
693
694
	// --------------------------------------------------------------------------
695
696
	/**
697
	 * Convenience method for get() with a where clause
698
	 *
699
	 * @param string $table
700
	 * @param array $where
701
	 * @param int|bool $limit
702
	 * @param int|bool $offset
703
	 * @return \PDOStatement
704
	 */
705
	public function get_where($table, $where=array(), $limit=FALSE, $offset=FALSE)
706
	{
707
		// Create the where clause
708
		$this->where($where);
709
710
		// Return the result
711
		return $this->get($table, $limit, $offset);
712
	}
713
714
	// --------------------------------------------------------------------------
715
716
	/**
717
	 * Retreive the number of rows in the selected table
718
	 *
719
	 * @param string $table
720
	 * @return int
721
	 */
722
	public function count_all($table)
723
	{
724
		$sql = 'SELECT * FROM '.$this->db->quote_table($table);
725
		$res = $this->db->query($sql);
726
		return (int) count($res->fetchAll());
727
	}
728
729
	// --------------------------------------------------------------------------
730
731
	/**
732
	 * Retrieve the number of results for the generated query - used
733
	 * in place of the get() method
734
	 *
735
	 * @param string $table
736
	 * @return int
737
	 */
738
	public function count_all_results($table='')
739
	{
740
		// Set the table
741
		if ( ! empty($table))
742
		{
743
			$this->from($table);
744
		}
745
746
		$result = $this->_run('get', $table);
747
		$rows = $result->fetchAll();
748
749
		return (int) count($rows);
750
	}
751
752
	// --------------------------------------------------------------------------
753
754
	/**
755
	 * Creates an insert clause, and executes it
756
	 *
757
	 * @param string $table
758
	 * @param mixed $data
759
	 * @return \PDOStatement
760
	 */
761
	public function insert($table, $data=array())
762
	{
763
		if ( ! empty($data))
764
		{
765
			$this->set($data);
766
		}
767
768
		return $this->_run("insert", $table);
769
	}
770
771
	// --------------------------------------------------------------------------
772
773
	/**
774
	 * Creates and executes a batch insertion query
775
	 *
776
	 * @param string $table
777
	 * @param array $data
778
	 * @return \PDOStatement
779
	 */
780
	public function insert_batch($table, $data=array())
781
	{
782
		// Get the generated values and sql string
783
		list($sql, $data) = $this->db->insert_batch($table, $data);
784
785
		return ( ! is_null($sql))
786
			? $this->_run('', $table, $sql, $data)
787
			: NULL;
788
	}
789
790
	// --------------------------------------------------------------------------
791
792
	/**
793
	 * Creates an update clause, and executes it
794
	 *
795
	 * @param string $table
796
	 * @param mixed $data
797
	 * @return \PDOStatement
798
	 */
799
	public function update($table, $data=array())
800
	{
801
		if ( ! empty($data))
802
		{
803
			$this->set($data);
804
		}
805
806
		return $this->_run("update", $table);
807
	}
808
809
	// --------------------------------------------------------------------------
810
811
	/**
812
	 * Deletes data from a table
813
	 *
814
	 * @param string $table
815
	 * @param mixed $where
816
	 * @return \PDOStatement
817
	 */
818
	public function delete($table, $where='')
819
	{
820
		// Set the where clause
821
		if ( ! empty($where))
822
		{
823
			$this->where($where);
824
		}
825
826
		return $this->_run("delete", $table);
827
	}
828
829
	// --------------------------------------------------------------------------
830
	// ! SQL Returning Methods
831
	// --------------------------------------------------------------------------
832
833
834
835
	/**
836
	 * Returns the generated 'select' sql query
837
	 *
838
	 * @param string $table
839
	 * @param bool $reset
840
	 * @return string
841
	 */
842
	public function get_compiled_select($table='', $reset=TRUE)
843
	{
844
		// Set the table
845
		if ( ! empty($table))
846
		{
847
			$this->from($table);
848
		}
849
850
		return $this->_get_compile('select', $table, $reset);
851
	}
852
853
	// --------------------------------------------------------------------------
854
855
	/**
856
	 * Returns the generated 'insert' sql query
857
	 *
858
	 * @param string $table
859
	 * @param bool $reset
860
	 * @return string
861
	 */
862
	public function get_compiled_insert($table, $reset=TRUE)
863
	{
864
		return $this->_get_compile('insert', $table, $reset);
865
	}
866
867
	// --------------------------------------------------------------------------
868
869
	/**
870
	 * Returns the generated 'update' sql query
871
	 *
872
	 * @param string $table
873
	 * @param bool $reset
874
	 * @return string
875
	 */
876
	public function get_compiled_update($table='', $reset=TRUE)
877
	{
878
		return $this->_get_compile('update', $table, $reset);
879
	}
880
881
	// --------------------------------------------------------------------------
882
883
	/**
884
	 * Returns the generated 'delete' sql query
885
	 *
886
	 * @param string $table
887
	 * @param bool $reset
888
	 * @return string
889
	 */
890
	public function get_compiled_delete($table="", $reset=TRUE)
891
	{
892
		return $this->_get_compile('delete', $table, $reset);
893
	}
894
895
896
	// --------------------------------------------------------------------------
897
	// ! Miscellaneous Methods
898
	// --------------------------------------------------------------------------
899
900
	/**
901
	 * Clear out the class variables, so the next query can be run
902
	 *
903
	 * @return void
904
	 */
905
	public function reset_query()
906
	{
907
		// Reset strings and booleans
908
		foreach($this->string_vars as $var)
909
		{
910
			$this->$var = NULL;
911
		}
912
913
		// Reset arrays
914
		foreach($this->array_vars as $var)
915
		{
916
			$this->$var = array();
917
		}
918
	}
919
}
920
// End of query_builder.php