PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/fuel/packages/orm/classes/query.php

https://bitbucket.org/arkross/venus
PHP | 1203 lines | 729 code | 159 blank | 315 comment | 65 complexity | f4bddee646a6d93b0fbfed8360b72636 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause
  1. <?php
  2. /**
  3. * Fuel is a fast, lightweight, community driven PHP5 framework.
  4. *
  5. * @package Fuel
  6. * @version 1.0
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2011 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Orm;
  13. class Query
  14. {
  15. /**
  16. * This method is deprecated...use forge() instead.
  17. *
  18. * @deprecated until 1.2
  19. */
  20. public static function factory($model, $connection = null, $options = array())
  21. {
  22. logger(\Fuel::L_WARNING, 'This method is deprecated. Please use a forge() instead.', __METHOD__);
  23. return static::forge($model, $connection, $options);
  24. }
  25. public static function forge($model, $connection = null, $options = array())
  26. {
  27. return new static($model, $connection, $options);
  28. }
  29. /**
  30. * @var string classname of the model
  31. */
  32. protected $model;
  33. /**
  34. * @var null|string connection name to use
  35. */
  36. protected $connection;
  37. /**
  38. * @var array database view to use with keys 'view' and 'columns'
  39. */
  40. protected $view;
  41. /**
  42. * @var string table alias
  43. */
  44. protected $alias = 't0';
  45. /**
  46. * @var array relations to join on
  47. */
  48. protected $relations = array();
  49. /**
  50. * @var array tables to join without returning any info
  51. */
  52. protected $joins = array();
  53. /**
  54. * @var array fields to select
  55. */
  56. protected $select = array();
  57. /**
  58. * @var int max number of returned base model instances
  59. */
  60. protected $limit;
  61. /**
  62. * @var int offset of base model table
  63. */
  64. protected $offset;
  65. /**
  66. * @var int max number of requested rows
  67. */
  68. protected $rows_limit;
  69. /**
  70. * @var int offset of requested rows
  71. */
  72. protected $rows_offset;
  73. /**
  74. * @var array where conditions
  75. */
  76. protected $where = array();
  77. /**
  78. * @var array order by clauses
  79. */
  80. protected $order_by = array();
  81. /**
  82. * @var array values for insert or update
  83. */
  84. protected $values = array();
  85. protected function __construct($model, $connection, $options, $table_alias = null)
  86. {
  87. $this->model = $model;
  88. $this->connection = $connection;
  89. foreach ($options as $opt => $val)
  90. {
  91. switch ($opt)
  92. {
  93. case 'select':
  94. $val = (array) $val;
  95. call_user_func_array(array($this, 'select'), $val);
  96. break;
  97. case 'related':
  98. $val = (array) $val;
  99. $this->related($val);
  100. break;
  101. case 'use_view':
  102. $this->use_view($val);
  103. break;
  104. case 'where':
  105. $this->_parse_where_array($val);
  106. break;
  107. case 'order_by':
  108. $val = (array) $val;
  109. $this->order_by($val);
  110. break;
  111. case 'limit':
  112. $this->limit($val);
  113. break;
  114. case 'offset':
  115. $this->offset($val);
  116. break;
  117. case 'rows_limit':
  118. $this->rows_limit($val);
  119. break;
  120. case 'rows_offset':
  121. $this->rows_offset($val);
  122. break;
  123. }
  124. }
  125. }
  126. /**
  127. * Select which properties are included, each as its own param. Or don't give input to retrieve
  128. * the current selection.
  129. *
  130. * @return void|array
  131. */
  132. public function select()
  133. {
  134. $fields = func_get_args();
  135. if (empty($fields))
  136. {
  137. if (empty($this->select))
  138. {
  139. $fields = array_keys(call_user_func($this->model.'::properties'));
  140. if (empty($fields))
  141. {
  142. throw new \FuelException('No properties found in model.');
  143. }
  144. foreach ($fields as $field)
  145. {
  146. $this->select($field);
  147. }
  148. if ($this->view)
  149. {
  150. foreach ($this->view['columns'] as $field)
  151. {
  152. $this->select($field);
  153. }
  154. }
  155. }
  156. // backup select before adding PKs
  157. $select = $this->select;
  158. // ensure all PKs are being selected
  159. $pks = call_user_func($this->model.'::primary_key');
  160. foreach($pks as $pk)
  161. {
  162. if ( ! in_array($this->alias.'.'.$pk, $this->select))
  163. {
  164. $this->select($pk);
  165. }
  166. }
  167. // convert selection array for DB class
  168. $out = array();
  169. foreach($this->select as $k => $v)
  170. {
  171. $out[] = array($v, $k);
  172. }
  173. // set select back to before the PKs were added
  174. $this->select = $select;
  175. return $out;
  176. }
  177. $i = count($this->select);
  178. foreach ($fields as $val)
  179. {
  180. $this->select[$this->alias.'_c'.$i++] = (strpos($val, '.') === false ? $this->alias.'.' : '').$val;
  181. }
  182. return $this;
  183. }
  184. /**
  185. * Set a view to use instead of the table
  186. *
  187. * @param string
  188. * @return Query
  189. */
  190. public function use_view($view)
  191. {
  192. $views = call_user_func(array($this->model, 'views'));
  193. if ( ! array_key_exists($view, $views))
  194. {
  195. throw new \OutOfBoundsException('Cannot use undefined database view, must be defined with Model.');
  196. }
  197. $this->view = $views[$view];
  198. $this->view['_name'] = $view;
  199. return $this;
  200. }
  201. /**
  202. * Set the limit
  203. *
  204. * @param int
  205. * @return Query
  206. */
  207. public function limit($limit)
  208. {
  209. $this->limit = intval($limit);
  210. return $this;
  211. }
  212. /**
  213. * Set the offset
  214. *
  215. * @param int
  216. * @return Query
  217. */
  218. public function offset($offset)
  219. {
  220. $this->offset = intval($offset);
  221. return $this;
  222. }
  223. /**
  224. * Set the limit of rows requested
  225. *
  226. * @param int
  227. * @return Query
  228. */
  229. public function rows_limit($limit)
  230. {
  231. $this->rows_limit = intval($limit);
  232. return $this;
  233. }
  234. /**
  235. * Set the offset of rows requested
  236. *
  237. * @param int
  238. * @return Query
  239. */
  240. public function rows_offset($offset)
  241. {
  242. $this->rows_offset = intval($offset);
  243. return $this;
  244. }
  245. /**
  246. * Set where condition
  247. *
  248. * @param string property
  249. * @param string comparison type (can be omitted)
  250. * @param string comparison value
  251. * @return Query
  252. */
  253. public function where()
  254. {
  255. $condition = func_get_args();
  256. is_array(reset($condition)) and $condition = reset($condition);
  257. return $this->_where($condition);
  258. }
  259. /**
  260. * Set or_where condition
  261. *
  262. * @param string property
  263. * @param string comparison type (can be omitted)
  264. * @param string comparison value
  265. * @return Query
  266. */
  267. public function or_where()
  268. {
  269. $condition = func_get_args();
  270. is_array(reset($condition)) and $condition = reset($condition);
  271. return $this->_where($condition, 'or_where');
  272. }
  273. /**
  274. * Does the work for where() and or_where()
  275. *
  276. * @param array
  277. * @param string
  278. * @return Query
  279. */
  280. public function _where($condition, $type = 'and_where')
  281. {
  282. if (is_array(reset($condition)) or is_string(key($condition)))
  283. {
  284. foreach ($condition as $k_c => $v_c)
  285. {
  286. is_string($k_c) and $v_c = array($k_c, $v_c);
  287. $this->_where($v_c, $type);
  288. }
  289. return $this;
  290. }
  291. // prefix table alias when not yet prefixed and not a DB expression object
  292. if (strpos($condition[0], '.') === false and ! $condition[0] instanceof \Fuel\Core\Database_Expression)
  293. {
  294. $condition[0] = $this->alias.'.'.$condition[0];
  295. }
  296. if (count($condition) == 2)
  297. {
  298. $this->where[] = array($type, array($condition[0], '=', $condition[1]));
  299. }
  300. elseif (count($condition) == 3)
  301. {
  302. $this->where[] = array($type, $condition);
  303. }
  304. else
  305. {
  306. throw new \FuelException('Invalid param count for where condition.');
  307. }
  308. return $this;
  309. }
  310. /**
  311. * Open a nested and_where condition
  312. *
  313. * @return Query
  314. */
  315. public function and_where_open()
  316. {
  317. $this->where[] = array('and_where_open', array());
  318. return $this;
  319. }
  320. /**
  321. * Close a nested and_where condition
  322. *
  323. * @return Query
  324. */
  325. public function and_where_close()
  326. {
  327. $this->where[] = array('and_where_close', array());
  328. return $this;
  329. }
  330. /**
  331. * Alias to and_where_open()
  332. *
  333. * @return Query
  334. */
  335. public function where_open()
  336. {
  337. $this->where[] = array('and_where_open', array());
  338. return $this;
  339. }
  340. /**
  341. * Alias to and_where_close()
  342. *
  343. * @return Query
  344. */
  345. public function where_close()
  346. {
  347. $this->where[] = array('and_where_close', array());
  348. return $this;
  349. }
  350. /**
  351. * Open a nested or_where condition
  352. *
  353. * @return Query
  354. */
  355. public function or_where_open()
  356. {
  357. $this->where[] = array('or_where_open', array());
  358. return $this;
  359. }
  360. /**
  361. * Close a nested or_where condition
  362. *
  363. * @return Query
  364. */
  365. public function or_where_close()
  366. {
  367. $this->where[] = array('or_where_close', array());
  368. return $this;
  369. }
  370. /**
  371. * Parses an array of where conditions into the query
  372. *
  373. * @param array $val
  374. * @param string $base
  375. * @param bool $or
  376. */
  377. protected function _parse_where_array(array $val, $base = '', $or = false)
  378. {
  379. $or and $this->or_where_open();
  380. foreach ($val as $k_w => $v_w)
  381. {
  382. if (is_array($v_w) and ! empty($v_w[0]) and is_string($v_w[0]))
  383. {
  384. ! $v_w[0] instanceof \Database_Expression and strpos($v_w[0], '.') === false and $v_w[0] = $base.$v_w[0];
  385. call_user_func_array(array($this, ($k_w === 'or' ? 'or_' : '').'where'), $v_w);
  386. }
  387. elseif (is_int($k_w) or $k_w == 'or')
  388. {
  389. $k_w === 'or' ? $this->or_where_open() : $this->where_open();
  390. $this->_parse_where_array($v_w, $base, $k_w === 'or');
  391. $k_w === 'or' ? $this->or_where_close() : $this->where_close();
  392. }
  393. else
  394. {
  395. ! $k_w instanceof \Database_Expression and strpos($k_w, '.') === false and $k_w = $base.$k_w;
  396. $this->where($k_w, $v_w);
  397. }
  398. }
  399. $or and $this->or_where_close();
  400. }
  401. /**
  402. * Set the order_by
  403. *
  404. * @param string|array
  405. * @param string|null
  406. * @return Query
  407. */
  408. public function order_by($property, $direction = 'ASC')
  409. {
  410. if (is_array($property))
  411. {
  412. foreach ($property as $p => $d)
  413. {
  414. if (is_int($p))
  415. {
  416. is_array($d) ? $this->order_by($d[0], $d[1]) : $this->order_by($d, $direction);
  417. }
  418. else
  419. {
  420. $this->order_by($p, $d);
  421. }
  422. }
  423. return $this;
  424. }
  425. // prefix table alias when not yet prefixed and not a DB expression object
  426. if ( ! $property instanceof \Fuel\Core\Database_Expression and strpos($property, '.') === false)
  427. {
  428. $property = $this->alias.'.'.$property;
  429. }
  430. $this->order_by[] = array($property, $direction);
  431. return $this;
  432. }
  433. /**
  434. * Set a relation to include
  435. *
  436. * @param string
  437. * @return Query
  438. */
  439. public function related($relation, $conditions = array())
  440. {
  441. if (is_array($relation))
  442. {
  443. foreach ($relation as $k_r => $v_r)
  444. {
  445. is_array($v_r) ? $this->related($k_r, $v_r) : $this->related($v_r);
  446. }
  447. return $this;
  448. }
  449. if (strpos($relation, '.'))
  450. {
  451. $rels = explode('.', $relation);
  452. $model = $this->model;
  453. foreach ($rels as $r)
  454. {
  455. $rel = call_user_func(array($model, 'relations'), $r);
  456. if (empty($rel))
  457. {
  458. throw new \UnexpectedValueException('Relation "'.$r.'" was not found in the model "'.$model.'".');
  459. }
  460. $model = $rel->model_to;
  461. }
  462. }
  463. else
  464. {
  465. $rel = call_user_func(array($this->model, 'relations'), $relation);
  466. if (empty($rel))
  467. {
  468. throw new \UnexpectedValueException('Relation "'.$relation.'" was not found in the model.');
  469. }
  470. }
  471. $this->relations[$relation] = array($rel, $conditions);
  472. if ( ! empty($conditions['related']))
  473. {
  474. $conditions['related'] = (array) $conditions['related'];
  475. foreach ($conditions['related'] as $k_r => $v_r)
  476. {
  477. is_array($v_r) ? $this->related($relation.'.'.$k_r, $v_r) : $this->related($relation.'.'.$v_r);
  478. }
  479. unset($conditions['related']);
  480. }
  481. return $this;
  482. }
  483. /**
  484. * Add a table to join, consider this a protect method only for Orm package usage
  485. *
  486. * @param array
  487. * @return Query
  488. */
  489. public function _join(array $join)
  490. {
  491. $this->joins[] = $join;
  492. return $this;
  493. }
  494. /**
  495. * Set any properties for insert or update
  496. *
  497. * @param string|array
  498. * @param mixed
  499. * @return Query
  500. */
  501. public function set($property, $value = null)
  502. {
  503. if (is_array($property))
  504. {
  505. foreach ($property as $p => $v)
  506. {
  507. $this->set($p, $v);
  508. }
  509. return $this;
  510. }
  511. $this->values[$property] = $value;
  512. return $this;
  513. }
  514. /**
  515. * Build a select, delete or update query
  516. *
  517. * @param \Fuel\Core\Database_Query_Builder_Where
  518. * @param string|select either array for select query or string update, delete, insert
  519. * @return array with keys query and relations
  520. */
  521. public function build_query(\Fuel\Core\Database_Query_Builder_Where $query, $columns = array(), $type = 'select')
  522. {
  523. // Get the limit
  524. if ( ! is_null($this->limit))
  525. {
  526. $query->limit($this->limit);
  527. }
  528. // Get the offset
  529. if ( ! is_null($this->offset))
  530. {
  531. $query->offset($this->offset);
  532. }
  533. // Get the order
  534. $order_by = $this->order_by;
  535. if ( ! empty($order_by))
  536. {
  537. foreach ($order_by as $key => $ob)
  538. {
  539. if ( ! $ob[0] instanceof \Fuel\Core\Database_Expression and strpos($ob[0], $this->alias.'.') === 0)
  540. {
  541. $query->order_by($type == 'select' ? $ob[0] : substr($ob[0], strlen($this->alias.'.')), $ob[1]);
  542. }
  543. }
  544. }
  545. $where_backup = $this->where;
  546. if ( ! empty($this->where))
  547. {
  548. $open_nests = 0;
  549. foreach ($this->where as $key => $w)
  550. {
  551. list($method, $conditional) = $w;
  552. if ($type == 'select' and (empty($conditional) or $open_nests > 0))
  553. {
  554. strpos($method, '_open') and $open_nests++;
  555. strpos($method, '_close') and $open_nests--;
  556. continue;
  557. }
  558. if (empty($conditional)
  559. or strpos($conditional[0], $this->alias.'.') === 0
  560. or ($type != 'select' and $conditional[0] instanceof \Fuel\Core\Database_Expression))
  561. {
  562. if ($type != 'select' and ! empty($conditional)
  563. and ! $conditional[0] instanceof \Fuel\Core\Database_Expression)
  564. {
  565. $conditional[0] = substr($conditional[0], strlen($this->alias.'.'));
  566. }
  567. call_user_func_array(array($query, $method), $conditional);
  568. unset($this->where[$key]);
  569. }
  570. }
  571. }
  572. // If it's not a select we're done
  573. if ($type != 'select')
  574. {
  575. return array('query' => $query, 'models' => array());
  576. }
  577. $i = 1;
  578. $models = array();
  579. foreach ($this->relations as $name => $rel)
  580. {
  581. // when there's a dot it must be a nested relation
  582. if ($pos = strrpos($name, '.'))
  583. {
  584. if (empty($models[substr($name, 0, $pos)]['table'][1]))
  585. {
  586. throw new \UnexpectedValueException('Trying to get the relation of an unloaded relation, make sure you load the parent relation before any of its children.');
  587. }
  588. $alias = $models[substr($name, 0, $pos)]['table'][1];
  589. }
  590. else
  591. {
  592. $alias = $this->alias;
  593. }
  594. $models = array_merge($models, $rel[0]->join($alias, $name, $i++, $rel[1]));
  595. }
  596. if ($this->use_subquery())
  597. {
  598. // Get the columns for final select
  599. foreach ($models as $m)
  600. {
  601. foreach ($m['columns'] as $c)
  602. {
  603. $columns[] = $c;
  604. }
  605. }
  606. // make current query subquery of ultimate query
  607. $new_query = call_user_func_array('DB::select', $columns);
  608. $query = $new_query->from(array($query, $this->alias));
  609. }
  610. else
  611. {
  612. // add additional selected columns
  613. foreach ($models as $m)
  614. {
  615. foreach ($m['columns'] as $c)
  616. {
  617. $query->select($c);
  618. }
  619. }
  620. }
  621. // join tables
  622. foreach ($this->joins as $j)
  623. {
  624. $join_query = $query->join($j['table'], $j['join_type']);
  625. foreach ($j['join_on'] as $on)
  626. {
  627. $join_query->on($on[0], $on[1], $on[2]);
  628. }
  629. }
  630. foreach ($models as $m)
  631. {
  632. if ($m['connection'] != $this->connection)
  633. {
  634. throw new \FuelException('Models cannot be related between connection.');
  635. }
  636. $join_query = $query->join($m['table'], $m['join_type']);
  637. foreach ($m['join_on'] as $on)
  638. {
  639. $join_query->on($on[0], $on[1], $on[2]);
  640. }
  641. }
  642. // Add any additional order_by and where clauses from the relations
  643. foreach ($models as $m_name => $m)
  644. {
  645. if ( ! empty($m['order_by']))
  646. {
  647. foreach ((array) $m['order_by'] as $k_ob => $v_ob)
  648. {
  649. if (is_int($k_ob))
  650. {
  651. $v_dir = is_array($v_ob) ? $v_ob[1] : 'ASC';
  652. $v_ob = is_array($v_ob) ? $v_ob[0] : $v_ob;
  653. if ( ! $v_ob instanceof \Fuel\Core\Database_Expression and strpos($v_ob, '.') === false)
  654. {
  655. $v_ob = $m_name.'.'.$v_ob;
  656. }
  657. $order_by[] = array($v_ob, 'ASC');
  658. }
  659. else
  660. {
  661. strpos($k_ob, '.') === false and $k_ob = $m_name.'.'.$k_ob;
  662. $order_by[] = array($k_ob, $v_ob);
  663. }
  664. }
  665. }
  666. if ( ! empty($m['where']))
  667. {
  668. $this->_parse_where_array($m['where'], $m_name.'.');
  669. }
  670. }
  671. // Get the order
  672. if ( ! empty($order_by))
  673. {
  674. foreach ($order_by as $ob)
  675. {
  676. if ( ! $ob[0] instanceof \Fuel\Core\Database_Expression)
  677. {
  678. // try to rewrite conditions on the relations to their table alias
  679. $dotpos = strrpos($ob[0], '.');
  680. $relation = substr($ob[0], 0, $dotpos);
  681. if ($dotpos > 0 and array_key_exists($relation, $models))
  682. {
  683. $ob[0] = $models[$relation]['table'][1].substr($ob[0], $dotpos);
  684. }
  685. }
  686. $query->order_by($ob[0], $ob[1]);
  687. }
  688. }
  689. // put omitted where conditions back
  690. if ( ! empty($this->where))
  691. {
  692. foreach ($this->where as $w)
  693. {
  694. list($method, $conditional) = $w;
  695. // try to rewrite conditions on the relations to their table alias
  696. if ( ! empty($conditional))
  697. {
  698. $dotpos = strrpos($conditional[0], '.');
  699. $relation = substr($conditional[0], 0, $dotpos);
  700. if ($dotpos > 0 and array_key_exists($relation, $models))
  701. {
  702. $conditional[0] = $models[$relation]['table'][1].substr($conditional[0], $dotpos);
  703. }
  704. }
  705. call_user_func_array(array($query, $method), $conditional);
  706. }
  707. }
  708. $this->where = $where_backup;
  709. // Set the row limit and offset, these are applied to the outer query when a subquery
  710. // is used or overwrite limit/offset when it's a normal query
  711. ! is_null($this->rows_limit) and $query->limit($this->rows_limit);
  712. ! is_null($this->rows_offset) and $query->offset($this->rows_offset);
  713. return array('query' => $query, 'models' => $models);
  714. }
  715. /**
  716. * Determines whether a subquery is needed, is the case if there was a limit/offset on a join
  717. *
  718. * @return bool
  719. */
  720. public function use_subquery()
  721. {
  722. return ( ! empty($this->relations) and ( ! empty($this->limit) or ! empty($this->offset)));
  723. }
  724. /**
  725. * Hydrate model instances with retrieved data
  726. *
  727. * @param array row from the database
  728. * @param array relations to be expected
  729. * @param array current result array (by reference)
  730. * @param string model classname to hydrate
  731. * @param array columns to use
  732. * @return Model
  733. */
  734. public function hydrate(&$row, $models, &$result, $model = null, $select = null, $primary_key = null)
  735. {
  736. // First check the PKs, if null it's an empty row
  737. $r1c1 = reset($select);
  738. $prefix = substr($r1c1[0], 0, strpos($r1c1[0], '.') + 1);
  739. $obj = array();
  740. foreach ($primary_key as $pk)
  741. {
  742. $pk_c = null;
  743. foreach ($select as $s)
  744. {
  745. $s[0] === $prefix.$pk and $pk_c = $s[1];
  746. }
  747. if (is_null($row[$pk_c]))
  748. {
  749. return false;
  750. }
  751. $obj[$pk] = $row[$pk_c];
  752. }
  753. // Check for cached object
  754. $pk = count($primary_key) == 1 ? reset($obj) : '['.implode('][', $obj).']';
  755. $obj = Model::cached_object($pk, $model);
  756. // Create the object when it wasn't found
  757. if ( ! $obj)
  758. {
  759. // Retrieve the object array from the row
  760. $obj = array();
  761. foreach ($select as $s)
  762. {
  763. $obj[substr($s[0], strpos($s[0], '.') + 1)] = $row[$s[1]];
  764. unset($row[$s[1]]);
  765. }
  766. $obj = $model::forge($obj, false, $this->view ? $this->view['_name'] : null);
  767. }
  768. // if the result to be generated is an array and the current object is not yet in there
  769. if (is_array($result) and ! array_key_exists($pk, $result))
  770. {
  771. $result[$pk] = $obj;
  772. }
  773. // if the result to be generated is a single object and empty
  774. elseif ( ! is_array($result) and empty($result))
  775. {
  776. $result = $obj;
  777. }
  778. // start fetching relationships
  779. $rel_objs = $obj->_relate();
  780. foreach ($models as $m)
  781. {
  782. // when the expected model is empty, there's nothing to be done
  783. if (empty($m['model']))
  784. {
  785. continue;
  786. }
  787. // when not yet set, create the relation result var with null or array
  788. if ( ! array_key_exists($m['rel_name'], $rel_objs))
  789. {
  790. $rel_objs[$m['rel_name']] = $m['relation']->singular ? null : array();
  791. }
  792. // when result is array or singular empty, try to fetch the new relation from the row
  793. $this->hydrate(
  794. $row,
  795. ! empty($m['models']) ? $m['models'] : array(),
  796. $rel_objs[$m['rel_name']],
  797. $m['model'],
  798. $m['columns'],
  799. $m['primary_key']
  800. );
  801. }
  802. // attach the retrieved relations to the object and update its original DB values
  803. $obj->_relate($rel_objs);
  804. $obj->_update_original_relations();
  805. return $obj;
  806. }
  807. /**
  808. * Build the query and return hydrated results
  809. *
  810. * @return array
  811. */
  812. public function get()
  813. {
  814. // Get the columns
  815. $columns = $this->select();
  816. // Start building the query
  817. $select = $columns;
  818. if ($this->use_subquery())
  819. {
  820. $select = array();
  821. foreach ($columns as $c)
  822. {
  823. $select[] = $c[0];
  824. }
  825. }
  826. $query = call_user_func_array('DB::select', $select);
  827. // Set from view/table
  828. $table = $this->view ? $this->view['view'] : call_user_func($this->model.'::table');
  829. $query->from(array($table, $this->alias));
  830. // Build the query further
  831. $tmp = $this->build_query($query, $columns);
  832. $query = $tmp['query'];
  833. $models = $tmp['models'];
  834. // Make models hierarchical
  835. foreach ($models as $name => $values)
  836. {
  837. if (strpos($name, '.'))
  838. {
  839. unset($models[$name]);
  840. $rels = explode('.', $name);
  841. $ref =& $models[array_shift($rels)];
  842. foreach ($rels as $rel)
  843. {
  844. empty($ref['models']) and $ref['models'] = array();
  845. empty($ref['models'][$rel]) and $ref['models'][$rel] = array();
  846. $ref =& $ref['models'][$rel];
  847. }
  848. $ref = $values;
  849. }
  850. }
  851. $rows = $query->execute($this->connection)->as_array();
  852. $result = array();
  853. $model = $this->model;
  854. $select = $this->select();
  855. $primary_key = $model::primary_key();
  856. foreach ($rows as $id => $row)
  857. {
  858. $this->hydrate($row, $models, $result, $model, $select, $primary_key);
  859. unset($rows[$id]);
  860. }
  861. // It's all built, now lets execute and start hydration
  862. return $result;
  863. }
  864. /**
  865. * Get the Query as it's been build up to this point and return it as an object
  866. *
  867. * @return Database_Query
  868. */
  869. public function get_query()
  870. {
  871. // Get the columns
  872. $columns = $this->select();
  873. // Start building the query
  874. $select = $columns;
  875. if ($this->use_subquery())
  876. {
  877. $select = array();
  878. foreach ($columns as $c)
  879. {
  880. $select[] = $c[0];
  881. }
  882. }
  883. $query = call_user_func_array('DB::select', $select);
  884. // Set from table
  885. $query->from(array(call_user_func($this->model.'::table'), $this->alias));
  886. // Build the query further
  887. $tmp = $this->build_query($query, $columns);
  888. return $tmp['query'];
  889. }
  890. /**
  891. * Build the query and return single object hydrated
  892. *
  893. * @return Model
  894. */
  895. public function get_one()
  896. {
  897. // get current limit and save it while fetching the first result
  898. $limit = $this->limit;
  899. $this->limit = 1;
  900. // get the result using normal find
  901. $result = $this->get();
  902. // put back the old limit
  903. $this->limit = $limit;
  904. return $result ? reset($result) : null;
  905. }
  906. /**
  907. * Count the result of a query
  908. *
  909. * @param bool false for random selected column or specific column, only works for main model currently
  910. * @return int number of rows OR false
  911. */
  912. public function count($column = null, $distinct = true)
  913. {
  914. $select = $column ?: \Arr::get(call_user_func($this->model.'::primary_key'), 0);
  915. $select = (strpos($select, '.') === false ? $this->alias.'.'.$select : $select);
  916. // Get the columns
  917. $columns = \DB::expr('COUNT('.($distinct ? 'DISTINCT ' : '').
  918. \Database_Connection::instance()->quote_identifier($select).
  919. ') AS count_result');
  920. // Remove the current select and
  921. $query = call_user_func('DB::select', $columns);
  922. // Set from table
  923. $query->from(array(call_user_func($this->model.'::table'), $this->alias));
  924. $tmp = $this->build_query($query, $columns, 'select');
  925. $query = $tmp['query'];
  926. $count = $query->execute($this->connection)->get('count_result');
  927. // Database_Result::get('count_result') returns a string | null
  928. if ($count === null)
  929. {
  930. return false;
  931. }
  932. return (int) $count;
  933. }
  934. /**
  935. * Get the maximum of a column for the current query
  936. *
  937. * @param string column
  938. * @return mixed maximum value OR false
  939. */
  940. public function max($column)
  941. {
  942. is_array($column) and $column = array_shift($column);
  943. // Get the columns
  944. $columns = \DB::expr('MAX('.
  945. \Database_Connection::instance()->quote_identifier($this->alias.'.'.$column).
  946. ') AS max_result');
  947. // Remove the current select and
  948. $query = call_user_func('DB::select', $columns);
  949. // Set from table
  950. $query->from(array(call_user_func($this->model.'::table'), $this->alias));
  951. $tmp = $this->build_query($query, $columns, 'max');
  952. $query = $tmp['query'];
  953. $max = $query->execute($this->connection)->get('max_result');
  954. // Database_Result::get('max_result') returns a string | null
  955. if ($max === null)
  956. {
  957. return false;
  958. }
  959. return $max;
  960. }
  961. /**
  962. * Get the minimum of a column for the current query
  963. *
  964. * @param string column
  965. * @return mixed minimum value OR false
  966. */
  967. public function min($column)
  968. {
  969. is_array($column) and $column = array_shift($column);
  970. // Get the columns
  971. $columns = \DB::expr('MIN('.
  972. \Database_Connection::instance()->quote_identifier($this->alias.'.'.$column).
  973. ') AS min_result');
  974. // Remove the current select and
  975. $query = call_user_func('DB::select', $columns);
  976. // Set from table
  977. $query->from(array(call_user_func($this->model.'::table'), $this->alias));
  978. $tmp = $this->build_query($query, $columns, 'min');
  979. $query = $tmp['query'];
  980. $min = $query->execute($this->connection)->get('min_result');
  981. // Database_Result::get('min_result') returns a string | null
  982. if ($min === null)
  983. {
  984. return false;
  985. }
  986. return $min;
  987. }
  988. /**
  989. * Run INSERT with the current values
  990. *
  991. * @return mixed last inserted ID or false on failure
  992. */
  993. public function insert()
  994. {
  995. $res = \DB::insert(call_user_func($this->model.'::table'), array_keys($this->values))
  996. ->values(array_values($this->values))
  997. ->execute($this->connection);
  998. // Failed to save the new record
  999. if ($res[0] === 0)
  1000. {
  1001. return false;
  1002. }
  1003. return $res[0];
  1004. }
  1005. /**
  1006. * Run UPDATE with the current values
  1007. *
  1008. * @return bool success of update operation
  1009. */
  1010. public function update()
  1011. {
  1012. // temporary disable relations
  1013. $tmp_relations = $this->relations;
  1014. $this->relations = array();
  1015. // Build query and execute update
  1016. $query = \DB::update(call_user_func($this->model.'::table'));
  1017. $tmp = $this->build_query($query, array(), 'update');
  1018. $query = $tmp['query'];
  1019. $res = $query->set($this->values)->execute($this->connection);
  1020. // put back any relations settings
  1021. $this->relations = $tmp_relations;
  1022. // Update can affect 0 rows when input types are different but outcome stays the same
  1023. return $res >= 0;
  1024. }
  1025. /**
  1026. * Run DELETE with the current values
  1027. *
  1028. * @return bool success of delete operation
  1029. */
  1030. public function delete()
  1031. {
  1032. // temporary disable relations
  1033. $tmp_relations = $this->relations;
  1034. $this->relations = array();
  1035. // Build query and execute update
  1036. $query = \DB::delete(call_user_func($this->model.'::table'));
  1037. $tmp = $this->build_query($query, array(), 'delete');
  1038. $query = $tmp['query'];
  1039. $res = $query->execute($this->connection);
  1040. // put back any relations settings
  1041. $this->relations = $tmp_relations;
  1042. return $res > 0;
  1043. }
  1044. }