PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lithium/libraries/lithium/data/model/Query.php

https://github.com/brtriver/sukonv
PHP | 510 lines | 270 code | 45 blank | 195 comment | 47 complexity | 4c3b0c7f84cc6e9968d9a4929872d90f MD5 | raw file
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\data\model;
  9. use lithium\data\Source;
  10. use lithium\data\model\QueryException;
  11. /**
  12. * The `Query` class acts as a container for all information necessary to perform a particular
  13. * database operation. Each `Query` object instance has a type, which is usually one of `'create'`,
  14. * `'read'`, `'update'` or `'delete'`.
  15. *
  16. * Because of this, `Query` objects are the primary method of communication between `Model` classes
  17. * and backend data sources. This helps to keep APIs abstract and flexible, since a model is only
  18. * required to call a single method against its backend. Since the `Query` object simply acts as a
  19. * structured data container, each backend can choose how to operate on the data the `Query`
  20. * contains. See each class method for more details on what data this class supports.
  21. *
  22. * @see lithium\data\Model
  23. * @see lithium\data\Source
  24. */
  25. class Query extends \lithium\core\Object {
  26. /**
  27. * The 'type' of query to be performed. This is either `'create'`, `'read'`, `'update'` or
  28. * `'delete'`, and corresponds to the method to be executed.
  29. *
  30. * @var string
  31. */
  32. protected $_type = null;
  33. /**
  34. * Array containing mappings of relationship and field names, which allow database results to
  35. * be mapped to the correct objects.
  36. *
  37. * @var array
  38. */
  39. protected $_map = array();
  40. /**
  41. * If a `Query` is bound to a `Record` or `Document` object (i.e. for a `'create'` or
  42. * `'update'` query).
  43. *
  44. * @var object
  45. */
  46. protected $_entity = null;
  47. /**
  48. * An array of data used in a write context. Only used if no binding object is present in the
  49. * `$_entity` property.
  50. *
  51. * @var array
  52. */
  53. protected $_data = array();
  54. /**
  55. * Auto configuration properties.
  56. *
  57. * @var array
  58. */
  59. protected $_autoConfig = array('type', 'map');
  60. /**
  61. * Class constructor, which initializes the default values this object supports. Even though
  62. * only a specific list of configuration parameters is available by default, the `Query` object
  63. * uses the `__call()` method to implement automatic getters and setters for any arbitrary piece
  64. * of data.
  65. *
  66. * This means that any information may be passed into the constructor may be used by the backend
  67. * data source executing the query (or ignored, if support is not implemented). This is useful
  68. * if, for example, you wish to extend a core data source and implement custom fucntionality.
  69. *
  70. * @param array $config
  71. * @return void
  72. */
  73. public function __construct(array $config = array()) {
  74. $defaults = array(
  75. 'calculate' => null,
  76. 'conditions' => array(),
  77. 'fields' => array(),
  78. 'model' => null,
  79. 'alias' => null,
  80. 'source' => null,
  81. 'order' => null,
  82. 'offset' => null,
  83. 'limit' => null,
  84. 'page' => null,
  85. 'group' => null,
  86. 'comment' => null,
  87. 'joins' => array(),
  88. 'with' => array(),
  89. 'map' => array(),
  90. 'whitelist' => array(),
  91. );
  92. parent::__construct($config + $defaults);
  93. }
  94. protected function _init() {
  95. parent::_init();
  96. unset($this->_config['type']);
  97. foreach ($this->_config as $key => $val) {
  98. if (method_exists($this, $key) && $val !== null) {
  99. $this->_config[$key] = is_array($this->_config[$key]) ? array() : null;
  100. $this->{$key}($val);
  101. }
  102. }
  103. if ($list = $this->_config['whitelist']) {
  104. $this->_config['whitelist'] = array_combine($list, $list);
  105. }
  106. if ($this->_config['with']) {
  107. $this->_associate($this->_config['with']);
  108. }
  109. if ($this->_entity && !$this->_config['model']) {
  110. $this->model($this->_entity->model());
  111. }
  112. unset($this->_config['entity'], $this->_config['init'], $this->_config['with']);
  113. }
  114. /**
  115. * Get method of type, i.e. 'read', 'update', 'create', 'delete'.
  116. *
  117. * @return string
  118. */
  119. public function type() {
  120. return $this->_type;
  121. }
  122. /**
  123. * Generates a schema map of the query's result set, where the keys are fully-namespaced model
  124. * class names, and the values are arrays of field names.
  125. *
  126. * @return array
  127. */
  128. public function map($map = null) {
  129. if ($map !== null) {
  130. $this->_map = $map;
  131. return $this;
  132. }
  133. return $this->_map;
  134. }
  135. /**
  136. * Accessor method for `Query` calculate values.
  137. *
  138. * @param string $calculate Value for calculate config setting.
  139. * @return mixed Current calculate config value.
  140. */
  141. public function calculate($calculate = null) {
  142. if ($calculate) {
  143. $this->_config['calculate'] = $calculate;
  144. return $this;
  145. }
  146. return $this->_config['calculate'];
  147. }
  148. /**
  149. * Set and get method for the model associated with the `Query`.
  150. * Will also set the source table, i.e. `$this->_config['source']`.
  151. *
  152. * @param string $model
  153. * @return string
  154. */
  155. public function model($model = null) {
  156. if ($model) {
  157. $this->_config['model'] = $model;
  158. $this->_config['source'] = $model::meta('source');
  159. $this->_config['name'] = $model::meta('name');
  160. return $this;
  161. }
  162. return $this->_config['model'];
  163. }
  164. /**
  165. * Set and get method for conditions.
  166. *
  167. * If no conditions are set in query, it will ask the bound entity for condition array.
  168. *
  169. * @param mixed $conditions String or array to append to existing conditions.
  170. * @return array Returns an array of all conditions applied to this query.
  171. */
  172. public function conditions($conditions = null) {
  173. if ($conditions) {
  174. $conditions = (array) $conditions;
  175. $this->_config['conditions'] = (array) $this->_config['conditions'];
  176. $this->_config['conditions'] = array_merge($this->_config['conditions'], $conditions);
  177. return $this;
  178. }
  179. return $this->_config['conditions'] ?: $this->_entityConditions();
  180. }
  181. /**
  182. * Set, get or reset fields option for query.
  183. *
  184. * Usage:
  185. * {{{
  186. * // to add a field
  187. * $query->fields('created');
  188. * }}}
  189. * {{{
  190. * // to add several fields
  191. * $query->fields(array('title','body','modified'));
  192. * }}}
  193. * {{{
  194. * // to reset fields to none
  195. * $query->fields(false);
  196. * // should be followed by a 2nd call to fields with required fields
  197. * }}}
  198. *
  199. * @param mixed $fields string, array or `false`
  200. * @param boolean $overwrite If `true`, existing fields will be removed before adding `$fields`.
  201. * @return array Returns an array containing all fields added to the query.
  202. */
  203. public function fields($fields = null, $overwrite = false) {
  204. if ($fields === false || $overwrite) {
  205. $this->_config['fields'] = array();
  206. }
  207. $this->_config['fields'] = (array) $this->_config['fields'];
  208. if (is_array($fields)) {
  209. $this->_config['fields'] = array_merge($this->_config['fields'], $fields);
  210. } elseif ($fields && !isset($this->_config['fields'][$fields])) {
  211. $this->_config['fields'][] = $fields;
  212. }
  213. if ($fields !== null) {
  214. return $this;
  215. }
  216. return $this->_config['fields'];
  217. }
  218. /**
  219. * Set and get method for query's limit of amount of records to return
  220. *
  221. * @param integer $limit
  222. * @return integer
  223. */
  224. public function limit($limit = null) {
  225. if ($limit) {
  226. $this->_config['limit'] = intval($limit);
  227. return $this;
  228. }
  229. return $this->_config['limit'];
  230. }
  231. /**
  232. * Set and get method for query's offset, i.e. which records to get
  233. *
  234. * @param integer $offset
  235. * @return integer
  236. */
  237. public function offset($offset = null) {
  238. if ($offset !== null) {
  239. $this->_config['offset'] = intval($offset);
  240. return $this;
  241. }
  242. return $this->_config['offset'];
  243. }
  244. /**
  245. * Set and get method for page, in relation to limit, of which records to get
  246. *
  247. * @param integer $page
  248. * @return integer
  249. */
  250. public function page($page = null) {
  251. if ($page) {
  252. $this->_config['page'] = $page = (intval($page) ?: 1);
  253. $this->offset(($page - 1) * $this->_config['limit']);
  254. return $this;
  255. }
  256. return $this->_config['page'];
  257. }
  258. /**
  259. * Set and get method for the query's order specification.
  260. *
  261. * @param array|string $order
  262. * @return mixed
  263. */
  264. public function order($order = null) {
  265. if ($order) {
  266. $this->_config['order'] = $order;
  267. return $this;
  268. }
  269. return $this->_config['order'];
  270. }
  271. /**
  272. * Set and get method for the `Query` group config setting.
  273. *
  274. * @param string $group New group config setting.
  275. * @return mixed Current group config setting.
  276. */
  277. public function group($group = null) {
  278. if ($group) {
  279. $this->_config['group'] = $group;
  280. return $this;
  281. }
  282. return $this->_config['group'];
  283. }
  284. /**
  285. * Set and get method for current query's comment.
  286. *
  287. * Comment will have no effect on query, but will be passed along so data source can log it.
  288. *
  289. * @param string $comment
  290. * @return string
  291. */
  292. public function comment($comment = null) {
  293. if ($comment) {
  294. $this->_config['comment'] = $comment;
  295. return $this;
  296. }
  297. return $this->_config['comment'];
  298. }
  299. /**
  300. * Set and get method for the query's entity instance.
  301. *
  302. * @param object $entity Reference to the query's current entity object.
  303. * @return object Reference to the query's current entity object.
  304. */
  305. public function &entity(&$entity = null) {
  306. if ($entity) {
  307. $this->_entity = $entity;
  308. return $this;
  309. }
  310. return $this->_entity;
  311. }
  312. /**
  313. * Set and get method for the query's record's data.
  314. *
  315. * @param array $data if set, will set given array.
  316. * @return array Empty array if no data, array of data if the record has it.
  317. */
  318. public function data($data = array()) {
  319. $bind =& $this->_entity;
  320. if ($data) {
  321. $bind ? $bind->set($data) : $this->_data = array_merge($this->_data, $data);
  322. return $this;
  323. }
  324. $data = $bind ? $bind->data() : $this->_data;
  325. return ($list = $this->_config['whitelist']) ? array_intersect_key($data, $list) : $data;
  326. }
  327. /**
  328. * Set and get the join queries
  329. *
  330. * @param string $name Optional name of join. Unless two parameters are passed, this parameter
  331. * is regonized as `$join`.
  332. * @param object|string $join A single query object or an array of query objects
  333. * @return array of query objects
  334. */
  335. public function join($name = null, $join = null) {
  336. if ($name && !$join) {
  337. $join = $name;
  338. $name = null;
  339. }
  340. if ($join) {
  341. $name ? $this->_config['joins'][$name] = $join : $this->_config['joins'][] = $join;
  342. return $this;
  343. }
  344. return $this->_config['joins'];
  345. }
  346. /**
  347. * Convert the query's properties to the data sources' syntax and return it as an array.
  348. *
  349. * @param object $dataSource Instance of the data source to use for conversion.
  350. * @param array $options Options to use when exporting the data.
  351. * @return array Returns an array containing a data source-specific representation of a query.
  352. */
  353. public function export(Source $dataSource, array $options = array()) {
  354. $defaults = array('keys' => array());
  355. $options += $defaults;
  356. $keys = $options['keys'] ?: array_keys($this->_config);
  357. $methods = $dataSource->methods();
  358. $results = array('type' => $this->_type);
  359. $apply = array_intersect($keys, $methods);
  360. $copy = array_diff($keys, $apply);
  361. foreach ($apply as $item) {
  362. $results[$item] = $dataSource->{$item}($this->{$item}(), $this);
  363. }
  364. foreach ($copy as $item) {
  365. if (in_array($item, $keys)) {
  366. $results[$item] = $this->_config[$item];
  367. }
  368. }
  369. $entity =& $this->_entity;
  370. $data = $this->_data;
  371. if ($entity) {
  372. $data = $entity->export(array('whitelist' => $this->_config['whitelist']));
  373. } elseif ($list = $this->_config['whitelist']) {
  374. $list = array_combine($list, $list);
  375. $data = array('update' => array_intersect_key($data, $list));
  376. }
  377. $results['data'] = $data;
  378. if (isset($results['source'])) {
  379. $results['source'] = $dataSource->name($results['source']);
  380. }
  381. if (!isset($results['fields'])) {
  382. return $results;
  383. }
  384. $created = array('fields', 'values');
  385. if (is_array($results['fields']) && array_keys($results['fields']) == $created) {
  386. $results = $results['fields'] + $results;
  387. }
  388. return $results;
  389. }
  390. public function schema($field = null) {
  391. if (is_array($field)) {
  392. $this->_config['schema'] = $field;
  393. return $this;
  394. }
  395. if (isset($this->_config['schema'])) {
  396. $schema = $this->_config['schema'];
  397. if ($field) {
  398. return isset($schema[$field]) ? $schema[$field] : null;
  399. }
  400. return $schema;
  401. }
  402. if ($model = $this->model()) {
  403. return $model::schema($field);
  404. }
  405. }
  406. public function alias($alias = null) {
  407. if ($alias) {
  408. $this->_config['alias'] = $alias;
  409. return $this;
  410. }
  411. if (!$this->_config['alias'] && ($model = $this->_config['model'])) {
  412. $this->_config['alias'] = $model::meta('name');
  413. }
  414. return $this->_config['alias'];
  415. }
  416. /**
  417. * Gets a custom query field which does not have an accessor method.
  418. *
  419. * @param string $method Query part.
  420. * @param string $params Query parameters.
  421. * @return mixed Returns the value as set in the `Query` object's constructor.
  422. */
  423. public function __call($method, $params = array()) {
  424. if ($params) {
  425. $this->_config[$method] = current($params);
  426. return $this;
  427. }
  428. return isset($this->_config[$method]) ? $this->_config[$method] : null;
  429. }
  430. /**
  431. * Will return a find first condition on the associated model if a record is connected.
  432. * Called by conditions when it is called as a get and no condition is set.
  433. *
  434. * @return array Returns an array in the following format:
  435. * `([model's primary key'] => [that key set in the record])`.
  436. */
  437. protected function _entityConditions() {
  438. if (!$this->_entity || !($model = $this->_config['model'])) {
  439. return;
  440. }
  441. if (is_array($key = $model::key($this->_entity->data()))) {
  442. return $key;
  443. }
  444. $key = $model::meta('key');
  445. $val = $this->_entity->{$key};
  446. return $val ? array($key => $val) : array();
  447. }
  448. protected function _associate($related) {
  449. if (!$model = $this->model()) {
  450. return;
  451. }
  452. $queryClass = get_class($this);
  453. foreach ((array) $related as $name => $config) {
  454. if (is_int($name)) {
  455. $name = $config;
  456. $config = array();
  457. }
  458. if (!$relation = $model::relations($name)) {
  459. throw new QueryException("Related model not found");
  460. }
  461. $config += $relation->data();
  462. }
  463. }
  464. }
  465. ?>