-
Notifications
You must be signed in to change notification settings - Fork 28
Improve Typecasting #9
Comments
Been pondering about the idea of casting to custom objects myself. Rough idea: classes that implement an interface with some sort of serialize & unserialize method, the class name could then be passed in the interface DatabaseCastThing
{
public function fromDatabase();
public function toDatabase();
} class Email implements DatabaseCastThing
{
// ...
} protected $casts = ['email' => Email::class]; |
Is there a reason you don't want to use Accessors & Mutators for this purpose? I haven't seen any of the other discussions, so I don't know if it's been brought up before... but it's worth explaining why here, if it has. |
Well for starters, if you use accessors and mutators the result is an awful lot of code duplication. If you have a Model with 10 castable values, that's 10 accessors and mutuators you'd have to setup, chances are this would be across multiple models. The only way I can see you'd avoid that is either through an abstract model (assuming each model shares similar values) or through a castable trait. The other problem with accessors & mutators (and even the existing casting) is that nullable values required additional logic which again adds code duplication. Imagine reproducing this: public function getOriginalPriceAttribute($value)
{
if (is_null($value)) {
return $value;
}
return new Money($value);
}
public function setOriginalPriceAttribute($value)
{
if (is_null($value)) {
$this->attributes['money'] = $value;
}
$this->attributes['money'] = $value->toDecimal(); // or automatically to string via __toString(); etc.
} ^That's an awful lot of code for what is essentially some incredibly simple typecasting. Neither of these is a terribly elegant solution, compared to that of @sebastiandedeyne's idea above. Even there the toDatabase() method shouldn't be required in the interface... since an a value casted to an object with __toString(); would work just fine (assuming that is what the end-developer wants). |
I’m in the Accessors & Mutators camp for this. |
Casting to and from a JSON blob would be useful too |
How about dynamic casting (like polymorphism) ? can be also sorted in a custom db column (castables). |
Thanks for the detailed answer @AdenFraser - that's definitely a valid use case, would simplify a lot of repeated code for common things (common value objects come to mind!). Accessors and Mutators are great for simple one-off stuff, but it'd be nice to have the power of a class too without a lot of scaffolding. :-)
|
Not sure why anyone would ever be against this. I actually expected it to just allow a custom type when I first used casting. It's just something one would naturally expect it to support and would be very easy to do so. |
@taylorotwell What are your thoughts in regards to this? Obviously there are quite a few thumbs ups above aswell but having your go ahead before spending some time on a PR would be great! |
@AdenFraser I think you need to make the PR man :) the idea is quite interesting, now it's time to discuss implementation. |
Agree could be cool! |
Going to start putting some code together today and will put a PR together as a WIP |
Thrown together an early draft laravel/framework#13315 |
Casts are usually one-way right? Just for getting the data from the database, with the exception of json/datetime, which are also converted back. So do you want to account for the 'back', case as well? Also, isn't it easier to register custom casts, like the Validation? https://laravel.com/docs/5.2/validation#custom-validation-rules TypeCaster::extend('money', function($attribute, $value, $parameters, $caster) {
$currency = isset($parameters[0]) ? $parameters[0] : '$';
return new Money($value, $currency);
});
TypeCaster::extend('html', 'App\Html@convert'); Usage like this:
If you want to convert back, perhaps we could consider a second (optional) callback. TypeCaster::extend('money', function($attribute, $value, $parameters, $caster) {
return Money::fromString($value);
}, function($attribute, $value, $parameters, $caster) {
return Money::toString($value);
});
TypeCaster::extend('html', 'App\Html@encode', 'App\Html@decode'); Which would essentially be mutators I guess, but with easier configuration. |
Just do it like mutators in the model. You are overthinking this a lot. It Models dont use a factory so doing it like the validator extending or blade On Tue, 26 Apr 2016 22:37 Barry vd. Heuvel [email protected] wrote:
|
Also why would this need converting back? On Wed, 27 Apr 2016 07:35 Robbo [email protected] wrote:
|
If you cast it to an object and modify it, you want to return it to a value you can store in database, right? |
Yeah I definitely think there should be a |
What, no? If you need to return it to the database you are using this On Wed, Apr 27, 2016 at 7:47 AM Tom Schlick [email protected]
|
If you modify something in the cast object and then save the model it has to find its way back to the database. This is how the current casts system works. Try it with the carbon dates, it uses __toString() on the carbon instance. |
I don't see how that is modifying the model at all? It just casts for the attributes it is returning? |
Shows how the dates are formatted with the |
Yes, for the |
If you cast to datetime and simply use Carbon's ->addDay () method before For instance $product->released_at->addDay ();
$product->save (); On the save action the Carbon instance is converted __toString in the The same way if you modify a casted json array and subsequently save... it
|
Then you are using casts wrong and shouldn't do that. Why would you ever cast something and then save it? Casting happens as the last thing to send a response. And even if you did need to save after casting it you have a cast method that changes the model attributes in some way that is a bug in your code because you are doing it wrong. |
The exact thing could happen with attribute mutators because it is simply not how you should do things. |
In the case of a Money cast, something like this could be very simple (and $invoice->balance->reduce (2.50):
$invoice->due_date->addYear ();
$invoice->save (); The resulting row update for the model would perform the Money reduction If you cast to datetime and simply use Carbon's ->addDay () method before For instance $product->released_at->addDay ();
$product->save (); On the save action the Carbon instance is converted __toString in the The same way if you modify a casted json array and subsequently save... it
|
What? You are making zero sense. What has that code got to do with casting at all? |
This is achievable using mutators, you are correct. But i don't understand how you can claim that casting can only be the last thing that happens before we send a response. If that's the case then why is Eloquent casting dates to Carbon objects? Presumably based on what you are saying, just for response purposes, rather than for providing an API for manipulating dates. |
What if we'd have the caster mutate the model instead of returning a serialized value? Another example implementation, similar to @barryvdh's: class MyModel extends Model
{
protected $casts = [
'price' => 'money',
];
} class CastsMoney
{
public function get(Model $model)
{
return new Money($model->price_amount, $model->price_currency);
}
public function set(Model $model, Money $value)
{
$model->price_amount = $value->getAmount();
$model->price_currency = $value->getCurrency();
}
}
Casts::extend('price', CastsMoney::class); Advanced: You could pass values as parameters, for example the field names, or a hard coded currency like in the previous example. class MyModel extends Model
{
protected $casts = [
'total' => 'money:total_amount,total_currency',
'shipping_costs' => 'money:shipping_costs_amount,shipping_costs_currency',
];
} class CastsMoney
{
public function get(Model $model, $amountField, $currencyField)
{
return new Money($model->$amountField, $model->$currencyField);
}
public function set(Model $model, Money $value, $amountField, $currencyField)
{
$model->$amountField = $value->getAmount();
$model->$currencyField = $value->getCurrency();
}
}
Casts::extend('price', CastsMoney::class); I think this would cover everything we want:
|
FYI, a new PR has been opened by @tillkruss to further the work that was started on this. For now discussion it is locked as it is a work in progress. |
That was actually a typo, hence the $user->token = 'larav3l-secr3t'; // does not work
$user->save(); |
Why is it locked?
…On Sun, 26 Feb 2017, 02:44 Till Krüss ***@***.***> wrote:
That was actually a typo. I fixed it earlier today.
$user->token = 'larav3l-secr3t'; // does not work$user->save();
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA0UF6oGUaBwLgBW07q_p-viPbLcmp9rks5rgFpogaJpZM4Hwgiw>
.
|
Because I don't want people to comment, yet. If you have valuable feedback, ping me in Slack. |
What a bizzare thing to do. Just don't PR until ready...
…On Sun, 26 Feb 2017, 02:47 Till Krüss ***@***.***> wrote:
Because I don't want people to comment, yet. If you have valuable
feedback, ping me in Slack.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA0UFyBtqnOluRkwBPU8Cdcy5nKqb9jVks5rgFsrgaJpZM4Hwgiw>
.
|
Valueless, off-topic, thread-hijacking comments like that. That why it's locked. |
Wow I see now. Because you're a disrespectful little snowflake.
…On Sun, 26 Feb 2017, 03:29 Till Krüss ***@***.***> wrote:
Valueless, off-topic comments like that. That why it's locked.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA0UF0gCSZKB74uKD86eBioIezi3cIPTks5rgGTpgaJpZM4Hwgiw>
.
|
Ignoring Robbo's comments for a while, I too agree that it would be helpful to allow feedback directly on the PR, especially since it is work in progress. ;) |
That's actually agreeing with my comments. It's the entire point of a pull
request. Locking it is just disrespectful to anyone following this feature
request. It is saying to every one of them that they aren't good enough to
comment on the super amazing PR.
Regardless they will just discuss here anyway. So not only is the reason
for the lock stupid but it is ineffective.
…On Sun, 26 Feb 2017, 04:09 Franz Liedke ***@***.***> wrote:
Ignoring Robbo's comments for a while, I too agree that it would be
helpful to allow feedback directly on the PR, *especially* since it is
work in progress. ;)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AA0UF-yihFvHihrdcUCDacZW4DvPkUgpks5rgG5pgaJpZM4Hwgiw>
.
|
Well, I don't agree with your tone. |
Yeah because his tone was so welcoming. He was hostile first and the reason
for "valueless" discourse in the first place. Ironic really.
|
The current implementation in laravel/framework#18106 has a large flaw; it requires ownership of the objects you cast to. That is often not the case; an example are people using https://github.com/nicolopignatelli/valueobjects which would require an intermediate casting layer. Something like a TokenConverter that has a |
Can someone explain why this issue is so secretive that I need to talk to @tillkruss in a hidden thread in #internals on Slack instead of commenting in the PR? Additional things I've told @tillkruss in previously mentioned secretive thread;
|
I don't get it either. If you're not sure about the architecture, discuss that privately before creating a PR. If you do create a PR, accept feedback on it. These discussions are very valuable to read back later, to understand why something is done this way. Redirecting to Slack just means it's lost after a few days. I really hope this isn't a trend.. IMHO locking should be reserved for out of control threads with just spam, not reasonable discussions.. |
Can we please stop this secretive nonsense. The point of this repository is so that [expletive deleted] isn't just on slack which disappears. This creates paper trails so other people can actually see the conversations and IDK maybe not waste their [expletive deleted] time on things that have already been discussed that they couldn't possibly [expletive deleted] know because they weren't on slack. There has to be somewhere this information lives that is accessible. @barryvdh agreed pre-emptive heavy-handed moderating is not good |
Unlocked the thread and closed the PR. Will open new PR when it's ready. |
For those that missed it; a new PR was opened. Every single point I made was ignored.
A quick reminder from that hidden-in-history Slack thread. Did any of that happen? |
It's way early in the morning and I misread the PR. It's not yet merged, and I updated my previous comment to remove that part. I know I am bitter and filled of hatred, but could there at least be some notes about the different suggested implementations that show why those were declined? It's almost a year since the most thumbed-up (which I think means some kind of approval) comment suggested a way to do this, why wasn't that implemented? #9 (comment) |
I mean... his first PR showed he doesn't care what other people think. Instead of closing he will just ignore everyone because he is better than them (unless one of the Laravel "inner circle twitter gods"). |
This has been discussed before in different issues raised laravel/framework.
I think it would be rather handy if Eloquent's typecasting was extendable, so that as well as being able to automatically cast to the existing:
integer
,real
,float
,double
,string
,boolean
,object
,array
,collection
,date
anddatetime
That we could also cast to custom cast types.
For instance when using value objects this would allow the automatic casting of a dec to a Money type object.
Thoughts?
The text was updated successfully, but these errors were encountered: