PageRenderTime 73ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

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

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