Subscription Coupon Update Flow0:00
Next up, I'll show you one little technique that I like to use. Nothing big here, but you might find it useful. Okay, so we're going to stick with this idea of like a SubscriptionController, and maybe you can update a plan. Now, the example I've picked out is one where we want to update a coupon, maybe with Stripe or Braintree or something like that. Okay, so maybe, obviously, some pseudocode here, but you'll get the basic idea. We're going to reference the User, and then we will fetch their subscription. Maybe that would return a new instance of a class or something like that. And then we basically just want to say, using the given coupon code,
Maybe that would return a new instance of a class or something like that. And then we basically just want to say, using the given coupon code, I want to swap to this new plan I have here. All right, so this is a nice, fluent API that you could write on your own. Find the authenticated user, give me their billing gateway class, basically, and then using the coupon, that's just a glorified setter, and then swap would be what actually updates the user's plan with your billing provider. Now, it stands to reason that the coupon code would come from the user that they enter into a form. So maybe you have something like this. You have your Illuminate\Http\Request class, if you're using Laravel.
So maybe you have something like this. You have your Illuminate\Http\Request class, if you're using Laravel. We'll accept that. And then, yeah, this would then become something like requestCoupon. However, once again, we got to do validation. We have to figure out if it is, in fact, a valid code. Of course, you can check to see if it's a valid code with JavaScript, where they type in something into the input. You then take that value, fire off an AJAX request to your server, and your server, your API, lets you know, is this valid?
The tutorials are always very basic. But in reality, you have all of these different rules that determine something as simple as this. Can I apply a coupon? Now, one option for this method, we could either check the value of coupon and use that to determine if we should call the method at all. Or we can just keep it simple, where if null is set here, then you don't need to do anything. Using coupon can handle that entirely behind the scenes. So that's the method we'll take.
Coupon Validation Rules2:21
Using coupon can handle that entirely behind the scenes. So that's the method we'll take. But we still have other things we need to check, like I talked about. For example, if the coupon code does not exist in our system, then maybe we're just going to say something like this. coupon is requestCoupon. And then we'll say, in that case, we're just going to set it to an empty string or null or something like that. And maybe to check that, you're performing some kind of query. So maybe you have a Coupon class, and you have a coupons table.
And maybe to check that, you're performing some kind of query. So maybe you have a Coupon class, and you have a coupons table. And you say something like, coupon having code. And we'll pass that in. And let's see, if we do not have that coupon, yeah, maybe we're just going to set this value to null. OK, but then later, you have another rule where you find out, OK, well, if the coupon does exist, maybe we need to check to see if it's active or not. So if the coupon has been deactivated, that's no longer valid.
if it's active or not. So if the coupon has been deactivated, that's no longer valid. Another thing you might check is, does the coupon work for the current plan? So you would do something like, if the coupon, and actually, why don't we do this real quick? Let's change this value to code. And then that way, I can save this to coupon. OK, so if the coupon, and maybe we'll add a method to that class. Remember, always think about responsibilities. So as just a quick aside, what a lot of people will do is,
Move Checks to Model3:36
Remember, always think about responsibilities. So as just a quick aside, what a lot of people will do is, on their class, they will create a method. And they will prefix it with, often, this is kind of a sign, they will prefix it with the thing that should be responsible for doing the check. So we'll say, coupon works with plan. I used to do this stuff all the time. It took me a long time to really make the connection. Oh, because I'm using this prefix here, that probably means I'm missing a responsibility somewhere.
Oh, because I'm using this prefix here, that probably means I'm missing a responsibility somewhere. But anyways, yeah, you would accept the coupon, and you would accept the plan, and you would do your check here, in your controller, or wherever it might be. Now, the prefix tells us, hmm, maybe the coupon should be responsible for this. So instead, yeah, you would create your Coupon class. This would be like an Eloquent model, maybe, in this particular case. Coupon extends the Eloquent model. And then there, you would extract this out. You would paste it in there.
And then there, you would extract this out. You would paste it in there. You would paste it in there. And then you remove the prefix. And once again, there's your sign. You got rid of the prefix, so you know the responsibility is now in the right place. Coupon works with Plan. And that way, you can also get rid of the coupon variable, because that would be stored on the instance, right? You could do this coupon, or really, this code would probably be the value.
because that would be stored on the instance, right? You could do this coupon, or really, this code would probably be the value. Cool, right? Okay, total sidetrack there. Let's get back to the main refactor we're talking about. So if we say, if the coupon does not work with the given plan, well, once again, maybe you're going to set the code equal to null. Finally, once we've sort of normalized everything, you would then pass that value to your setter here. Now, notice that I use this term, normalize.
Normalize Inputs Concept5:14
you would then pass that value to your setter here. Now, notice that I use this term, normalize. That's how I like to think of it. You have a value, and in some cases, you have to normalize it down. You have to do some checks. Maybe you even have to check to see, well, is this value a function? And if so, the user passed me a closure, and I'm going to trigger that and return the value, right? Lots of little things there that can make your code more dynamic. What I mean by that example is, yeah, if you were to say,
Lots of little things there that can make your code more dynamic. What I mean by that example is, yeah, if you were to say, do this, and you can give it a value, or you can give it a closure that returns the value. Well, yeah, behind the scenes, you can apply a little normalizer there, where you check to see, well, did you give me a closure? If so, I'm going to trigger that function and then use the returned value instead. Otherwise, if you just gave me a string, then I'm going to use that. And this is the sort of thing, actually, you'll see in Laravel all over the place.
Extract normalizeCoupon Method6:06
And this is the sort of thing, actually, you'll see in Laravel all over the place. That's what makes it nice and flexible. Okay, so how could we refactor this, extract a normalizer, as we might call it? Maybe we could create a method, and I know I'm going to normalize it. And what am I normalizing? The coupon code. So normalizeCoupon, and that will accept the code. Now, if we think about it, we could just get rid of all of this, and instead do something like this.
Now, if we think about it, we could just get rid of all of this, and instead do something like this. This normalizeCoupon, and then pass through the coupon code. So now, this sort of logic, whatever it might be, this is kind of dummy code, but whatever it might be would be referenced here. Then we could do things like this. Well, if I don't have a code at all, or I have some falsy or something weird, then we're just going to return either null, that would be fine, or an empty string, whatever you want, or even false. It'll all end up turning into the same value.
Otherwise, if the coupon does not work with the given plan, and it looks like we would need to pass that through too, right? Anyways, if that does not work with the given plan either, then we could return false. Finally, we could just return the code. Next, we can see here, those can probably be grouped together. So I could say, if we don't have a coupon at all, or we do have a coupon, but it doesn't work with the given plan, then return false, in which case I can get rid of that. Finally, for this very last line, if we do have a code,
then return false, in which case I can get rid of that. Finally, for this very last line, if we do have a code, and the code exists in our system, and the code works with the given plan, then we should be good to go. So we will just return that normalized value. And don't forget, like we were saying earlier, if you wanted to do something where the code could be a closure in some cases, then you could do something like, if is_callable code, well, then in that case, we're actually going to trigger it, or even do call_user_func if you need to,
Now, is there any other refactoring we could do in this case? And really, it's just a matter of how much you want to do. Like, for example, a little thing. Here, if you want, you could remove that entirely. You're still going to perform the query, though, but because code would be like an empty string, then it's still going to be a falsy value, in which case, yeah, you don't really have to worry about it. So we could try that if you want. Next, a final step we might consider is, once again,
So we could try that if you want. Next, a final step we might consider is, once again, does this belong within the controller here? And the answer you might have is, yes. I'm using a lot of form request data, and I think the controller is the right place for this. That's fine. You might also decide, well, I think coupon can be more responsible for this sort of checking. The important thing, as always, is it just kind of depends.
If you want to keep it simpler, if you're going to reuse this code, there's 101 different reasons you might make a decision. Anyways, if you did want to put this on coupon, and I don't have anything prepared, but what might that look like using coupon? Maybe we could say coupon, and I don't know, I'd really need to think about it, but maybe we have a coupon method called validate for plan, and there you would give the coupon code as well as the plan.
but maybe we have a coupon method called validate for Plan, and there you would give the coupon code as well as the plan. I don't know, would that be better? I don't know. Let's play around with it for a minute. So some kind of static method, validate for Plan. This accepts the code and accepts the plan. We would then say coupon equals static, where the code is equal to what I give you, or we could create that query scope.
where the code is equal to what I give you, or we could create that query scope. So something like scopeHaving code like we did before, and of course, that would accept the query builder, and we would say return $query where code is equal to this. Then I could say, give me a coupon having the given code, and I want the first result, or really because this would always return the first result, we could do it there and keep it like this. That gives us the coupon.
depending upon how you want to name the method. Anyways, that might be an option. What else could we do? And this is really one of my favorite things about writing code is you get to tinker around, and you get to try something out, and you get to go, no, that doesn't feel quite right. Let's do something else. So it's really a very creative thing. Maybe we could do maybe something a bit more fluent, like coupon, and if we are normalizing a code,
Fluent Normalizer and Null Object12:07
Okay, so for that one, what would we do? We can get rid of all of these. We could say, add a static method here called normalize. We'll accept the couponCode. But now here, this is really just kind of a static constructor, where we could do something like return static::query(), where the code is equal to the given code, and give me the first result. So really, this is just a bit of sugar that's going to fetch the record for you. But now here's one thing you'll often,
So really, this is just a bit of sugar that's going to fetch the record for you. But now here's one thing you'll often, this is kind of a trap you'll fall into. So in this case, we want it to be nice and fluent, so I want to have an against method. So I create that, we're back to public methods. This is all good. However, here's the problem. In one case, the code exists in the database, so we return a Coupon object,
In one case, the code exists in the database, so we return a Coupon object, in which case we call an against method. But in another case, the coupon they give us is gibberish, we perform the query, and ultimately null gets returned here. At which point, when you try to continue calling the against method, it's going to error out, right? So how do we handle these situations? Lots of choices. One option is to just return a new instance of Coupon,
Lots of choices. One option is to just return a new instance of Coupon, almost like a null object, but not quite. So for example, we could say $coupon equals the first record. So then you could do something like, if no $coupon was returned, then in that case, give me a new instance of Coupon, and that way I can still call methods like against and anything else. Otherwise, return the Coupon instance itself. Or, of course, we could just say return $coupon.
Otherwise, return the coupon instance itself. Or, of course, we could just say return coupon. If you had one, return that. Otherwise, return a new static. So yeah, now in the case where an invalid coupon code is provided, all you get in return is just a new instance of coupon, in which case you can call the against method. And then this even becomes quite a bit simpler. You can just say, if not, this works with plan and pass it in. Then in that case, you return false.
You can just say, if not, this works with plan and pass it in. Then in that case, you return false. Otherwise, you return the coupon code itself. Finally, then the worksWithPlan method, I'm not sure if that should be public or not. Just, once again, depends on if the users of your API would need to access that directly. And here, you know, however you would verify this in your system. Maybe you have a pivot table. Maybe you're just storing a bit of JSON as a column within the coupons table,
Maybe you have a pivot table. Maybe you're just storing a bit of JSON as a column within the coupons table, in which case you can just do an array once you parse it. Anything you want there would be fine. But yeah, now this is another way we might go about it. A bit more code, but this is definitely a bit nicer to work with in my mind. Normalize the current code against the given plan. Or, of course, remember, you could always extract that. Save it up here. And then if you want, you could even use this value to determine
Save it up here. And then if you want, you could even use this value to determine if you want to call this method at all. Any option like that is perfectly fine. Okay, so with the fluent syntax, our normalized method is basically a static constructor. And then from then on, we're just performing basic checks like we always would. Okay, so that's been your technique for this lesson. As always, there's really no right answer, whether keeping it in the controller was better or worse.
