Introducing PHP enums0:00
Laravel 9 also ships with better support for enums, and this includes things like attribute casting and even implicit route binding that is backed by an enum object. Now, if you're not familiar with an enum, here, I'll set up an example. Maybe you have a directory for enums. A enum, think of it as a type with a fixed number of values. For example, if you're creating a Post, you might want to track the state that the Post is in. For example, is it in a draft state? Is it published? Is it archived? Is it something else? You can use an enum for that. But notice real quick, I'm getting a squiggly,
Upgrading to PHP 8.10:29
Is it something else? You can use an enum for that. But notice real quick, I'm getting a squiggly, and that's because enumerations are exclusive to PHP 8.1 and up. So why don't we bump our language level and continue with the assumption that your local PHP install needs to be at least PHP 8.1. So notice what I have on my machine is actually 8.0.9, but I also have 8.1 as an alias. So if I run that, I do get actually a pretty old version of 8.1, but it should still work. But yeah, mental note to update that.
but it should still work. But yeah, mental note to update that. Okay, so let's set up the various states for a Post. We could have a draft state. We could have a published. We could have an archived. If you have any other ideas on hold, I don't know. Any other ideas, you could put them here. And now what's cool is in Laravel 9, we can do attribute casting to and from an enum.
Adding state to posts1:17
And now what's cool is in Laravel 9, we can do attribute casting to and from an enum. So if you want to see that in action, the first step is to update our original migration to include the state that the Post is in. And actually, a couple of notes we should go over here. So we could set the state, and then maybe if the default is draft, we could make that explicit. And this is probably what I would do. You also have the option, though, of creating an enum type,
And this is probably what I would do. You also have the option, though, of creating an enum type, so an enum column. And initially, that sounds pretty good. So we could do something like this, and then you could say draft, archived, published. So now, as opposed to what we did here, this creates a column that exclusively allows these three values and nothing else. So in this case, the column is sort of protecting its own integrity.
you could potentially end up in a situation where two members of your team have a different database state. Imagine I run the migrations with this configuration. So now I have a DB with a POST table with a state column that includes these three values. But then three months later, we add a new case. Maybe this is a hold or something like that. And then somebody new who joins my team runs the migrations. Now, he will have a POST table with a state column with four options, whereas I only have three columns.
Now, he will have a POST table with a state column with four options, whereas I only have three columns. And that's not what we want. It sort of defeats the purpose of migrations entirely. We don't want a situation where you and I have different database environments. So that is something to be aware of, and it's probably a good argument for allowing the duplication to remain. Okay, so that's one thing to consider. The next thing to consider is enums sometimes are frowned upon. Yes, they protect their integrity,
The next thing to consider is enums sometimes are frowned upon. Yes, they protect their integrity, but many developers prefer to handle that on the PHP side. And that's because maybe if down the line you do need to add or change which values are acceptable, that would require when you run your migrations, it would lock the database entirely to rewrite that, which might potentially be a problem. So with that in mind, often you'll find that developers will skip the enum type and instead stick with a standard varchar string column.
Migrating and verifying schema4:03
So with that in mind, often you'll find that developers will skip the enum type and instead stick with a standard VARCHAR string column. And then again, they protect the integrity on the php side. All right, so let's try this out. Now remember, I am using enums, so I'm going to use my php 8.1 install. So I will say php artisan migrate:fresh, and then also I will seed my PostSeeder. Okay, so now have a look. If I take a look at the structure of my Post table,
Okay, so now have a look. If I take a look at the structure of my posts table, I do have a new state column that defaults to draft. All right, so check this out. If we go to our routes file on my home page, why don't we just return a random Post? All right, let's just grab the first one, and then I will grab the state. And of course, right now we both know that's going to return a string of draft. Let's give it a shot.
Casting attributes to enums4:50
And of course, right now we both know that's going to return a string of draft. Let's give it a shot. So I will set up a local server using php 8.1, and of course we see draft. Okay, now let's set up attribute casting. If I go to my Post model, I can say protected $casts, and I'm going to, from the database, cast the state column to our new enum. Just like that.
cast the state column to our new enum. Just like that. Okay, but now before we try this, think about what's happening here. Attribute casting is a two-way process. We need some way to cast it to a value that's saved in the database, and then we also need the opposite, a way to take the value from the database and cast it to the proper case on your enum. So we have our cases here,
and cast it to the proper case on your enum. So we have our cases here, but we don't have any values associated with them. And if we're going to use attribute casting, that's a requirement. Okay, so let's make them equal to, and even if it's the exact same value, that's entirely fine and common. So no problem there. Now, if I run this, though, you'll see my editor is squawking,
So no problem there. Now, if I run this, though, you'll see my editor is squawking, and that's because when you add values to an enum case, you are now turning it into what's known as a backed enum. And when you do that, you have to declare the return type. So we'll do that here. And now that goes away. Okay, so now if you think about it, when I grab state, that's no longer a scalar that I can easily turn into a response,
when I grab state, that's no longer a scalar that I can easily turn into a response, and we can see that right here in the browser. Symfony's squawking. It's trying to figure out how to format this response, and that makes sense. Let's just dd it for now, and you'll see it's no longer the string draft. It is now an enum object. And by the way, if you do ever need to grab the actual scalar value,
It is now an enum object. And by the way, if you do ever need to grab the actual scalar value, then just call value, because remember now state is an enum object. So you can grab its name. You can grab its value. You can even call methods on it if you need to. And there you go. Now, a couple more things. Here's some cool stuff.
Now, a couple more things. Here's some cool stuff. If I were creating a brand new Post, well, again, we protect the integrity of that column a little bit by enforcing that what you provide for state has to match something from your post state enum. So in this case, if I try to set it to gibberish, Laravel's going to squawk. It's not a valid backing value for this enum. But if I set it to anything that is supported,
It's not a valid backing value for this enum. But if I set it to anything that is supported, so it could be a string archived, like something that's coming from a form. No problems there. Or, of course, I could reference one of the cases. And notice when I'm using an editor, I get nice completion here, which is also a little win. So maybe I want to set the new Post to a state of published.
which is also a little win. So maybe I want to set the new Post to a state of published. No problems there whatsoever. Kind of cool. So you get some protection out of the box. And, of course, when the Post is persisted, let's just finish this up really quick. My title. And then for the body, my body.
And then for the body, my body. I think that's everything, right? Let's save it. Oh, and you know what? We do have to set the userId, don't we? Here, let's just fake it. postUserId equals one. Okay, so if we return done, and we run this in the browser,
And it's all being declared here. So now, when you are working with, let's grab some kind of existing Post. A little tip here. Did you know if you ever want a random Post, you could say Post::inRandomOrder(), and then give me the first()? So maybe for your blog, if you ever want to promote some random Post for every page load,
if you ever want to promote some random Post for every page load, that would be one way to do it. But yeah, if I ever want to say something like, is the Post currently in draft form? Of course, I might have a inDraft method on my Post model, but behind the scenes, it would probably do something like, check if the state is equal to PostState, and then see if it's in a draft form.
Enum route binding constraints8:47
check if the state is equal to postState, and then see if it's in a draft form. And if it is, we can say it is a draft, or proceed however you need to. Come back, refresh, and it works. Okay, the final thing, and then I'm going to let you go. So we can leverage enums for attribute casting, and you can also use them as implicit route binding constraints, so to speak. So maybe we have something like,
as implicit route binding constraints, so to speak. So maybe we have something like, I don't know, it's going to be a little superfluous. Maybe we want to grab all Post models that are in a current state. Then we could maybe do, there's a few different ways we could handle this, but maybe if we're doing it as part of the URI, we could accept state as a wildcard, and then I'm going to add a constraint.
we could accept state as a wildcard, and then I'm going to add a constraint by providing an enum type, so Post::state, like this. Okay, so let's just do a dd(state), and see what we get here. So posts/, and then draft, and we get our draft object archived, but now what if I provide something that is not a case on that enum, like that?
but now what if I provide something that is not a case on that enum, like that? Aha, we get a 404. So again, this is a new thing in Laravel 9. We can use an enum as a constraint for this wildcard binding here. As long as the wildcard's value is included here, it's going to work, and it will proceed, but if the wildcard is something different, then a 404 will be thrown.
but if the wildcard is something different, then a 404 will be thrown because the route does not match up in that case, and that's what you'd expect. Okay, we covered quite a bit there. Watch it two times if you need to, but yeah, I am very happy to see this in Laravel 9.
