From e3ffb3f1713a813473279c374c45ae6557896ffa Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 17 Jun 2011 14:50:07 -0700 Subject: [PATCH] Refactoring Eloquent\Hydrator. --- system/db/eloquent/hydrator.php | 164 +++++++++++++++++--------------- 1 file changed, 87 insertions(+), 77 deletions(-) diff --git a/system/db/eloquent/hydrator.php b/system/db/eloquent/hydrator.php index 4689c955..beb5c44c 100644 --- a/system/db/eloquent/hydrator.php +++ b/system/db/eloquent/hydrator.php @@ -1,5 +1,7 @@ query->get()); @@ -27,7 +29,7 @@ public static function hydrate($eloquent) throw new \Exception("Attempting to eager load [$include], but the relationship is not defined."); } - static::eagerly($eloquent, $include, $results); + static::eagerly($eloquent, $results, $include); } } @@ -47,16 +49,14 @@ private static function base($class, $results) foreach ($results as $result) { - $model = new $class; + $model = new $class((array) $result); - // ----------------------------------------------------- - // Set the attributes and existence flag on the model. - // ----------------------------------------------------- - $model->attributes = (array) $result; $model->exists = true; // ----------------------------------------------------- - // The results are keyed by the ID on the record. + // The results are keyed by the ID on the record. This + // will allow us to conveniently match them to child + // models during eager loading. // ----------------------------------------------------- $models[$model->id] = $model; } @@ -68,140 +68,157 @@ private static function base($class, $results) * Eagerly load a relationship. * * @param object $eloquent + * @param array $parents * @param string $include - * @param array $results * @return void */ - private static function eagerly($eloquent, $include, &$results) + private static function eagerly($eloquent, &$parents, $include) { // ----------------------------------------------------- // Get the relationship Eloquent model. // - // We spoof the "belongs_to" key to allow the query - // to be fetched without any problems. + // We temporarily spoof the "belongs_to" key to allow + // the query to be fetched without any problems. // ----------------------------------------------------- $eloquent->attributes[$spoof = $include.'_id'] = 0; - $model = $eloquent->$include(); + $relationship = $eloquent->$include(); unset($eloquent->attributes[$spoof]); // ----------------------------------------------------- // Reset the WHERE clause and bindings on the query. + // We'll add our own WHERE clause soon. // ----------------------------------------------------- - $model->query->where = 'WHERE 1 = 1'; - $model->query->bindings = array(); + $relationship->query->where = 'WHERE 1 = 1'; + $relationship->query->bindings = array(); // ----------------------------------------------------- - // Initialize the relationship on the parent models. + // Initialize the relationship attribute on the parents. + // As expected, "many" relationships are initialized to + // an array and "one" relationships to null. // ----------------------------------------------------- - foreach ($results as &$result) + foreach ($parents as &$parent) { - $result->ignore[$include] = (strpos($eloquent->relating, 'has_many') === 0) ? array() : null; + $parent->ignore[$include] = (strpos($eloquent->relating, 'has_many') === 0) ? array() : null; } // ----------------------------------------------------- - // Eagerly load the relationship. + // Eagerly load the relationships. Phew, almost there! // ----------------------------------------------------- - if ($eloquent->relating == 'has_one' or $eloquent->relating == 'has_many') + if ($eloquent->relating == 'has_one') { - static::eagerly_load_one_or_many($eloquent->relating_key, $eloquent->relating, $include, $model, $results); + static::eagerly_load_one($relationship, $parents, $eloquent->relating_key, $include); + } + elseif ($eloquent->relating == 'has_many') + { + static::eagerly_load_many($relationship, $parents, $eloquent->relating_key, $include); } elseif ($eloquent->relating == 'belongs_to') { - static::eagerly_load_belonging($eloquent->relating_key, $include, $model, $results); + static::eagerly_load_belonging($relationship, $parents, $eloquent->relating_key, $include); } else { - static::eagerly_load_many_to_many($eloquent->relating_key, $eloquent->relating_table, strtolower(get_class($eloquent)).'_id', $include, $model, $results); + static::eagerly_load_many_to_many($relationship, $parents, $eloquent->relating_key, $eloquent->relating_table, $include); } } /** - * Eagerly load a 1:1 or 1:* relationship. + * Eagerly load a 1:1 relationship. * + * @param object $relationship + * @param array $parents * @param string $relating_key * @param string $relating * @param string $include - * @param object $model - * @param array $results * @return void */ - private static function eagerly_load_one_or_many($relating_key, $relating, $include, $model, &$results) + private static function eagerly_load_one($relationship, &$parents, $relating_key, $include) { // ----------------------------------------------------- - // Get the related models. + // Get the all of the related models by the parent IDs. + // + // Remember, the parent results are keyed by ID. So, we + // can simply pass the keys of the array into the query. + // + // After getting the models, we'll match by ID. // ----------------------------------------------------- - $inclusions = $model->where_in($relating_key, array_keys($results))->get(); - - // ----------------------------------------------------- - // Match the child models with their parent. - // ----------------------------------------------------- - foreach ($inclusions as $key => $inclusion) + foreach ($relationship->where_in($relating_key, array_keys($parents))->get() as $key => $child) { - if ($relating == 'has_one') - { - $results[$inclusion->$relating_key]->ignore[$include] = $inclusion; - } - else - { - $results[$inclusion->$relating_key]->ignore[$include][$inclusion->id] = $inclusion; - } + $parents[$child->$relating_key]->ignore[$include] = $child; + } + } + + /** + * Eagerly load a 1:* relationship. + * + * @param object $relationship + * @param array $parents + * @param string $relating_key + * @param string $relating + * @param string $include + * @return void + */ + private static function eagerly_load_many($relationship, &$parents, $relating_key, $include) + { + foreach ($relationship->where_in($relating_key, array_keys($parents))->get() as $key => $child) + { + $parents[$child->$relating_key]->ignore[$include][$child->id] = $child; } } /** * Eagerly load a 1:1 belonging relationship. * + * @param object $relationship + * @param array $parents * @param string $relating_key * @param string $include - * @param object $model - * @param array $results * @return void */ - private static function eagerly_load_belonging($relating_key, $include, $model, &$results) + private static function eagerly_load_belonging($relationship, &$parents, $relating_key, $include) { // ----------------------------------------------------- - // Gather the keys from the parent models. + // Gather the keys from the parent models. Since the + // foreign key is on the parent model for this type of + // relationship, we have to gather them individually. // ----------------------------------------------------- $keys = array(); - foreach ($results as &$result) + foreach ($parents as &$parent) { - $keys[] = $result->$relating_key; + $keys[] = $parent->$relating_key; } // ----------------------------------------------------- // Get the related models. // ----------------------------------------------------- - $inclusions = $model->where_in('id', array_unique($keys))->get(); + $children = $relationship->where_in('id', array_unique($keys))->get(); // ----------------------------------------------------- - // Match the child models with their parent. + // Match the child models with their parent by ID. // ----------------------------------------------------- - foreach ($results as &$result) + foreach ($parents as &$parent) { - $result->ignore[$include] = $inclusions[$result->$relating_key]; + $parent->ignore[$include] = $children[$parent->$relating_key]; } } /** * Eagerly load a many-to-many relationship. * + * @param object $relationship + * @param array $parents * @param string $relating_key * @param string $relating_table - * @param string $foreign_key * @param string $include - * @param object $model - * @param array $results + * * @return void */ - private static function eagerly_load_many_to_many($relating_key, $relating_table, $foreign_key, $include, $model, &$results) + private static function eagerly_load_many_to_many($relationship, &$parents, $relating_key, $relating_table, $include) { - // ----------------------------------------------------- - // Reset the SELECT clause. - // ----------------------------------------------------- - $model->query->select = null; + $relationship->query->select = null; // ----------------------------------------------------- // Retrieve the raw results as stdClasses. @@ -209,38 +226,31 @@ private static function eagerly_load_many_to_many($relating_key, $relating_table // We also add the foreign key to the select which will allow us // to match the models back to their parents. // ----------------------------------------------------- - $inclusions = $model->query - ->where_in($relating_key, array_keys($results)) - ->get(\System\DB\Eloquent::table(get_class($model)).'.*', $relating_table.'.'.$foreign_key); + $children = $relationship->query + ->where_in($relating_table.'.'.$relating_key, array_keys($parents)) + ->get(Eloquent::table(get_class($relationship)).'.*', $relating_table.'.'.$relating_key); - // ----------------------------------------------------- - // Get the class name of the related model. - // ----------------------------------------------------- - $class = get_class($model); + $class = get_class($relationship); // ----------------------------------------------------- // Create the related models. // ----------------------------------------------------- - foreach ($inclusions as $inclusion) + foreach ($children as $child) { - $related = new $class; + $related = new $class((array) $child); - // ----------------------------------------------------- - // Set the attributes and existence flag on the model. - // ----------------------------------------------------- $related->exists = true; - $related->attributes = (array) $inclusion; // ----------------------------------------------------- // Remove the foreign key from the attributes since it - // was only added to the query to help us match the models. + // was added to the query to help us match the models. // ----------------------------------------------------- - unset($related->attributes[$foreign_key]); + unset($related->attributes[$relating_key]); // ----------------------------------------------------- - // Add the related model to the parent model's array. + // Match the child model its parent by ID. // ----------------------------------------------------- - $results[$inclusion->$foreign_key]->ignore[$include][$inclusion->id] = $related; + $parents[$child->$relating_key]->ignore[$include][$child->id] = $related; } }