PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/QueryBuilder.php

https://github.com/Kazuzeya/OpenFlame-Dbal
PHP | 546 lines | 290 code | 88 blank | 168 comment | 35 complexity | 02b1e689c1a146cbf772420a0f0b180d MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * @package OpenFlame Dbal
  5. * @copyright (c) 2011 openflame-project.org
  6. * @license http://opensource.org/licenses/mit-license.php The MIT License
  7. * @link https://github.com/OpenFlame/OpenFlame-Dbal
  8. *
  9. * Minimum Requirement: PHP 5.3.0
  10. */
  11. namespace OpenFlame\Dbal;
  12. /**
  13. * OpenFlame Dbal - Query Builder
  14. * Wraps around PDO to create an interface to query the database
  15. *
  16. *
  17. * @license http://opensource.org/licenses/mit-license.php The MIT License
  18. * @link https://github.com/OpenFlame/OpenFlame-Dbal
  19. */
  20. class QueryBuilder extends Query
  21. {
  22. /*
  23. * @var flag - Type of query (determined by the first clause)
  24. */
  25. protected $type = -1;
  26. /*
  27. * @var array - Fields to select
  28. */
  29. protected $select = array();
  30. /*
  31. * @var array - Tables that being affect in this query
  32. */
  33. protected $tables = array();
  34. /*
  35. * @var array - Sets
  36. */
  37. protected $sets = array();
  38. /*
  39. * @var array - Raw sets
  40. */
  41. protected $rawSets = array();
  42. /*
  43. * @var array - Rows for insert
  44. */
  45. protected $rows = array();
  46. /*
  47. * @var array - Complex array for wheres
  48. */
  49. protected $wheres = array();
  50. /*
  51. * Limits and offsets
  52. */
  53. protected $limit = 0;
  54. protected $offset = 0;
  55. /*
  56. * @var string - Fields to order by
  57. */
  58. protected $orderBy = '';
  59. protected $orderDirection = '';
  60. /*
  61. * consts - Query types
  62. */
  63. const TYPE_SELECT = 0;
  64. const TYPE_UPDATE = 1;
  65. const TYPE_INSERT = 2;
  66. const TYPE_MULTII = 3;
  67. const TYPE_DELETE = 4;
  68. const TYPE_UPSERT = 5;
  69. /**
  70. * Start a SELECT statement
  71. * @param mixed - Fields to select
  72. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  73. */
  74. public function select($fields)
  75. {
  76. $this->select = array_merge($this->select, $this->normalizeArray($fields));
  77. $this->type = static::TYPE_SELECT;
  78. return $this;
  79. }
  80. /**
  81. * Start an UPDATE statement
  82. * @param mixed - tables
  83. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  84. */
  85. public function update($tables)
  86. {
  87. $this->tables = array_merge($this->tables, $this->normalizeArray($tables));
  88. $this->type = static::TYPE_UPDATE;
  89. return $this;
  90. }
  91. /**
  92. * Start an INSERT statement
  93. * @param mixed - tables
  94. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  95. */
  96. public function insert($table)
  97. {
  98. $this->tables = array_slice($this->normalizeArray($table), 0, 1);
  99. $this->type = static::TYPE_INSERT;
  100. return $this;
  101. }
  102. /**
  103. * Start an INSERT statement
  104. * @param mixed - tables
  105. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  106. */
  107. public function multiInsert($table)
  108. {
  109. $this->tables = array_slice($this->normalizeArray($table), 0, 1);
  110. $this->type = static::TYPE_MULTII;
  111. return $this;
  112. }
  113. /**
  114. * Start an UPSERT statement
  115. * @param mixed - tables
  116. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  117. */
  118. public function upsert($table)
  119. {
  120. $this->tables = array_slice($this->normalizeArray($table), 0, 1);
  121. $this->type = static::TYPE_UPSERT;
  122. return $this;
  123. }
  124. /**
  125. * Start a DELETE statement
  126. * @param mixed - tables
  127. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  128. */
  129. public function delete($tables)
  130. {
  131. $this->tables = $this->normalizeArray($tables);
  132. $this->type = static::TYPE_DELETE;
  133. return $this;
  134. }
  135. /**
  136. * FROM clause
  137. * @param mixed - tables
  138. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  139. */
  140. public function from($tables)
  141. {
  142. $this->tables = array_merge($this->tables, $this->normalizeArray($tables));
  143. return $this;
  144. }
  145. /**
  146. * SET clause
  147. * @param mixed - key/vals
  148. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  149. */
  150. public function set()
  151. {
  152. $args = func_get_args();
  153. switch($this->type)
  154. {
  155. case static::TYPE_MULTII:
  156. if(!is_array($args[0]))
  157. {
  158. throw new \LogicException("Argument to QueryBuilder::set() must be an array when Multi-INSERTing.");
  159. }
  160. $this->rows[] = $args[0];
  161. break;
  162. case static::TYPE_INSERT:
  163. if(!is_array($args[0]))
  164. {
  165. throw new \LogicException("Argument to QueryBuilder::set() must be an array when INSERTing.");
  166. }
  167. $this->rows[0] = $args[0];
  168. break;
  169. default:
  170. $this->sets = array_merge($this->sets, $this->inputKeyVals($args));
  171. break;
  172. }
  173. return $this;
  174. }
  175. /*
  176. * Limit
  177. * @param int $limit
  178. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  179. */
  180. public function limit($limit)
  181. {
  182. $this->limit = (int) $limit;
  183. return $this;
  184. }
  185. /*
  186. * Offset
  187. * @param int $offset
  188. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  189. */
  190. public function offset($offset)
  191. {
  192. $this->offset = (int) $offset;
  193. return $this;
  194. }
  195. /*
  196. * Order by
  197. * @param string $fields - Comma separated list of fields to order by
  198. * @param string $direction - ASC or DESC
  199. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  200. */
  201. public function orderBy($fields, $direction = 'ASC')
  202. {
  203. $this->orderBy = (string) $fields;
  204. $this->orderDirection = (strtoupper($direction) == 'ASC') ? 'ASC' : 'DESC';
  205. return $this;
  206. }
  207. /*
  208. * Increment field value
  209. * Hackaround for set(), we can't really use it to add/subtract values from the fields
  210. * @param string $field - Name of the field
  211. * @param int $amount - Can be any signed integer, defaults to 1
  212. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  213. */
  214. public function increment($field, $amount = 1)
  215. {
  216. $this->rawSets[$field] = $field . ' + ' . (int) $amount;
  217. return $this;
  218. }
  219. /*
  220. * Decrement field value
  221. * Shortcut for increment()
  222. * @param string $field - Name of the field
  223. * @param int $amount - Can be any signed integer, defaults to 1
  224. * @return \OpenFlame\Dbal\QueryBuilder - Provides a fluent interface.
  225. */
  226. public function decrement($field, $amount = -1)
  227. {
  228. return $this->increment($field, $amount);
  229. }
  230. /*
  231. * WHERE clause
  232. * @param string $statement - PDO style prepared statement
  233. * @param mixed ... - Addtional params to be placed in the placeholders of the PDO statement
  234. * @return \OpenFlame\Dbal\QueryBuilder - Provides an fluent interface
  235. */
  236. public function where()
  237. {
  238. $args = func_get_args();
  239. $statement = array_shift($args);
  240. $this->wheres[] = array('WHERE', $statement, $args);
  241. return $this;
  242. }
  243. /*
  244. * AND clause
  245. * @param string $statement - PDO style prepared statement
  246. * @param mixed ... - Addtional params to be placed in the placeholders of the PDO statement
  247. * @return \OpenFlame\Dbal\QueryBuilder - Provides an fluent interface
  248. */
  249. public function andWhere()
  250. {
  251. $args = func_get_args();
  252. $statement = array_shift($args);
  253. $this->wheres[] = array('AND', $statement, $args);
  254. return $this;
  255. }
  256. /*
  257. * OR clause
  258. * @param string $statement - PDO style prepared statement
  259. * @param mixed ... - Addtional params to be placed in the placeholders of the PDO statement
  260. * @return \OpenFlame\Dbal\QueryBuilder - Provides an fluent interface
  261. */
  262. public function orWhere()
  263. {
  264. $args = func_get_args();
  265. $statement = array_shift($args);
  266. $this->wheres[] = array('OR', $statement, $args);
  267. return $this;
  268. }
  269. /*
  270. * Build the query
  271. * @return \OpenFlame\Dbal\QueryBuilder - Provides an fluent interface
  272. */
  273. public function build()
  274. {
  275. // Accumulators
  276. $sql = '';
  277. $params = array();
  278. $sets = $insert = $where = false;
  279. switch($this->type)
  280. {
  281. case static::TYPE_SELECT:
  282. $sql .= 'SELECT ' . implode(',', $this->select) . "\nFROM ";
  283. $where = true;
  284. break;
  285. case static::TYPE_UPSERT:
  286. case static::TYPE_UPDATE:
  287. $sql .= 'UPDATE ';
  288. $sets = true;
  289. $where = true;
  290. break;
  291. case static::TYPE_MULTII:
  292. case static::TYPE_INSERT:
  293. $sql .= 'INSERT INTO ';
  294. $insert = true;
  295. break;
  296. case static::TYPE_DELETE:
  297. $sql .= 'DELETE FROM ';
  298. $where = true;
  299. break;;
  300. }
  301. // Tabletime
  302. $sql .= implode(', ', $this->tables) . "\n";
  303. // For inserts
  304. if ($insert && sizeof($this->rows[0]))
  305. {
  306. $_rows = array();
  307. $sql .= '(' . implode(',', array_keys($this->rows[0])) . ")\n";
  308. $_row = implode(',', array_fill(0,sizeof($this->rows[0]),'?'));
  309. foreach($this->rows as $i => $row)
  310. {
  311. foreach($row as $val)
  312. {
  313. $params[] = $val;
  314. }
  315. $_rows[$i] = $_row;
  316. }
  317. $sql .= 'VALUES (' . implode("),\n(", $_rows) . ')';
  318. }
  319. // Sets and raw sets
  320. if ($sets && (sizeof($this->sets) || sizeof($this->rawSets)))
  321. {
  322. $temp = array();
  323. foreach($this->sets as $col => $val)
  324. {
  325. if (is_null($val) || !strlen($col))
  326. {
  327. continue;
  328. }
  329. $temp[] = $col . ' = ?';
  330. $params[] = $val;
  331. }
  332. foreach($this->rawSets as $col => $val)
  333. {
  334. $temp[] = $col . ' = ' . $val;
  335. }
  336. $sql .= 'SET ' . implode(',', $temp) . "\n";
  337. unset($temp);
  338. }
  339. // Where
  340. if ($where && sizeof($this->wheres))
  341. {
  342. foreach($this->wheres as $key => $val)
  343. {
  344. $sql .= $val[0] . ' ' . $val[1] . "\n";
  345. if(isset($val[2]) && is_array($val[2]) && strpos($val[1], '?'))
  346. {
  347. $params = array_merge($params, $val[2]);
  348. }
  349. }
  350. }
  351. if (strlen($this->orderBy))
  352. {
  353. $sql .= "ORDER BY {$this->orderBy} {$this->orderDirection}\n";
  354. }
  355. if ($this->limit > 0)
  356. {
  357. $sql .= "LIMIT {$this->limit}\n";
  358. }
  359. if ($this->offset > 0)
  360. {
  361. $sql .= "OFFSET {$this->offset}\n";
  362. }
  363. $this->sql($sql);
  364. $this->setParams($params);
  365. return $this;
  366. }
  367. /**
  368. * Excecute a query, override of Query::exec()
  369. * @return int - Number of rows affected
  370. */
  371. public function exec()
  372. {
  373. $this->_query();
  374. $count = $this->stmt->rowCount();
  375. if(!$count && $this->type == static::TYPE_UPSERT)
  376. {
  377. // build insert from update
  378. $table = is_array($this->tables) ? array_shift($this->tables) : (string) $this->tables;
  379. $sql = 'INSERT INTO ' . $table . "\n";
  380. $sql .= '(' . implode(',', array_keys($this->sets)) . ")\n";
  381. $qs = array_fill(0,sizeof($this->sets),'?');
  382. $sql .= 'VALUES (' . implode(',', $qs) . ')';
  383. $this->sql($sql);
  384. $this->setParams(array_values($this->sets));
  385. $this->_query(true);
  386. $count = $this->stmt->rowCount();
  387. }
  388. return $count;
  389. }
  390. /*
  391. * Excecute a query (internally) Override of Query::_query()
  392. * @param bool $hard - Run it even if a query has been ran for this instance.
  393. * @throws \PDOException, \LogicException
  394. */
  395. protected function _query($hard = false)
  396. {
  397. if (!$this->queryRan || $hard)
  398. {
  399. if(!$hard)
  400. {
  401. $this->build();
  402. }
  403. parent::_query(true);
  404. }
  405. }
  406. /**
  407. * Used to (internally) normalize statements to an array
  408. * @todo change foreach() to an array_map() implementation
  409. * @param mixed - array or commma separated data
  410. * @return array - Normalized data
  411. */
  412. protected function normalizeArray($items)
  413. {
  414. if (!is_array($items))
  415. {
  416. $items = explode(',', $items);
  417. }
  418. $items = array_map('trim', $items);
  419. return $items;
  420. }
  421. /**
  422. * Used to (internally) input data with a key/value relationship
  423. * @param mixed
  424. * @return array - organized key/val
  425. */
  426. protected function inputKeyVals($args)
  427. {
  428. $buffer = array();
  429. // We are an array('field'=>'value', ...)
  430. if (is_array($args[0]))
  431. {
  432. $buffer = $args[0];
  433. }
  434. // PDO prepared query style
  435. else if (strrchr($args[0], '?') && sizeof($args) > 1)
  436. {
  437. $sets = array_map('trim', explode(',', $args[0]));
  438. $matches = array();
  439. $i = 1;
  440. foreach($sets as $item)
  441. {
  442. if (!isset($args[$i]))
  443. {
  444. break;
  445. }
  446. preg_match("#^([a-z]+)[\s]*\=[\s]*\?$#i", $item, $matches);
  447. $buffer[$matches[1]] = $args[$i];
  448. $i++;
  449. }
  450. }
  451. // Single Key/value set
  452. else if (isset($args[0]) && isset($args[1]) && !isset($args[2]) && is_string($args[0]))
  453. {
  454. $buffer[$args[0]] = $args[1];
  455. }
  456. return $buffer;
  457. }
  458. }