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

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

Taylor Otwell committed
9 10 11
abstract class Model {

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

	/**
19 20 21 22 23 24 25 26 27 28 29 30 31 32
	 * 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
33 34 35 36 37
	 * The model query instance.
	 *
	 * @var Query
	 */
	public $query;
38 39 40 41 42 43 44 45 46

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

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

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

	/**
	 * The model's ignored attributes.
	 *
65 66
	 * Ignored attributes will not be saved to the database, and are
	 * primarily used to hold relationships.
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 93
	 *
	 * @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;

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

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

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

		return $this;
127 128 129
	}

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

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

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

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

		return $model;
	}

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

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

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

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

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

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

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

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

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

	/**
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
	 * 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);
	}

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

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

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

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

	/**
	 * Retrieve the query for a 1:1 or 1:* relationship.
	 *
Taylor Otwell committed
280 281 282 283
	 * 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.
	 *
284 285 286 287 288 289
	 * @param  string  $model
	 * @param  string  $foreign_key
	 * @return mixed
	 */
	private function has_one_or_many($model, $foreign_key)
	{
290
		$this->relating_key = (is_null($foreign_key)) ? strtolower(static::model_name($this)).'_id' : $foreign_key;
291

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

	/**
	 * Retrieve the query for a 1:1 belonging relationship.
	 *
Taylor Otwell committed
298 299 300 301
	 * 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.
	 *
302
	 * @param  string  $model
303
	 * @param  string  $foreign_key
304 305
	 * @return mixed
	 */
306
	public function belongs_to($model, $foreign_key = null)
307
	{
308 309 310 311 312 313 314 315 316
		$this->relating = __FUNCTION__;

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

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

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

	/**
	 * Retrieve the query for a *:* relationship.
	 *
Taylor Otwell committed
327 328 329
	 * 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.
	 *
330
	 * @param  string  $model
331
	 * @param  string  $table
332 333
	 * @param  string  $foreign_key
	 * @param  string  $associated_key
334 335
	 * @return mixed
	 */
336
	public function has_and_belongs_to_many($model, $table = null, $foreign_key = null, $associated_key = null)
337
	{
338 339
		$this->relating = __FUNCTION__;

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

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

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

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

	/**
357 358 359 360
	 * 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
361
	 *
362 363 364 365 366
	 * @param  string  $model
	 * @return string
	 */
	private function intermediate_table($model)
	{
367
		$models = array(Inflector::plural(static::model_name($model)), Inflector::plural(static::model_name($this)));
368 369 370 371 372 373 374

		sort($models);

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

	/**
375 376
	 * Save the model to the database.
	 *
377
	 * @return bool
378 379 380
	 */
	public function save()
	{
Taylor Otwell committed
381 382 383
		// 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;
384

385 386
		$model = get_class($this);

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

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

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

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

409
		return $success;
410 411 412
	}

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

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

	/**
425
	 * Delete a model from the database.
426 427 428
	 *
	 * @param  int  $id
	 * @return int
429 430 431
	 */
	public function delete($id = null)
	{
Taylor Otwell committed
432 433 434 435
		// 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
436
		if ( ! $this->exists) return $this->query->delete();
437

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

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

	/**
	 * Magic method for retrieving model attributes.
	 */
	public function __get($key)
	{
448 449 450 451
		if (array_key_exists($key, $this->attributes))
		{
			return $this->attributes[$key];
		}
Taylor Otwell committed
452 453
		// Is the requested item a model relationship that has already been loaded?
		// All of the loaded relationships are stored in the "ignore" array.
454
		elseif (array_key_exists($key, $this->ignore))
455 456 457
		{
			return $this->ignore[$key];
		}
Taylor Otwell committed
458 459 460
		// 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))
461
		{
462
			$query = $this->$key();
463

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

	/**
	 * Magic Method for setting model attributes.
	 */
	public function __set($key, $value)
	{
473
		// If the key is a relationship, add it to the ignored attributes.
Taylor Otwell committed
474
		// Ignored attributes are not stored in the database.
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
		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
499
		unset($this->attributes[$key], $this->ignore[$key], $this->dirty[$key]);
500 501 502 503 504 505 506
	}

	/**
	 * Magic Method for handling dynamic method calls.
	 */
	public function __call($method, $parameters)
	{
Taylor Otwell committed
507 508 509
		// 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.
510
		if (in_array($method, array('with', 'get', 'first', 'paginate')))
511
		{
Taylor Otwell committed
512
			return call_user_func_array(array($this, '_'.$method), $parameters);
513 514
		}

Taylor Otwell committed
515 516
		// 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.
517
		if (in_array($method, array('insert', 'update', 'abs', 'count', 'sum', 'min', 'max', 'avg')))
518 519 520 521
		{
			return call_user_func_array(array($this->query, $method), $parameters);
		}

522
		// Pass the method to the query instance. This allows the chaining of methods
Taylor Otwell committed
523 524
		// from the query builder, providing the same convenient query API as the
		// query builder itself.
525 526 527 528 529 530 531 532 533 534
		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
535 536
		// 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);
537 538 539
	}

}