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

Why Use Frameworks0:33

And yet I still want you to understand the basic premise and the basic concept. So really, if you think about it, all of the things we've been building over the last ten episodes or so, like even validation, but our router, our database class, this container, we're going to add an App class. Once you start using a framework like Symfony or Laravel, they will provide these things for you for free. That's why it's called a framework. It gives you everything you need. So even though I'm showing you how to build some of these things in real life, most of the time you're not really going to maintain these yourself.

I'm going to show you how to build a container in this lesson, and we can make it as complicated or as simple as we want. But still, if it feels a little too much or a little too overwhelming, just remember, in the future, you won't be building this yourself. So if you just want to skip over it even, nobody's going to judge you. Okay, let's get going. I'm going to switch into PhpStorm. And if I go into, for example, my Controller, NotesController, destroy action, you will remember in a bunch of places, we have code just like this, where we need to instantiate our Database class in order to execute some SQL.

Problem: Repeated DB Setup1:59

a bunch of places, we have database code just like this, where we need to instantiate our Database class in order to execute some SQL. Okay, so keep in mind that each time we instantiate database, we also have to build up the necessary configuration to pass to the constructor. And you'll remember, we set that configuration right here. But yeah, my point is, it's just a little annoying that I have to redo this every single time I want to perform a database query. So we have it here, here, we have it here, and then, yeah, we have it there again. It's just a waste. And as you can imagine, for real applications, this will constantly be a concern, not just

Creating a Container3:01

So instead, it would be nice if I could just do this a single time and then throw it in a container of sorts, like just throw it in there. And then if I need it, I can take it out of the container. That's what we're going to build in this video. So I keep using this word container. That sounds like a pretty good name for our class. So I will do that now. And I'll call it Container, and PHPStorm will auto-create the class for me. And then I did say we'll need to add things to the container and then remove them from the container.

Bootstrap Playground Setup3:59

Okay, so what's the next step? All right, so let's do this. I'd like some kind of playground. So I'm going to create a new file, and we will call it bootstrap.php. And then our index file will require it. So for now, why don't we do it right here? Require base path/bootstrap.php. Okay, and we're probably going to keep that file too, but now I have a little playground where I can tinker and figure out how I want to interact with this container. So I know I want to say new container.

where I can tinker and figure out how I want to interact with this container. So I know I want to say new container. All right, and that automatically gets imported. And then next, I want to bind things into the container. And then let's see, if I want to work with our database instance, I could use a path to it using the namespace. So I could bind that key to a function that is responsible for building up the database object. That's what we're doing here. So I'm associating this little builder function with a string called core database.

That's what we're doing here. So I'm associating this little builder function with a string called coreDatabase. Okay, so if I were to go up to any place where we manually did it, like right here, let's switch back and paste it in here. Just like so. All right, and then we will return that from our function. Okay, and real quick, you'll notice that it's gray because I haven't set up the method signature on the container class, but we'll do that in just a minute. Okay, so now I can see that we have a string, which would be like an identifier or a key for this binding.

Okay, so now I can see that we have a string, which would be like an identifier or a key for this binding. And then we have a function that is responsible for building up the object. It's almost like a little factory function. Okay, so let's do this. Let's open up a split, go into my Container class so that you can see these side by side. And we'll say, accept some kind of key. All right, and then the second argument is our function here. Call it whatever you want. Some people make it super generic like this.

Call it whatever you want. Some people make it super generic like this. I don't really love that. But if you want to call it your builder or your factory function or your resolver, whatever you want here. Okay, so now when we bind something into the container, think about it. We need to store it somewhere. We need to save it. We need to cache it, just like with the router lesson. So I'm going to take mostly the same approach where I will have a field here called bindings.

We need to cache it, just like with the router lesson. So I'm going to take mostly the same approach where I will have a field here called bindings. And then when I call this bind method, I will push to this array, almost identical to the Router class. Okay, so I might do something like this. This bindings, this will be an associative array. I'll give it the key and I will make that equal to our resolver. Okay, so now if you think about it, the bindings array contains one item at this point. The key for that item is core/database. And the value associated with that item is a function that will build up the database

The key for that item is core/database. And the value associated with that item is a function that will build up the database class however is appropriate for your project, as you see here. All right, next within our resolve method, we want to do the opposite, right? We want to take the object out of the container. And how would we do that? Well, we will probably need to call or trigger this function and then return the result, right? Okay, so let's do that now. Resolve should accept a key, and that way I could say, for example, container::resolve

Okay, so let's do that now. resolve should accept a key, and that way I could say, for example, container resolve core/database. And then this method would look into our bindings array and say, all right, do we have anything called core/database? Oh, yeah, we do. So let's find the resolver or the function associated with it, call it, that will trigger this code, and then return the result. So in effect, we would have a database instance fully built up and configured, ready to go when we call this method here.

So in effect, we would have a database instance fully built up and configured, ready to go when we call this method here. Okay, so let's do that. Okay, so we might say if array_key_exists, look for this key inside of the $bindings array. In that case, we're good to go. So I could say, well, the $resolver is this $bindings key, and then I want to call that function. And there's a couple ways to do this, but why don't we stick with call_user_func. This will accept the name of our function, and it, well, calls it. And then finally, we will return the result.

This will accept the name of our function, and it, well, calls it. And then finally, we will return the result. Okay, but now what about the situation where you try to resolve something that is not bound within the container? Something like that. What should happen in this case? Okay, well, maybe we should reverse it. What if we do our guarding at the beginning? And I say, well, if not exists within the bindings, then, yeah, we don't know what to do.

And I say, well, if not exists within the bindings, then, yeah, we don't know what to do. We can't proceed. We have no idea what you want here. And in those situations, we can throw an Exception. Something like this, throw new Exception. This is provided by php, and this is what you should do in exceptional situations. And then later, you can either catch those and respond to them however you need to, or you can just let them happen. Now, when we create an Exception, we can give it a reason.

you can just let them happen. Now, when we create an exception, we can give it a reason. So in this case, what is our reason? Well, we couldn't find a matching binding. So maybe how about that? No matching binding found for your key. And yeah, once again, I can hit Option Enter in PHPStorm and switch that to string interpolation if you like that just a little better. Otherwise, if we get to this point, we're good to go. So I can remove this and reformat.

Otherwise, if we get to this point, we're good to go. So I can remove this and reformat. And yeah, I think we are off to the races. Okay, so we know that we are requiring our bootstrap class here. So why don't we just play around with this? Let's comment this out and then die and dump the database class to see if it's working properly. So if I switch to Firefox, there we go. We now have our database instance fully configured and ready to go, which is pretty cool. And yeah, also, if I were to instead resolve something that is not in the container, well,

We now have our database instance fully configured and ready to go, which is pretty cool. And yeah, also, if I were to instead resolve something that is not in the container, well, remember, we throw a new Exception, right? So let's see if that works and what that looks like. I give it a refresh, and there we go. Uncaught exception. So notice when you throw an exception, you do have the option of catching it and then responding in some way, maybe a more graceful way. But I don't want to touch on that just yet. For now, this is fine.

Adding an App Singleton11:02

Definitely not. But it does show that we need some place to build up our container and then have access to it from anywhere in the application. Okay, so to do that, I'm going to build up an App class. So just come along for the ride for a minute. I'm going to call it app.php. And I'm going to add a static method here called setContainer. A quick refresher, just in case you forgot, a static method is one that can be called without having to first instantiate an object. So for example, with this method being static, I can then say App::setContainer, just like

without having to first instantiate an object. So for example, with this method being static, I can then say app::setContainer, just like that. If I did not have static here, yeah, of course, I would have to build up a new instance of app. And then I could say, excuse me, app::setContainer. Okay, so let's keep it as static for now because this is going to be super helpful. Now, it's going to accept our container, and then I will store it. So why don't we create a static property called container, and then I will initialize it. static $container = $container;

So why don't we create a static property called container, and then I will initialize it. Static container equals container. All right, so now when I call this method, I give it my container object, and all it does is it saves it on this property. It's the only thing it does right now. All right, next, I need to get the container so I can interact with it, right? So why don't we do this? Let's copy this method. And yeah, if you want, you could call it get. I guess that would be consistent, and it's very common, but I don't really love using

And yeah, if you want, you could call it get. I guess that would be consistent, and it's very common, but I don't really love using the get prefix if I don't have to. Why don't we just name it container? And all this is going to do is it will return this value here. It's going to return the container to me. Okay, so now, from anywhere in my application, I have this app class. And if I say app container, I get access to the container, and I can then interact with it. And this is an example of a singleton, but again, jargon, terminology, you don't need.

it. And this is an example of a singleton, but again, jargon, terminology, you don't need to worry about it right now. Okay, so let's give this a shot. And actually, real quick, can't forget to add the namespace here. All right, so I will come back to Bootstrap. So here, we've instantiated our container. We've added our first binding. And don't forget, in real life, you'll do this dozens of times, potentially. And now, I want to set the container like so, and I will pass that in.

And don't forget, in real life, you'll do this dozens of times, potentially. And now, I want to set the container like so, and I will pass that in. And do note that I have imported app at the top automatically by phpStorm. All right, so I know we're covering a lot here. That's why we had that quick conversation at the beginning of the video. If you need to, watch this two times. Watch it three times, just to make sure you fully grok and understand what's going on here. Okay, I think we're ready to try this out. So I'm going to go back to that destroy action on my controller, where we were manually

Okay, I think we're ready to try this out. So I'm going to go back to that destroy action on my controller, where we were manually building up our database class. And I will comment it out. And instead, I want to say, app, container, give me the container. And then, I want to, well, let's see what we want to do. On my container class, I want to resolve my database instance. So I say resolve, and then we give it a path to the class or the key that I want to resolve out of the container. And once again, I'm just going to die and dump the db class to make sure that everything's

out of the container. And once again, I'm just going to dd and dump the db class to make sure that everything's working the way we'd expect. All right, and then let's go to notes. And I'm sorry, we don't even have a note here. Let's add one super quick. We view it. We click delete. That will hit our DestroyController. And then we should dd and dump the database object.

That will hit our destroy controller. And then we should die and dump the database object. And everything is working. Very, very cool. Okay, so now if we did everything correctly, all of this stuff here can now be replaced with our app, container, resolve our database. Pretty cool. And in fact, if you want, I'll show you a little tip. Notice how I am manually writing out the path to our Database class.

And in fact, if you want, I'll show you a little tip. Notice how I am manually writing out the path to our database class. But as it turns out, if you just reference the class name itself, and then ::class, that will basically translate to a string of the full namespace path to the class. And if you even want to inline it, you could do this. And that way you get some auto completion, some intellisense. You might prefer that. It's still going to work the same. Okay, cool. But now I want to focus on some creature comforts.

Delegating Resolve and Bind15:27

Okay, cool. But now I want to focus on some creature comforts. It would be nice if I didn't have to say app, container, resolve, database. Wouldn't it be cool if I could just say like app, resolve, and then it just works. And real quick, by the way, I would almost always import this. So I will bring that back. But yeah, wouldn't it be cool if I could just say app, resolve, and then the App class would delegate to the Container class and give me what I need? Okay. The problem is right now the resolve method is defined on Container right here and not

Okay. The problem is right now the resolve method is defined on container right here and not on app. But think about it. You're in charge. Is there anything preventing you from just having a static method here called resolve, and then you give it the key? No. And if you're instead thinking, well, there's nothing preventing you, but you're adding a bunch of duplication.

And if you're instead thinking, well, there's nothing preventing you, but you're adding a bunch of duplication. No, I'm really not. I'm not reproducing everything I have here. I'm just deferring. I'm delegating. So the App class can say, okay, well, let's grab our container. And then I will call resolve and pass through the key. You see how it's just kind of passing it through? App is just, it's adding an importance here.

You see how it's just kind of passing it through? App is just, it's adding an importance here. Yeah, you can call resolve. And if you do, I'm just going to delegate it over to the container class. It just makes for a more, I don't know, friendly and intuitive API to access it directly off of the App class. Okay, so with that in mind, maybe we should do the exact same thing for bind. Bind, and then, of course, I will need a resolver. Okay, cool. And again, those delegate.

Okay, cool. And again, those delegate. Okay, and then, of course, I want to return the result here. Okay, so now if I come back to our DestroyController, yeah, ideally, hopefully, this will just work. Let's give it a shot. View the node, click delete, and everything works just like it did before. But now we are resolving things out of the container, which means, yeah, I can switch over to any place I was referencing this before and swap out that instantiation with a reference to our container, as you see here.

It also has support for things like automatic dependency resolution, which is a fancy term that just means there are situations where you wouldn't even need to specify how to build up an object. The container could possibly do it for you automagically, to use a made-up word there. And yeah, basically, it will look at the class, and then it will look at the dependencies on the constructor. And then don't forget, each dependency could have its own dependency. And those sub-dependencies could have their own dependencies. And yeah, the container could recursively go through each of those and build it all up on the fly.

Service ContainersStatic MethodsAutomatic Resolution

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