PageRenderTime 61ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

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

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