1 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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
|
<?php
/**
* Database query class
*
* This file is part of Zoph.
*
* Zoph is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Zoph is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Zoph; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* @package Zoph
* @author Jeroen Roos
*/
namespace db;
use \PDO;
/**
* The query object is used to create queries
*
* @package Zoph
* @author Jeroen Roos
*/
abstract class query {
/** @var string db table to query */
protected $table;
/** @var array tables in this query */
protected $tables=array();
/** @var string alias of db table to query */
protected $alias;
/** @var array fields to query */
protected $fields=null;
/** @var array parameters for prepared queries */
protected $params=null;
/** @var string WHERE clause */
protected $where=null;
/** @var string HAVING clause */
protected $having=null;
/** @var array ORDER clause */
protected $order=array();
/** @var array count for LIMIT clause */
protected $count=null;
/** @var array offset for LIMIT clause */
protected $offset=null;
/**
* Create new query
* @param string Table to query
*/
public function __construct($table) {
if (is_array($table)) {
$tbl=reset($table);
$alias=key($table);
if (!is_numeric($alias)) {
$this->alias=$alias;
}
$table=$tbl;
$this->tables[$alias]=$tbl;
} else {
$this->tables[$table]=$table;
}
$table=db::getPrefix() . $table;
$this->table=$table;
}
/**
* Add one or more fields to a query
* @param array list of fields [ "alias" => "field"]
* @param bool Whether or not this is a DISTINCT query.
* @return query
*/
public function addFields(array $fields, $distinct=false) {
if (isset($this->alias)) {
$table = $this->alias;
} else if (isset($this->table)) {
$table = $this->table;
} else {
$table = null;
}
foreach ($fields as $alias => $field) {
if ($table && strpos($field, ".") === false) {
$field=$table . "." . $field;
}
if ($distinct) {
$field="DISTINCT " . $field;
}
if (!is_numeric($alias)) {
$field .= " AS " . $alias;
}
$this->fields[]=$field;
}
}
/**
* Add one or more fields to a query that is calculated using an SQL function
* @param array Array of functions [ "alias" => "function()"]
*/
public function addFunction(array $functions) {
foreach ($functions as $alias => $function) {
$this->fields[]=$function . " AS " . $alias;
}
return $this;
}
/**
* Add a parameter for a prepared query
* @param param parameter object
*/
public function addParam(param $param) {
$this->params[]=$param;
}
/**
* Add parameters for a prepared query
* @param array parameters
*/
public function addParams(array $params) {
foreach ($params as $param) {
$this->addParam($param);
}
}
/**
* Get array of params
*/
public function getParams() {
$params=array();
if (!is_array($this->params)) {
return $params;
}
foreach ($this->params as $param) {
if (!$param instanceof param) {
continue;
}
$value=$param->getValue();
if (is_array($value)) {
$value=array_values($value);
$name=array_values($param->getName());
$type=$param->getType();
for ($n=0; $n<sizeof($value); $n++) {
$params[]=new param($name[$n], $value[$n], $type);
}
} else {
$params[]=$param;
}
}
return $params;
}
/**
* Add a WHERE clause to the query
* @param clause WHERE clause
* @return query return the query to enable chaining
*/
public function where(clause $clause) {
$this->where=$clause;
return $this;
}
/**
* Add a subclause to the WHERE, or set the clause as a WHERE if it is not yet set
* @param clause clause to add
* @param string AND|OR
* @return query return the query to enable chaining
*/
public function addClause(clause $clause, $conj="AND") {
if ($this->where instanceof clause) {
if (strtoupper($conj) == "AND") {
$this->where->addAnd($clause);
} else if (strtoupper($conj) == "OR") {
$this->where->addOr($clause);
} else {
throw new exception("Unknown conjunction: " . e($conj));
}
} else {
$this->where($clause);
}
return $this;
}
/**
* Add a HAVING clause to the query
* @param clause HAVING clause
* @return query return the query to enable chaining
*/
public function having(clause $hclause) {
$this->having=$hclause;
return $this;
}
/**
* Add ORDER BY clause to query
* @param string order to add
* @example $qry->addOrder("name DESC");
* @return query return the query to enable chaining
*/
public function addOrder($order) {
$this->order[]=$order;
return $this;
}
/**
* Get ORDER BY for query
* @return string ORDER clause
*/
protected function getOrder() {
$order=$this->order;
if (is_array($order) && sizeof($order) > 0) {
return " ORDER BY " . implode(", ", $order);
}
return "";
}
/**
* Add LIMIT clause to query
* Be warned that count and offset are reversed compared to how they appear
* in the query!
* @param int count
* @param int offset
* @example $qry->addLimit(1,3);
* @return query return the query to enable chaining
*/
public function addLimit($count, $offset=null) {
$this->count=$count;
$this->offset=$offset;
return $this;
}
/**
* Get LIMIT clause for query
* @return string LIMIT clause
*/
protected function getLimit() {
if (!is_null($this->offset)) {
$limit=" LIMIT " . (int) $this->offset;
if (is_null($this->count)) {
$limit.= ", " . 999999999999;
} else {
$limit.=", " . (int) $this->count;
}
} else {
if (!is_null($this->count)) {
$limit=" LIMIT " . (int) $this->count;
} else {
$limit="";
}
}
return $limit;
}
/**
* Check if a table is already included in this query
* @param string query;
*/
public function hasTable($table) {
return in_array($table, $this->tables);
}
/**
* Execute a query
*/
public function execute() {
return db::query($this);
}
/**
* Add WHERE clause, by building it from a constraints array
* @param array Constraints, conditions that the records must comply to
* @param array Conjunctions, and/or
* @param array Operators =, !=, >, <, >= or <=
* @return query $this
*/
public function addWhereFromConstraints(array $constraints, $conj = "AND", $ops = null) {
$where=null;
foreach ($constraints as $name => $value) {
$op = "=";
if ($ops && !empty($ops["$name"])) {
$op = $ops["$name"];
}
$n = strpos($name, "#");
if ($n > 1) {
$paramNumber=substr($name, $n + 1);
$name = substr($name, 0, $n);
$paramName=":" . $name . "_" . $paramNumber;
} else {
$paramName=":" . $name;
}
if ($value == "null" || $value == "''") {
$value = null;
}
$clause=new clause($name . " " . $op . " " . $paramName);
$this->addParam(new param($paramName, $value, PDO::PARAM_STR));
if ($where instanceof clause) {
if ($conj == "AND") {
$where->addAnd($clause);
} else if ($conj == "OR") {
$where->addOr($clause);
} else {
throw new \zophException("Illegal conjunction (" . e($conj) .
") should be AND or OR, please file a bug");
}
} else {
$where = $clause;
}
}
if ($where instanceof clause) {
$this->where($where);
}
return $this;
}
/**
* Log the query to file, for debugging purposes
* @param string Characters to be added at end of line
* @param string Name of file to log the query to
* @codeCoverageIgnore
*/
public function logToFile($eol="\n", $file="/tmp/zophdebug") {
file_put_contents($file, $this->prettyPrint() . $eol, FILE_APPEND);
return microtime(true);
}
/**
* Format a query, including all parameters, for debugging purposes
* @codeCoverageIgnore
* @param bool Output with HTML
*/
public function prettyPrint($withHTML=false) {
$sql=(string) $this;
$allParams=$this->getParams();
// Here we sort the parameters by the length of their name,
// longest first.
// This is so we don't overwrite part of a parameter name
// in case the first part of the name is the same
$sort=function ($a, $b) {
return(strlen($b->getName()) - strlen($a->getName()));
};
usort($allParams, $sort);
foreach ($allParams as $param) {
$value=$param->getValue();
if ($withHTML) {
$value="<b>" . $value . "</b>";
}
if ($param->getType() == PDO::PARAM_INT) {
$sql=str_replace($param->getName(), $value, $sql);
} else {
$sql=str_replace($param->getName(), "\"" . $value . "\"", $sql);
}
}
return $sql;
}
/**
* The __toString() magic function creates the query to be fed to the db
* each inheritance of this class will have to implement it.
* @return string SQL query
*/
abstract public function __toString();
}
|