Source for file DibiFluent.php
Documentation is available at DibiFluent.php
4: * dibi - tiny'n'smart database abstraction layer
5: * ----------------------------------------------
7: * @copyright Copyright (c) 2005, 2010 David Grudl
8: * @license http://dibiphp.com/license dibi license
9: * @link http://dibiphp.com
16: * dibi SQL builder via fluent interfaces. EXPERIMENTAL!
18: * @copyright Copyright (c) 2005, 2010 David Grudl
21: * @property-read string $command
22: * @property-read DibiConnection $connection
23: * @property-read DibiResultIterator $iterator
24: * @method DibiFluent select($field)
25: * @method DibiFluent distinct()
26: * @method DibiFluent from($table)
27: * @method DibiFluent where($cond)
28: * @method DibiFluent groupBy($field)
29: * @method DibiFluent having($cond)
30: * @method DibiFluent orderBy($field)
31: * @method DibiFluent limit(int $limit)
32: * @method DibiFluent offset(int $offset)
39: public static $masks =
array(
40: 'SELECT' =>
array('SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
41: 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET'),
42: 'UPDATE' =>
array('UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'),
43: 'INSERT' =>
array('INSERT', 'INTO', 'VALUES', 'SELECT'),
44: 'DELETE' =>
array('DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'),
47: /** @var array default modifiers for arrays */
48: public static $modifiers =
array(
60: /** @var array clauses separators */
61: public static $separators =
array(
75: /** @var array clauses */
76: public static $clauseSwitches =
array(
78: 'INNER JOIN' =>
'FROM',
79: 'LEFT JOIN' =>
'FROM',
80: 'RIGHT JOIN' =>
'FROM',
83: /** @var DibiConnection */
90: private $clauses =
array();
93: private $flags =
array();
98: /** @var DibiLazyStorage normalized clauses */
99: private static $normalizer;
104: * @param DibiConnection
108: $this->connection =
$connection;
110: if (self::$normalizer ===
NULL) {
111: self::$normalizer =
new DibiLazyStorage(array(__CLASS__
, '_formatClause'));
118: * Appends new argument to the clause.
119: * @param string clause name
120: * @param array arguments
121: * @return DibiFluent provides a fluent interface
125: $clause =
self::$normalizer->$clause;
127: // lazy initialization
128: if ($this->command ===
NULL) {
129: if (isset(self::$masks[$clause])) {
130: $this->clauses =
array_fill_keys(self::$masks[$clause], NULL);
132: $this->cursor =
& $this->clauses[$clause];
133: $this->cursor =
array();
134: $this->command =
$clause;
137: // auto-switch to a clause
138: if (isset(self::$clauseSwitches[$clause])) {
139: $this->cursor =
& $this->clauses[self::$clauseSwitches[$clause]];
142: if (array_key_exists($clause, $this->clauses)) {
144: $this->cursor =
& $this->clauses[$clause];
146: // TODO: really delete?
147: if ($args ===
array(self::REMOVE)) {
148: $this->cursor =
NULL;
152: if (isset(self::$separators[$clause])) {
153: $sep =
self::$separators[$clause];
154: if ($sep ===
FALSE) { // means: replace
155: $this->cursor =
array();
157: } elseif (!empty($this->cursor)) {
158: $this->cursor[] =
$sep;
163: // append to currect flow
164: if ($args ===
array(self::REMOVE)) {
168: $this->cursor[] =
$clause;
171: if ($this->cursor ===
NULL) {
172: $this->cursor =
array();
175: // special types or argument
178: // TODO: really ignore TRUE?
179: if ($arg ===
TRUE) { // flag
183: $args =
array('%n', $arg);
185: } elseif ($arg instanceof
self) {
188: } elseif (is_array($arg) ||
$arg instanceof
Traversable) { // any array
189: if (isset(self::$modifiers[$clause])) {
190: $args =
array(self::$modifiers[$clause], $arg);
192: } elseif (is_string(key($arg))) { // associative array
193: $args =
array('%a', $arg);
195: } // case $arg === FALSE is handled above
198: foreach ($args as $arg) $this->cursor[] =
$arg;
206: * Switch to a clause.
207: * @param string clause name
208: * @return DibiFluent provides a fluent interface
210: public function clause($clause, $remove =
FALSE)
212: $this->cursor =
& $this->clauses[self::$normalizer->$clause];
214: if ($remove) { // deprecated, use removeClause
215: trigger_error(__METHOD__ .
'(..., TRUE) is deprecated; use removeClause() instead.', E_USER_NOTICE);
216: $this->cursor =
NULL;
218: } elseif ($this->cursor ===
NULL) {
219: $this->cursor =
array();
229: * @param string clause name
230: * @return DibiFluent provides a fluent interface
234: $this->clauses[self::$normalizer->$clause] =
NULL;
241: * Change a SQL flag.
242: * @param string flag name
244: * @return DibiFluent provides a fluent interface
250: $this->flags[$flag] =
TRUE;
252: unset($this->flags[$flag]);
261: * @param string flag name
272: * Returns SQL command.
277: return $this->command;
283: * Returns the dibi connection.
284: * @return DibiConnection
288: return $this->connection;
293: /********************* executing ****************d*g**/
298: * Generates and executes SQL query.
299: * @param mixed what to return?
300: * @return DibiResult|int result set object (if any)
301: * @throws DibiException
312: * Generates, executes SQL query and fetches the single row.
313: * @return DibiRow|FALSE array on success, FALSE if no next record
317: if ($this->command ===
'SELECT') {
318: return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetch();
327: * Like fetch(), but returns only first field.
328: * @return mixed value on success, FALSE if no next record
332: if ($this->command ===
'SELECT') {
333: return $this->connection->query($this->_export(NULL, array('%lmt', 1)))->fetchSingle();
342: * Fetches all records from table.
347: public function fetchAll($offset =
NULL, $limit =
NULL)
349: return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->fetchAll();
355: * Fetches all records from table and returns associative tree.
356: * @param string associative descriptor
367: * Fetches all records from table like $key => $value pairs.
368: * @param string associative key
369: * @param string value
374: return $this->connection->query($this->_export())->fetchPairs($key, $value);
380: * Required by the IteratorAggregate interface.
383: * @return DibiResultIterator
387: return $this->connection->query($this->_export(NULL, array('%ofs %lmt', $offset, $limit)))->getIterator();
393: * Generates and prints SQL query or it's part.
394: * @param string clause name
397: public function test($clause =
NULL)
410: 'SELECT COUNT(*) FROM (%ex', $this->_export(), ') AS [data]'
416: /********************* exporting ****************d*g**/
421: * @return DibiDataSource
431: * Returns SQL query.
442: * Generates parameters for DibiTranslator.
443: * @param string clause name
446: protected function _export($clause =
NULL, $args =
array())
448: if ($clause ===
NULL) {
449: $data =
$this->clauses;
452: $clause =
self::$normalizer->$clause;
453: if (array_key_exists($clause, $this->clauses)) {
454: $data =
array($clause =>
$this->clauses[$clause]);
460: foreach ($data as $clause =>
$statement) {
461: if ($statement !==
NULL) {
463: if ($clause ===
$this->command &&
$this->flags) {
466: foreach ($statement as $arg) $args[] =
$arg;
476: * Format camelCase clause name to UPPER CASE.
481: public static function _formatClause($s)
483: if ($s ===
'order' ||
$s ===
'group') {
494: // remove references
495: foreach ($this->clauses as $clause =>
$val) {
496: $this->clauses[$clause] =
& $val;
499: $this->cursor =
& $foo;