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 DibiHashMap(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 (is_array($arg) || ($arg instanceof Traversable && !$arg instanceof self)) {
185: if (isset(self::$modifiers[$clause])) {
186: $args = array(self::$modifiers[$clause], $arg);
187:
188: } elseif (is_string(key($arg))) {
189: $args = array('%a', $arg);
190: }
191: }
192: }
193:
194: foreach ($args as $arg) {
195: if ($arg instanceof self) {
196: $arg = "($arg)";
197: }
198: $this->cursor[] = $arg;
199: }
200:
201: return $this;
202: }
203:
204:
205:
206: 207: 208: 209: 210:
211: public function clause($clause, $remove = FALSE)
212: {
213: $this->cursor = & $this->clauses[self::$normalizer->$clause];
214:
215: if ($remove) {
216: trigger_error(__METHOD__ . '(..., TRUE) is deprecated; use removeClause() instead.', E_USER_NOTICE);
217: $this->cursor = NULL;
218:
219: } elseif ($this->cursor === NULL) {
220: $this->cursor = array();
221: }
222:
223: return $this;
224: }
225:
226:
227:
228: 229: 230: 231: 232:
233: public function removeClause($clause)
234: {
235: $this->clauses[self::$normalizer->$clause] = NULL;
236: return $this;
237: }
238:
239:
240:
241: 242: 243: 244: 245: 246:
247: public function setFlag($flag, $value = TRUE)
248: {
249: $flag = strtoupper($flag);
250: if ($value) {
251: $this->flags[$flag] = TRUE;
252: } else {
253: unset($this->flags[$flag]);
254: }
255: return $this;
256: }
257:
258:
259:
260: 261: 262: 263: 264:
265: final public function getFlag($flag)
266: {
267: return isset($this->flags[strtoupper($flag)]);
268: }
269:
270:
271:
272: 273: 274: 275:
276: final public function getCommand()
277: {
278: return $this->command;
279: }
280:
281:
282:
283: 284: 285: 286:
287: final public function getConnection()
288: {
289: return $this->connection;
290: }
291:
292:
293:
294:
295:
296:
297:
298: 299: 300: 301: 302: 303:
304: public function execute($return = NULL)
305: {
306: $res = $this->connection->query($this->_export());
307: return $return === dibi::IDENTIFIER ? $this->connection->getInsertId() : $res;
308: }
309:
310:
311:
312: 313: 314: 315:
316: public function fetch()
317: {
318: if ($this->command === 'SELECT') {
319: return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetch();
320: } else {
321: return $this->connection->query($this->_export())->fetch();
322: }
323: }
324:
325:
326:
327: 328: 329: 330:
331: public function fetchSingle()
332: {
333: if ($this->command === 'SELECT') {
334: return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetchSingle();
335: } else {
336: return $this->connection->query($this->_export())->fetchSingle();
337: }
338: }
339:
340:
341:
342: 343: 344: 345: 346: 347:
348: public function fetchAll($offset = NULL, $limit = NULL)
349: {
350: return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->fetchAll();
351: }
352:
353:
354:
355: 356: 357: 358: 359:
360: public function fetchAssoc($assoc)
361: {
362: return $this->connection->query($this->_export())->fetchAssoc($assoc);
363: }
364:
365:
366:
367: 368: 369: 370: 371: 372:
373: public function fetchPairs($key = NULL, $value = NULL)
374: {
375: return $this->connection->query($this->_export())->fetchPairs($key, $value);
376: }
377:
378:
379:
380: 381: 382: 383: 384: 385:
386: public function getIterator($offset = NULL, $limit = NULL)
387: {
388: return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->getIterator();
389: }
390:
391:
392:
393: 394: 395: 396: 397:
398: public function test($clause = NULL)
399: {
400: return $this->connection->test($this->_export($clause));
401: }
402:
403:
404:
405: 406: 407:
408: public function count()
409: {
410: return (int) $this->connection->query(
411: 'SELECT COUNT(*) FROM (%ex', $this->_export(), ') AS [data]'
412: )->fetchSingle();
413: }
414:
415:
416:
417:
418:
419:
420:
421: 422: 423:
424: public function toDataSource()
425: {
426: return new DibiDataSource($this->connection->translate($this->_export()), $this->connection);
427: }
428:
429:
430:
431: 432: 433: 434:
435: final public function __toString()
436: {
437: return $this->connection->translate($this->_export());
438: }
439:
440:
441:
442: 443: 444: 445: 446:
447: protected function _export($clause = NULL, $args = array())
448: {
449: if ($clause === NULL) {
450: $data = $this->clauses;
451:
452: } else {
453: $clause = self::$normalizer->$clause;
454: if (array_key_exists($clause, $this->clauses)) {
455: $data = array($clause => $this->clauses[$clause]);
456: } else {
457: return array();
458: }
459: }
460:
461: foreach ($data as $clause => $statement) {
462: if ($statement !== NULL) {
463: $args[] = $clause;
464: if ($clause === $this->command && $this->flags) {
465: $args[] = implode(' ', array_keys($this->flags));
466: }
467: foreach ($statement as $arg) $args[] = $arg;
468: }
469: }
470:
471: return $args;
472: }
473:
474:
475:
476: 477: 478: 479: 480: 481:
482: public static function _formatClause($s)
483: {
484: if ($s === 'order' || $s === 'group') {
485: $s .= 'By';
486: trigger_error("Did you mean '$s'?", E_USER_NOTICE);
487: }
488: return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
489: }
490:
491:
492:
493: public function __clone()
494: {
495:
496: foreach ($this->clauses as $clause => $val) {
497: $this->clauses[$clause] = & $val;
498: unset($val);
499: }
500: $this->cursor = & $foo;
501: }
502:
503: }
504: