query.php 7.11 KB
Newer Older
1 2
<?php namespace Laravel\Database\Eloquent;

3
use Laravel\Event;
4 5
use Laravel\Database;
use Laravel\Database\Eloquent\Relationships\Has_Many_And_Belongs_To;
Taylor Otwell committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

class Query {

	/**
	 * The model instance being queried.
	 *
	 * @var Model
	 */
	public $model;

	/**
	 * The fluent query builder for the query instance.
	 *
	 * @var Query
	 */
	public $table;

	/**
	 * The relationships that should be eagerly loaded by the query.
	 *
	 * @var array
	 */
	public $includes = array();

	/**
	 * The methods that should be returned from the fluent query builder.
	 *
	 * @var array
	 */
	public $passthru = array(
36
		'lists', 'only', 'insert', 'insert_get_id', 'update', 'increment',
37
		'delete', 'decrement', 'count', 'min', 'max', 'avg', 'sum',
Taylor Otwell committed
38 39 40
	);

	/**
Taylor Otwell committed
41
	 * Creat a new query instance for a model.
Taylor Otwell committed
42 43 44 45 46 47 48 49
	 *
	 * @param  Model  $model
	 * @return void
	 */
	public function __construct($model)
	{
		$this->model = ($model instanceof Model) ? $model : new $model;

50
		$this->table = $this->table();
Taylor Otwell committed
51 52 53
	}

	/**
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
	 * Find a model by its primary key.
	 * 
	 * @param  mixed  $id
	 * @param  array  $columns
	 * @return mixed
	 */
	public function find($id, $columns = array('*'))
	{
		$model = $this->model;

		$this->table->where($model::$key, '=', $id);

		return $this->first($columns);
	}

	/**
Taylor Otwell committed
70 71 72 73 74 75 76
	 * Get the first model result for the query.
	 *
	 * @param  array  $columns
	 * @return mixed
	 */
	public function first($columns = array('*'))
	{
77
		$results = $this->hydrate($this->model, $this->table->take(1)->get($columns));
Taylor Otwell committed
78

79
		return (count($results) > 0) ? head($results) : null;
Taylor Otwell committed
80 81 82 83 84 85 86 87
	}

	/**
	 * Get all of the model results for the query.
	 *
	 * @param  array  $columns
	 * @return array
	 */
88
	public function get($columns = array('*'))
Taylor Otwell committed
89
	{
90
		return $this->hydrate($this->model, $this->table->get($columns));
91
	}
Taylor Otwell committed
92

93 94 95 96 97 98 99 100 101 102
	/**
	 * Get an array of paginated model results.
	 *
	 * @param  int        $per_page
	 * @param  array      $columns
	 * @return Paginator
	 */
	public function paginate($per_page = null, $columns = array('*'))
	{
		$per_page = $per_page ?: $this->model->per_page();
Taylor Otwell committed
103

104 105 106 107
		// First we'll grab the Paginator instance and get the results. Then we can
		// feed those raw database results into the hydrate method to get models
		// for the results, which we'll set on the paginator and return it.
		$paginator = $this->table->paginate($per_page, $columns);
Taylor Otwell committed
108

109
		$paginator->results = $this->hydrate($this->model, $paginator->results);
Taylor Otwell committed
110

111
		return $paginator;
Taylor Otwell committed
112 113 114 115 116 117 118 119 120
	}

	/**
	 * Hydrate an array of models from the given results.
	 *
	 * @param  Model  $model
	 * @param  array  $results
	 * @return array
	 */
121
	public function hydrate($model, $results)
Taylor Otwell committed
122 123 124 125 126 127 128 129 130 131 132 133
	{
		$class = get_class($model);

		$models = array();

		// We'll spin through the array of database results and hydrate a model
		// for each one of the records. We will also set the "exists" flag to
		// "true" so that the model will be updated when it is saved.
		foreach ((array) $results as $result)
		{
			$result = (array) $result;

134 135 136
			$new = new $class(array(), true);

			// We need to set the attributes manually in case the accessible property is
Taylor Otwell committed
137
			// set on the array which will prevent the mass assignemnt of attributes if
138
			// we were to pass them in using the constructor or fill methods.
139
			$new->fill_raw($result);
140

141
			$models[] = $new;
Taylor Otwell committed
142 143
		}

144
		if (count($results) > 0)
145
		{
146
			foreach ($this->model_includes() as $relationship => $constraints)
147
			{
148
				// If the relationship is nested, we will skip loading it here and let
149
				// the load method parse and set the nested eager loads on the right
150
				// relationship when it is getting ready to eager load.
151 152 153 154 155 156 157 158 159
				if (str_contains($relationship, '.'))
				{
					continue;
				}

				$this->load($models, $relationship, $constraints);
			}
		}

Taylor Otwell committed
160
		// The many to many relationships may have pivot table column on them
161 162 163 164
		// so we will call the "clean" method on the relationship to remove
		// any pivot columns that are on the model.
		if ($this instanceof Relationships\Has_Many_And_Belongs_To)
		{
165
			$this->hydrate_pivot($models);
166 167
		}

Taylor Otwell committed
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
		return $models;
	}

	/**
	 * Hydrate an eagerly loaded relationship on the model results.
	 *
	 * @param  array       $results
	 * @param  string      $relationship
	 * @param  array|null  $constraints
	 * @return void
	 */
	protected function load(&$results, $relationship, $constraints)
	{
		$query = $this->model->$relationship();

		$query->model->includes = $this->nested_includes($relationship);

		// We'll remove any of the where clauses from the relationship to give
		// the relationship the opportunity to set the constraints for an
		// eager relationship using a separate, specific method.
		$query->table->reset_where();

		$query->eagerly_constrain($results);

		// Constraints may be specified in-line for the eager load by passing
		// a Closure as the value portion of the eager load. We can use the
		// query builder's nested query support to add the constraints.
		if ( ! is_null($constraints))
		{
			$query->table->where_nested($constraints);
		}

		$query->initialize($results, $relationship);

202
		$query->match($relationship, $results, $query->get());
Taylor Otwell committed
203 204 205 206 207 208 209 210 211 212 213 214
	}

	/**
	 * Gather the nested includes for a given relationship.
	 *
	 * @param  string  $relationship
	 * @return array
	 */
	protected function nested_includes($relationship)
	{
		$nested = array();

215
		foreach ($this->model_includes() as $include => $constraints)
Taylor Otwell committed
216 217
		{
			// To get the nested includes, we want to find any includes that begin
Taylor Otwell committed
218
			// the relationship and a dot, then we will strip off the leading
Taylor Otwell committed
219 220 221 222 223 224 225 226 227 228 229
			// nesting indicator and set the include in the array.
			if (starts_with($include, $relationship.'.'))
			{
				$nested[substr($include, strlen($relationship.'.'))] = $constraints;
			}
		}

		return $nested;
	}

	/**
230 231 232 233 234 235
	 * Get the eagerly loaded relationships for the model.
	 *
	 * @return array
	 */
	protected function model_includes()
	{
Taylor Otwell committed
236
		$includes = array();
237

Taylor Otwell committed
238
		foreach ($this->model->includes as $relationship => $constraints)
239
		{
Taylor Otwell committed
240 241 242 243
			// When eager loading relationships, constraints may be set on the eager
			// load definition; however, is none are set, we need to swap the key
			// and the value of the array since there are no constraints.
			if (is_numeric($relationship))
244
			{
Taylor Otwell committed
245
				list($relationship, $constraints) = array($constraints, null);
246
			}
Taylor Otwell committed
247 248

			$includes[$relationship] = $constraints;
249 250
		}

Taylor Otwell committed
251
		return $includes;
252 253 254
	}

	/**
Taylor Otwell committed
255 256 257 258
	 * Get a fluent query builder for the model.
	 *
	 * @return Query
	 */
259
	protected function table()
Taylor Otwell committed
260 261 262 263 264 265 266 267 268
	{
		return $this->connection()->table($this->model->table());
	}

	/**
	 * Get the database connection for the model.
	 *
	 * @return Connection
	 */
269
	public function connection()
Taylor Otwell committed
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
	{
		return Database::connection($this->model->connection());
	}

	/**
	 * Handle dynamic method calls to the query.
	 *
	 * @param  string  $method
	 * @param  array   $parameters
	 * @return mixed
	 */
	public function __call($method, $parameters)
	{
		$result = call_user_func_array(array($this->table, $method), $parameters);

		// Some methods may get their results straight from the fluent query
286 287
		// builder such as the aggregate methods. If the called method is
		// one of these, we will just return the result straight away.
Taylor Otwell committed
288 289 290 291 292 293 294 295
		if (in_array($method, $this->passthru))
		{
			return $result;
		}

		return $this;
	}

Taylor Otwell committed
296
}