1: <?php namespace Laravel\Database\Eloquent;
2:
3: use Laravel\Str;
4: use Laravel\Event;
5: use Laravel\Database;
6: use Laravel\Database\Eloquent\Relationships\Has_Many_And_Belongs_To;
7:
8: abstract class Model {
9:
10: /**
11: * All of the model's attributes.
12: *
13: * @var array
14: */
15: public $attributes = array();
16:
17: /**
18: * The model's attributes in their original state.
19: *
20: * @var array
21: */
22: public $original = array();
23:
24: /**
25: * The relationships that have been loaded for the query.
26: *
27: * @var array
28: */
29: public $relationships = array();
30:
31: /**
32: * Indicates if the model exists in the database.
33: *
34: * @var bool
35: */
36: public $exists = false;
37:
38: /**
39: * The relationships that should be eagerly loaded.
40: *
41: * @var array
42: */
43: public $includes = array();
44:
45: /**
46: * The primary key for the model on the database table.
47: *
48: * @var string
49: */
50: public static $key = 'id';
51:
52: /**
53: * The attributes that are accessible for mass assignment.
54: *
55: * @var array
56: */
57: public static $accessible;
58:
59: /**
60: * The attributes that should be excluded from to_array.
61: *
62: * @var array
63: */
64: public static = array();
65:
66: /**
67: * Indicates if the model has update and creation timestamps.
68: *
69: * @var bool
70: */
71: public static $timestamps = true;
72:
73: /**
74: * The name of the table associated with the model.
75: *
76: * @var string
77: */
78: public static $table;
79:
80: /**
81: * The name of the database connection that should be used for the model.
82: *
83: * @var string
84: */
85: public static $connection;
86:
87: /**
88: * The name of the sequence associated with the model.
89: *
90: * @var string
91: */
92: public static $sequence;
93:
94: /**
95: * The default number of models to show per page when paginating.
96: *
97: * @var int
98: */
99: public static $per_page = 20;
100:
101: /**
102: * Create a new Eloquent model instance.
103: *
104: * @param array $attributes
105: * @param bool $exists
106: * @return void
107: */
108: public function __construct($attributes = array(), $exists = false)
109: {
110: $this->exists = $exists;
111:
112: $this->fill($attributes);
113: }
114:
115: /**
116: * Hydrate the model with an array of attributes.
117: *
118: * @param array $attributes
119: * @param bool $raw
120: * @return Model
121: */
122: public function fill(array $attributes, $raw = false)
123: {
124: foreach ($attributes as $key => $value)
125: {
126: // If the "raw" flag is set, it means that we'll just load every value from
127: // the array directly into the attributes, without any accessibility or
128: // mutators being accounted for. What you pass in is what you get.
129: if ($raw)
130: {
131: $this->set_attribute($key, $value);
132:
133: continue;
134: }
135:
136: // If the "accessible" property is an array, the developer is limiting the
137: // attributes that may be mass assigned, and we need to verify that the
138: // current attribute is included in that list of allowed attributes.
139: if (is_array(static::$accessible))
140: {
141: if (in_array($key, static::$accessible))
142: {
143: $this->$key = $value;
144: }
145: }
146:
147: // If the "accessible" property is not an array, no attributes have been
148: // white-listed and we are free to set the value of the attribute to
149: // the value that has been passed into the method without a check.
150: else
151: {
152: $this->$key = $value;
153: }
154: }
155:
156: // If the original attribute values have not been set, we will set
157: // them to the values passed to this method allowing us to easily
158: // check if the model has changed since hydration.
159: if (count($this->original) === 0)
160: {
161: $this->original = $this->attributes;
162: }
163:
164: return $this;
165: }
166:
167: /**
168: * Fill the model with the contents of the array.
169: *
170: * No mutators or accessibility checks will be accounted for.
171: *
172: * @param array $attributes
173: * @return Model
174: */
175: public function fill_raw(array $attributes)
176: {
177: return $this->fill($attributes, true);
178: }
179:
180: /**
181: * Set the accessible attributes for the given model.
182: *
183: * @param array $attributes
184: * @return void
185: */
186: public static function accessible($attributes = null)
187: {
188: if (is_null($attributes)) return static::$accessible;
189:
190: static::$accessible = $attributes;
191: }
192:
193: /**
194: * Create a new model and store it in the database.
195: *
196: * If save is successful, the model will be returned, otherwise false.
197: *
198: * @param array $attributes
199: * @return Model|false
200: */
201: public static function create($attributes)
202: {
203: $model = new static($attributes);
204:
205: $success = $model->save();
206:
207: return ($success) ? $model : false;
208: }
209:
210: /**
211: * Update a model instance in the database.
212: *
213: * @param mixed $id
214: * @param array $attributes
215: * @return int
216: */
217: public static function update($id, $attributes)
218: {
219: $model = new static(array(), true);
220:
221: $model->fill($attributes);
222:
223: if (static::$timestamps) $model->timestamp();
224:
225: return $model->query()->where($model->key(), '=', $id)->update($model->attributes);
226: }
227:
228: /**
229: * Get all of the models in the database.
230: *
231: * @return array
232: */
233: public static function all()
234: {
235: return with(new static)->query()->get();
236: }
237:
238: /**
239: * The relationships that should be eagerly loaded by the query.
240: *
241: * @param array $includes
242: * @return Model
243: */
244: public function _with($includes)
245: {
246: $this->includes = (array) $includes;
247:
248: return $this;
249: }
250:
251: /**
252: * Get the query for a one-to-one association.
253: *
254: * @param string $model
255: * @param string $foreign
256: * @return Relationship
257: */
258: public function has_one($model, $foreign = null)
259: {
260: return $this->has_one_or_many(__FUNCTION__, $model, $foreign);
261: }
262:
263: /**
264: * Get the query for a one-to-many association.
265: *
266: * @param string $model
267: * @param string $foreign
268: * @return Relationship
269: */
270: public function has_many($model, $foreign = null)
271: {
272: return $this->has_one_or_many(__FUNCTION__, $model, $foreign);
273: }
274:
275: /**
276: * Get the query for a one-to-one / many association.
277: *
278: * @param string $type
279: * @param string $model
280: * @param string $foreign
281: * @return Relationship
282: */
283: protected function has_one_or_many($type, $model, $foreign)
284: {
285: if ($type == 'has_one')
286: {
287: return new Relationships\Has_One($this, $model, $foreign);
288: }
289: else
290: {
291: return new Relationships\Has_Many($this, $model, $foreign);
292: }
293: }
294:
295: /**
296: * Get the query for a one-to-one (inverse) relationship.
297: *
298: * @param string $model
299: * @param string $foreign
300: * @return Relationship
301: */
302: public function belongs_to($model, $foreign = null)
303: {
304: // If no foreign key is specified for the relationship, we will assume that the
305: // name of the calling function matches the foreign key. For example, if the
306: // calling function is "manager", we'll assume the key is "manager_id".
307: if (is_null($foreign))
308: {
309: list(, $caller) = debug_backtrace(false);
310:
311: $foreign = "{$caller['function']}_id";
312: }
313:
314: return new Relationships\Belongs_To($this, $model, $foreign);
315: }
316:
317: /**
318: * Get the query for a many-to-many relationship.
319: *
320: * @param string $model
321: * @param string $table
322: * @param string $foreign
323: * @param string $other
324: * @return Has_Many_And_Belongs_To
325: */
326: public function has_many_and_belongs_to($model, $table = null, $foreign = null, $other = null)
327: {
328: return new Has_Many_And_Belongs_To($this, $model, $table, $foreign, $other);
329: }
330:
331: /**
332: * Save the model and all of its relations to the database.
333: *
334: * @return bool
335: */
336: public function push()
337: {
338: if (!$this->save()) return false;
339:
340: // To sync all of the relationships to the database, we will simply spin through
341: // the relationships, calling the "push" method on each of the models in that
342: // given relationship, this should ensure that each model is saved.
343: foreach ($this->relationships as $name => $models)
344: {
345: if ( ! is_array($models))
346: {
347: $models = array($models);
348: }
349:
350: foreach ($models as $model)
351: {
352: if (!$model->push()) return false;
353: }
354: }
355:
356: return true;
357: }
358:
359: /**
360: * Save the model instance to the database.
361: *
362: * @return bool
363: */
364: public function save()
365: {
366: if ( ! $this->dirty()) return true;
367:
368: if (static::$timestamps)
369: {
370: $this->timestamp();
371: }
372:
373: $this->fire_event('saving');
374:
375: // If the model exists, we only need to update it in the database, and the update
376: // will be considered successful if there is one affected row returned from the
377: // fluent query instance. We'll set the where condition automatically.
378: if ($this->exists)
379: {
380: $query = $this->query()->where(static::$key, '=', $this->get_key());
381:
382: $result = $query->update($this->get_dirty()) === 1;
383:
384: if ($result) $this->fire_event('updated');
385: }
386:
387: // If the model does not exist, we will insert the record and retrieve the last
388: // insert ID that is associated with the model. If the ID returned is numeric
389: // then we can consider the insert successful.
390: else
391: {
392: $id = $this->query()->insert_get_id($this->attributes, $this->key());
393:
394: $this->set_key($id);
395:
396: $this->exists = $result = is_numeric($this->get_key());
397:
398: if ($result) $this->fire_event('created');
399: }
400:
401: // After the model has been "saved", we will set the original attributes to
402: // match the current attributes so the model will not be viewed as being
403: // dirty and subsequent calls won't hit the database.
404: $this->original = $this->attributes;
405:
406: if ($result)
407: {
408: $this->fire_event('saved');
409: }
410:
411: return $result;
412: }
413:
414: /**
415: * Delete the model from the database.
416: *
417: * @return int
418: */
419: public function delete()
420: {
421: if ($this->exists)
422: {
423: $this->fire_event('deleting');
424:
425: $result = $this->query()->where(static::$key, '=', $this->get_key())->delete();
426:
427: $this->fire_event('deleted');
428:
429: return $result;
430: }
431: }
432:
433: /**
434: * Set the update and creation timestamps on the model.
435: *
436: * @return void
437: */
438: public function timestamp()
439: {
440: $this->updated_at = new \DateTime;
441:
442: if ( ! $this->exists) $this->created_at = $this->updated_at;
443: }
444:
445: /**
446: * Updates the timestamp on the model and immediately saves it.
447: *
448: * @return void
449: */
450: public function touch()
451: {
452: $this->timestamp();
453: $this->save();
454: }
455:
456: /**
457: * Get a new fluent query builder instance for the model.
458: *
459: * @return Query
460: */
461: protected function _query()
462: {
463: return new Query($this);
464: }
465:
466: /**
467: * Sync the original attributes with the current attributes.
468: *
469: * @return bool
470: */
471: final public function sync()
472: {
473: $this->original = $this->attributes;
474:
475: return true;
476: }
477:
478: /**
479: * Determine if a given attribute has changed from its original state.
480: *
481: * @param string $attribute
482: * @return bool
483: */
484: public function changed($attribute)
485: {
486: return array_get($this->attributes, $attribute) != array_get($this->original, $attribute);
487: }
488:
489: /**
490: * Determine if the model has been changed from its original state.
491: *
492: * Models that haven't been persisted to storage are always considered dirty.
493: *
494: * @return bool
495: */
496: public function dirty()
497: {
498: return ! $this->exists or count($this->get_dirty()) > 0;
499: }
500:
501: /**
502: * Get the name of the table associated with the model.
503: *
504: * @return string
505: */
506: public function table()
507: {
508: return static::$table ?: strtolower(Str::plural(class_basename($this)));
509: }
510:
511: /**
512: * Get the dirty attributes for the model.
513: *
514: * @return array
515: */
516: public function get_dirty()
517: {
518: $dirty = array();
519:
520: foreach ($this->attributes as $key => $value)
521: {
522: if ( ! array_key_exists($key, $this->original) or $value !== $this->original[$key])
523: {
524: $dirty[$key] = $value;
525: }
526: }
527:
528: return $dirty;
529: }
530:
531: /**
532: * Get the value of the primary key for the model.
533: *
534: * @return int
535: */
536: public function get_key()
537: {
538: return array_get($this->attributes, static::$key);
539: }
540:
541: /**
542: * Set the value of the primary key for the model.
543: *
544: * @param int $value
545: * @return void
546: */
547: public function set_key($value)
548: {
549: return $this->set_attribute(static::$key, $value);
550: }
551:
552: /**
553: * Get a given attribute from the model.
554: *
555: * @param string $key
556: */
557: public function get_attribute($key)
558: {
559: return array_get($this->attributes, $key);
560: }
561:
562: /**
563: * Set an attribute's value on the model.
564: *
565: * @param string $key
566: * @param mixed $value
567: * @return Model
568: */
569: public function set_attribute($key, $value)
570: {
571: $this->attributes[$key] = $value;
572: return $this;
573: }
574:
575: /**
576: * Remove an attribute from the model.
577: *
578: * @param string $key
579: */
580: final public function purge($key)
581: {
582: unset($this->original[$key]);
583:
584: unset($this->attributes[$key]);
585: }
586:
587: /**
588: * Get the model attributes and relationships in array form.
589: *
590: * @return array
591: */
592: public function to_array()
593: {
594: $attributes = array();
595:
596: // First we need to gather all of the regular attributes. If the attribute
597: // exists in the array of "hidden" attributes, it will not be added to
598: // the array so we can easily exclude things like passwords, etc.
599: foreach (array_keys($this->attributes) as $attribute)
600: {
601: if ( ! in_array($attribute, static::$hidden))
602: {
603: $attributes[$attribute] = $this->$attribute;
604: }
605: }
606:
607: foreach ($this->relationships as $name => $models)
608: {
609: // Relationships can be marked as "hidden", too.
610: if (in_array($name, static::$hidden)) continue;
611:
612: // If the relationship is not a "to-many" relationship, we can just
613: // to_array the related model and add it as an attribute to the
614: // array of existing regular attributes we gathered.
615: if ($models instanceof Model)
616: {
617: $attributes[$name] = $models->to_array();
618: }
619:
620: // If the relationship is a "to-many" relationship we need to spin
621: // through each of the related models and add each one with the
622: // to_array method, keying them both by name and ID.
623: elseif (is_array($models))
624: {
625: $attributes[$name] = array();
626:
627: foreach ($models as $id => $model)
628: {
629: $attributes[$name][$id] = $model->to_array();
630: }
631: }
632: elseif (is_null($models))
633: {
634: $attributes[$name] = $models;
635: }
636: }
637:
638: return $attributes;
639: }
640:
641: /**
642: * Fire a given event for the model.
643: *
644: * @param string $event
645: * @return array
646: */
647: protected function fire_event($event)
648: {
649: $events = array("eloquent.{$event}", "eloquent.{$event}: ".get_class($this));
650:
651: Event::fire($events, array($this));
652: }
653:
654: /**
655: * Handle the dynamic retrieval of attributes and associations.
656: *
657: * @param string $key
658: * @return mixed
659: */
660: public function __get($key)
661: {
662: // First we will check to see if the requested key is an already loaded
663: // relationship and return it if it is. All relationships are stored
664: // in the special relationships array so they are not persisted.
665: if (array_key_exists($key, $this->relationships))
666: {
667: return $this->relationships[$key];
668: }
669:
670: // Next we'll check if the requested key is in the array of attributes
671: // for the model. These are simply regular properties that typically
672: // correspond to a single column on the database for the model.
673: elseif (array_key_exists($key, $this->attributes))
674: {
675: return $this->{"get_{$key}"}();
676: }
677:
678: // If the item is not a loaded relationship, it may be a relationship
679: // that hasn't been loaded yet. If it is, we will lazy load it and
680: // set the value of the relationship in the relationship array.
681: elseif (method_exists($this, $key))
682: {
683: return $this->relationships[$key] = $this->$key()->results();
684: }
685:
686: // Finally we will just assume the requested key is just a regular
687: // attribute and attempt to call the getter method for it, which
688: // will fall into the __call method if one doesn't exist.
689: else
690: {
691: return $this->{"get_{$key}"}();
692: }
693: }
694:
695: /**
696: * Handle the dynamic setting of attributes.
697: *
698: * @param string $key
699: * @param mixed $value
700: * @return void
701: */
702: public function __set($key, $value)
703: {
704: $this->{"set_{$key}"}($value);
705: }
706:
707: /**
708: * Determine if an attribute exists on the model.
709: *
710: * @param string $key
711: * @return bool
712: */
713: public function __isset($key)
714: {
715: foreach (array('attributes', 'relationships') as $source)
716: {
717: if (array_key_exists($key, $this->{$source})) return ! empty($this->{$source}[$key]);
718: }
719:
720: return false;
721: }
722:
723: /**
724: * Remove an attribute from the model.
725: *
726: * @param string $key
727: * @return void
728: */
729: public function __unset($key)
730: {
731: foreach (array('attributes', 'relationships') as $source)
732: {
733: unset($this->{$source}[$key]);
734: }
735: }
736:
737: /**
738: * Handle dynamic method calls on the model.
739: *
740: * @param string $method
741: * @param array $parameters
742: * @return mixed
743: */
744: public function __call($method, $parameters)
745: {
746: $meta = array('key', 'table', 'connection', 'sequence', 'per_page', 'timestamps');
747:
748: // If the method is actually the name of a static property on the model we'll
749: // return the value of the static property. This makes it convenient for
750: // relationships to access these values off of the instances.
751: if (in_array($method, $meta))
752: {
753: return static::$$method;
754: }
755:
756: $underscored = array('with', 'query');
757:
758: // Some methods need to be accessed both staticly and non-staticly so we'll
759: // keep underscored methods of those methods and intercept calls to them
760: // here so they can be called either way on the model instance.
761: if (in_array($method, $underscored))
762: {
763: return call_user_func_array(array($this, '_'.$method), $parameters);
764: }
765:
766: // First we want to see if the method is a getter / setter for an attribute.
767: // If it is, we'll call the basic getter and setter method for the model
768: // to perform the appropriate action based on the method.
769: if (starts_with($method, 'get_'))
770: {
771: return $this->get_attribute(substr($method, 4));
772: }
773: elseif (starts_with($method, 'set_'))
774: {
775: return $this->set_attribute(substr($method, 4), $parameters[0]);
776: }
777:
778: // Finally we will assume that the method is actually the beginning of a
779: // query, such as "where", and will create a new query instance and
780: // call the method on the query instance, returning it after.
781: else
782: {
783: return call_user_func_array(array($this->query(), $method), $parameters);
784: }
785: }
786:
787: /**
788: * Dynamically handle static method calls on the model.
789: *
790: * @param string $method
791: * @param array $parameters
792: * @return mixed
793: */
794: public static function __callStatic($method, $parameters)
795: {
796: $model = get_called_class();
797:
798: return call_user_func_array(array(new $model, $method), $parameters);
799: }
800:
801: }
802: