Mocking Coupon Expectations1:07
So if I want to peek into that coupon instance, I can use Mockery for this. So take a look at this. If I diedump the coupon here, you'll see that it's receiving, there it is, so you'll see it's receiving the coupon that was instantiated here. So now I could say, all right, well I expect a call to createCoupon and the condition on which that passes will be if the coupon percentageDiscount is in fact equal to 10% off. So now if I run it, it's failing, which is exactly what I want. So if I switch back, resolve the error, there we go. We're back to green. Okay, great.
Refactoring with Test Safety1:46
We're back to green. Okay, great. Now that I have confidence that I'm not going to make a change and blow up the system, the next technique is to simply play. Try things out, extract methods, tweak variable names, and each step of the way, if I make a mistake, the test will instantly tell me. Here's an example. I begin by fetching a collection of User instances, and then we iterate over them using forEach. And that's fine. However, because I have a collection instance, I could also call each directly on it, and
And that's fine. However, because I have a Collection instance, I could also call each directly on it, and then accept a User. So with that in mind, I could get rid of this, and then bring up our code and nest it within. Okay, but now the only issue is we no longer have access to the gateway, and that's because we have a closure here. We will pass that through. Okay, did we make a mistake? Let's see. Run the test.
Let's see. Run the test. We still get green, so we're good to go. The next step is, you'll see we have this unused $variable here. Okay, let's get rid of it. Run the test. We're good. Yes, we are. Next, I'm thinking, rather than generating the coupon here, I will extract a method and then reference it.
Next, I'm thinking, rather than generating the coupon here, I will extract a method and then reference it. So we'll try it out. If we like it, we'll keep it. If we don't, we'll bring it right back to how it was. So maybe something like this. Make a coupon. And now I can grab all of this and make our coupon. Let's see what this looks like. Okay, so right off the bat, I can see I need to return the coupon.
Extracting Reusable Query Scopes4:06
Run the test, still good. What else can we toy around with? Hmm, how about this? So we are, this is effectively a scope, giving all the Users on the monthly plan. Now if this is the only place we would add that condition, then it's fine, keep it simple. On the other hand, if for your own project you would find yourself doing this kind of query scope over and over, give me the monthly users, give me the yearly ones, in that case you might want to make it official. So just for this lesson, I've added a subscribable trait that the User uses. So here, maybe we could add a scope called scopeMonthly.
So just for this lesson, I've added a Subscribable trait that the User uses. So here, maybe we could add a scope called scopeOnMonthly. So here, let's accept the $query, we would effectively do the exact same thing. And in fact, I can copy that, paste it in, and then say $query where the stripe_plan is monthly 15. That's the name of the plan on Stripe's end. Okay, so now, if I switch back, here's how I like to make these changes. First, I ensure everything's working, then I remove it entirely, run it, and I want to see red. Because then, when I swap it out with my scope, if I did everything correctly, it should all
Or in other words, every time you have a where condition, don't instantly create a scope. I would say only do it when it's something you'll reach for multiple times throughout your app. Otherwise, you'll just create endless scopes, and you'll litter up your User class or your trait, and it just creates a lot of noise and bloat that you have to take in every single time you open the file. So again, limit it to things like this. Or you could even say on plan, and then make it a little bit more dynamic, a little more configurable. So if you wanted to take that approach here, we would accept the name of the plan, and
Give me the new users in my system who registered in the last week, or the last day, or the last month. Very likely, you'll have a need for that all over your code base. All right. Well, we'll go to... This might go directly on User, but I'm going to keep it here so you don't have to see anything else on my User class. So we'll have another one here. And what would be a good name? Let's think.
And what would be a good name? Let's think. Give me the users who recently registered, maybe recentUsers. We'll try that one out. Scope recent. And now this will accept a Carbon instance. So we'll say, give me only the users where their createdAt date, and then we'll reference our Carbon instance. Okay. So let's give that a shot.
Okay. So let's give that a shot. I'm going to remove this. Everything's going to blow up. And we'll now say only the recent users, and then here I'll say recently, specifically will be threeMonthsAgo. And maybe if you want, your default could be, you could say $carbon equals itself or, you know, today()->subWeek(). So by default, it's going to give you all the users who signed up in the last week. Otherwise, if you want to override it, then you can do so.
So by default, it's going to give you all the users who signed up in the last week. Otherwise, if you want to override it, then you can do so. So we'll make that null. But anyways, if we run this, oh, see, we made a change. I screwed up. I didn't see something here. It looks like I changed it to onPlan. I must have forgotten to fix that. Run it. Okay.
Run it. Okay. We're now back in the green. Give me the active users. Let's reorder this. Give me active users who, okay, so here's another thing. We're toying around. Recent would make me think, when I see this six months from now, I'm thinking, give me users who signed up within the last three months. But that's not what we're doing here.
users who signed up within the last three months. But that's not what we're doing here. If we switch back, we're instead saying, give me User who signed up precisely three months ago. So maybe, with that in mind, this should be subscribedOn. And then we give it a date. And let's change this from Carbon to date. Yeah, so let's see what that looks like. subscribedOn. That makes it a little more clear to my future self that I'm looking for one date, not a
Subscribed on. That makes it a little more clear to my future self that I'm looking for one date, not a span of dates. So if I run that, I'm still at green. Okay. Let's read it again. Give me the active users who subscribed on the date specifically three months ago, who are on the monthly plan, and then fetch that collection and iterate over them. However, if you want, you can get rid of get entirely, because there is an each method on the query builder.
However, if you want, you can get rid of get entirely, because there is an each method on the query builder. So let's see if it works. We run it, and it still returns green. So if you're not familiar with the query builder's each method, it's kind of a wrapper around chunking, and by default, I think it's like 1,000 records. So it gives us a quick and future-proof way to fetch these users, no matter how many there might be. We'll chunk over them, and then for each one, we can then proceed, and that returns green as well.
Improving Coupon Generation9:36
We'll chunk over them, and then for each one, we can then proceed, and that returns green as well. Small little tweaks there. Okay. So what else can we do here? Here's one thing. When I make a Coupon, you'll see it requires a unique code. And at the moment, we're doing an md5 hash, where we get the userId, hyphen, and then the current time. Well, here's one thing.
Resolving Gateway Dependencies10:37
What else? Well, here's one thing. Maybe for our constructor here, maybe instead we will resolve the gateway, and then initialize it. All right. Now I don't have to resolve it here. I don't have to pass it to the closure, and I don't have to send it to makeCoupon. Instead, the coupon will simply defer. So here's our first pass. Now we made a number of changes there.
So here's our first pass. Now we made a number of changes there. Did we blow anything up? Yes, we did. You probably forgot to call the parent::__construct(). All right, let's see. Let's go to the parent. And sure enough, there's a bunch of stuff that happens in the constructor. Okay. parent::__construct(), run it, and we're back to green.
Okay. Parent, construct, run it, and we're back to green. But now let's take a look. Is it better? Well, we don't have to pass it through to the closure. The makeCoupon doesn't need to receive it. But we now have introduced a constructor that we didn't need before. We do have a property up here. In my opinion, it's kind of a wash. So we could revert, bring it back.
Gateway. Now, if I took that approach, all of this would leave. If I run it, no, it blows up. Call to a member function createCoupon. And that's because we're passing through the gateway just as a string, but it's instead expecting the instance. So what if here we just resolved it this way and we ran it? We would still get green. Now, if you're not familiar with the resolve function, it's identical to the app function. Just grab this thing out of the service container.
Now, if you're not familiar with the resolve function, it's identical to the app function. Just grab this thing out of the service container. But I often like to use resolve because it makes it a little more clear I am fetching something out of the service container. And again, it's simply an alias. Okay. I think that's okay here. If you're a big advocate for constructor injection, and there are certainly countless good use cases for that, then you'll want to stick with what we had before. But nonetheless, I think this might be okay.
Adding User Emailing Flow12:42
cases for that, then you'll want to stick with what we had before. But nonetheless, I think this might be okay. It's just a command class. There's nothing too crazy going on here. So I'd like to keep it fairly simple. This is what we have now. So one thing that might be useful is to expose that a User can be emailed. So let's switch over here and we'll visit our Subscribable trait here. And we're storing everything here just to keep it simple. But at the point where I have an email method, that's not really related to being subscribed.
And we're storing everything here just to keep it simple. But at the point where I have an email method, that's not really related to being subscribed. So keep that in mind. It would probably go on User directly. So what if we tried this? We would accept a callback that we would then call while sending through the user. Now if nothing else, I could say for each user, and we'll use higher order collections here, I could then call an email directly on each user. So I could say email and then pass it through and accept the user.
So I could say email and then pass it through and accept the User. Okay. Do we have an issue here? Yes, we do. So undefined property builder each. Okay. So that's because I'm trying to take advantage of higher order collections, but at the point I call each, it's still the each on the query builder. So at this point, I would have to say, okay, get me an Illuminate\Collection, and then I want to iterate over each one.
So at this point, I would have to say, okay, get me an Illuminate\Collection, and then I want to iterate over each one. So did we fix it? Yes, we did. All right. So that would be an option. So we're kind of getting a little fancy here, mostly for fun and mostly to toy around. But nonetheless, this would be an option as well. However, at this point, the email method doesn't necessarily email anyone. It's just calling a callback that you gave us.
However, at this point, the email method doesn't necessarily email anyone. It's just calling a callback that you gave us. So what if here we used mail to the current user, and then we're going to send, and then here I'm going to want some kind of mailable. So maybe we pass that through as the argument, or again, maybe we just trigger a callback where you can decide what needs to be returned there, like this. And we'll return that, clean it up. Okay, let's see what that looks like now. So now this method will send the email to the current user for the instance. So that means the send method needs to return our mailable.
So now this method will send the email to the current User for the instance. So that means the send method needs to return our mailable. So if we took that approach, we would have something like this, and I could add my little info method there, and then return the mailable. All right, did I make a mistake? No, still green. So this would be an option as well. Now here's another thing. For each User email, we're just logging something to the terminal. But we could also do this.
For each user email, we're just logging something to the terminal. But we could also do this. I could say, well, when you're done with all of the users you've fetched, give me the email in question, and take a look at this. So if I dd the emails, and we run it, you'll see I have a Collection of every User that was emailed, in this case, one. So now here, maybe I could say a bulk info. I could say, sent upgrade discounts to, and then here, I could say, I want the email addresses, and then implode, and separate them by a comma. Like that.
and then implode, and separate them by a comma. Like that. Yeah, that would be an option as well. So you'd have foo@example.com, comma, bar@example.com, comma. Which means I could now get rid of this, and I could say, finished. Sent upgrade coupons to these email addresses, which means I could get rid of that. And you know what? That honestly doesn't look half bad to me. So yeah, more than anything here, the key is you want to give yourself the flexibility, and more importantly, the confidence to play around with your code, even after it has been
So yeah, more than anything here, the key is you want to give yourself the flexibility, and more importantly, the confidence to play around with your code, even after it has been pushed to production. It's the easiest thing in the world to play around when it's still in a test phase. It's still local. Nobody's going to see it. But once you push it to production, it becomes much more dangerous to change. So if you have that series of tests backing you up, you then, exactly as we've done here, you then have the confidence to simply toy around, extract a method, use higher order collections here.
you then have the confidence to simply toy around, extract a method, use higher order collections here. Maybe you want an email method on User. How would that look? Let's try it out. And again, every step of the way, if you don't like it, no problem. Revert it and bring it back to what you had before. But again, the key thing here is confidence.
