RecordSet.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, 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\collection;
  9. use lithium\util\Set;
  10. class RecordSet extends \lithium\data\Collection {
  11. /**
  12. * A 2D array of column-mapping information, where the top-level key is the fully-namespaced
  13. * model name, and the sub-arrays are column names.
  14. *
  15. * @var array
  16. */
  17. protected $_columns = array();
  18. /**
  19. * A recursive array of relation dependencies where key are relations
  20. * and value are arrays with their relation dependencies
  21. *
  22. * @var array
  23. */
  24. protected $_dependencies = array();
  25. /**
  26. * Precompute index of the main model primary key(s) which allow to find
  27. * values directly is result data without the column name matching process
  28. *
  29. * @var array
  30. */
  31. protected $_keyIndex = array();
  32. /**
  33. * Initializes the record set and uses the database connection to get the column list contained
  34. * in the query that created this object.
  35. *
  36. * @see lithium\data\collection\RecordSet::$_columns
  37. * @return void
  38. * @todo The part that uses _handle->schema() should be rewritten so that the column list
  39. * is coming from the query object.
  40. */
  41. protected function _init() {
  42. parent::_init();
  43. if ($this->_result) {
  44. $this->_columns = $this->_columnMap();
  45. if ($this->_query) {
  46. $columns = array_filter(array_keys($this->_columns));
  47. $this->_dependencies = Set::expand(Set::normalize($columns));
  48. $this->_keyIndex = $this->_keyIndex('');
  49. }
  50. }
  51. }
  52. /**
  53. * Extract the next item from the result ressource and wraps it into a `Record` object.
  54. *
  55. * @return mixed Returns the next `Record` if exists. Returns `null` otherwise
  56. */
  57. protected function _populate() {
  58. if ($this->closed() || !$this->_result->valid()) {
  59. return;
  60. }
  61. $data = $this->_result->current();
  62. if ($this->_query) {
  63. $data = $this->_mapRecord($data);
  64. }
  65. $result = $this->_set($data, null, array('exists' => true));
  66. $this->_result->next();
  67. return $result;
  68. }
  69. protected function _set($data = null, $offset = null, $options = array()) {
  70. if ($model = $this->_model) {
  71. $data = !is_object($data) ? $model::connection()->item($model, $data, $options) : $data;
  72. $key = $model::key($data);
  73. } else {
  74. $key = $offset;
  75. }
  76. if (is_array($key)) {
  77. $key = count($key) === 1 ? current($key) : null;
  78. }
  79. return $key !== null ? $this->_data[$key] = $data : $this->_data[] = $data;
  80. }
  81. /**
  82. * Convert a PDO `Result` array to a nested `Record` object
  83. *
  84. * @param array $data 2 dimensional PDO `Result` array
  85. * @return object Returns a `Record` object
  86. */
  87. protected function _mapRecord($data) {
  88. $primary = $this->_model;
  89. $conn = $primary::connection();
  90. $main = $record = array();
  91. $i = 0;
  92. foreach ($this->_keyIndex as $key => $value) {
  93. $main[$key] = $data[$key];
  94. }
  95. do {
  96. $offset = 0;
  97. if ($i != 0) {
  98. $keys = array();
  99. foreach ($this->_keyIndex as $key => $value) {
  100. $keys[$key] = $data[$key];
  101. }
  102. if ($main != $keys) {
  103. $this->_result->prev();
  104. break;
  105. }
  106. }
  107. foreach ($this->_columns as $name => $fields) {
  108. $fieldCount = count($fields);
  109. $record[$i][$name] = array_combine(
  110. $fields, array_slice($data, $offset, $fieldCount)
  111. );
  112. $offset += $fieldCount;
  113. }
  114. $i++;
  115. } while ($main && $data = $this->_result->next());
  116. $relMap = $this->_query->relationships();
  117. return $this->_hydrateRecord(
  118. $this->_dependencies, $primary, $record, 0, $i, '', $relMap, $conn
  119. );
  120. }
  121. /**
  122. * Hydrate a 2 dimensional PDO `Result` array
  123. *
  124. * @param array $relations The cascading with relation
  125. * @param string $primary Model classname
  126. * @param array $record Loaded Records
  127. * @param integer $min
  128. * @param integer $max
  129. * @param string $name Alias name
  130. * @param array $relMap The query relationships array
  131. * @param object $conn The connection object
  132. * @return object Returns a `Record` object
  133. */
  134. protected function _hydrateRecord($relations, $primary, $record, $min, $max, $name, &$relMap, $conn) {
  135. $options = array('exists' => true);
  136. $count = count($record);
  137. if (!empty($relations)) {
  138. foreach ($relations as $relation => $subrelations) {
  139. $relName = $name ? $name . '.' . $relation : $relation;
  140. $field = $relMap[$relName]['fieldName'];
  141. $relModel = $relMap[$relName]['model'];
  142. if ($relMap[$relName]['type'] === 'hasMany') {
  143. $rel = array();
  144. $main = $relModel::key($record[$min][$relName]);
  145. $i = $min;
  146. $j = $i + 1;
  147. while ($j < $max) {
  148. $keys = $relModel::key($record[$j][$relName]);
  149. if ($main != $keys) {
  150. $rel[] = $this->_hydrateRecord(
  151. $subrelations, $relModel, $record, $i, $j, $relName, $relMap, $conn
  152. );
  153. $main = $keys;
  154. $i = $j;
  155. }
  156. $j++;
  157. }
  158. if (array_filter($record[$i][$relName])) {
  159. $rel[] = $this->_hydrateRecord(
  160. $subrelations, $relModel, $record, $i, $j, $relName, $relMap, $conn
  161. );
  162. }
  163. $opts = array('class' => 'set') + $options;
  164. $record[$min][$name][$field] = $conn->item($primary, $rel, $opts);
  165. } else {
  166. $record[$min][$name][$field] = $this->_hydrateRecord(
  167. $subrelations, $relModel, $record, $min, $max, $relName, $relMap, $conn
  168. );
  169. }
  170. }
  171. }
  172. return $conn->item(
  173. $primary, isset($record[$min][$name]) ? $record[$min][$name] : array(), $options
  174. );
  175. }
  176. protected function _columnMap() {
  177. if ($this->_query && $map = $this->_query->map()) {
  178. return $map;
  179. }
  180. if (!($model = $this->_model)) {
  181. return array();
  182. }
  183. if (!is_object($this->_query) || !$this->_query->join()) {
  184. $map = $model::connection()->schema($this->_query);
  185. return $map;
  186. }
  187. $model = $this->_model;
  188. $map = $model::connection()->schema($this->_query);
  189. return $map;
  190. }
  191. /**
  192. * Result object contain SQL result which are generally a 2 dimentionnal array
  193. * where line are records and columns are fields.
  194. * This function extract from the Result object the index of primary key(s).
  195. *
  196. * @param string $name The name of the relation to retreive the index of
  197. * corresponding primary key(s).
  198. * @return array An array where key are index and value are primary key fieldname
  199. */
  200. protected function _keyIndex($name) {
  201. if (!($model = $this->_model) || !isset($this->_columns[$name])) {
  202. return array();
  203. }
  204. $index = 0;
  205. foreach ($this->_columns as $key => $value) {
  206. if ($key === $name) {
  207. $flip = array_flip($value);
  208. $keys = $model::meta('key');
  209. if (!is_array($keys)) {
  210. $keys = array($keys);
  211. }
  212. $keys = array_flip($keys);
  213. $keys = array_intersect_key($flip, $keys);
  214. foreach ($keys as &$value) {
  215. $value += $index;
  216. }
  217. return array_flip($keys);
  218. }
  219. $index += count($value);
  220. }
  221. }
  222. }
  223. ?>