PageRenderTime 51ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

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

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