hydrator.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <?php namespace System\DB\Eloquent;
  2. class Hydrator {
  3. /**
  4. * Load the array of hydrated models.
  5. *
  6. * @param object $eloquent
  7. * @return array
  8. */
  9. public static function hydrate($eloquent)
  10. {
  11. // -----------------------------------------------------
  12. // Load the base models.
  13. // -----------------------------------------------------
  14. $results = static::base(get_class($eloquent), $eloquent->query->get());
  15. // -----------------------------------------------------
  16. // Load all of the eager relationships.
  17. // -----------------------------------------------------
  18. if (count($results) > 0)
  19. {
  20. foreach ($eloquent->includes as $include)
  21. {
  22. if ( ! method_exists($eloquent, $include))
  23. {
  24. throw new \Exception("Attempting to eager load [$include], but the relationship is not defined.");
  25. }
  26. static::eagerly($eloquent, $include, $results);
  27. }
  28. }
  29. return $results;
  30. }
  31. /**
  32. * Hydrate the base models for a query.
  33. *
  34. * @param string $class
  35. * @param array $results
  36. * @return array
  37. */
  38. private static function base($class, $results)
  39. {
  40. $models = array();
  41. foreach ($results as $result)
  42. {
  43. $model = new $class;
  44. // -----------------------------------------------------
  45. // Set the attributes and existence flag on the model.
  46. // -----------------------------------------------------
  47. $model->attributes = (array) $result;
  48. $model->exists = true;
  49. // -----------------------------------------------------
  50. // The results are keyed by the ID on the record.
  51. // -----------------------------------------------------
  52. $models[$model->id] = $model;
  53. }
  54. return $models;
  55. }
  56. /**
  57. * Eagerly load a relationship.
  58. *
  59. * @param object $eloquent
  60. * @param string $include
  61. * @param array $results
  62. * @return void
  63. */
  64. private static function eagerly($eloquent, $include, &$results)
  65. {
  66. // -----------------------------------------------------
  67. // Get the relationship Eloquent model.
  68. //
  69. // We spoof the "belongs_to" key to allow the query
  70. // to be fetched without any problems.
  71. // -----------------------------------------------------
  72. $eloquent->attributes[$spoof = $include.'_id'] = 0;
  73. $model = $eloquent->$include();
  74. unset($eloquent->attributes[$spoof]);
  75. // -----------------------------------------------------
  76. // Reset the WHERE clause and bindings on the query.
  77. // -----------------------------------------------------
  78. $model->query->where = 'WHERE 1 = 1';
  79. $model->query->bindings = array();
  80. // -----------------------------------------------------
  81. // Initialize the relationship on the parent models.
  82. // -----------------------------------------------------
  83. foreach ($results as &$result)
  84. {
  85. $result->ignore[$include] = (strpos($eloquent->relating, 'has_many') === 0) ? array() : null;
  86. }
  87. // -----------------------------------------------------
  88. // Eagerly load the relationship.
  89. // -----------------------------------------------------
  90. if ($eloquent->relating == 'has_one' or $eloquent->relating == 'has_many')
  91. {
  92. static::eagerly_load_one_or_many($eloquent->relating_key, $eloquent->relating, $include, $model, $results);
  93. }
  94. elseif ($eloquent->relating == 'belongs_to')
  95. {
  96. static::eagerly_load_belonging($eloquent->relating_key, $include, $model, $results);
  97. }
  98. else
  99. {
  100. static::eagerly_load_many_to_many($eloquent->relating_key, $eloquent->relating_table, strtolower(get_class($eloquent)).'_id', $include, $model, $results);
  101. }
  102. }
  103. /**
  104. * Eagerly load a 1:1 or 1:* relationship.
  105. *
  106. * @param string $relating_key
  107. * @param string $relating
  108. * @param string $include
  109. * @param object $model
  110. * @param array $results
  111. * @return void
  112. */
  113. private static function eagerly_load_one_or_many($relating_key, $relating, $include, $model, &$results)
  114. {
  115. // -----------------------------------------------------
  116. // Get the related models.
  117. // -----------------------------------------------------
  118. $inclusions = $model->where_in($relating_key, array_keys($results))->get();
  119. // -----------------------------------------------------
  120. // Match the child models with their parent.
  121. // -----------------------------------------------------
  122. foreach ($inclusions as $key => $inclusion)
  123. {
  124. if ($relating == 'has_one')
  125. {
  126. $results[$inclusion->$relating_key]->ignore[$include] = $inclusion;
  127. }
  128. else
  129. {
  130. $results[$inclusion->$relating_key]->ignore[$include][$inclusion->id] = $inclusion;
  131. }
  132. }
  133. }
  134. /**
  135. * Eagerly load a 1:1 belonging relationship.
  136. *
  137. * @param string $relating_key
  138. * @param string $include
  139. * @param object $model
  140. * @param array $results
  141. * @return void
  142. */
  143. private static function eagerly_load_belonging($relating_key, $include, $model, &$results)
  144. {
  145. // -----------------------------------------------------
  146. // Gather the keys from the parent models.
  147. // -----------------------------------------------------
  148. $keys = array();
  149. foreach ($results as &$result)
  150. {
  151. $keys[] = $result->$relating_key;
  152. }
  153. // -----------------------------------------------------
  154. // Get the related models.
  155. // -----------------------------------------------------
  156. $inclusions = $model->where_in('id', array_unique($keys))->get();
  157. // -----------------------------------------------------
  158. // Match the child models with their parent.
  159. // -----------------------------------------------------
  160. foreach ($results as &$result)
  161. {
  162. $result->ignore[$include] = $inclusions[$result->$relating_key];
  163. }
  164. }
  165. /**
  166. * Eagerly load a many-to-many relationship.
  167. *
  168. * @param string $relating_key
  169. * @param string $relating_table
  170. * @param string $foreign_key
  171. * @param string $include
  172. * @param object $model
  173. * @param array $results
  174. * @return void
  175. */
  176. private static function eagerly_load_many_to_many($relating_key, $relating_table, $foreign_key, $include, $model, &$results)
  177. {
  178. // -----------------------------------------------------
  179. // Reset the SELECT clause.
  180. // -----------------------------------------------------
  181. $model->query->select = null;
  182. // -----------------------------------------------------
  183. // Retrieve the raw results as stdClasses.
  184. //
  185. // We also add the foreign key to the select which will allow us
  186. // to match the models back to their parents.
  187. // -----------------------------------------------------
  188. $inclusions = $model->query
  189. ->where_in($relating_key, array_keys($results))
  190. ->get(\System\DB\Eloquent::table(get_class($model)).'.*', $relating_table.'.'.$foreign_key);
  191. // -----------------------------------------------------
  192. // Get the class name of the related model.
  193. // -----------------------------------------------------
  194. $class = get_class($model);
  195. // -----------------------------------------------------
  196. // Create the related models.
  197. // -----------------------------------------------------
  198. foreach ($inclusions as $inclusion)
  199. {
  200. $related = new $class;
  201. // -----------------------------------------------------
  202. // Set the attributes and existence flag on the model.
  203. // -----------------------------------------------------
  204. $related->exists = true;
  205. $related->attributes = (array) $inclusion;
  206. // -----------------------------------------------------
  207. // Remove the foreign key from the attributes since it
  208. // was only added to the query to help us match the models.
  209. // -----------------------------------------------------
  210. unset($related->attributes[$foreign_key]);
  211. // -----------------------------------------------------
  212. // Add the related model to the parent model's array.
  213. // -----------------------------------------------------
  214. $results[$inclusion->$foreign_key]->ignore[$include][$inclusion->id] = $related;
  215. }
  216. }
  217. }