Commit e3ffb3f1 authored by Taylor Otwell's avatar Taylor Otwell

Refactoring Eloquent\Hydrator.

parent eaaac620
<?php namespace System\DB\Eloquent; <?php namespace System\DB\Eloquent;
use System\DB\Eloquent;
class Hydrator { class Hydrator {
/** /**
...@@ -11,7 +13,7 @@ class Hydrator { ...@@ -11,7 +13,7 @@ class Hydrator {
public static function hydrate($eloquent) public static function hydrate($eloquent)
{ {
// ----------------------------------------------------- // -----------------------------------------------------
// Load the base models. // Load the base / parent models from the query results.
// ----------------------------------------------------- // -----------------------------------------------------
$results = static::base(get_class($eloquent), $eloquent->query->get()); $results = static::base(get_class($eloquent), $eloquent->query->get());
...@@ -27,7 +29,7 @@ class Hydrator { ...@@ -27,7 +29,7 @@ class Hydrator {
throw new \Exception("Attempting to eager load [$include], but the relationship is not defined."); 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 @@ class Hydrator { ...@@ -47,16 +49,14 @@ class Hydrator {
foreach ($results as $result) 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; $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; $models[$model->id] = $model;
} }
...@@ -68,140 +68,157 @@ class Hydrator { ...@@ -68,140 +68,157 @@ class Hydrator {
* Eagerly load a relationship. * Eagerly load a relationship.
* *
* @param object $eloquent * @param object $eloquent
* @param array $parents
* @param string $include * @param string $include
* @param array $results
* @return void * @return void
*/ */
private static function eagerly($eloquent, $include, &$results) private static function eagerly($eloquent, &$parents, $include)
{ {
// ----------------------------------------------------- // -----------------------------------------------------
// Get the relationship Eloquent model. // Get the relationship Eloquent model.
// //
// We spoof the "belongs_to" key to allow the query // We temporarily spoof the "belongs_to" key to allow
// to be fetched without any problems. // the query to be fetched without any problems.
// ----------------------------------------------------- // -----------------------------------------------------
$eloquent->attributes[$spoof = $include.'_id'] = 0; $eloquent->attributes[$spoof = $include.'_id'] = 0;
$model = $eloquent->$include(); $relationship = $eloquent->$include();
unset($eloquent->attributes[$spoof]); unset($eloquent->attributes[$spoof]);
// ----------------------------------------------------- // -----------------------------------------------------
// Reset the WHERE clause and bindings on the query. // Reset the WHERE clause and bindings on the query.
// We'll add our own WHERE clause soon.
// ----------------------------------------------------- // -----------------------------------------------------
$model->query->where = 'WHERE 1 = 1'; $relationship->query->where = 'WHERE 1 = 1';
$model->query->bindings = array(); $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') 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 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_key
* @param string $relating * @param string $relating
* @param string $include * @param string $include
* @param object $model
* @param array $results
* @return void * @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.
// ----------------------------------------------------- //
$inclusions = $model->where_in($relating_key, array_keys($results))->get(); // Remember, the parent results are keyed by ID. So, we
// can simply pass the keys of the array into the query.
// ----------------------------------------------------- //
// Match the child models with their parent. // After getting the models, we'll match by ID.
// ----------------------------------------------------- // -----------------------------------------------------
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; $parents[$child->$relating_key]->ignore[$include] = $child;
} }
else
{
$results[$inclusion->$relating_key]->ignore[$include][$inclusion->id] = $inclusion;
} }
/**
* 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. * Eagerly load a 1:1 belonging relationship.
* *
* @param object $relationship
* @param array $parents
* @param string $relating_key * @param string $relating_key
* @param string $include * @param string $include
* @param object $model
* @param array $results
* @return void * @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(); $keys = array();
foreach ($results as &$result) foreach ($parents as &$parent)
{ {
$keys[] = $result->$relating_key; $keys[] = $parent->$relating_key;
} }
// ----------------------------------------------------- // -----------------------------------------------------
// Get the related models. // 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. * Eagerly load a many-to-many relationship.
* *
* @param object $relationship
* @param array $parents
* @param string $relating_key * @param string $relating_key
* @param string $relating_table * @param string $relating_table
* @param string $foreign_key
* @param string $include * @param string $include
* @param object $model *
* @param array $results
* @return void * @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)
{ {
// ----------------------------------------------------- $relationship->query->select = null;
// Reset the SELECT clause.
// -----------------------------------------------------
$model->query->select = null;
// ----------------------------------------------------- // -----------------------------------------------------
// Retrieve the raw results as stdClasses. // Retrieve the raw results as stdClasses.
...@@ -209,38 +226,31 @@ class Hydrator { ...@@ -209,38 +226,31 @@ class Hydrator {
// We also add the foreign key to the select which will allow us // We also add the foreign key to the select which will allow us
// to match the models back to their parents. // to match the models back to their parents.
// ----------------------------------------------------- // -----------------------------------------------------
$inclusions = $model->query $children = $relationship->query
->where_in($relating_key, array_keys($results)) ->where_in($relating_table.'.'.$relating_key, array_keys($parents))
->get(\System\DB\Eloquent::table(get_class($model)).'.*', $relating_table.'.'.$foreign_key); ->get(Eloquent::table(get_class($relationship)).'.*', $relating_table.'.'.$relating_key);
// ----------------------------------------------------- $class = get_class($relationship);
// Get the class name of the related model.
// -----------------------------------------------------
$class = get_class($model);
// ----------------------------------------------------- // -----------------------------------------------------
// Create the related models. // 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->exists = true;
$related->attributes = (array) $inclusion;
// ----------------------------------------------------- // -----------------------------------------------------
// Remove the foreign key from the attributes since it // 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;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment