model.php 13.3 KB
Newer Older
1
<?php namespace Laravel\Database\Eloquent;
2

Taylor Otwell committed
3 4
use Laravel\Str;
use Laravel\Inflector;
5
use Laravel\Paginator;
Taylor Otwell committed
6
use Laravel\Database\Manager as DB;
7

Taylor Otwell committed
8 9 10
abstract class Model {

	/**
11
	 * The connection that should be used for the model.
Taylor Otwell committed
12 13 14 15 16 17
	 *
	 * @var string
	 */
	public static $connection;

	/**
18 19 20 21 22 23 24 25 26 27 28 29 30 31
	 * Indicates if the model has creation and update timestamps.
	 *
	 * @var bool
	 */
	public static $timestamps = false;

	/**
	 * The name of the auto-incrementing sequence associated with the model.
	 *
	 * @var string
	 */
	public static $sequence = null;

	/**
Taylor Otwell committed
32 33 34 35 36
	 * The model query instance.
	 *
	 * @var Query
	 */
	public $query;
37 38 39 40 41 42 43 44 45

	/**
	 * Indicates if the model exists in the database.
	 *
	 * @var bool
	 */
	public $exists = false;

	/**
46 47 48
	 * The model's attributes. 
	 *
	 * Typically, a model has an attribute for each column on the table.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
	 *
	 * @var array
	 */
	public $attributes = array();

	/**
	 * The model's dirty attributes.
	 *
	 * @var array
	 */
	public $dirty = array();

	/**
	 * The model's ignored attributes.
	 *
64 65
	 * Ignored attributes will not be saved to the database, and are
	 * primarily used to hold relationships.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
	 *
	 * @var array
	 */
	public $ignore = array();

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

	/**
	 * The relationship type the model is currently resolving.
	 *
	 * @var string
	 */
	public $relating;

	/**
	 * The foreign key of the "relating" relationship.
	 *
	 * @var string
	 */
	public $relating_key;

	/**
93 94 95
	 * The table name of the model being resolved. 
	 *
	 * This is used during many-to-many eager loading.
96 97 98 99 100 101
	 *
	 * @var string
	 */
	public $relating_table;

	/**
102 103 104 105 106 107 108
	 * Create a new Eloquent model instance.
	 *
	 * @param  array  $attributes
	 * @return void
	 */
	public function __construct($attributes = array())
	{
109 110 111 112 113 114 115
		$this->fill($attributes);
	}

	/**
	 * Set the attributes of the model using an array.
	 *
	 * @param  array  $attributes
116
	 * @return Model
117 118 119
	 */
	public function fill($attributes)
	{
120 121 122 123
		foreach ($attributes as $key => $value)
		{
			$this->$key = $value;
		}
124 125

		return $this;
126 127 128
	}

	/**
Taylor Otwell committed
129
	 * Set the eagerly loaded models on the queryable model.
130
	 *
Taylor Otwell committed
131
	 * @return Model
132
	 */
Taylor Otwell committed
133
	private function _with()
134
	{
Taylor Otwell committed
135
		$this->includes = func_get_args();
136

Taylor Otwell committed
137
		return $this;
138 139 140
	}

	/**
Taylor Otwell committed
141
	 * Factory for creating queryable Eloquent model instances.
142 143 144 145
	 *
	 * @param  string  $class
	 * @return object
	 */
Taylor Otwell committed
146
	public static function query($class)
147 148
	{
		$model = new $class;
149

Taylor Otwell committed
150
		// Since this method is only used for instantiating models for querying
151
		// purposes, we will go ahead and set the Query instance on the model.
Taylor Otwell committed
152
		$model->query = DB::connection(static::$connection)->table(static::table($class));
153 154 155 156 157

		return $model;
	}

	/**
Taylor Otwell committed
158
	 * Get the table name for a model.
159
	 *
Taylor Otwell committed
160 161
	 * @param  string  $class
	 * @return string
162
	 */
Taylor Otwell committed
163
	public static function table($class)
164
	{
Taylor Otwell committed
165
		if (property_exists($class, 'table')) return $class::$table;
Taylor Otwell committed
166

167
		return strtolower(Inflector::plural(static::model_name($class)));
Taylor Otwell committed
168 169 170 171 172 173 174 175
	}

	/**
	 * Get an Eloquent model name without any namespaces.
	 *
	 * @param  string|Model  $model
	 * @return string
	 */
176
	public static function model_name($model)
Taylor Otwell committed
177 178 179 180 181 182
	{
		$class = (is_object($model)) ? get_class($model) : $model;

		$segments = array_reverse(explode('\\', $class));

		return $segments[0];
Taylor Otwell committed
183
	}
184

Taylor Otwell committed
185 186 187 188 189 190 191 192
	/**
	 * Get all of the models from the database.
	 *
	 * @return array
	 */
	public static function all()
	{
		return Hydrator::hydrate(static::query(get_called_class()));
193 194 195 196 197 198 199 200 201 202
	}

	/**
	 * Get a model by the primary key.
	 *
	 * @param  int  $id
	 * @return mixed
	 */
	public static function find($id)
	{
Taylor Otwell committed
203
		return static::query(get_called_class())->where('id', '=', $id)->first();
204 205 206 207 208 209 210
	}

	/**
	 * Get an array of models from the database.
	 *
	 * @return array
	 */
211
	private function _get()
212
	{
Taylor Otwell committed
213
		return Hydrator::hydrate($this);
214 215 216 217
	}

	/**
	 * Get the first model result
218
	 *
219
	 * @return mixed
220
	 */
221
	private function _first()
222
	{
223
		return (count($results = $this->take(1)->_get()) > 0) ? reset($results) : null;
224 225 226
	}

	/**
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
	 * Get paginated model results as a Paginator instance.
	 *
	 * @param  int        $per_page
	 * @return Paginator
	 */
	private function _paginate($per_page = null)
	{
		$total = $this->query->count();

		// The number of models to show per page may be specified as a static property
		// on the model. The models shown per page may also be overriden for the model
		// by passing the number into this method. If the models to show per page is
		// not available via either of these avenues, a default number will be shown.
		if (is_null($per_page))
		{
			$per_page = (property_exists(get_class($this), 'per_page')) ? static::$per_page : 20;
		}

		return Paginator::make($this->for_page(Paginator::page($total, $per_page), $per_page)->get(), $total, $per_page);
	}

	/**
249 250 251
	 * Retrieve the query for a 1:1 relationship.
	 *
	 * @param  string  $model
252
	 * @param  string  $foreign_key
253 254
	 * @return mixed
	 */
255
	public function has_one($model, $foreign_key = null)
256
	{
257
		$this->relating = __FUNCTION__;
258

259
		return $this->has_one_or_many($model, $foreign_key);
260 261 262 263 264 265
	}

	/**
	 * Retrieve the query for a 1:* relationship.
	 *
	 * @param  string  $model
266
	 * @param  string  $foreign_key
267 268
	 * @return mixed
	 */
269
	public function has_many($model, $foreign_key = null)
270
	{
271
		$this->relating = __FUNCTION__;
272

273 274 275 276 277 278
		return $this->has_one_or_many($model, $foreign_key);
	}

	/**
	 * Retrieve the query for a 1:1 or 1:* relationship.
	 *
Taylor Otwell committed
279 280 281 282
	 * The default foreign key for has one and has many relationships is the name
	 * of the model with an appended _id. For example, the foreign key for a
	 * User model would be user_id. Photo would be photo_id, etc.
	 *
283 284 285 286 287 288
	 * @param  string  $model
	 * @param  string  $foreign_key
	 * @return mixed
	 */
	private function has_one_or_many($model, $foreign_key)
	{
289
		$this->relating_key = (is_null($foreign_key)) ? strtolower(static::model_name($this)).'_id' : $foreign_key;
290

Taylor Otwell committed
291
		return static::query($model)->where($this->relating_key, '=', $this->id);
292 293 294 295 296
	}

	/**
	 * Retrieve the query for a 1:1 belonging relationship.
	 *
Taylor Otwell committed
297 298 299 300
	 * The default foreign key for belonging relationships is the name of the
	 * relationship method name with _id. So, if a model has a "manager" method
	 * returning a belongs_to relationship, the key would be manager_id.
	 *
301
	 * @param  string  $model
302
	 * @param  string  $foreign_key
303 304
	 * @return mixed
	 */
305
	public function belongs_to($model, $foreign_key = null)
306
	{
307 308 309 310 311 312 313 314 315
		$this->relating = __FUNCTION__;

		if ( ! is_null($foreign_key))
		{
			$this->relating_key = $foreign_key;
		}
		else
		{
			list(, $caller) = debug_backtrace(false);
316

317 318 319
			$this->relating_key = $caller['function'].'_id';
		}

Taylor Otwell committed
320
		return static::query($model)->where('id', '=', $this->attributes[$this->relating_key]);
321 322 323 324 325
	}

	/**
	 * Retrieve the query for a *:* relationship.
	 *
Taylor Otwell committed
326 327 328
	 * The default foreign key for many-to-many relations is the name of the model
	 * with an appended _id. This is the same convention as has_one and has_many.
	 *
329
	 * @param  string  $model
330
	 * @param  string  $table
331 332
	 * @param  string  $foreign_key
	 * @param  string  $associated_key
333 334
	 * @return mixed
	 */
335
	public function has_and_belongs_to_many($model, $table = null, $foreign_key = null, $associated_key = null)
336
	{
337 338
		$this->relating = __FUNCTION__;

339
		$this->relating_table = (is_null($table)) ? $this->intermediate_table($model) : $table;
340

341 342
		// Allowing the overriding of the foreign and associated keys provides
		// the flexibility for self-referential many-to-many relationships.
343
		$this->relating_key = (is_null($foreign_key)) ? strtolower(static::model_name($this)).'_id' : $foreign_key;
344

345 346
		// The associated key is the foreign key name of the related model. 
		// If the related model is "Role", the key would be "role_id".
347
		$associated_key = (is_null($associated_key)) ? strtolower(static::model_name($model)).'_id' : $associated_key;
348

Taylor Otwell committed
349
		return static::query($model)
Taylor Otwell committed
350
                             ->select(array(static::table($model).'.*'))
351
                             ->join($this->relating_table, static::table($model).'.id', '=', $this->relating_table.'.'.$associated_key)
Taylor Otwell committed
352
                             ->where($this->relating_table.'.'.$this->relating_key, '=', $this->id);
353 354 355
	}

	/**
356 357 358 359
	 * Determine the intermediate table name for a given model.
	 *
	 * By default, the intermediate table name is the plural names of the models
	 * arranged alphabetically and concatenated with an underscore.
Taylor Otwell committed
360
	 *
361 362 363 364 365
	 * @param  string  $model
	 * @return string
	 */
	private function intermediate_table($model)
	{
366
		$models = array(Inflector::plural(static::model_name($model)), Inflector::plural(static::model_name($this)));
367 368 369 370 371 372 373

		sort($models);

		return strtolower($models[0].'_'.$models[1]);
	}

	/**
374 375
	 * Save the model to the database.
	 *
376
	 * @return bool
377 378 379
	 */
	public function save()
	{
Taylor Otwell committed
380 381 382
		// If the model does not have any dirty attributes, there is no reason
		// to save it to the database.
		if ($this->exists and count($this->dirty) == 0) return true;
383

384 385
		$model = get_class($this);

386
		// Since the model was instantiated using "new", a query instance has not been set.
387
		// Only models being used for querying have their query instances set by default.
Taylor Otwell committed
388
		$this->query = DB::connection(static::$connection)->table(static::table($model));
389 390 391

		if (property_exists($model, 'timestamps') and $model::$timestamps)
		{
Taylor Otwell committed
392
			$this->timestamp();
393 394
		}

Taylor Otwell committed
395 396
		// If the model already exists in the database, we will just update it.
		// Otherwise, we will insert the model and set the ID attribute.
397 398
		if ($this->exists)
		{
399
			$success = ($this->query->where_id($this->attributes['id'])->update($this->dirty) === 1);
400 401 402
		}
		else
		{
403
			$success = is_numeric($this->attributes['id'] = $this->query->insert_get_id($this->attributes, static::$sequence));
404
		}
405

406
		($this->exists = true) and $this->dirty = array();
407

408
		return $success;
409 410 411
	}

	/**
Taylor Otwell committed
412 413 414 415
	 * Set the creation and update timestamps on the model.
	 *
	 * @return void
	 */
416
	protected function timestamp()
Taylor Otwell committed
417 418 419
	{
		$this->updated_at = date('Y-m-d H:i:s');

Taylor Otwell committed
420
		if ( ! $this->exists) $this->created_at = $this->updated_at;
Taylor Otwell committed
421 422 423
	}

	/**
424
	 * Delete a model from the database.
425 426 427
	 *
	 * @param  int  $id
	 * @return int
428 429 430
	 */
	public function delete($id = null)
	{
Taylor Otwell committed
431 432 433 434
		// If the delete method is being called on an existing model, we only want to delete
		// that model. If it is being called from an Eloquent query model, it is probably
		// the developer's intention to delete more than one model, so we will pass the
		// delete statement to the query instance.
Taylor Otwell committed
435
		if ( ! $this->exists) return $this->query->delete();
436

437 438
		$table = static::table(get_class($this));

Taylor Otwell committed
439
		return DB::connection(static::$connection)->table($table)->delete($this->id);
440 441 442 443 444 445 446
	}

	/**
	 * Magic method for retrieving model attributes.
	 */
	public function __get($key)
	{
447 448 449 450
		if (array_key_exists($key, $this->attributes))
		{
			return $this->attributes[$key];
		}
Taylor Otwell committed
451 452
		// Is the requested item a model relationship that has already been loaded?
		// All of the loaded relationships are stored in the "ignore" array.
453
		elseif (array_key_exists($key, $this->ignore))
454 455 456
		{
			return $this->ignore[$key];
		}
Taylor Otwell committed
457 458 459
		// Is the requested item a model relationship? If it is, we will dynamically
		// load it and return the results of the relationship query.
		elseif (method_exists($this, $key))
460
		{
461
			$query = $this->$key();
462

463
			return $this->ignore[$key] = (in_array($this->relating, array('has_one', 'belongs_to'))) ? $query->first() : $query->get();
464 465 466 467 468 469 470 471
		}
	}

	/**
	 * Magic Method for setting model attributes.
	 */
	public function __set($key, $value)
	{
472
		// If the key is a relationship, add it to the ignored attributes.
Taylor Otwell committed
473
		// Ignored attributes are not stored in the database.
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
		if (method_exists($this, $key))
		{
			$this->ignore[$key] = $value;
		}
		else
		{
			$this->attributes[$key] = $value;
			$this->dirty[$key] = $value;
		}
	}

	/**
	 * Magic Method for determining if a model attribute is set.
	 */
	public function __isset($key)
	{
		return (array_key_exists($key, $this->attributes) or array_key_exists($key, $this->ignore));
	}

	/**
	 * Magic Method for unsetting model attributes.
	 */
	public function __unset($key)
	{
Taylor Otwell committed
498
		unset($this->attributes[$key], $this->ignore[$key], $this->dirty[$key]);
499 500 501 502 503 504 505
	}

	/**
	 * Magic Method for handling dynamic method calls.
	 */
	public function __call($method, $parameters)
	{
Taylor Otwell committed
506 507 508
		// To allow the "with", "get", "first", and "paginate" methods to be called both
		// staticly and on an instance, we need to have private, underscored versions
		// of the methods and handle them dynamically.
509
		if (in_array($method, array('with', 'get', 'first', 'paginate')))
510
		{
Taylor Otwell committed
511
			return call_user_func_array(array($this, '_'.$method), $parameters);
512 513
		}

Taylor Otwell committed
514 515
		// All of the aggregate and persistance functions can be passed directly to the query
		// instance. For these functions, we can simply return the response of the query.
516
		if (in_array($method, array('insert', 'update', 'increment', 'decrement', 'abs', 'count', 'sum', 'min', 'max', 'avg')))
517 518 519 520
		{
			return call_user_func_array(array($this->query, $method), $parameters);
		}

521
		// Pass the method to the query instance. This allows the chaining of methods
Taylor Otwell committed
522 523
		// from the query builder, providing the same convenient query API as the
		// query builder itself.
524 525 526 527 528 529 530 531 532 533
		call_user_func_array(array($this->query, $method), $parameters);

		return $this;
	}

	/**
	 * Magic Method for handling dynamic static method calls.
	 */
	public static function __callStatic($method, $parameters)
	{
Taylor Otwell committed
534 535
		// Just pass the method to a model instance and let the __call method take care of it.
		return call_user_func_array(array(static::query(get_called_class()), $method), $parameters);
536 537 538
	}

}