query.php 6.83 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 41 42 43 44 45 46 47 48 49
	);

	/**
	 * Creat a new query instance for a model.
	 *
	 * @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
	}

	/**
	 * Get the first model result for the query.
	 *
	 * @param  array  $columns
	 * @return mixed
	 */
	public function first($columns = array('*'))
	{
61
		$results = $this->hydrate($this->model, $this->table->take(1)->get($columns));
Taylor Otwell committed
62

63
		return (count($results) > 0) ? head($results) : null;
Taylor Otwell committed
64 65 66 67 68 69 70 71
	}

	/**
	 * Get all of the model results for the query.
	 *
	 * @param  array  $columns
	 * @return array
	 */
72
	public function get($columns = array('*'))
Taylor Otwell committed
73
	{
74
		return $this->hydrate($this->model, $this->table->get($columns));
75
	}
Taylor Otwell committed
76

77 78 79 80 81 82 83 84 85 86
	/**
	 * 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
87

88 89 90 91
		// 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
92

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

95
		return $paginator;
Taylor Otwell committed
96 97 98 99 100 101 102 103 104
	}

	/**
	 * Hydrate an array of models from the given results.
	 *
	 * @param  Model  $model
	 * @param  array  $results
	 * @return array
	 */
105
	public function hydrate($model, $results)
Taylor Otwell committed
106 107 108 109 110 111 112 113 114 115 116 117
	{
		$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;

118 119 120 121 122
			$new = new $class(array(), true);

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

125
			$models[] = $new;
Taylor Otwell committed
126 127
		}

128
		if (count($results) > 0)
129 130 131
		{
			foreach ($this->model_includes() as $relationship => $constraints)
			{
132
				// If the relationship is nested, we will skip loading it here and let
133
				// the load method parse and set the nested eager loads on the right
134
				// relationship when it is getting ready to eager load.
135 136 137 138 139 140 141 142 143 144 145 146 147 148
				if (str_contains($relationship, '.'))
				{
					continue;
				}

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

		// The many to many relationships may have pivot table column on them
		// 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)
		{
149
			$this->hydrate_pivot($models);
150 151
		}

Taylor Otwell committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
		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);

186
		$query->match($relationship, $results, $query->get());
Taylor Otwell committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	}

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

		foreach ($this->model_includes() as $include => $constraints)
		{
			// To get the nested includes, we want to find any includes that begin
			// the relationship and a dot, then we will strip off the leading
			// nesting indicator and set the include in the array.
			if (starts_with($include, $relationship.'.'))
			{
				$nested[substr($include, strlen($relationship.'.'))] = $constraints;
			}
		}

		return $nested;
	}

	/**
	 * Get the eagerly loaded relationships for the model.
	 *
	 * @return array
	 */
	protected function model_includes()
	{
		$includes = array();

		foreach ($this->model->includes as $relationship => $constraints)
		{
			// 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))
			{
				list($relationship, $constraints) = array($constraints, null);
			}

			$includes[$relationship] = $constraints;
		}

		return $includes;
	}

	/**
	 * Get a fluent query builder for the model.
	 *
	 * @return Query
	 */
243
	protected function table()
Taylor Otwell committed
244 245 246 247 248 249 250 251 252
	{
		return $this->connection()->table($this->model->table());
	}

	/**
	 * Get the database connection for the model.
	 *
	 * @return Connection
	 */
253
	public function connection()
Taylor Otwell committed
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
	{
		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
270 271
		// 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
272 273 274 275 276 277 278 279 280
		if (in_array($method, $this->passthru))
		{
			return $result;
		}

		return $this;
	}

}