PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/models/datasources/array_source.php

http://github.com/cakephp/datasources
PHP | 458 lines | 327 code | 15 blank | 116 comment | 80 complexity | 2db90615f575459d2c235cf45b11c40b MD5 | raw file
  1. <?php
  2. /**
  3. * Array Datasource
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package datasources
  16. * @subpackage datasources.models.datasources
  17. * @since CakePHP Datasources v 0.3
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. App::import('Core', 'Set');
  21. /**
  22. * ArraySource
  23. *
  24. * Datasource by Array
  25. */
  26. class ArraySource extends Datasource {
  27. /**
  28. * Description string for this Data Source.
  29. *
  30. * @var string
  31. */
  32. public $description = 'Array Datasource';
  33. /**
  34. * List of requests ("queries")
  35. *
  36. * @var array
  37. */
  38. protected $_requestsLog = array();
  39. /**
  40. * Base Config
  41. *
  42. * @var array
  43. */
  44. public $_baseConfig = array(
  45. 'driver' => '' // Just to avoid DebugKit warning
  46. );
  47. /**
  48. * Returns a Model description (metadata) or null if none found.
  49. *
  50. * @param Model $model
  51. * @return array Show only id
  52. */
  53. public function describe(&$model) {
  54. return array('id' => array());
  55. }
  56. /**
  57. * List sources
  58. *
  59. * @param mixed $data
  60. * @return boolean Always false. It's not supported
  61. */
  62. public function listSources($data = null) {
  63. return false;
  64. }
  65. /**
  66. * Used to read records from the Datasource. The "R" in CRUD
  67. *
  68. * @param Model $model The model being read.
  69. * @param array $queryData An array of query data used to find the data you want
  70. * @return mixed
  71. */
  72. public function read(&$model, $queryData = array()) {
  73. if (!isset($model->records) || !is_array($model->records) || empty($model->records)) {
  74. $this->_requestsLog[] = array(
  75. 'query' => 'Model ' . $model->alias,
  76. 'error' => __('No records found in model.', true),
  77. 'affected' => 0,
  78. 'numRows' => 0,
  79. 'took' => 0
  80. );
  81. return array($model->alias => array());
  82. }
  83. $startTime = getMicrotime();
  84. $data = array();
  85. $i = 0;
  86. $limit = false;
  87. if (!isset($queryData['recursive'])) {
  88. $queryData['recursive'] = $model->recursive;
  89. }
  90. if (is_integer($queryData['limit']) && $queryData['limit'] > 0) {
  91. $limit = $queryData['page'] * $queryData['limit'];
  92. }
  93. foreach ($model->records as $pos => $record) {
  94. // Tests whether the record will be chosen
  95. if (!empty($queryData['conditions'])) {
  96. $queryData['conditions'] = (array)$queryData['conditions'];
  97. if (!$this->conditionsFilter($model, $record, $queryData['conditions'])) {
  98. continue;
  99. }
  100. }
  101. $data[$i][$model->alias] = $record;
  102. $i++;
  103. // Test limit
  104. if ($limit !== false && $i == $limit && empty($queryData['order'])) {
  105. break;
  106. }
  107. }
  108. if ($queryData['fields'] === 'COUNT') {
  109. $this->_registerLog($model, $queryData, getMicrotime() - $startTime, 1);
  110. if ($limit !== false) {
  111. $data = array_slice($data, ($queryData['page'] - 1) * $queryData['limit'], $queryData['limit'], false);
  112. }
  113. return array(array(array('count' => count($data))));
  114. }
  115. // Order
  116. if (!empty($queryData['order'])) {
  117. if (is_string($queryData['order'][0])) {
  118. $field = $queryData['order'][0];
  119. $alias = $model->alias;
  120. if (strpos($field, '.') !== false) {
  121. list($alias, $field) = explode('.', $field, 2);
  122. }
  123. if ($alias === $model->alias) {
  124. $sort = 'ASC';
  125. if (strpos($field, ' ') !== false) {
  126. list($field, $sort) = explode(' ', $field, 2);
  127. }
  128. $data = Set::sort($data, '{n}.' . $model->alias . '.' . $field, $sort);
  129. }
  130. }
  131. }
  132. // Limit
  133. if ($limit !== false) {
  134. $data = array_slice($data, ($queryData['page'] - 1) * $queryData['limit'], $queryData['limit'], false);
  135. }
  136. // Filter fields
  137. if (!empty($queryData['fields'])) {
  138. $listOfFields = array();
  139. foreach ((array)$queryData['fields'] as $field) {
  140. if (strpos($field, '.') !== false) {
  141. list($alias, $field) = explode('.', $field, 2);
  142. if ($alias !== $model->alias) {
  143. continue;
  144. }
  145. }
  146. $listOfFields[] = $field;
  147. }
  148. foreach ($data as $id => $record) {
  149. foreach ($record[$model->alias] as $field => $value) {
  150. if (!in_array($field, $listOfFields)) {
  151. unset($data[$id][$model->alias][$field]);
  152. }
  153. }
  154. }
  155. }
  156. $this->_registerLog($model, $queryData, getMicrotime() - $startTime, count($data));
  157. $_associations = $model->__associations;
  158. if ($queryData['recursive'] > -1) {
  159. foreach ($_associations as $type) {
  160. foreach ($model->{$type} as $assoc => $assocData) {
  161. $linkModel =& $model->{$assoc};
  162. if ($model->useDbConfig == $linkModel->useDbConfig) {
  163. $db =& $this;
  164. } else {
  165. $db =& ConnectionManager::getDataSource($linkModel->useDbConfig);
  166. }
  167. if (isset($db)) {
  168. if (method_exists($db, 'queryAssociation')) {
  169. $stack = array($assoc);
  170. $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $queryData, true, $data, $queryData['recursive'] - 1, $stack);
  171. }
  172. unset($db);
  173. }
  174. }
  175. }
  176. }
  177. if ($model->findQueryType === 'first') {
  178. if (!isset($data[0])) {
  179. $data = array();
  180. } else {
  181. $data = array($data[0]);
  182. }
  183. }
  184. return $data;
  185. }
  186. /**
  187. * Conditions Filter
  188. *
  189. * @param Model $model
  190. * @param string $record
  191. * @param array $conditions
  192. * @param boolean $or
  193. * @return void
  194. */
  195. public function conditionsFilter(&$model, $record, $conditions, $or = false) {
  196. foreach ($conditions as $field => $value) {
  197. $return = null;
  198. if ($value === '') {
  199. continue;
  200. }
  201. if (is_array($value) && in_array(strtoupper($field), array('AND', 'NOT', 'OR'))) {
  202. switch (strtoupper($field)) {
  203. case 'AND':
  204. $return = $this->conditionsFilter($model, $record, $value);
  205. break;
  206. case 'NOT':
  207. $return = !$this->conditionsFilter($model, $record, $value);
  208. break;
  209. case 'OR':
  210. $return = $this->conditionsFilter($model, $record, $value, true);
  211. break;
  212. }
  213. } else {
  214. if (is_array($value)) {
  215. $type = 'IN';
  216. } elseif (preg_match('/^(\w+\.?\w+)\s+(=|!=|LIKE|IN)$/i', $field, $matches)) {
  217. $field = $matches[1];
  218. $type = strtoupper($matches[2]);
  219. } elseif (preg_match('/^(\w+\.?\w+)\s+(=|!=|LIKE|IN)\s+(.*)$/i', $value, $matches)) {
  220. $field = $matches[1];
  221. $type = strtoupper($matches[2]);
  222. $value = $matches[3];
  223. } else {
  224. $type = '=';
  225. }
  226. if (strpos($field, '.') !== false) {
  227. list($alias, $field) = explode('.', $field, 2);
  228. if ($alias != $model->alias) {
  229. continue;
  230. }
  231. }
  232. switch ($type) {
  233. case '=':
  234. $return = (isset($record[$field]) && $record[$field] == $value);
  235. break;
  236. case '!=':
  237. $return = (!isset($record[$field]) || $record[$field] != $value);
  238. break;
  239. case 'LIKE':
  240. $value = preg_replace(array('#(^|[^\\\\])_#', '#(^|[^\\\\])%#'), array('$1.', '$1.*'), $value);
  241. $return = (isset($record[$field]) && preg_match('#^' . $value . '$#i', $record[$field]));
  242. break;
  243. case 'IN':
  244. $items = array();
  245. if (is_array($value)) {
  246. $items = $value;
  247. } elseif (preg_match('/^\(\w+(,\s*\w+)*\)$/', $value)) {
  248. $items = explode(',', trim($value, '()'));
  249. $items = array_map('trim', $items);
  250. }
  251. $return = (isset($record[$field]) && in_array($record[$field], (array)$items));
  252. break;
  253. }
  254. }
  255. if ($return === $or) {
  256. return $or;
  257. }
  258. }
  259. return !$or;
  260. }
  261. /**
  262. * Returns an calculation
  263. *
  264. * @param model $model
  265. * @param string $type Lowercase name type, i.e. 'count' or 'max'
  266. * @param array $params Function parameters (any values must be quoted manually)
  267. * @return string Calculation method
  268. */
  269. public function calculate(&$model, $type, $params = array()) {
  270. return 'COUNT';
  271. }
  272. /**
  273. * Queries associations. Used to fetch results on recursive models.
  274. *
  275. * @param Model $model Primary Model object
  276. * @param Model $linkModel Linked model that
  277. * @param string $type Association type, one of the model association types ie. hasMany
  278. * @param unknown_type $association
  279. * @param unknown_type $assocData
  280. * @param array $queryData
  281. * @param boolean $external Whether or not the association query is on an external datasource.
  282. * @param array $resultSet Existing results
  283. * @param integer $recursive Number of levels of association
  284. * @param array $stack
  285. */
  286. public function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
  287. $assocData = array_merge(array('conditions' => null, 'fields' => null, 'order' => null), $assocData);
  288. if (isset($queryData['conditions'])) {
  289. $assocData['conditions'] = array_merge((array)$queryData['conditions'], (array)$assocData['conditions']);
  290. }
  291. if (isset($queryData['fields'])) {
  292. $assocData['fields'] = array_merge((array)$queryData['fields'], (array)$assocData['fields']);
  293. }
  294. foreach ($resultSet as $id => $result) {
  295. if (!array_key_exists($model->alias, $result)) {
  296. continue;
  297. }
  298. if ($type === 'belongsTo' && array_key_exists($assocData['foreignKey'], $result[$model->alias])) {
  299. $find = $model->{$association}->find('first', array(
  300. 'conditions' => array_merge((array)$assocData['conditions'], array($model->{$association}->primaryKey => $result[$model->alias][$assocData['foreignKey']])),
  301. 'fields' => $assocData['fields'],
  302. 'order' => $assocData['order'],
  303. 'recursive' => $recursive
  304. ));
  305. } elseif (in_array($type, array('hasOne', 'hasMany')) && array_key_exists($model->primaryKey, $result[$model->alias])) {
  306. if ($type === 'hasOne') {
  307. $find = $model->{$association}->find('first', array(
  308. 'conditions' => array_merge((array)$assocData['conditions'], array($association . '.' . $assocData['foreignKey'] => $result[$model->alias][$model->primaryKey])),
  309. 'fields' => $assocData['fields'],
  310. 'order' => $assocData['order'],
  311. 'recursive' => $recursive
  312. ));
  313. } else {
  314. $find = $model->{$association}->find('all', array(
  315. 'conditions' => array_merge((array)$assocData['conditions'], array($association . '.' . $assocData['foreignKey'] => $result[$model->alias][$model->primaryKey])),
  316. 'fields' => $assocData['fields'],
  317. 'order' => $assocData['order'],
  318. 'recursive' => $recursive
  319. ));
  320. $find = array(
  321. $association => Set::extract('{n}.' . $association, $find)
  322. );
  323. }
  324. } elseif ($type === 'hasAndBelongsToMany' && array_key_exists($model->primaryKey, $result[$model->alias])) {
  325. $hABTMModel = ClassRegistry::init($assocData['with']);
  326. $ids = $hABTMModel->find('all', array(
  327. 'fields' => array(
  328. $assocData['with'] . '.' . $assocData['associationForeignKey']
  329. ),
  330. 'conditions' => array(
  331. $assocData['with'] . '.' . $assocData['foreignKey'] => $result[$model->alias][$model->primaryKey]
  332. )
  333. ));
  334. $ids = Set::extract('{n}.' . $assocData['with'] . '.' . $assocData['associationForeignKey'], $ids);
  335. $find = $model->{$association}->find('all', array(
  336. 'conditions' => array_merge((array)$assocData['conditions'], array($association . '.' . $linkModel->primaryKey => $ids)),
  337. 'fields' => $assocData['fields'],
  338. 'order' => $assocData['order'],
  339. 'recursive' => $recursive
  340. ));
  341. $find = array(
  342. $association => Set::extract('{n}.' . $association, $find)
  343. );
  344. }
  345. if (empty($find)) {
  346. $find = array($association => array());
  347. }
  348. $resultSet[$id] = array_merge($find, $resultSet[$id]);
  349. }
  350. }
  351. /**
  352. * Get the query log as an array.
  353. *
  354. * @param boolean $sorted Get the queries sorted by time taken, defaults to false.
  355. * @param boolean $clear Clear after return logs
  356. * @return array Array of queries run as an array
  357. */
  358. public function getLog($sorted = false, $clear = true) {
  359. if ($sorted) {
  360. $log = sortByKey($this->_requestsLog, 'took', 'desc', SORT_NUMERIC);
  361. } else {
  362. $log = $this->_requestsLog;
  363. }
  364. if ($clear) {
  365. $this->_requestsLog = array();
  366. }
  367. return array('log' => $log, 'count' => count($log), 'time' => array_sum(Set::extract('{n}.took', $log)));
  368. }
  369. /**
  370. * Generate a log registry
  371. *
  372. * @param object $model
  373. * @param array $queryData
  374. * @param float $took
  375. * @param integer $numRows
  376. * @return void
  377. */
  378. public function _registerLog(&$model, &$queryData, $took, $numRows) {
  379. if (!Configure::read()) {
  380. return;
  381. }
  382. $this->_requestsLog[] = array(
  383. 'query' => $this->_pseudoSelect($model, $queryData),
  384. 'error' => '',
  385. 'affected' => 0,
  386. 'numRows' => $numRows,
  387. 'took' => round($took, 3)
  388. );
  389. }
  390. /**
  391. * Generate a pseudo select to log
  392. *
  393. * @param object $model Model
  394. * @param array $queryData Query data sended by find
  395. * @return string Pseudo query
  396. */
  397. protected function _pseudoSelect(&$model, &$queryData) {
  398. $out = '(symbolic) SELECT ';
  399. if (empty($queryData['fields'])) {
  400. $out .= '*';
  401. } elseif ($queryData['fields']) {
  402. $out .= 'COUNT(*)';
  403. } else {
  404. $out .= implode(', ', $queryData['fields']);
  405. }
  406. $out .= ' FROM ' . $model->alias;
  407. if (!empty($queryData['conditions'])) {
  408. $out .= ' WHERE';
  409. foreach ($queryData['conditions'] as $id => $condition) {
  410. if (empty($condition)) {
  411. continue;
  412. }
  413. if (is_array($condition)) {
  414. $condition = '(' . implode(', ', $condition) . ')';
  415. if (strpos($id, ' ') === false) {
  416. $id .= ' IN';
  417. }
  418. }
  419. if (is_string($id)) {
  420. if (strpos($id, ' ') !== false) {
  421. $condition = $id . ' ' . $condition;
  422. } else {
  423. $condition = $id . ' = ' . $condition;
  424. }
  425. }
  426. if (preg_match('/^(\w+\.)?\w+ /', $condition, $matches)) {
  427. if (!empty($matches[1]) && substr($matches[1], 0, -1) !== $model->alias) {
  428. continue;
  429. }
  430. }
  431. $out .= ' (' . $condition . ') &&';
  432. }
  433. $out = substr($out, 0, -3);
  434. }
  435. if (!empty($queryData['order'][0])) {
  436. $out .= ' ORDER BY ' . implode(', ', $queryData['order']);
  437. }
  438. if (!empty($queryData['limit'])) {
  439. $out .= ' LIMIT ' . (($queryData['page'] - 1) * $queryData['limit']) . ', ' . $queryData['limit'];
  440. }
  441. return $out;
  442. }
  443. }