Attribute Casting Overview0:00
For a while now, Laravel has allowed us to cast attributes. So in this example, the email_verified_at column will be cast to a datetime. Or here's another example. If I boot up php artisan tinker, I have a User instance here, and notice that our settings are stored as a string. But when we access them, I'd like to interact with it as if it were an object or an associative array. Here's how. And again, this is nothing new in Laravel 7. But I could say I want to cast the settings column to an array or JSON. OK, so now if I run it again, and I fetch the settings, notice that this time it was cast to an array, and I can interact with it. Neat. So now what's new in Laravel 7 is the ability to create custom casting. For example, some people like to store an email address as a value object. But that's always been difficult with Laravel and Eloquent until now. All right, if you'd like to do that, let's create a new custom
Creating Email Custom Cast0:54
some people like to store an email address as a value object. But that's always been difficult with Laravel and Eloquent until now. All right, if you'd like to do that, let's create a new custom cast, and I'm going to call it EmailCast. All right, that doesn't exist. We will create it now. And I'm going to store it in a directory, whatever you want, but we'll call it casts. All right. Now, in order to make this work, we need to implement the CastAttributes interface. And this has two methods here, get and set. Get would be when you get it from the database, how do you want to transform it into your desired type? And in this case, we would transform it into an Email instance. Set is the opposite. When it's time to set it back to the database, how do we transform it? All right, let's give it a shot. So to get it from the database, we're going to transform it into a new Email value object, right? So we need to create that.
how do we transform it? All right, let's give it a shot. So to get it from the database, we're going to transform it into a new Email value object, right? So we need to create that. We'll do it now. Email. This is, it'll just be mutable, that's fine. It'll have the address, and I'll create the constructor here. And we can come back to that in a minute. But that'll get us started. So now, when we get it from the database, we will pass the value to this new Email instance. And actually, real quick, this is confusing. The model, so let's say we have our User model, and we're grabbing the email there. The model is the User instance. The key variable or parameter is the string email. The value would be the email address itself. And then attributes will be all the raw attributes from the table that you can work with. And that can be useful when multiple columns should be passed to a value object. And we'll look at that in a moment. Okay, so now,
the raw attributes from the table that you can work with. And that can be useful when multiple columns should be passed to a value object. And we'll look at that in a moment. Okay, so now, we've converted an email address into an Email type. set will be the opposite. So value is now our Email instance. How do we convert it back? Well, let's just grab the address there, or a method if you want that to be immutable. And return it. And that's it. So now, back to our User model, we now have our custom cast that I will import at the very top, and we're ready to try it. Boot up php artisan tinker. Track down our User. And now, if I grab their email address, I won't get a string, I will get a new Email type. Okay, so now, if you have any extra logic or validation logic, you can throw it directly on that class. For example, if you want to protect the integrity, we could say if not, and we'll use filter_var, that's fine, and validate the email address.
Adding Email Value Logic3:21
you can throw it directly on that class. For example, if you want to protect the integrity, we could say if not, and we'll use filter_var, that's fine, and validate the email address. If it's not a valid email address, then throw a new InvalidArgumentException. All right, otherwise set it. So now we know the condition for creating an Email instance is a valid email address. All right, so next, yeah, if there's any additional logic you might need, even something as simple as get the domain for the email address, well, now you have a place to store that logic. And just for fun, you could even use the str helpers that you learned about in the last episode, just for fun. Give me everything after the @ symbol, and we will assume that is the domain itself. All right, one more time. Boot up tinker. Grab our User. And now, look at their email address and check the domain. Ah, in this case, we do have a stringable type. Of course, we can convert that.
Casting Multiple Columns Together4:09
itself. All right, one more time. Boot up tinker. Grab our User. And now, look at their email address and check the domain. Ah, in this case, we do have a stringable type. Of course, we can convert that to a string, like so, and that does work. But in this case, let's just convert it. And there you go, your first custom cast. All right, one more. So I have a Vacation model here that I've created. And you'll see in the migration file, for a Vacation, you give it a name, and you give it your start date and your end date. All right, well, sometimes you may find that two particular columns are always passed around together, almost like they're linked. And the most ubiquitous of examples of this would be a currency type and an amount, right? The dollar sign and five, those always go along together. So in those situations, if they're connected, make sure you group them in their own type. And maybe that's the case here. The start date and the end date might represent
always go along together. So in those situations, if they're connected, make sure you group them in their own type. And maybe that's the case here. The start date and the end date might represent the schedule for your vacation, or the range, whatever you want to call it. All right, let's see if we can make it work. So I'm going to go to my Vacation model, and we're going to set our casts, and I'm going to add a whole new one here called schedule, and that will be cast to a ScheduleCast. Now, this name and convention, do anything you want. You don't have to add cast there. The only thing is, often your cast type will be the same name as your value object. So you might want to alias it or distinguish it in some way. All right, so let's go ahead and create that, once again, in a cast directory, and we will implement the cast attributes. All right, so now it sounds like we need a new Schedule value object. So I will create that now.
once again, in a cast directory, and we will implement the cast attributes. All right, so now it sounds like we need a new Schedule value object. So I will create that now. And this will consist of the start as well as the end date, and then we'll add our constructor here. There we go. Now, when we cast it, let's switch back, that will return a new Schedule. However, in this case, there is no value associated with that type. So this is where attributes come into play. Let's say attributes start and attributes end. All right, that looks good. So now, this value param here would be equal to our Schedule instance. Let's prepare it for the database, and we do that by returning an array. So start would be the value start_date, and the end would be the other one there. Are we all on the same page? So once again, our Vacation model now uses a custom cast that we import. This new field, it's like a magic getter, schedule,
Syncing Value Object Updates6:42
end would be the other one there. Are we all on the same page? So once again, our Vacation model now uses a custom cast that we import. This new field, it's like a magic getter, schedule, will now be cast using ScheduleCast, that will create a brand new Schedule value object consisting of these two attributes. But now here's what's really cool. So I'm going to boot up tinker again, and I should already have a Vacation saved. Yep, let's go ahead and save that. And I can access the schedule. But here's what's really cool. If I update the value object itself, and then I persist the model, everything will be synced up. So let's change the endDate to how about now add 12 hours or something. All right, so now if you take a look at the schedule, very short trip. But if I save the Vacation, all right, let's come back to SQL Pro. Notice the endDate here was set to March 12. If I give it a refresh, now that is updated as well. And that's
Encapsulating Schedule Logic8:24
we'll use Carbon here, wrap the end date in Carbon and check to see if it's less than or equal to tomorrow. Would that be okay? We're basically checking to see, look at the end date and see if it's less than tomorrow. And if it is, we're getting very close, we're nearing the end. All right, so let's wrap up by making sure this works, and then we'll call it a day. Boot up php artisan tinker, find our vacation, take a look at the schedule. All right, and check if we are nearing the end. Yes, we are. So again, the whole point is you now have a place to store this sort of logic. If you think it's necessary. So again, don't go crazy with this. I would not start creating custom casts for every single attribute. You only want to do it in areas where, where you get a notable benefit. Otherwise, keep it simple.
