در حال بارگذاری ...

Subscription Behaviors Overview0:00

Welcome back. Next up on the agenda is object composition. And you know what? Let's just jump right into an example. Let's say you have a Subscription class, just like you can subscribe to Laracast, or at least you should subscribe to Laracast. Alright, so what's some of the behavior that we might find on a class like this? Well, of course, you could create a subscription. Fine. You could cancel a subscription, which you would never do at Laracast, right? Never, never, never, never, never, never, ever, ever cancel your Laracast subscription. You could swap to a new plan, which would be a great idea for Laracast. You could swap out your monthly plan for a lifetime plan. So maybe we'll accept the new plan name that you want to swap to. And then finally, maybe one more, you could invoice a subscription for your new billing period. Alright, you get the idea, right? This is all behavior that could be represented within a Subscription class, and that's great. But now, yeah, if you think about it, things are never quite so simple, right? So if we come up to the create method in real life, that probably consists of more than just updating a column within a database table. In real life, you're probably interacting with some kind of third-party billing provider like Stripe. Maybe you need to send an API call to Stripe to create a new Stripe customer and then add them to a particular product or a subscription plan, right? So we need some way to interact with a billing provider like Stripe. So as you can imagine, there might be things like, well, we need to get a Stripe customer that is associated with this user in our system.

Billing Provider Integration Needs1:25

In real life, you're probably interacting with some kind of third-party billing provider like Stripe. Maybe you need to send an API call to Stripe to create a new Stripe customer and then add them to a particular product or a subscription plan, right? So we need some way to interact with a billing provider like Stripe. So as you can imagine, there might be things like, well, we need to get a Stripe customer that is associated with this User in our system. So get a Stripe customer. Or maybe you have things like, well, get the Stripe-specific subscription for the customer. And yeah, this is where it can sometimes get tricky. You would have subscriptions in your app, but then you could also have a Stripe.com-specific customer, which is like a User on their end, or a subscription on their end. So get a Stripe subscription. Yeah, these are just things you run into in real life. All right, so now if I want to create a subscription, I need to interact with Stripe, and I need methods to grab or access a Stripe customer or maybe a subscription that the customer is on. All right, so where do we put this sort of logic? Well, you actually have a few different options here, and I want to show you all of them while discussing maybe the benefits as well as the downsides. All right, so first up, we could just add another method to the Subscription class. All right, right down here, if we need to get a Stripe customer, then we might have a method called getStripeCustomer.

Adding Stripe Methods2:53

All right, so first up, we could just add another method to the Subscription class. All right, right down here, if we need to get a Stripe customer, then we might have a method called getStripeCustomer. And yeah, we've learned a little bit about visibility, right? So maybe we've realized this is sort of an implementation detail, and maybe we don't need to expose this method to the outside world. So with that in mind, maybe we make this or we mark this as protected. All right, so that's done, and then we need another one to get a Stripe-specific subscription in some cases. So we'd have another one, getStripeSubscription. Okay, so yeah, that works, but now it's starting to feel a little weird, isn't it? You know what I mean? Originally, we had methods that were very specific to a subscription in our app,

Using Inheritance Option3:42

You know what I mean? Originally, we had methods that were very specific to a Subscription in our app, but now we have methods that are more related to how we go about doing things. But does this really describe a Subscription? No, it's just something we have to do, and that feels a little weird, don't you think? So one option is, well, maybe we could reach for inheritance, and we've learned a little bit about that. So it wouldn't be my preferred choice, but I want to show you. So we have a Subscription class. Maybe we could extend from that, and we could maybe have a BillableSubscription. All right, a BillableSubscription extends Subscription.

Maybe we could extend from that, and we could maybe have a BillableSubscription. All right, a BillableSubscription extends Subscription. And yeah, I'm just going to take these two methods here and move them down. So now if I scroll up, we have our Subscription class, but then we also have a BillableSubscription-specific subscription. And yeah, I don't know, maybe that's a little better, but to be honest, I don't really love this. And further, think about this. Would it make sense to access methods like this from other areas of your code base? Maybe within a webhook, maybe within a test, things like that. And the answer is absolutely, but now they are sort of linked to the internals of a Subscription. And because they have protected visibility, we can't access them from outside of this Subscription.

Using a Trait Option4:55

And the answer is absolutely, but now they are sort of linked to the internals of a subscription. And because they have protected visibility, we can't access them from outside of this Subscription. And that feels a little weird. I want to reuse these methods, but now they're sort of jailed by this subscription class. All right, so what other things might we be able to do here? Well, what if we turned it into a trait? I still don't love it, but maybe it's an option. Let's see what that looks like. We would change this to traits, and let's give it a name of something like Billable. Now, if you're not familiar with traits, think of them just like globs, little collections of methods.

We would change this to traits, and let's give it a name of something like billable. Now, if you're not familiar with traits, think of them just like globs, little collections of methods that can be literally mixed in to any class in your code base or multiple classes within your code base. It's sort of like a way of saying, all right, here are some methods. I want you to mix this in with this class or that class or that class over there. Okay, so now if I came up, Subscription could use billable. So now we have methods that are specific to a subscription, but then we can also pull in this trait to add additional behavior. Now, it is true we're kind of hiding this billable-specific behavior, even though it's still ultimately being mixed in with subscription.

Now, it is true we're kind of hiding this billable-specific behavior, even though it's still ultimately being mixed in with subscription. So, yeah, it's still not ideal, but maybe a little bit cleaner, I would say. But let's keep thinking. What else could we do? Well, if you think about it, this code, what does it do? Well, it interacts with Stripe, right? It's sort of like our gateway to our billing portal, and in this case, our Stripe billing portal. So what if we made this a brand-new class? All right, let's give that a shot.

Extracting Billing Portal Class6:42

So what if we made this a brand-new class? All right, let's give that a shot. Class, and again, let's think of a good name. I said billing portal. I don't hate that. Some people like to use the term gateway when talking about billing, so you'd have a billing gateway. It doesn't really matter. Next, what kind of billing portal is it? What are we interacting with? Well, we are interacting with Stripe.

What are we interacting with? Well, we are interacting with Stripe. So what if we called this Stripe billing portal? Okay. Now, we have a class, and we have these two methods, but here's the cool thing. We can now make these public methods on this class because these are part of the API of a Stripe billing portal, whereas they really weren't part of the public API for a Subscription class. Within this class, they were more of like an internal detail rather than part of the API. Okay, so now I'm going to update the visibility back to public.

Within this class, they were more of like an internal detail rather than part of the API. Okay, so now I'm going to update the visibility back to public. Next, check this out. We have a Stripe billing portal, and then we have Stripe within the method names. So we're sort of getting a little redundant, right? Think about it. If we had a Stripe billing portal, and let's instantiate that. All right, now let's call a method on it just to see how it feels to interact with the API, and immediately you'll notice, oh, that kind of feels gross. Stripe billing portal, getStripeSubscription.

and immediately you'll notice, oh, that kind of feels gross. Stripe billing portal, get Stripe subscription. There's just too many references to Stripe. Instead, I should be able to just say get subscription, right, or get customer. Stripe being part of the method name is redundant, and if ever you can make that noun redundant, usually that's a sign that you're on the right track, just as a little tip. So I'm going to change this to get customer and get subscription. So we have two methods that are part of the public API for a brand new class. Okay, so now if I come back to subscription,

Composition and Dependency Injection8:38

So we have two methods that are part of the public API for a brand new class. Okay, so now if I come back to subscription, I'm going to declare that subscription is composed, keyword alert, is composed of a, in this case, Stripe billing portal. All right, so what does composition mean? Well, there's a couple definitions we might use. First up, we might say that composition refers to combining various types to build up more complex behavior. True. Or we might say object composition is simply when one object or class has a pointer to another class.

True. Or we might say object composition is simply when one object or class has a pointer to another class. That's another easy way to think of it. All right, so let's put this into motion. I'm going to create a constructor, and at least to start, we'll just do it in line here. We'll say, all right, I need access to the Stripe billing portal, and I will instantiate it directly here, Stripe billing portal. But, of course, I need to access this from other methods in the class. So I'm going to assign it to a property. All right, so we'll say protected Stripe billing portal.

So I'm going to assign it to a property. All right, so we'll say protected stripeBillingPortal. And what is the type? Well, it is of type StripeBillingPortal, just like this. And, yeah, that's effectively composition in a nutshell. It's that Subscription is now composed of a StripeBillingPortal. And, yeah, it could be composed of multiple types. Generally, it'll be one or two or three, maybe, maybe four, maybe possibly five. That's a lot, though. And if you ever have a situation where your class has like ten different dependencies,

That's a lot, though. And if you ever have a situation where your class has like ten different dependencies, there's just too much going on here, and you should probably break things down quite a bit more. So now we are building up this composition by instantiating any dependencies we might have directly within the constructor. And maybe that's okay, but often we will reach for dependency injection instead. And DI, dependency injection, just refers to, well, injecting the dependency. Just reverse it, and you have the definition. The definition of dependency injection literally is injecting the dependencies into the class in some way or another. All right, so let's do that. Let's inject Stripe billing portal like this.

All right, so let's do that. Let's inject Stripe billing portal like this. And then, of course, we can leverage constructor promotion. So I could just say assign this to a protected property, and this is what we get. All right, and I don't have a ton of real estate here, so let's put this like so. Okay, so now think about it. In order to work with Subscription, I have to satisfy its requirements. So Subscription is sort of saying, hey, if you want to work with me, then you also have to give me a billing portal that I can interact with. So if I scroll down now, yeah, right down here, let's instantiate our Subscription. And we will satisfy the requirement, which is a billing portal.

So if I scroll down now, yeah, right down here, let's instantiate our subscription. And we will satisfy the requirement, which is a billing portal. We'll send that through, and yeah, now think about it. If I were to say subscription->create, well, if we come up here, now all of this tricky logic that interacts with an API can simply defer to a class whose sole responsibility is interacting with that API. Notice, though, that the subscription doesn't have to be—has to be—what's the word? The subscription doesn't have to be concerned. That's the word I was looking for. It doesn't have to be concerned with billing-specific logic and API calls. No, it just wants to know that it can interact with an object that can do that work. All right, so it would handle that through things like this.

No, it just wants to know that it can interact with an object that can do that work. All right, so it would handle that through things like this. Stripe billing portal, get me the customer or get me the subscription, and you can see how that works. All right, so this is cool, and again, this is basic composition in a nutshell. But now, let's take it maybe one step further. In order for Subscription to be instantiated, we have to feed it a Stripe billing portal. All right, but why? Why Stripe specifically, right? Why not Braintree? Why not PayPal?

Why not Braintree? Why not PayPal? And the answer is probably something like, well, when we built the project, we used Stripe, so you have to give us the Stripe version. And by the way, that's totally fine. Just imagine something like 98% of applications, they pick something like Stripe or Braintree, and then that's just what the project uses for the lifetime of the app, and that's totally fine. It's a very small number of applications that grow to a point where maybe they need to branch out to offer a different billing provider entirely or multiple billing providers. So this is probably fine. But still, in terms of spirit, it does seem weird that subscription knows uniquely about Stripe. You know what I mean? Normally, I would expect a Subscription class to just need access to a billing portal.

Introducing Interface Abstraction13:34

You know what I mean? Normally, I would expect a Subscription class to just need access to a billing portal. Hey, in order for me to work, I need a way to send API calls to fetch a customer or to create a subscription. But does it need to know that I'm working uniquely with Stripe rather than PayPal? Well, in a perfect world, no. It shouldn't have to know that. So this is where we can reach for abstractions. And if you've ever heard the phrase, depend on abstractions, that's kind of what this is referring to. Rather than passing a concrete implementation, we will instead pass something a little more abstract, something that represents the behavior we're looking for but doesn't point its finger at any specific provider like Stripe or PayPal or Braintree. Okay, let's get to work.

Rather than passing a concrete implementation, we will instead pass something a little more abstract, something that represents the behavior we're looking for but doesn't point its finger at any specific provider like Stripe or PayPal or Braintree. Okay, let's get to work. Come down here and let's do this. We have our class. I'm just going to copy this and create a new one. And this will be our interface instead. And of course, an interface doesn't have behavior. It just declares a contract of sorts as we've learned in this course. So we might have something like this. But now, of course, an interface that is responsible for interacting with a billing portal that is called Stripe Billing Portal is not a very good interface name, right?

So we might have something like this. But now, of course, an interface that is responsible for interacting with a billing portal that is called StripeBillingPortal is not a very good interface name, right? So let's make it more generic. And usually, you make it more generic by slicing off the specific portion. And in this case, that would be Stripe. All right, so now we have an interface called BillingPortal. And again, in real life, you'd have many methods here, getInvoice, getSubscriptions, charge, things like that. But for now, we're just keeping it super, super simple. Okay, so now I have a contract that declares if you want to interact with me or if you want to implement me, you have to provide these methods. Okay, let's now have our StripeBillingPortal sign that contract.

Okay, so now I have a contract that declares if you want to interact with me or if you want to implement me, you have to provide these methods. Okay, let's now have our Stripe Billing Portal sign that contract. And we do that through the implement keyword just like this. All right, we already have these methods. But of course, if I deleted them, my editor would get mad at me, and it would allow me to add these method stubs if I wanted to. Great. So now we're going to scroll back up to subscription, and we're going to depend on an abstraction rather than a concrete implementation. But before I make this change, just a quick note. These are guidelines, right? These are things to think about, but they are always intended to improve the flexibility of your code base.

These are guidelines, right? These are things to think about, but they are always intended to improve the flexibility of your code base. They are not rules that thou must not shall break, right? So there are countless situations where I will depend on a concrete class. It's not like I'm always creating an interface every single time I need to depend on a piece of functionality. Absolutely not. I would only do it if it improves the flexibility of my code base in a meaningful way. And by meaningful, that means something that I can see myself using right away. Not, well, in 10 years you might need to do this, and therefore you need to construct your code base in such and such way right now. That's a trap that we don't want to fall into.

Not, well, in 10 years you might need to do this, and therefore you need to construct your code base in such and such way right now. That's a trap that we don't want to fall into. Okay. With that said, I'm going to make this generic. I don't need a Stripe billing portal. I just need some kind of billing portal. So now I can make this more generic as well. So I will rename this to simply billingPortal, and that will update all of the references. Okay. So check this out.

Okay. So check this out. I have a class called Subscription that is now composed of a billing portal. That portal could be a Stripe-specific version, or it could be a PayPal version, or a Braintree version. And, yeah, all I would have to do is update this, and this is my Braintree version. And now I would have specific API calls or SDKs that are unique to Braintree here. And then if I want to swap those out, I would simply replace this like so. And now my Subscription class is referencing the Braintree logic, which is really cool. Yeah, let's keep it simple. Let's bring it back.

Yeah, let's keep it simple. Let's bring it back. Delete this. And now if we come back, when I do need to create a Subscription, I just need to know that I am composed of some kind of dependency that can fetch me a Customer so that I can then attach a Subscription to that Customer, things like that. Very, very cool. And now Subscription is just that much more flexible. It doesn't know that we work with Stripe because it doesn't care. It's none of its business, and that's the entire point.

It doesn't know that we work with Stripe because it doesn't care. It's none of its business, and that's the entire point. All right, so now in this video, you've learned about basic composition. And, again, that just means you have a class, and that class is composed of other types or other objects or classes that together can create more complex functionality. Or just think of it as, well, this class has a pointer to another class or another object, and that's effectively what it is. All right, and then finally, again, we reviewed a bit more about interfaces, where we can take a bit of logic and make it more generic so that it serves more as a contract of what behavior is expected.

where we can take a bit of logic and make it more generic so that it serves more as a contract of what behavior is expected. And then when we need to, we can sign that contract, and we sign it by implementing the interface. All right, that's going to do it. This is a confusing topic, so please let me know if anything didn't make sense here. If you're watching the video and you're thinking, I don't get it, that's when you need to ask a question. And, of course, as always, be sure, if you're subscribed, to take the exam below this video. It really helps.

And, of course, as always, be sure, if you're subscribed, to take the exam below this video. It really helps.

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