Introducing Custom Collections0:00
Next, I want to talk to you about custom collections. This is a useful tool to reach for when it makes sense. So here we have the Achievement Eloquent model. This means I can do things like this, Achievement, and give me all from the database, right? Standard stuff. Now, when we run that query, we're going to get an instance of the collection class, Illuminate\Database\Eloquent\Collection. However, if and when you need to, you could have it return your own custom collection class. For example, what if on this result set, you want methods like, we don't offer it right now, but maybe I want only the expert-level achievements.
For example, what if on this result set, you want methods like, we don't offer it right now, but maybe I want only the expert-level achievements. So wouldn't it be cool if I could say, okay, I had this Collection class, but now give me only the subset. Give me only the expert-level achievements, or the beginner-level achievements. Or maybe give me only the achievements for the given User. Wouldn't that be really useful? And we can do it really easily. So let's do this. Well, first, I want a test.
Writing the Collection Test1:01
So let's do this. Well, first, I want a test. So I'm going to go to my AchievementsTest, and you'll see we have one for the general feature, and then I have a lower-level unit test from earlier. We're going to reuse that. Okay, so we'll say here, when we fetch all of our achievements, it returns a custom AchievementsCollection. Okay, so now if I were to say, give me all achievements, we'll return that. That should be an instance of our new collection class. So I could say assertInstanceOf, it's going to be called Achievements.
Creating Collection Class1:31
That should be an instance of our new collection class. So I could say assertInstanceOf, it's going to be called achievements. So something like that. And we will import that. It doesn't yet exist, but we're going to import it. All right, let's give this test a run, and of course it fails. Let's make a pass. I'm going to add a new class here called Achievements, and we'll have it extend the Illuminate\Database\Eloquent\Collection class. That way we inherit all of the standard collection functionality that you would be used to.
Overriding newCollection1:59
Eloquent collection class. That way we inherit all of the standard collection functionality that you would be used to. But now if I give it another run, of course it's still going to fail, and that's because at no point have we told Eloquent what to return. We haven't overridden that. Okay, well there is a method called newCollection on the parent model class. Create a new Eloquent collection instance. So behind the scenes, Laravel is going to call this method, and this is what determines what we return when you call the various Eloquent query methods. Okay, so why don't we override that?
what we return when you call the various Eloquent query methods. Okay, so why don't we override that? New collection, there it is. And instead, I'm going to return a new instance of our Achievements class, and pass in the various models. Okay, now if we give this a run, we get green. All right, so take a look. If we try this out, we'll say, what, it's in the Achievements, Achievement class. Let's grab all of them, and look at the top. They're now wrapped within our custom collection, which means now we have a point to add all
Let's grab all of them, and look at the top. They're now wrapped within our custom collection, which means now we have a point to add all of that extra functionality that we were talking about. So if I do separate Achievements according to their skill level, the expert level Achievements, now we have a place to filter that down. Useful, right? What about the other one where we said, give me only the Achievements for the given User? Now has has always been a reserved word, but I think, and I can't remember which version, php 7.1 or 2, that became an acceptable method name. But yeah, if you get an error there, you're probably on an older version of php, in which
PHP 7.1 or 2, that became an acceptable method name. But yeah, if you get an error there, you're probably on an older version of PHP, in which case you can just change the name there. Okay, so in these cases, you have two choices. You have access to your items. So if we go to the collection, where's items? I'm sorry, it's going to be on the base collection. And if we scroll down, all of the items in the collection will be stored within this array. So anyways, if we were to come back, yeah, if you want, you could just map over them,
array. So anyways, if we were to come back, yeah, if you want, you could just map over them, or filter them down is probably what you actually want. Filter it down, and only return the ones that match this bit of criteria. If you need to, you could even collect that into a new subset. But in our case, we already have that userAchievements relationship, and we know we'd have to do a database query anyways to figure out which achievements the user has been assigned. So you know what? I'm just going to do this, and that will return to me a new achievements collection. But why don't we write a test for that?
Testing User Award Filter4:19
I'm just going to do this, and that will return to me a new Achievements collection. But why don't we write a test for that? So let's comment this out temporarily. We'll switch back, and we'll say, it can filter achievements. It's really not filtering it, it's doing a new relationship query. But for all intents and purposes, it's a filter. It can filter achievements down to only those that the given User has been awarded. Okay, so let's fetch all achievements. And you know what? We need to build a bit of a world here.
And you know what? We need to build a bit of a world here. So let's do this. Let's say factory, and we're going to whip up an achievement. There we go. Give me two achievements in our database. All right, next, I need to create a User. So I have this helpful method in my project called signIn that whips up a new User and sets them as the authenticated user. I now want to say that authenticatedUser should be awarded one of these achievements.
sets them as the authenticated User. I now want to say that authenticated User should be awarded one of these achievements. So we'll say, give me the first one. And you'll remember on our Achievement class a long time ago, we added this method, awardTo. So let's go ahead and reference that, awardTo the authenticated User, or I can access that through the user property in my system alone. Okay, so now, if we fetch all of our achievements, and let's see, to start, we could say, well, we know there should be two total achievements. So if we were to run that, and I'm sorry, did I delete that by accident? Anyways, if we were to run that, we should get green, and we do.
So if we were to run that, and I'm sorry, did I delete that by accident? Anyways, if we were to run that, we should get green, and we do. But now we want to ensure that if we filter it down to only those awarded to the given User, that should actually be one. So let's say for this User should be one. We give it a run, and it fails, because that method doesn't yet exist. You'll remember we commented it out. So we add that back on, we run it, and we're back to green. And yeah, that's all there is to it. So we now have a good place to add any extra functionality.
Extending Collection Features6:17
And yeah, that's all there is to it. So we now have a good place to add any extra functionality. Like I said, I'm pretty sure at some point I will split achievements into kind of like a skill level. And you can imagine maybe I use that to determine the background color. Like maybe basic achievements have a green color, medium or yellow, and expert level achievements get a red color. Well, maybe we could use a custom collection method like this to filter those down and group them together. Maybe we could add additional methods, like give me only the achievements that are, I
group them together. Maybe we could add additional methods, like give me only the achievements that are, I don't know, what's a good name, remaining for the given User. So if this method returns the achievements the User has been awarded, this would be almost like the opposite. This would be the achievements that the User has yet to be awarded, if that makes sense. And once again, we could filter those down however we need to. So a lot of flexibility here. Again, this is a really good tool to have in your belt when and if you need it.
