تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Rethinking TDD Approach0:22

As an example, imagine a songwriter who constructs a perfect song using the very first idea and the very first line that came to their head. It's simply not the way it works. So what's useful about TDD is it gives you a scratch file of sorts to figure out how you want things to behave. So let me elaborate a bit more. I've been doing some thinking. We're trying to get this test to pass, right? And at the moment, of course, it's failing. So if we create a User, and then we award a User a certain number of experience points,

Code-Defined Achievements Idea0:43

And at the moment, of course, it's failing. So if we create a User, and then we award a User a certain number of experience points, well, when we collect their achievements collection, we expect one achievement to be returned. And the way we constructed this is we were thinking, well, we'll have users, and we'll have a table for achievements, and then we'll have a pivot table to store a relationship between the User and the Achievement. This is a very common and traditional approach. But one thing I've been considering is where do we store the logic that specifies whether or not the User has earned this achievement? So yes, we can have a row in the database, but where do we store the logic that determines

or not the User has earned this achievement? So yes, we can have a row in the database, but where do we store the logic that determines has this User earned or unlocked that badge, right? Well, that needs to be stored within code. So if that's the case, what I've been thinking is maybe we can store all of the achievements within code. That way, we have a single point to declare the name of the achievement, what the icon is, as well as a function that can determine has the User earned this badge. So if we took that approach, we may not need these at all. So in this episode, together, I just want us to tinker around and see what that might

Registering Achievements Provider1:46

So if we took that approach, we may not need these at all. So in this episode, together, I just want us to tinker around and see what that might look like. Now, in terms of registering these achievements, why don't we create a dedicated provider for that? So we'll call this AchievementsSurfaceProvider, all right? Now, within the boot method, what I'm thinking is we'll have some kind of collection class like Achievements, and that way I can say, Achievements, register a new badge. We'll give this some kind of fun name like MovingOnUp. We'll then give it a handler that accepts the User, and now this logic will determine

We'll give this some kind of fun name like MovingOnUp. We'll then give it a handler that accepts the $user, and now this logic will determine if this $user has unlocked the MovingOnUp achievement badge, okay? So in this case, we'll say, get their experiencePoints and let me know if it is at least 1,000, and if so, they have earned it. Finally, as the third argument, we can give it some kind of badge or stick with the default. So this is what I'm thinking here. No longer are we storing the achievements in a database table, we're instead doing it all through code. So now you can imagine another one here, something like LayerCastMastery, and maybe that will

Building Achievements Collection2:52

all through code. So now you can imagine another one here, something like LayerCastMastery, and maybe that will check the number of completed lessons, and if you've completed the entire catalog, you get this badge, right? I'm not going to do that. We'll just say, if you have 10,000 points, then you have unlocked that badge. All right, we'll stick with these two as an example. Now, let's work on this Achievements class. Let's come back. I'm going to create a new file, and this will be Achievements, and that will be located

Let's come back. I'm going to create a new file, and this will be Achievements, and that will be located within a new Achievements folder. So we know that we need a static function to register a new achievement. We need the name, the handler, as well as the icon file name. So now I need to decide, do I want this to extend a Laravel Collection? Because it is basically a collection class, and I know I'm going to want things like foreach and filter. So I could either implement those myself or extend the Collection class. Why don't we try the latter and see how that goes?

So I could either implement those myself or extend the Collection class. Why don't we try the latter and see how that goes? So I will extend Illuminate\Support\Collection. Okay, and now I could say something like this, but I do have a static function. Let's make that non-static. And I can say this push. So this is a method, of course, on the Collection class. It will push a new item onto the end of the collection. So let's push a new object, and this will just be the name, the handler, and the icon. Okay, so now we have a collection of one item, and that item is equal to an object where

So let's push a new object, and this will just be the name, the handler, and the icon. Okay, so now we have a collection of one item, and that item is equal to an object where we have the name of the badge, a handler that validates if the user has earned it, as well as an icon file name. So now, if I switch back to our ServiceProvider, let's instantiate it now. And now here, we can swap that out. Okay, so at this point, we should have exactly two items. Let's go ahead and register it. Within config/app.php, I'll scroll down to my list of application service providers, and now I'm going to have a new one here for the achievements.

Within config/app.php, I'll scroll down to my list of application service providers, and now I'm going to have a new one here for the achievements. Okay, so now if we switch back, take a look. Let's dd the list of achievements, and if I run a test, we'll hit that service provider, and you'll see, there we go, we have a collection of two items. So now, I can use my collection-friendly methods, for example, to grab the first one, and that will be an object. So let's get the name of the first one, moving on up, or the icon, and of course, we do have a handler that we can trigger. Now you'll notice that in this case, I didn't start with a test, and that's why I said I

a handler that we can trigger. Now you'll notice that in this case, I didn't start with a test, and that's why I said I wanted to tinker around. I think often people get caught in this trap where they think, I don't even really know what I'm supposed to do, but I have to start with a test, but I don't even know what I want it to look like, right? So the fact that you're following TDD does not mean you can't ever spike things out. It just doesn't, right? Some people might want to convince you of that, I'm not going to do it, okay? Sometimes you do need to tinker around, that's why I use that word.

Resolving Achievements in User6:24

So now if I switch back to our main feature test here, we can get rid of all of that now. If I run this, of course it's still going to fail, but let's think about it. Yes, we've awarded a User a bunch of experience points. So now, because this will no longer return a database table or a collection, let's implement that method. If I switch to my User class, I will comment out our relationship here, and instead, well, I want to access that collection of achievements here, right? So if I switch back, we registered them, but I can't get access to that. So here's what we're going to do. Let's move this all to the register method of our service provider, and then I'm going

So here's what we're going to do. Let's move this all to the register method of our ServiceProvider, and then I'm going to throw it into Laravel's service container. So we'll grab the container, and all we need is a singleton here. So let's say Achievement class, and now we'll nest all of this here. And return it. Okay, does this make sense? We're registering a single instance of an object within Laravel's service container. And if I ever need to resolve it out of the container, I'm going to get this collection here.

And if I ever need to resolve it out of the container, I'm going to get this collection here. Okay, so now take a look. If I were to switch back to our User class, let's now resolve it out of the service container. And I will die and dump that. So now if I run it, sure enough, we get that instance there. Excellent. So let's say, resolve this out of the service container, and then I'm going to filter over it. So we're going to run through all of the achievements.

Let's send that through and pass the user. So to be clear, when we call handler and we provide the user, what is happening? Well, we specified what would happen right here. This will be stored as the handler. So we are calling that function, passing through the user, and then each of these handlers will inspect the user and return a Boolean that indicates if the user deserves the badge. And remember, yes, we're doing it as a callback function here, but you could also accept a class. That way, if you have kind of a bulky amount of work to do to check if a user has earned a badge, you can delegate that to a full class if you need to.

That way, if you have kind of a bulky amount of work to do to check if a User has earned a badge, you can delegate that to a full class if you need to. OK. So if we switch back, I'm going to filter down our collection to only the achievements that the User has earned. And return it. OK. Let's switch back to our test and we'll do it right here. UserAchievements. And run it.

Debugging and Test Fixes9:09

And run it. Hmm. Call the undefined method Handler. So it sounds like, let's look into this. So in our Achievements class, we're pushing to the collection. We have our Handler. All right. Let's go back to User. Yeah, and this is the issue.

Let's go back to User. Yeah, and this is the issue. So let's do a quick check here, see if I missed something. Let's run that. We have our name. There's the Handler. Ah, you know what? I better call it like this instead. Let's say call UserFunc Handler, and then I'll give it the this. All right.

Let's say call UserFunc Handler, and then I'll give it the this. All right. Let's give it another run. There we go. Now it's not blowing up. So we get a collection of zero items, which means the User has zero achievements, but we know that's not true. I wonder if we have an issue here. So we've awarded the User experience, but we haven't updated the User. So let's get a fresh instance from the database and then call it.

So we've awarded the User experience, but we haven't updated the User. So let's get a fresh instance from the database and then call it. There we go. So now the User has earned exactly one achievement. Let's imagine that we awarded them enough points that they should have both of those achievements. Run it again. All right. So now when we fetch the User's achievements, we get exactly two items. So you can imagine on a User's profile page now, we could get the collection of all of

So now when we fetch the user's achievements, we get exactly two items. So you can imagine on a user's profile page now, we could get the collection of all of their achievements, filter through them, and render each of the little icons on the page. So this means at this point, if I were to get a fresh instance, now you'll remember earlier we were calling achievements as like a property of sorts, as an attribute, because we assumed we were going to have a relationship there. But if I run it now, it's going to blow up. It must return a relationship instance. So we can either call it as a method, or if we really want it to be like this, we can come back and say, using Eloquent, get achievements attribute.

So we can either call it as a method, or if we really want it to be like this, we can come back and say, using Eloquent, get achievements attribute. Now let's get rid of that. If I give it a run, failed asserting an actual size two. Ah, yeah. That's because we changed the test temporarily. Let's bring it back to the point that the User has only earned a single badge. And if we run that, we do get green. Okay, so we're going to leave it like that. We're going to take some time and just think about this design here, and if we like it.

Okay, so we're going to leave it like that. We're going to take some time and just think about this design here, and if we like it. We now have an AchievementsCollection class. We have a ServiceProvider, and this is where we'd register every possible achievement for the site. Finally, on our User class, when we fetch their given achievements, we just go through that entire list, every achievement on the site, and we filter it down to only the achievements that the User has earned. So take some time, think about this, compare it to the database approach that we had earlier, and let me know your thoughts.

So take some time, think about this, compare it to the database approach that we had earlier, and let me know your thoughts.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟