ResultSet.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <?php
  2. /**
  3. * PHP Version 5.4
  4. *
  5. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice.
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://cakephp.org CakePHP(tm) Project
  14. * @since CakePHP(tm) v 3.0.0
  15. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  16. */
  17. namespace Cake\ORM;
  18. use Cake\Collection\CollectionTrait;
  19. use Cake\Database\Exception;
  20. use Cake\Database\Type;
  21. use \Countable;
  22. use \Iterator;
  23. use \JsonSerializable;
  24. use \Serializable;
  25. /**
  26. * Represents the results obtained after executing a query for an specific table
  27. * This object is responsible for correctly nesting result keys reported from
  28. * the query, casting each field to the correct type and executing the extra
  29. * queries required for eager loading external associations.
  30. *
  31. */
  32. class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
  33. use CollectionTrait;
  34. /**
  35. * Original query from where results were generated
  36. *
  37. * @var Query
  38. */
  39. protected $_query;
  40. /**
  41. * Database statement holding the results
  42. *
  43. * @var \Cake\Database\StatementInterface
  44. */
  45. protected $_statement;
  46. /**
  47. * Points to the next record number that should be fetched
  48. *
  49. * @var integer
  50. */
  51. protected $_index = 0;
  52. /**
  53. * Last record fetched from the statement
  54. *
  55. * @var array
  56. */
  57. protected $_current;
  58. /**
  59. * Default table instance
  60. *
  61. * @var \Cake\ORM\Table
  62. */
  63. protected $_defaultTable;
  64. /**
  65. * List of associations that should be eager loaded
  66. *
  67. * @var array
  68. */
  69. protected $_associationMap = [];
  70. /**
  71. * Map of fields that are fetched from the statement with
  72. * their type and the table they belong to
  73. *
  74. * @var string
  75. */
  76. protected $_map;
  77. /**
  78. * Results that have been fetched or hydrated into the results.
  79. *
  80. * @var array
  81. */
  82. protected $_results = [];
  83. /**
  84. * Whether to hydrate results into objects or not
  85. *
  86. * @var boolean
  87. */
  88. protected $_hydrate = true;
  89. /**
  90. * The fully namespaced name of the class to use for hydrating results
  91. *
  92. * @var string
  93. */
  94. protected $_entityClass;
  95. /**
  96. * Whether or not to buffer results fetched from the statement
  97. *
  98. * @var boolean
  99. */
  100. protected $_useBuffering = true;
  101. /**
  102. * Constructor
  103. *
  104. * @param Query from where results come
  105. * @param \Cake\Database\StatementInterface $statement
  106. * @return void
  107. */
  108. public function __construct($query, $statement) {
  109. $this->_query = $query;
  110. $this->_statement = $statement;
  111. $this->_defaultTable = $this->_query->repository();
  112. $this->_calculateAssociationMap();
  113. $this->_hydrate = $this->_query->hydrate();
  114. $this->_entityClass = $query->repository()->entityClass();
  115. $this->_useBuffering = $query->bufferResults();
  116. }
  117. /**
  118. * Returns the current record in the result iterator
  119. *
  120. * Part of Iterator interface.
  121. *
  122. * @return array|object
  123. */
  124. public function current() {
  125. return $this->_current;
  126. }
  127. /**
  128. * Returns the key of the current record in the iterator
  129. *
  130. * Part of Iterator interface.
  131. *
  132. * @return integer
  133. */
  134. public function key() {
  135. return $this->_index;
  136. }
  137. /**
  138. * Advances the iterator pointer to the next record
  139. *
  140. * Part of Iterator interface.
  141. *
  142. * @return void
  143. */
  144. public function next() {
  145. $this->_index++;
  146. }
  147. /**
  148. * Rewind a ResultSet.
  149. *
  150. * Part of Iterator interface.
  151. *
  152. * @throws Cake\Database\Exception
  153. * @return void
  154. */
  155. public function rewind() {
  156. if ($this->_index == 0) {
  157. return;
  158. }
  159. if (!$this->_useBuffering) {
  160. $msg = 'You cannot rewind an un-buffered ResultSet. Use Query::bufferResults() to get a buffered ResultSet.';
  161. throw new Exception($msg);
  162. }
  163. $this->_index = 0;
  164. }
  165. /**
  166. * Whether there are more results to be fetched from the iterator
  167. *
  168. * Part of Iterator interface.
  169. *
  170. * @return boolean
  171. */
  172. public function valid() {
  173. if (isset($this->_results[$this->_index])) {
  174. $this->_current = $this->_results[$this->_index];
  175. return true;
  176. }
  177. $this->_current = $this->_fetchResult();
  178. $valid = $this->_current !== false;
  179. if (!$valid && $this->_statement) {
  180. $this->_statement->closeCursor();
  181. }
  182. if ($valid) {
  183. $this->_bufferResult($this->_current);
  184. }
  185. return $valid;
  186. }
  187. /**
  188. * Serialize a resultset.
  189. *
  190. * Part of Serializable interface.
  191. *
  192. * @return string Serialized object
  193. */
  194. public function serialize() {
  195. while ($this->valid()) {
  196. $this->next();
  197. }
  198. return serialize($this->_results);
  199. }
  200. /**
  201. * Unserialize a resultset.
  202. *
  203. * Part of Serializable interface.
  204. *
  205. * @param string Serialized object
  206. */
  207. public function unserialize($serialized) {
  208. $this->_results = unserialize($serialized);
  209. }
  210. /**
  211. * Returns the first result in this set and blocks the set so that no other
  212. * results can be fetched.
  213. *
  214. * When using serialized results, the index will be incremented past the
  215. * end of the results simulating the behavior when the result set is backed
  216. * by a statement.
  217. *
  218. * @return array|object
  219. */
  220. public function first() {
  221. if (isset($this->_results[0])) {
  222. return $this->_results[0];
  223. }
  224. if ($this->valid()) {
  225. if ($this->_statement) {
  226. $this->_statement->closeCursor();
  227. }
  228. if (!$this->_statement && $this->_results) {
  229. $this->_index = count($this->_results);
  230. }
  231. return $this->_current;
  232. }
  233. return null;
  234. }
  235. /**
  236. * Gives the number of rows in the result set.
  237. *
  238. * Part of the Countable interface.
  239. *
  240. * @return integer
  241. */
  242. public function count() {
  243. if ($this->_statement) {
  244. return $this->_statement->rowCount();
  245. }
  246. return count($this->_results);
  247. }
  248. /**
  249. * Calculates the list of associations that should get eager loaded
  250. * when fetching each record
  251. *
  252. * @return void
  253. */
  254. protected function _calculateAssociationMap() {
  255. $contain = $this->_query->eagerLoader()->normalized($this->_defaultTable);
  256. if (!$contain) {
  257. return;
  258. }
  259. $map = [];
  260. $visitor = function($level) use (&$visitor, &$map) {
  261. foreach ($level as $assoc => $meta) {
  262. $map[$assoc] = [
  263. 'instance' => $meta['instance'],
  264. 'canBeJoined' => $meta['canBeJoined'],
  265. 'entityClass' => $meta['instance']->target()->entityClass()
  266. ];
  267. if (!empty($meta['associations'])) {
  268. $visitor($meta['associations']);
  269. }
  270. }
  271. };
  272. $visitor($contain, []);
  273. $this->_associationMap = $map;
  274. }
  275. /**
  276. * Helper function to fetch the next result from the statement or
  277. * seeded results.
  278. *
  279. * @return mixed
  280. */
  281. protected function _fetchResult() {
  282. if (!$this->_statement) {
  283. return false;
  284. }
  285. $row = $this->_statement->fetch('assoc');
  286. if ($row === false) {
  287. return $row;
  288. }
  289. return $this->_groupResult($row);
  290. }
  291. /**
  292. * Correctly nest results keys including those coming from associations
  293. *
  294. * @param mixed|boolean $row array containing columns and values or false if there is no results
  295. * @return array
  296. */
  297. protected function _groupResult($row) {
  298. $defaultAlias = $this->_defaultTable->alias();
  299. $results = [];
  300. foreach ($row as $key => $value) {
  301. $table = $defaultAlias;
  302. $field = $key;
  303. if (empty($this->_map[$key])) {
  304. $parts = explode('__', $key);
  305. if (count($parts) > 1) {
  306. $this->_map[$key] = $parts;
  307. }
  308. }
  309. if (!empty($this->_map[$key])) {
  310. list($table, $field) = $this->_map[$key];
  311. }
  312. $results[$table][$field] = $value;
  313. }
  314. $results[$defaultAlias] = $this->_castValues(
  315. $this->_defaultTable,
  316. $results[$defaultAlias]
  317. );
  318. $options = [
  319. 'useSetters' => false,
  320. 'markClean' => true,
  321. 'markNew' => false,
  322. 'guard' => false
  323. ];
  324. foreach (array_reverse($this->_associationMap) as $alias => $assoc) {
  325. if (!isset($results[$alias])) {
  326. continue;
  327. }
  328. $instance = $assoc['instance'];
  329. $results[$alias] = $this->_castValues($instance->target(), $results[$alias]);
  330. if ($this->_hydrate && $assoc['canBeJoined']) {
  331. $entity = new $assoc['entityClass']($results[$alias], $options);
  332. $entity->clean();
  333. $results[$alias] = $entity;
  334. }
  335. $results = $instance->transformRow($results);
  336. }
  337. $results = $results[$defaultAlias];
  338. if ($this->_hydrate && !($results instanceof Entity)) {
  339. $results = new $this->_entityClass($results, $options);
  340. }
  341. return $results;
  342. }
  343. /**
  344. * Casts all values from a row brought from a table to the correct
  345. * PHP type.
  346. *
  347. * @param Table $table
  348. * @param array $values
  349. * @return array
  350. */
  351. protected function _castValues($table, $values) {
  352. $alias = $table->alias();
  353. $driver = $this->_query->connection()->driver();
  354. if (empty($this->types[$alias])) {
  355. $schema = $table->schema();
  356. foreach ($schema->columns() as $col) {
  357. $this->types[$alias][$col] = Type::build($schema->columnType($col));
  358. }
  359. }
  360. foreach ($values as $field => $value) {
  361. if (!isset($this->types[$alias][$field])) {
  362. continue;
  363. }
  364. $values[$field] = $this->types[$alias][$field]->toPHP($value, $driver);
  365. }
  366. return $values;
  367. }
  368. /**
  369. * Conditionally buffer the passed result
  370. *
  371. * @param array $result the result fetch from the database
  372. * @return void
  373. */
  374. protected function _bufferResult($result) {
  375. if ($this->_useBuffering) {
  376. $this->_results[] = $result;
  377. }
  378. }
  379. }