hydrator.php 5.85 KB
Newer Older
1 2
<?php namespace System\DB\Eloquent;

3 4
use System\DB\Eloquent;

5
class Hydrator {
6 7 8 9 10 11 12

	/**
	 * Load the array of hydrated models.
	 *
	 * @param  object  $eloquent
	 * @return array
	 */
13
	public static function hydrate($eloquent)
14
	{
15
		// Load the base / parent models from the query results.
16 17 18 19 20 21 22 23 24 25 26 27
		$results = static::base(get_class($eloquent), $eloquent->query->get());

		// Load all of the eager relationships.
		if (count($results) > 0)
		{
			foreach ($eloquent->includes as $include)
			{
				if ( ! method_exists($eloquent, $include))
				{
					throw new \Exception("Attempting to eager load [$include], but the relationship is not defined.");
				}

28
				static::eagerly($eloquent, $results, $include);
29 30 31 32 33 34 35 36 37 38
			}
		}

		return $results;
	}

	/**
	 * Hydrate the base models for a query.
	 *
	 * @param  string  $class
39
	 * @param  array   $results
40 41
	 * @return array
	 */
42
	private static function base($class, $results)
43
	{
44
		$models = array();
45

46
		foreach ($results as $result)
47
		{
48
			$model = new $class;
49

50
			$model->attributes = (array) $result;
51
			$model->exists = true;
52

53 54
			// The results are keyed by the ID on the record. This will allow us to conveniently
			// match them to child models during eager loading.
55
			$models[$model->id] = $model;
56 57
		}

58
		return $models;
59 60 61 62 63 64
	}

	/**
	 * Eagerly load a relationship.
	 *
	 * @param  object  $eloquent
65
	 * @param  array   $parents
66 67 68
	 * @param  string  $include
	 * @return void
	 */
69
	private static function eagerly($eloquent, &$parents, $include)
70 71 72
	{
		// Get the relationship Eloquent model.
		//
73 74
		// We temporarily spoof the belongs_to key to allow the query to be fetched without
		// any problems, since the belongs_to method actually gets the attribute.
75 76
		$eloquent->attributes[$spoof = $include.'_id'] = 0;

77
		$relationship = $eloquent->$include();
78 79 80

		unset($eloquent->attributes[$spoof]);

81
		// Reset the WHERE clause and bindings on the query. We'll add our own WHERE clause soon.
82 83
		$relationship->query->where = 'WHERE 1 = 1';
		$relationship->query->bindings = array();
84

85 86
		// Initialize the relationship attribute on the parents. As expected, "many" relationships
		// are initialized to an array and "one" relationships are initialized to null.
87
		foreach ($parents as &$parent)
88
		{
89
			$parent->ignore[$include] = (strpos($eloquent->relating, 'has_many') === 0) ? array() : null;
90 91
		}

92
		if ($eloquent->relating == 'has_one')
93
		{
94 95 96 97 98
			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);
99 100 101
		}
		elseif ($eloquent->relating == 'belongs_to')
		{
102
			static::eagerly_load_belonging($relationship, $parents, $eloquent->relating_key, $include);
103 104 105
		}
		else
		{
106
			static::eagerly_load_many_to_many($relationship, $parents, $eloquent->relating_key, $eloquent->relating_table, $include);
107 108 109 110
		}
	}

	/**
111
	 * Eagerly load a 1:1 relationship.
112
	 *
113 114
	 * @param  object  $relationship
	 * @param  array   $parents
115 116 117 118 119
	 * @param  string  $relating_key
	 * @param  string  $relating
	 * @param  string  $include
	 * @return void
	 */
120
	private static function eagerly_load_one($relationship, &$parents, $relating_key, $include)
121
	{
122 123 124 125 126
		foreach ($relationship->where_in($relating_key, array_keys($parents))->get() as $key => $child)
		{
			$parents[$child->$relating_key]->ignore[$include] = $child;
		}
	}
127

128 129 130 131 132 133 134 135 136 137 138 139 140
	/**
	 * 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)
141
		{
142
			$parents[$child->$relating_key]->ignore[$include][$child->id] = $child;
143 144 145 146 147 148
		}
	}

	/**
	 * Eagerly load a 1:1 belonging relationship.
	 *
149 150
	 * @param  object  $relationship
	 * @param  array   $parents
151 152 153 154
	 * @param  string  $relating_key
	 * @param  string  $include
	 * @return void
	 */
155
	private static function eagerly_load_belonging($relationship, &$parents, $relating_key, $include)
156
	{
157 158
		// 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.
159 160
		$keys = array();

161
		foreach ($parents as &$parent)
162
		{
163
			$keys[] = $parent->$relating_key;
164 165
		}

166
		$children = $relationship->where_in('id', array_unique($keys))->get();
167

168
		foreach ($parents as &$parent)
169
		{
170 171 172 173
			if (array_key_exists($parent->$relating_key, $children))
			{
				$parent->ignore[$include] = $children[$parent->$relating_key];
			}
174 175 176 177 178 179
		}
	}

	/**
	 * Eagerly load a many-to-many relationship.
	 *
180 181
	 * @param  object  $relationship
	 * @param  array   $parents
182 183 184
	 * @param  string  $relating_key
	 * @param  string  $relating_table
	 * @param  string  $include
185
	 *
186 187
	 * @return void	
	 */
188
	private static function eagerly_load_many_to_many($relationship, &$parents, $relating_key, $relating_table, $include)
189
	{
190
		$relationship->query->select = null;
191 192 193

		// Retrieve the raw results as stdClasses.
		//
194 195
		// We also add the foreign key to the select which will allow us to match the
		// models back to their parents.
196
		$children = $relationship->query
197 198
                                     ->where_in($relating_table.'.'.$relating_key, array_keys($parents))
                                     ->get(Eloquent::table(get_class($relationship)).'.*', $relating_table.'.'.$relating_key);
199

200
		$class = get_class($relationship);
201

202
		foreach ($children as $child)
203
		{
204
			$related = new $class;
205

206
			$related->attributes = (array) $child;
207 208
			$related->exists = true;

209 210
			// Remove the foreign key from the attributes since it was added to the query
			// to help us match the models.
211
			unset($related->attributes[$relating_key]);
212

213
			$parents[$child->$relating_key]->ignore[$include][$child->id] = $related;
214 215 216 217
		}
	}

}