1: <?php
2:
3: namespace zsql;
4:
5: 6: 7:
8: abstract class Query
9: {
10: 11: 12: 13: 14:
15: protected $database;
16:
17: 18: 19: 20: 21:
22: protected $interpolation = false;
23:
24: 25: 26: 27: 28:
29: protected $table;
30:
31: 32: 33: 34: 35:
36: protected $params;
37:
38: 39: 40: 41: 42:
43: protected $parts;
44:
45: 46: 47: 48: 49:
50: protected $quoteCallback;
51:
52: 53: 54: 55: 56:
57: protected $query;
58:
59: 60: 61: 62: 63:
64: protected $queryCallback;
65:
66: 67: 68: 69: 70:
71: protected $quoteChar = "'";
72:
73: 74: 75: 76: 77:
78: protected $quoteIdentifierChar = '`';
79:
80: 81: 82: 83: 84:
85: public function __construct($queryCallback = null)
86: {
87: if( $queryCallback instanceof \zsql\Database ) {
88: $this->database = $queryCallback;
89: $this->interpolation();
90: } else if( is_callable($queryCallback) ) {
91: $this->queryCallback = $queryCallback;
92: } else if( $queryCallback !== null ) {
93: throw new \zsql\Exception('Invalid query executor');
94: }
95: }
96:
97: 98: 99: 100: 101:
102: public function __toString()
103: {
104: try {
105: return $this->toString();
106: } catch( Exception $e ) {
107: trigger_error($e->getMessage(), E_USER_WARNING);
108: return '';
109: }
110: }
111:
112: 113: 114:
115: abstract protected function assemble();
116:
117: 118: 119: 120: 121:
122: protected function interpolateParams()
123: {
124: if( count($this->params) <= 0 ) {
125: return;
126: }
127: if( $this->quoteCallback ) {
128: $cb = $this->quoteCallback;
129: } else if( $this->database ) {
130: $cb = array($this->database, 'quote');
131: } else {
132: throw new \zsql\Exception('Interpolation not available without '
133: . 'setting a quote callback or database adapter');
134: }
135: if( substr_count($this->query, '?') != count($this->params) ) {
136: throw new \zsql\Exception('Parameter count mismatch');
137: }
138:
139: $parts = explode('?', $this->query);
140: $query = $parts[0];
141: for( $i = 0, $l = count($this->params); $i < $l; $i++ ) {
142: $query .= call_user_func($cb, $this->params[$i]) . $parts[$i+1];
143: }
144: $this->query = $query;
145: }
146:
147: 148: 149: 150: 151:
152: public function interpolation($interpolation = true)
153: {
154: $this->interpolation = (bool) $interpolation;
155: return $this;
156: }
157:
158: 159: 160: 161: 162:
163: public function params()
164: {
165: return (array) $this->params;
166: }
167:
168: 169: 170: 171: 172:
173: public function parts()
174: {
175: return (array) $this->parts;
176: }
177:
178: 179: 180: 181: 182: 183:
184: protected function push($string)
185: {
186: $this->parts[] = $string;
187: return $this;
188: }
189:
190: 191: 192: 193: 194: 195:
196: protected function pushTable()
197: {
198: if( empty($this->table) ) {
199: throw new \zsql\Exception('No table specified');
200: }
201: $this->parts[] = $this->quoteIdentifierIfNotExpression($this->table);
202: return $this;
203: }
204:
205: 206: 207: 208: 209: 210:
211: protected function pushValues()
212: {
213: if( empty($this->values) ) {
214: throw new \zsql\Exception('No values specified');
215: }
216: foreach( $this->values as $key => $value ) {
217: if( !is_int($key) ) {
218: $this->parts[] = $this->quoteIdentifierIfNotExpression($key);
219: $this->parts[] = '=';
220: }
221: if( $value instanceof Expression ) {
222: $this->parts[] = (string) $value;
223: } else if( !is_int($key) ) {
224: $this->parts[] = '?';
225: $this->params[] = $value;
226: }
227: $this->parts[] = ',';
228: }
229: array_pop($this->parts);
230:
231: return $this;
232: }
233:
234: 235: 236: 237: 238: 239:
240: public function query()
241: {
242: if( $this->database ) {
243: $this->interpolateParams();
244: return $this->database->query($this);
245: } else if( $this->queryCallback ) {
246: $query = $this->toString();
247: $params = $this->params();
248: if( $this->interpolation ) {
249: return call_user_func($this->queryCallback, $query);
250: } else {
251: return call_user_func($this->queryCallback, $query, $params);
252: }
253: } else {
254: throw new \zsql\Exception('query() called when no callback or database adapter set');
255: }
256: }
257:
258: 259: 260: 261: 262: 263:
264: protected function quoteIdentifier($identifier)
265: {
266: $c = $this->quoteIdentifierChar;
267: return $c . str_replace('.',
268: $c . '.' . $c,
269: str_replace($c, $c . $c, $identifier)) . $c;
270: }
271:
272: 273: 274: 275: 276: 277:
278: protected function quoteIdentifierIfNotExpression($identifier)
279: {
280: if( $identifier instanceof Expression ) {
281: return (string) $identifier;
282: } else {
283: return $this->quoteIdentifier($identifier);
284: }
285: }
286:
287: 288: 289: 290: 291: 292: 293:
294: public function setQuoteCallback($callback)
295: {
296: if( !is_callable($callback) ) {
297: throw new \zsql\Exception('Invalid callback specified');
298: }
299: $this->quoteCallback = $callback;
300: return $this;
301: }
302:
303: 304: 305: 306: 307: 308:
309: public function table($table)
310: {
311: if( $table instanceof Expression ) {
312: $this->table = $table;
313: } else {
314: $this->table = (string) $table;
315: }
316: return $this;
317: }
318:
319: 320: 321: 322: 323:
324: public function toString()
325: {
326: $this->parts = array();
327: $this->params = array();
328: $this->assemble();
329: $this->query = join(' ', $this->parts);
330: if( $this->interpolation ) {
331: $this->interpolateParams();
332: }
333: return $this->query;
334: }
335: }
336: