As suggested here: http://stackoverflow.com/questions/18747500/how-to-set-a-default-attribute-value-for-a-laravel-eloquent-model/
After discussed here: http://irclogs.laravel.io/2013-09-11
I just want to make new instance of my Eloquent model to behave coherently with my database schema (which has default values set up).
I've created this PR: https://github.com/laravel/framework/pull/2264/files
I've closed the PR because I had not seen the "Laravel Contribution Guide" before I've opened it... anyway, since it is there, I am linking to it from here. Sorry, guys! I didn't know I had to open the Proposal first. I apologize.
Thank you.
:+1:
I would add the method getDefaults() so it would be possible to use defaults in schema migration.
The typical use of a constructor is initializing values. Why does it deserves to be in the Eloquent Model? I mean, if you are needing this in a project you could extend Eloquent like
class MyBasicModel extends Eloquent{function __construct(){..}; function getDefaults()...etc};
and then:
class AnyModel extends MyBasicModel
I think the way it works currently is just fine. Featuritis is a hard problem to solve and this PR doesn't bring us any needed functionality.
Sorry Jbruni, it's just my opinion. I hope it doesn't bother you..
I am actually extending Eloquent (in fact, the class name is _Model_) Ardent to create my own CustomModel class, base for all my application models, only because of this issue.
It is not as simple as stated above... the Model constructor accepts a parameter... we need to get it and pass to the parent::__construct... why all this hassle? And what if Ardent or Eloquent's Model constructor signature changes?
Not only this, but also: even inside the constructor (or "getDefaults"), the syntax needs to be $this->attributes['field_name'] = 'default';
All these considerations... only to have my default value there?
Doctrine also does not handle defaults, but everything is perfect if you simply assign a default value to a model property:
public $field = 'value';
Instead, using Eloquent's Model as it is now, one needs to do something like:
class MyModel extends Model {
public __construct(array $attributes)
{
$this->attributes['field'] = 'value';
parent::__construct($attributes);
}
}
This doesn't seem quite right. But it is not equivalent to Doctrine's example yet! Keep reading.
We don't have tackled the core issue yet: by doing like above, $this->original becomes different than $this->attributes. Eloquent thinks the model is dirty. A call to getDirty will return true, even though we have merely set the database's default to the attribute!
This is why in the solution I am actually using in my extended Model base class, which can be seen both at Stack Overflow and the premature Pull request, I am using setRawAttributes... it is because, through this method we have syncOrignal called, and then the _dirty_ issue is resolved.
Enough? Sounds too much to who just wanted to have a default value... luckily, Laravel's code is really good, well written, commented, understandable!
Finally, having tackled around this issue for a while, I propose a new and better solution.
In order to set default values (and this info should be included in the docs), just do:
class MyModel extends Eloquent {
protected $attributes = array(
'field' => 'default',
'field2' => 'default2',
);
}
Voil脿! BUT, in order for this to work, instead of declaring the same array again as protected $original, we just need to introduce a single new line into Eloquent's Model constructor:
$this->syncOriginal();
This should go above the $this->fill($attributes); line.
Of course, instead of the syncOriginal() call, we could simply have:
$this->original = $this->attributes;
With this simple, straight and innofensive line, it is possible to set defaults as described above. Just add a snippet about it to the docs, and others will not need to dive into all these Laravel's internals details mentioned above to find out how to do it. Not to mention not needing to extend, override constructor, and so on.
So, the Pull Request to solve the issue can be reduced to a single liner. What do you think? @taylorotwell? Makes sense?
Let's do a new comparison, considering this newly suggested PR was merged.
Doctrine:
public $field = 'value';
Eloquent:
protected $attributes = array('field' => 'value');
Slightly better than before, IMO.
:+1: +1
I don't even get the point of this? Your database should already insert the default values if you don't pass them, correct?
So lets say you create a new model and omit the type field. In the database, this defaults to 'large' but your Eloquent model isn't aware of this because it hasn't been explicitly set. Most of the time it's nice to have complete data, for example if you want to do anything further with the newly created model. Choices are to either set the default values locally or to make another call to the database to get the 'complete' model, which slows things down.
OK, so how is this different from just overriding the $attributes property on your model?
Can this be done on the model itself? e.g.
class Table extends Eloquent {
$attributes = [
'type' => 'wooden',
'legs' => 4
];
}
@BlueHayes: New to Eloquent, I thought I could do:
class Table extends Eloquent {
public $type = 'wooden';
public $legs = 4;
}
After some iterations, and learning about it basically by looking at the Model class code, and also playing with it, I've understood the $attributes mechanism. It took me a little while.
So, yes, you can just override the $attributes property, but:
1 - Nobody at Stack Overflow could provide this information when asked; and not even right here (people is telling about extending / overriding constructor); right now you yourself are asking for confirmation! Anyway... a snippet of documentation would resolve this.
2 - By doing this (i.e., overriding $attributes), if you call isDirty in a new instance the result will be true, even though your model is not dirty. That's why my request became as simple as adding either a
$this->original = $this->attributes;
or
$this->syncOriginal();
line in the Model constructor, before the call to fill. It is innofensive and it properly and completely resolves the issue.
Added syncOriginal before fill in parent constructor.
Most helpful comment
I am actually extending
Eloquent (in fact, the class name is _Model_)Ardent to create my own CustomModel class, base for all my application models, only because of this issue.It is not as simple as stated above... the Model constructor accepts a parameter... we need to get it and pass to the
parent::__construct... why all this hassle? And what if Ardent or Eloquent's Model constructor signature changes?Not only this, but also: even inside the constructor (or "getDefaults"), the syntax needs to be
$this->attributes['field_name'] = 'default';All these considerations... only to have my default value there?
Doctrine also does not handle defaults, but everything is perfect if you simply assign a default value to a model property:
Instead, using Eloquent's Model as it is now, one needs to do something like:
This doesn't seem quite right. But it is not equivalent to Doctrine's example yet! Keep reading.
We don't have tackled the core issue yet: by doing like above,
$this->originalbecomes different than$this->attributes. Eloquent thinks the model is dirty. A call togetDirtywill returntrue, even though we have merely set the database's default to the attribute!This is why in the solution I am actually using in my extended Model base class, which can be seen both at Stack Overflow and the premature Pull request, I am using
setRawAttributes... it is because, through this method we havesyncOrignalcalled, and then the _dirty_ issue is resolved.Enough? Sounds too much to who just wanted to have a default value... luckily, Laravel's code is really good, well written, commented, understandable!
Finally, having tackled around this issue for a while, I propose a new and better solution.
In order to set default values (and this info should be included in the docs), just do:
Voil脿! BUT, in order for this to work, instead of declaring the same array again as
protected $original, we just need to introduce a single new line into Eloquent's Model constructor:This should go above the
$this->fill($attributes);line.Of course, instead of the
syncOriginal()call, we could simply have:With this simple, straight and innofensive line, it is possible to set defaults as described above. Just add a snippet about it to the docs, and others will not need to dive into all these Laravel's internals details mentioned above to find out how to do it. Not to mention not needing to extend, override constructor, and so on.
So, the Pull Request to solve the issue can be reduced to a single liner. What do you think? @taylorotwell? Makes sense?
Let's do a new comparison, considering this newly suggested PR was merged.
Doctrine:
Eloquent:
Slightly better than before, IMO.