1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
33: class DibiFluent extends DibiObject implements IDataSource
34: {
35: const REMOVE = FALSE;
36:
37:
38: public static $masks = array(
39: 'SELECT' => array('SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
40: 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET'),
41: 'UPDATE' => array('UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'),
42: 'INSERT' => array('INSERT', 'INTO', 'VALUES', 'SELECT'),
43: 'DELETE' => array('DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'),
44: );
45:
46:
47: public static $modifiers = array(
48: 'SELECT' => '%n',
49: 'FROM' => '%n',
50: 'IN' => '%in',
51: 'VALUES' => '%l',
52: 'SET' => '%a',
53: 'WHERE' => '%and',
54: 'HAVING' => '%and',
55: 'ORDER BY' => '%by',
56: 'GROUP BY' => '%by',
57: );
58:
59:
60: public static $separators = array(
61: 'SELECT' => ',',
62: 'FROM' => ',',
63: 'WHERE' => 'AND',
64: 'GROUP BY' => ',',
65: 'HAVING' => 'AND',
66: 'ORDER BY' => ',',
67: 'LIMIT' => FALSE,
68: 'OFFSET' => FALSE,
69: 'SET' => ',',
70: 'VALUES' => ',',
71: 'INTO' => FALSE,
72: );
73:
74:
75: public static $clauseSwitches = array(
76: 'JOIN' => 'FROM',
77: 'INNER JOIN' => 'FROM',
78: 'LEFT JOIN' => 'FROM',
79: 'RIGHT JOIN' => 'FROM',
80: );
81:
82:
83: private $connection;
84:
85:
86: private $command;
87:
88:
89: private $clauses = array();
90:
91:
92: private $flags = array();
93:
94:
95: private $cursor;
96:
97:
98: private static $normalizer;
99:
100:
101:
102: 103: 104:
105: public function __construct(DibiConnection $connection)
106: {
107: $this->connection = $connection;
108:
109: if (self::$normalizer === NULL) {
110: self::$normalizer = new DibiLazyStorage(array(__CLASS__, '_formatClause'));
111: }
112: }
113:
114:
115:
116: 117: 118: 119: 120: 121:
122: public function __call($clause, $args)
123: {
124: $clause = self::$normalizer->$clause;
125:
126: 127: if ($this->command === NULL) {
128: if (isset(self::$masks[$clause])) {
129: $this->clauses = array_fill_keys(self::$masks[$clause], NULL);
130: }
131: $this->cursor = & $this->clauses[$clause];
132: $this->cursor = array();
133: $this->command = $clause;
134: }
135:
136: 137: if (isset(self::$clauseSwitches[$clause])) {
138: $this->cursor = & $this->clauses[self::$clauseSwitches[$clause]];
139: }
140:
141: if (array_key_exists($clause, $this->clauses)) {
142: 143: $this->cursor = & $this->clauses[$clause];
144:
145: 146: if ($args === array(self::REMOVE)) {
147: $this->cursor = NULL;
148: return $this;
149: }
150:
151: if (isset(self::$separators[$clause])) {
152: $sep = self::$separators[$clause];
153: if ($sep === FALSE) { 154: $this->cursor = array();
155:
156: } elseif (!empty($this->cursor)) {
157: $this->cursor[] = $sep;
158: }
159: }
160:
161: } else {
162: 163: if ($args === array(self::REMOVE)) {
164: return $this;
165: }
166:
167: $this->cursor[] = $clause;
168: }
169:
170: if ($this->cursor === NULL) {
171: $this->cursor = array();
172: }
173:
174: 175: if (count($args) === 1) {
176: $arg = $args[0];
177: 178: if ($arg === TRUE) { 179: return $this;
180:
181: } elseif (is_string($arg) && preg_match('#^[a-z:_][a-z0-9_.:]*$#i', $arg)) { 182: $args = array('%n', $arg);
183:
184: } elseif ($arg instanceof self) {
185: $args = array_merge(array('('), $arg->_export(), array(')'));
186:
187: } elseif (is_array($arg) || $arg instanceof Traversable) { 188: if (isset(self::$modifiers[$clause])) {
189: $args = array(self::$modifiers[$clause], $arg);
190:
191: } elseif (is_string(key($arg))) { 192: $args = array('%a', $arg);
193: }
194: } 195: }
196:
197: foreach ($args as $arg) $this->cursor[] = $arg;
198:
199: return $this;
200: }
201:
202:
203:
204: 205: 206: 207: 208:
209: public function clause($clause, $remove = FALSE)
210: {
211: $this->cursor = & $this->clauses[self::$normalizer->$clause];
212:
213: if ($remove) { 214: trigger_error(__METHOD__ . '(..., TRUE) is deprecated; use removeClause() instead.', E_USER_NOTICE);
215: $this->cursor = NULL;
216:
217: } elseif ($this->cursor === NULL) {
218: $this->cursor = array();
219: }
220:
221: return $this;
222: }
223:
224:
225:
226: 227: 228: 229: 230:
231: public function removeClause($clause)
232: {
233: $this->clauses[self::$normalizer->$clause] = NULL;
234: return $this;
235: }
236:
237:
238:
239: 240: 241: 242: 243: 244:
245: public function setFlag($flag, $value = TRUE)
246: {
247: $flag = strtoupper($flag);
248: if ($value) {
249: $this->flags[$flag] = TRUE;
250: } else {
251: unset($this->flags[$flag]);
252: }
253: return $this;
254: }
255:
256:
257:
258: 259: 260: 261: 262:
263: final public function getFlag($flag)
264: {
265: return isset($this->flags[strtoupper($flag)]);
266: }
267:
268:
269:
270: 271: 272: 273:
274: final public function getCommand()
275: {
276: return $this->command;
277: }
278:
279:
280:
281: 282: 283: 284:
285: final public function getConnection()
286: {
287: return $this->connection;
288: }
289:
290:
291:
292:
293:
294:
295:
296: 297: 298: 299: 300: 301:
302: public function execute($return = NULL)
303: {
304: $res = $this->connection->query($this->_export());
305: return $return === dibi::IDENTIFIER ? $this->connection->getInsertId() : $res;
306: }
307:
308:
309:
310: 311: 312: 313:
314: public function fetch()
315: {
316: if ($this->command === 'SELECT') {
317: return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetch();
318: } else {
319: return $this->connection->query($this->_export())->fetch();
320: }
321: }
322:
323:
324:
325: 326: 327: 328:
329: public function fetchSingle()
330: {
331: if ($this->command === 'SELECT') {
332: return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetchSingle();
333: } else {
334: return $this->connection->query($this->_export())->fetchSingle();
335: }
336: }
337:
338:
339:
340: 341: 342: 343: 344: 345:
346: public function fetchAll($offset = NULL, $limit = NULL)
347: {
348: return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->fetchAll();
349: }
350:
351:
352:
353: 354: 355: 356: 357:
358: public function fetchAssoc($assoc)
359: {
360: return $this->connection->query($this->_export())->fetchAssoc($assoc);
361: }
362:
363:
364:
365: 366: 367: 368: 369: 370:
371: public function fetchPairs($key = NULL, $value = NULL)
372: {
373: return $this->connection->query($this->_export())->fetchPairs($key, $value);
374: }
375:
376:
377:
378: 379: 380: 381: 382: 383:
384: public function getIterator($offset = NULL, $limit = NULL)
385: {
386: return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->getIterator();
387: }
388:
389:
390:
391: 392: 393: 394: 395:
396: public function test($clause = NULL)
397: {
398: return $this->connection->test($this->_export($clause));
399: }
400:
401:
402:
403: 404: 405:
406: public function count()
407: {
408: return (int) $this->connection->query(
409: 'SELECT COUNT(*) FROM (%ex', $this->_export(), ') AS [data]'
410: )->fetchSingle();
411: }
412:
413:
414:
415:
416:
417:
418:
419: 420: 421:
422: public function toDataSource()
423: {
424: return new DibiDataSource($this->connection->translate($this->_export()), $this->connection);
425: }
426:
427:
428:
429: 430: 431: 432:
433: final public function __toString()
434: {
435: return $this->connection->translate($this->_export());
436: }
437:
438:
439:
440: 441: 442: 443: 444:
445: protected function _export($clause = NULL, $args = array())
446: {
447: if ($clause === NULL) {
448: $data = $this->clauses;
449:
450: } else {
451: $clause = self::$normalizer->$clause;
452: if (array_key_exists($clause, $this->clauses)) {
453: $data = array($clause => $this->clauses[$clause]);
454: } else {
455: return array();
456: }
457: }
458:
459: foreach ($data as $clause => $statement) {
460: if ($statement !== NULL) {
461: $args[] = $clause;
462: if ($clause === $this->command && $this->flags) {
463: $args[] = implode(' ', array_keys($this->flags));
464: }
465: foreach ($statement as $arg) $args[] = $arg;
466: }
467: }
468:
469: return $args;
470: }
471:
472:
473:
474: 475: 476: 477: 478: 479:
480: public static function _formatClause($s)
481: {
482: if ($s === 'order' || $s === 'group') {
483: $s .= 'By';
484: trigger_error("Did you mean '$s'?", E_USER_NOTICE);
485: }
486: return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
487: }
488:
489:
490:
491: public function __clone()
492: {
493: 494: foreach ($this->clauses as $clause => $val) {
495: $this->clauses[$clause] = & $val;
496: unset($val);
497: }
498: $this->cursor = & $foo;
499: }
500:
501: }
502: