123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- <?php
- namespace PHPixie\DB;
- /**
- * Query builder. It allows building queries by using methods to set specific query parameters.
- * Database drivers extend this class so that they can generate database specific queries.
- * The idea is to provide a database agnostic interface to query writing.
- *
- * @method mixed table(string $table = null) Set table to query.
- * Without arguments returns current table, returns self otherwise.
- *
- * @method mixed data(array $data = null) Set data for insert or update queries.
- * Without arguments returns current data, returns self otherwise.
- *
- * @method mixed limit(int $limit = null) Set number of rows to return. If NULL is passed than no limit is used.
- * If NULL is passed than no limit is used.
- * Without arguments returns current limit, returns self otherwise.
- *
- * @method mixed offset(string $offset = null) Set the offset for the first row in result.
- * If NULL is passed than no offset is used.
- * Without arguments returns current offset, returns self otherwise.
- *
- * @method mixed group_by(string $group_by = null) A column to group rows by for aggregator functions.
- * If NULL is passed than no grouping is done.
- * Without arguments returns current group_by argument, returns self otherwise.
- *
- * @method mixed type(string $type = null) Set query type. Available types: select, update, insert, delete, count.
- * Without arguments returns current type argument, returns self otherwise.
- * @package Database
- */
- abstract class Query
- {
- /**
- * Array of conditions that rows must meet
- * @var array
- */
- protected $_conditions = array();
- /**
- * Table to query
- * @var unknown
- */
- protected $_table;
- /**
- * Fields to return in the query
- * @var array
- */
- protected $_fields;
- /**
- * Data for row insertion or update
- * @var unknown
- */
- protected $_data;
- /**
- * Query type. Available types: select, update, insert, delete, count
- * @var string
- */
- protected $_type;
- /**
- * Parameters for tables to join
- * @var array
- */
- protected $_joins = array();
- /**
- * Number of rows to return
- * @var int
- */
- protected $_limit;
- /**
- * Offset of the first row
- * @var int
- */
- protected $_offset;
- /**
- * Columns and directions to order by
- * @var array
- */
- protected $_order_by = array();
- /**
- * Database connection
- * @var DB
- */
- protected $_db;
- /**
- * Conditions for aggregator functions
- * @var array
- */
- protected $_having = array();
- /**
- * Column to group by for aggregator functions
- * @var string
- */
- protected $_group_by;
- /**
- * Last alias used on the table
- * @var string
- */
- protected $_alias = null;
- /**
- * Methods and type of value they allow that are available via __call
- * @var array
- */
- protected $methods = array('data' => 'array', 'limit' => array('integer', 'NULL'), 'offset' => array('integer', 'NULL'), 'group_by' => array('string', 'NULL'), 'type' => 'string');
- /**
- * UNION queries
- * @var array
- */
- protected $_union = array();
- /**
- * Generates a query in format that can be executed on current database implementation
- *
- */
- public abstract function query();
- /**
- * Creates a new query
- *
- * @param DB $db Database connection
- * @param string $type Query type. Available types: select, update, insert, delete, count
- * @return void
- */
- public function __construct($db, $type)
- {
- $this->_db = $db;
- $this->_type = $type;
- }
- /**
- * Sets fields to be queried from the database. You can add aliases to the fields
- * by passing them as:
- *
- * array('field_name','alias')
- *
- * Example: $query->fields('id', array('name', 'fairy_name'))
- *
- * @param mixed $field,... Fields to be selected from the table
- * @return mixed If no parameters are passed returns current array of fields,
- * otherwise returns self.
- */
- public function fields()
- {
- $p = func_get_args();
- if (empty($p))
- {
- return $this->_fields;
- }
- else
- {
- $this->_fields = $p;
- }
- return $this;
- }
- /**
- * Sets the table to perform operations on, also supports subqueries
- *
- * @param string|Query_database|Expression_database $table table to select from
- * @param string $alias Alias for this table
- * @return mixed Returns self if a table is passed, otherwise returns the table
- */
- public function table($table = null, $alias = null)
- {
- if ($table == null)
- {
- return is_array($this->_table) ? $this->_table[1] : $this->_table;
- }
- if (!is_string($table) && $alias == null)
- {
- $alias = $this->add_alias();
- }
- $this->_table = $alias == null ? $table : array($table, $alias);
- return $this;
- }
- /**
- * Magic methods to create methods for all generic query parts
- *
- * @param string $method Name of the method to call
- * @param array $args Array of parameters
- * @return mixed If no arguments are passed returns the current value of the property,
- * otherwise returns self.
- * @throws \Exception If method doesn't exist
- * @throws \Exception If value is of incorrect type
- * @see $methods
- */
- public function __call($method, $args)
- {
- if (isset($this->methods[$method]))
- {
- $property = '_'.$method;
- if (empty($args))
- {
- return $this->$property;
- }
- $val = $args[0];
- if (is_numeric($val))
- {
- $val = (int) $val;
- }
- $allowed_types = $this->methods[$method];
- if (!is_array($allowed_types))
- {
- $allowed_types = array($allowed_types);
- }
- if (!in_array(gettype($val), $allowed_types))
- {
- throw new \Exception("Method '{$method}' only accepts values of type: ".implode(' or ', $allowed_types).", '{$val}' was passed");
- }
- $this->$property = $val;
- return $this;
- }
- throw new \Exception("Method '{$method}' doesn't exist.");
- }
- /**
- * Executes the query
- *
- * @return object Executes current query on its database connection
- * @see DB
- */
- public function execute()
- {
- $query = $this->query();
- $result = $this->_db->execute($query[0], $query[1]);
- if ($this->_type == 'count')
- {
- return $result->get('count');
- }
- return $result;
- }
- /**
- * Adds a joined table to the query.
- *
- * @param string $table Table to join
- * @param array $conds Conditions to join tables on, same behavior as with where() method
- * @param string $type Type of join. Defaults to 'left'
- * @return Query_Database Returns self
- * @see where()
- */
- public function join($table, $conds, $type = 'left')
- {
- $this->_joins[] = array($table, $type, $this->get_condition_part($conds));
- return $this;
- }
- /**
- * Sets conditions for aggregate functions, same behavior as with where() method
- *
- * @return Query_Database Returns self
- * @see where()
- */
- public function having()
- {
- $p = func_get_args();
- $cond = $this->get_condition_part($p);
- $this->_having = array_merge($this->_having, array($cond));
- return $this;
- }
- /**
- * Adds a column to ordering parameters.
- *
- * @param string $column Column to order by
- * @param string $dir Ordering direction.
- * @return Query_Database Returns self
- * @throws \Exception If ordering direction isn't DESC or ASC
- */
- public function order_by($column, $dir = 'ASC')
- {
- $dir = strtoupper($dir);
- if ($dir != 'DESC' && $dir != 'ASC')
- {
- throw new \Exception("Invalid sorting direction {$dir} specified");
- }
- $this->_order_by[] = array($column, $dir);
- return $this;
- }
- /**
- * Sets conditions for the query.
- * Can be called in many ways, examples:
- * Shorthand equals condition:
- * <code>
- * $q->where('name', 'Tinkerbell')
- * </code>
- * Conditions with operator:
- * <code>
- * $q->where('id', '>', 3)
- * </code>
- * OR logic:
- * <code>
- * $q->where('or', array('name', 'Tinkerbell'))
- * </code>
- * OR logic with operator
- * <code>
- * ->where('or', array('id', '>', 3))
- * </code>
- * Arrays represent brackets. e.g
- * <code>
- * $q->where('name', 'Tinkerbell')
- * ->where('or', array(
- * array('id', '>', 7),
- * array('id', '<', 15)
- * );
- * //Will produce "WHERE `name`='Tinkerbell' OR (`id` > 7 AND `id` < 15)"
- * </code>
- * Multiple calls to where() append new conditions to previous ones
- *
- * @param mixed $column Column name, logic parameter 'OR' or 'AND' or an array of conditions
- * @param mixed $operator Condition value, operator or an array of parameters
- * @param mixed $val Condition value
- *
- * @return Query_Database Returns self
- */
- public function where()
- {
- $p = func_get_args();
- $cond = $this->get_condition_part($p);
- $this->_conditions = array_merge($this->_conditions, array($cond));
- return $this;
- }
- /**
- * Recursively builds condition arrays for methods like where(), having()
- *
- * @param array $p Parameters passed to the method
- * @return array Array in condition format
- * @throws \Exception If condition format is incorrect
- */
- private function get_condition_part($p)
- {
- if (is_string($p[0]) && (strtolower($p[0]) == 'or' || strtolower($p[0]) == 'and') && isset($p[1]) && is_array($p[1]))
- {
- $cond = $this->get_condition_part($p[1]);
- $cond['logic'] = strtolower($p[0]);
- return $cond;
- }
- if (is_array($p[0]))
- {
- if (count($p) == 1)
- {
- return $this->get_condition_part($p[0]);
- }
- $conds = array();
- foreach ($p as $q)
- {
- $conds[] = $this->get_condition_part($q);
- }
- if (count($conds) == 1)
- {
- return $conds;
- }
- return array('logic' => 'and', 'conditions' => $conds);
- }
- if ((is_string($p[0]) || $p[0] instanceof \PHPixie\DB\Expression) && isset($p[1]))
- {
- if (is_string($p[0]) && strpos($p[0], '.') === false)
- {
- $p[0] = $this->last_alias().'.'.$p[0];
- }
- return array(
- 'logic' => 'and',
- 'conditions' => array(
- 'field' => $p[0],
- 'operator' => isset($p[2]) ? $p[1] : '=',
- 'value' => isset($p[2]) ? $p[2] : $p[1]
- )
- );
- }
- throw new \Exception('Incorrect conditional statement passed');
- }
- /**
- * Adds a UNION to the query
- *
- * @param Query_Database|Expression_Database $query Query for the UNION
- * @param string $all whether to do a UNION ALL, e.g. keep duplicate rows
- * @return Query_Database Returns self
- */
- public function union($query,$all=true) {
- $this->_union[] = array($query,$all);
- return $this;
- }
-
- /**
- * Gets last generated alias
- *
- * @return string Last generated alias. If no alias were created returns table name.
- */
- public function last_alias()
- {
- if ($this->_alias === null)
- {
- return $this->_table;
- }
- return 'a'.$this->_alias;
- }
- /**
- * Generates new alias. Useful for dynamically adding aliases to joins.
- * Alias is just a letter 'a' with an incremented number.
- *
- * @return string New alias
- */
- public function add_alias()
- {
- if ($this->_alias === null)
- {
- $this->_alias = 0;
- }
- else
- {
- $this->_alias++;
- }
- return $this->last_alias();
- }
- }
|