Project Overview0:00
[♪ music playing ♪ Alright, so now let's take everything you've learned in this course and wrap it up into a real-life example. And here's what we'll do. We'll build a very basic file storage API that has two different implementations, but they both conform to the same contract. One we'll save locally to our file system, and another one we'll push files up to Amazon S3. And yeah, you'll see this allows us to flex
Local Storage Prototype0:27
and another one we'll push files up to Amazon S3. And yeah, you'll see this allows us to flex all of the muscles you acquired in this course. Let's get going. Alright, so let's do this. We'll create each of the implementations individually and very procedurally, and then we will extract them into dedicated classes. So first up, we will have a local implementation. So let's create a new file.
So first up, we will have a local implementation. So let's create a new file. I will call this local.php. And yeah, again, remember this is mostly our playground for now. Alright, so if I want to save a file locally, of course it's very simple. I first need the name of the file. So why don't we call this, I don't know, test.php or txt. A simple text file is fine. And then the contents of this will be hello world.
A simple text file is fine. And then the contents of this will be hello world. And that's it. So I want to create a file on my local file system that is called test.txt and contains hello world. Alright, so as you know, this is pretty simple. I can say file_put_contents, file, and contents. However, it would make sense to have maybe a dedicated folder or a dedicated root folder for any storage like this. So with that in mind, I'm going to open this sidebar.
or a dedicated root folder for any storage like this. So with that in mind, I'm going to open this sidebar and create a new directory called storage. Name this whatever you want. Okay, so now we need to append the file, and of course this could be a full path, a directory path to a file. And I want to proceed it with our root. So why don't we say root folder will be, what did we call it, storage.
So why don't we say root folder will be, what did we call it, storage. Okay, so now I could say we're going to save to the storage directory, then add a forward slash, and then whatever file that we have here. Alright, let's give this a shot. And I'm sorry, I called it storage. It should be root. Alright, so let's open up the terminal, and I'm going to run php on this current file. And if we did everything correctly, if I open up the storage folder,
and I'm going to run php on this current file. And if we did everything correctly, if I open up the storage folder, of course we get test.txt. It's working. And, yeah, of course there are some edge cases. For example, what if we try to write to 123 test.txt. Well, those nested folders do not exist, so I think this will fail. And if we give it a run, yes, we get failed to open stream. No such file or directory exists.
And if we give it a run, yes, we get failed to open stream. No such file or directory exists. So, yeah, maybe we should do some security, like why don't we call mkdir on root, but then notice that I'm repeating myself, right? So why don't we say $savePath will be the root, and then the file, like so. And then I could say, alright, well, make that $savePath. We will make it publicly accessible, and I'm going to set recursive to true.
We will make it publicly accessible, and I'm going to set recursive to true. And that means recursively create any nested folders if they do not exist. Then we could write to this file like this. Okay, I think that's right. Let's give it a shot. php on the current file, oh, no, is a directory. So let's see what we have here. Oh, yeah, I forgot to strip off the file name. So it's trying to make a directory including the name of the file.
Oh, yeah, I forgot to strip off the file name. So it's trying to make a directory including the name of the file. So, of course, let's substitute this with only the path to the current directory. So that would be storage 1, 2, 3. Make sure that directory exists. Alright, so let's clear that out. Sorry about that, and we'll try it one more time. And, yeah, now it recursively creates the folder and builds up the file. And, yeah, again, there's a couple edge cases we could look at, but this is good enough.
S3 Upload Prototype3:58
And, yeah, again, there's a couple edge cases we could look at, but this is good enough. Alright, so this is our procedural local implementation. Now let's create a version that uploads the file to Amazon S3. Once again, create a new file, and this one will be our S3 playground. And, yeah, I'm going to take everything here and move it over, and then we'll swap things out. Alright, so first up I need to pull in Amazon's PHP SDK, and I'll show you how. Now, of course, we are using Composer, so I can open up the Packagist website, and let's just look for Amazon S3.
Now, of course, we are using Composer, so I can open up the Packagist website, and let's just look for Amazon S3. And, of course, you'll see there's a very, very, very, very, very popular package directly from the AWS team. Alright, so let's take a look at how to use this. I'll go to the GitHub page, and if I scroll down, getting started. Alright, first up, of course, we need to sign up for AWS. It's very possible you've done this. If not, you can probably do so for free. They probably give you some free credit so that you can play around.
If not, you can probably do so for free. They probably give you some free credit so that you can play around. And then you will need to create some AWS credentials. Now, that's a little tricky. If you need help, you can ask AI or Google. But, yeah, it's mostly creating an account, setting a set of permissions, and then they will give you an access key as well as an access secret. And I've already done that. For example, if I switch over to my AWS console, I'm going to delete this when we're done, but sure enough, you can see I've created a testing key.
For example, if I switch over to my AWS console, I'm going to delete this when we're done, but sure enough, you can see I've created a testing key that we can use in our projects. So I'm going to reference that right now before we move on. So right at the top, again, temporarily, we will have our S3 key, and I'll paste that in, and then we also have an S3 secret that, again, you can get from that dashboard. And I'll paste that one in. Great. Alright, let's head back to the instructions.
Great. Alright, let's head back to the instructions. Alright, next up, we need to pull in the SDK. So let's grab that through Composer, and I'll paste that in. Great. And now we can review some quick examples. So here's how we create an S3Client. And, again, this class comes through the package, of course. And then we can upload any file by using this putObject method. Alright, so let's try to use as much of this as we can.
And then we can upload any file by using this putObject method. Alright, so let's try to use as much of this as we can. So let's come back to our editor, and I'll paste it in right here and get rid of our local implementation. Alright, so we begin by pulling in vendor/autoload, and this is necessary, of course, if we want to leverage the automatic imports as we see here. So let's make sure we put this at the top of the file. Alright, next, let's declare anything that we need to use, in this case, the S3 client.
Alright, next, let's declare anything that we need to use, in this case, the S3 client. Next, we instantiate the S3 client, and I need to specify my preferred region. So we can grab that if I switch back. Yeah, we can see if I search for region, we want US East 1, in my case. Alright, so let's update this, like so. Finally, we need to specify our credentials. Alright, and yeah, here we go, AI to the rescue. We pass through our S3 key, as well as the secret. Alright, so now we have an S3 client ready to go.
We pass through our S3 key, as well as the secret. Alright, so now we have an S3 client ready to go. If I switch back to the instructions, if I scroll down, yeah, here's how we upload a file. I'm going to grab all of that, and we'll paste it in right down here. Alright, so we have our S3 object. We call putObject. We need to specify a bucket, so why don't we declare that up here. How about this? And yeah, I've already created a bucket, and it's called laracast-testing.
How about this? And yeah, I've already created a bucket, and it's called LaracastTesting. Okay, so we can substitute bucket. Now, the key is going to be, effectively, the file name. So if I scroll up, the file will be what you see right here. And yeah, just for now, let's make it very simple. Next, the body will be the contents of the file. So I could say file_get_contents, or remember, we already declared what the contents should be, so I can reference that here.
we already declared what the contents should be, so I can reference that here. Okay, and then ACL, we can stick with the defaults. So try to put this new file up to our S3 bucket. Catch any exception that might occur, and echo something to the console, in this case. So are we all set? Let's just do a little updating here. Because this is my S3 implementation, we don't have a root folder. The bucket itself is really the root folder, right?
Because this is my S3 implementation, we don't have a root folder. The bucket itself is really the root folder, right? And then I don't need the save path. Yeah, and I think this is what we end up with. So let's give it a shot. Let's run php on this new file. And we don't echo anything, but it should have worked properly. So now I'm going to switch over to my bucket. All right, and there we go. So I'm using transmit in this case to connect to my bucket.
All right, and there we go. So I'm using transmit in this case to connect to my bucket. And yeah, sure enough, we have test.txt. And here is the contents. Hello, world. Everything is working. Great. Okay, so now we can get to work. If I open my sidebar, I can see that we have two different implementations or strategies for how we could go about writing a file.
Interface and Implementations9:01
If I open my sidebar, I can see that we have two different implementations or strategies for how we could go about writing a file. But now, yeah, I want it to be easily swappable. So here's what I think we should do. First up, I'm going to create a new directory here, and I'm going to call it source. Think of this like the source folder for our application. So if I want to autoload classes within this folder, of course I need to return to composer.json, and let's populate this.
of course I need to return to composure.json, and let's populate this. Let's declare, and again, thank you, AI. We're just getting to the point where I don't write anything at all, and that's fine by me. So we're going to declare PSR for autoloading, and I'm going to say the app namespace is located within the source directory, and that should do it. Okay, so now let's create a directory here called storage. Actually, you know what?
Okay, so now let's create a directory here called storage. Actually, you know what? Let's keep it simple. Let's put everything within the root src folder there. All right, so I'm going to have an interface here. So declare an interface Template, and this will be our file storage contract. This is the contract for how we go about storing or fetching files. So all I want to do for now, just for the demo, is we're going to have one to save a file, or you know what?
So all I want to do for now, just for the demo, is we're going to have one to save a file, or you know what? puts and get are such common terms when it comes to file storage. Why don't we call it put? You need to give us the name of the file as well as the contents. So why don't we say give me the file path, and again, the contents. That looks good to me. All right, so now we have our contract. Next, let's create an implementation and sign that contract,
All right, so now we have our contract. Next, let's create an implementation and sign that contract, and we'll do that now. Create a class called LocalStorage, and I'm going to have that implement the FileStorage contract. All right, I can have my editor add the method steps, and then I'm going to do the exact same thing, and this time I'll just copy and paste it, and this will be for our S3 storage, as you see right here. All right, so here's the fun part.
and this will be for our S3 storage, as you see right here. All right, so here's the fun part. Now we just migrate over our procedural code. So let's start with the easy one, local storage. Let's create a split, and I'll open up local.php. All right, so we know this is effectively what we need to do here. So I'm going to paste this in, close this out. We figure out what the root is, and yeah, we're just going to hard code it here. Yep, it's going to be in the current directory,
and yeah, we're just going to hard code it here. Yep, it's going to be in the current directory, move up a level, and that's the root for any stored files. That's fine. So next we have our savePath, which will be the root, followed by the path or the file that you give us. Next, we recursively create that nested folder tree, and then finally we will write to this file, and I believe that should do it. And then what's wrong here?
and I believe that should do it. And then what's wrong here? Okay, I'm sorry, content, contents, it could be both really. We'll stick with content, that's fine. All right, so here is our local storage driver, you might even say. It accepts a path and the contents that should be associated with that path. We create the folder, and we write to it. So if we were to give this a shot, here's what we should do. Let's create a new entry point, and this will be index.php. Here's what I'll do.
Let's create a new entry point, and this will be index.php. Here's what I'll do. I want to pull in these classes or import them. So at the very beginning, this is like the init for my entire application. I'll start by pulling in vendor/autoload.php. That's Composer's autoloader. Next, I'm going to use our local Storage class. I will instantiate it, and then I can say storage::put. So once again, this time let's do file.txt, and it will once again say hello world.
So once again, this time let's do file.txt, and it will once again say hello world. And then I will say done. And if we did everything correctly, we should now have a file.txt file within the storage folder. Let's give it a shot. php index.php, and it's never quite so easy, is it? Class LocalStorage not found. So what did we do wrong here? Let's open up LocalStorage, and that looks good.
So what did we do wrong here? Let's open up localStorage, and that looks good. Yep, that's all right. I know what we need to do. We updated our composer.json file, but we didn't dump the autoloader, and this is a common thing we can run into. Let's run composer dump-autoload once again, and this time I think it'll work. php once again, and yeah, well, now we're still getting a warning, but we've gotten past that composer issue.
php once again, and yeah, well, now we're still getting a warning, but we've gotten past that Composer issue. So yeah, now it's saying, hey, this file already exists, but you're asking us to make it, and that's kind of weird. But if we take a look, sure enough, we're all set there. All right, so here's what we should do. Let's come back into our localStorage implementation, and yeah, maybe we could say, well, if not, is_directory save_path, and again, I should say dirname. If that's not already a directory, only on that condition should we create it.
is directory save path, and again, I should say dir name. If that's not already a directory, only on that condition should we create it. Okay, and yeah, that should probably resolve that issue. php on the current file, and we get done. The file has been created. We're in good shape. Okay, so now let's create our S3 implementation, and we're going to do the exact same thing. Open up our playground for S3, and this will give us the opportunity to make a bunch of little changes here.
Open up our playground for S3, and this will give us the opportunity to make a bunch of little changes here. So yeah, let's just grab all of this, close it out, and paste it in. And yeah, one by one, we're just going to clean things up. We will hide secret keys that should not be here. We will leverage dependency injection, which you learned about in a number of episodes, and then we'll perform general cleanup as needed. All right, let's get going. So we begin by, all right, we have our S3 secret.
All right, let's get going. So we begin by, all right, we have our S3 secret. I don't like that. The file is going to be passed in. The content is going to be passed in, and the bucket, well, for now, I'm just going to keep it here, and then we'll extract that to an .env file. All right, next, we instantiate the S3 client. We pass through the credentials. That's all good, and then we try to put to this bucket.
We pass through the credentials. That's all good, and then we try to put to this bucket. The key will be the path to that file or the file name, and then the body will be just content. All right, next, let's just go ahead and import that at the top. Use S3 exception, like so. And yeah, I think it'll at least work. We have some work to do in general here, but in terms of the functionality, it should work. So let's give it a shot.
but in terms of the functionality, it should work. So let's give it a shot. I'm going to return to index.php, and this time I will instantiate the S3 storage class, like so. And notice the API remains the same, and that's the entire point. We have different logic, different ways, or different strategies for going about writing the file, but the API remains the same, and it's the same because of the interface or the contract that we've talked about so much in this course.
and it's the same because of the interface or the contract that we've talked about so much in this course. So let's give it a shot. This time I will say, hello, how about hello S3, and let's give it a go. php on index.php. We get done. Let's have a look in our bucket. I'll give it a refresh, and sure enough, I have file.txt. Here's the contents, hello S3.
I'll give it a refresh, and sure enough, I have file.txt. Here's the contents, hello S3. It's working. Yay. All right, so now I'm feeling pretty good here. We have two different implementations, but if I return to S3, let's talk about some general code cleanup here. Yeah, as a basic rule, you never want to store secret keys within your files. You might have to reference them in multiple places.
Move Secrets to .env16:50
you never want to store secret keys within your files. You might have to reference them in multiple places within the project, which isn't good, but also if this is under version control and you push it up to GitHub or something like that, well now, bang, everybody now has access to your secret codes and can access your bucket. All right, so let's do this instead. We're going to throw these keys and configuration items into a dedicated .env file,
We're going to throw these keys and configuration items into a dedicated environment file, and I'll show you a common pattern for that. In my root project, I'll create a .env file, and now I'm going to declare keys and values, key-value pairs. So we'll have S3_KEY, S3_SECRET, and then we can even declare what bucket we want to use here. So yeah, there it is, S3_BUCKET. All right, so let's switch back.
So yeah, there it is, S3 bucket. All right, so let's switch back. Where was it? S3 storage. And yeah, now I'm going to take all of this junk and throw it into my .env file, and then we will substitute. All right, so here's our key. Here's our secret. And then finally, here's our bucket. All right, get rid of that php code.
And then finally, here's our bucket. All right, get rid of that php code. All right, so now, yeah, our .env file contains potentially sensitive key-value pairs, and the whole idea is you would never commit this to version control. Instead, you would manually create the file on your production or your staging server, and that way, your private secret never enters version control.
and that way, your private secret never enters version control and can never be accessed on GitHub or something like that. Okay, but now, how do we load these? Well, we could write the code manually, but again, there are dedicated packages to assist with this. So let's pull one in now. I'm going to return to packages.org,
So let's pull one in now. I'm going to return to packages.org, and I'm going to look for environment. And yeah, actually, it looks like I'm not familiar with this one from Sebastian, but this is the one I'm most familiar with. Loads environment variables from .env into a dedicated superglobal, and that's what we want. All right, so let's go to the GitHub page,
and that's what we want. All right, so let's go to the GitHub page, and let's install it. Let's see. Yep, copy that. I will paste that in. All right, and next, let's see. Usage, let's scroll down. Yep, we've created our key-value pairs. Next, we need to load those values, and it looks like we can do so like this.
Next, we need to load those values, and it looks like we can do so like this. All right, so back into our entry point for our app, we could do this right at the top. Let's use it, use .env. All right, so it's going to find any .env file within the current directory, which is fine. We will load those environment variables, and magically, they will then be available, for example, in a $env superglobal.
and magically, they will then be available, for example, in a $env superglobal or $server, I believe, as well. Okay, so check this out now. If I go into S3 storage, I no longer need to reference these values. Instead, I could say, at least to start again, look for S3, what did we call it? There it is, key, and let's do another one, S3 secrets, and then finally, S3 bucket.
There it is, key, and let's do another one, S3 secrets, and then finally, S3 bucket. All right, no more sensitive keys in our code, which is good, and I'm sorry, not an array, Jeffrey. All right, so let's give that a run, make sure we didn't break anything, and of course, we did. There was an error uploading the file, so it's going to be something related to
There was an error uploading the file, so it's going to be something related to how we loaded these. S3 key, oh, that's wrong. I'm sorry, I didn't paste that properly. All right, one more time. I bet that fixes it, and it does. All right, so I think we're in good shape, and we've solved that particular problem. Next, well, every time we call put,
and we've solved that particular problem. Next, well, every time we call put, I'm instantiating the client, which isn't ideal. Hmm, there's a number of ways to deal with this, but why don't we take advantage of dependency injection, which you learned about. How about when we work with this class, we need to inject an implementation of the client, like this,
we need to inject an implementation of the client, like this, protected S3 client. We'll make that available on the class, and then, yeah, ultimately, I could say, well, this client putObject. Okay, so that would mean this code will now leave the put method. The bucket, how do we want to do that? Let's say, hmm, how about when you
The bucket, how do we want to do that? Let's say, hmm, how about when you instantiate this class? You got to give us a preferred bucket, and then we can reference that as well. Okay, cool. So now, if I go back to index.php, yeah, when I instantiate S3 storage, I have to provide the client, and then the name of the bucket,
I have to provide the client, and then the name of the bucket, which, again, was env('S3_BUCKET'). All right, let's create our client. I'm going to paste that code in. We called it client. Yeah, so we're just building up the dependency tree. We have our client. We instantiate S3 storage,
We have our client. We instantiate S3 storage, and now we're just cleaning things up a little bit, aren't we? Clear it out, run it again, and we still get done. It's all working. But now, S3 storage, yeah, it's just that much cleaner. This is a bit more of what we would expect.
it's just that much cleaner. This is a bit more of what we would expect. And, of course, we can either let the exception be thrown, or you could say this will return void or false. Just design the API however you want in this case. Yeah, in this case, you know what? Let's just keep it simple.
Yeah, in this case, you know what? Let's just keep it simple. We will let the exception be thrown, and then you can catch that and deal with it however you want to. All right, this is looking pretty good to me, and it's fairly simple. This is our S3 storage implementation, and here is our local storage implementation.
Factory Storage Resolver22:42
and here is our local storage implementation. All right, so finally, if I come back to index.php, this is the code we have, but now I want this to be as reusable as possible. Ideally, anywhere in my project, I should be able to just say storage put without always having to construct
I should be able to just say storage put without always having to construct the object and all of its dependencies, which can be somewhat cumbersome. So again, there's many different ways we could configure this. Some projects have what's known as a service container, which is sort of like a toy box. You put everything you need
which is sort of like a toy box. You put everything you need within the toy box, and then you can take it out anywhere in the project where you need it. Why don't we just go with a simple, kind of like a proxy, where we just have one class that will automatically figure out do you want to use S3
that will automatically figure out do you want to use S3 or do you want to use your local driver? And remember, it could be the case that when you're working locally, everything is saved locally, but in production, everything is saved to S3, and we want to toggle that, basically, dependent upon the environment.
and we want to toggle that, basically, dependent upon the environment. So let's do this. Let's play around. Why don't we have kind of an entry point? And I'm just going to call this storage. And storage is basically a glorified factory. So I'm going to have a static method here called resolve, and this is going to resolve
called like resolve, and this is going to resolve whatever file storage implementation or driver you might say that you want to use. And here's how we would do it. We would declare our preferred driver within the .env file. So maybe I could say FILE_STORAGE equals local or S3 or whatever new one you create. So we have a key that specifies
or S3 or whatever new one you create. So we have a key that specifies which preferred storage implementation we want to use. So let's come back. Be quiet. And now, right here, I can say, all right, well, let's grab the file storage key. Storage method, something like that. Yeah, I could say, well,
Storage method, something like that. Yeah, I could say, well, if the storage method is local, then, and I noted this is basically a factory, right? Then we will build up a local storage object. Otherwise, we will build up an S3 storage object. So yeah, now I can return here, and I can take all this kind of junk that you see here, and I can move it into our factory class of sorts. So let's, and then let's do this.
and I can move it into our factory class of sorts. So let's, and then let's do this. I don't think these need to be variables. Let's inline that one, inline that one. Bucket is already inlined, so we don't need that. Reformat. Yep. And then finally, yeah, we can just return that object. Anything else we could do here? Yeah, we are returning early, which I love to do.
Anything else we could do here? Yeah, we are returning early, which I love to do. In terms of adding the else, this is more of a stylistic thing. It's not really necessary, because if you get to this line, well, then you're good to go, because otherwise we would have returned early. But some people like the clarity of if else. If this is local, otherwise do that.
But some people like the clarity of if else. If this is local, otherwise do that. But it's up to you. I'm going to remove it, because I kind of like this approach. Return early if you need to, otherwise proceed. And we will assume in this case that if it's not local, it has to be S3. But remember, if you had more than two storage methods, you would need to tweak this somewhat. But this is good enough.
you would need to tweak this somewhat. But this is good enough. So now, because S3 storage and local storage have signed that file storage contract, I can declare that this resolve method is going to return some implementation of file storage. And that's what I mean when I say this is kind of a fancy factory. A factory is just something that builds up an object, basically, almost like a real-life factory.
A factory is just something that builds up an object, basically, almost like a real-life factory. Real-life factories build things, right? They build toys. They build pencils. In this case, our class is building up a preferred file storage object, including any dependencies that it may require. All right, so check this out. Now, I can get rid of a bunch of this kind of annoying junk. Our entry point to our app loads our autoloader, normal stuff.
Now, I can get rid of a bunch of this kind of annoying junk. Our entry point to our app loads our autoloader, normal stuff. Next, let's get rid of this. It loads our .env, or I'm sorry, ENV key-value pairs, which is great. And then next, all I want to do is say, all right, I'm going to reference my storage proxy or façade or whatever you want to call it. I want to resolve whatever the appropriate driver or implementation is,
I want to resolve whatever the appropriate driver or implementation is, and then I'm going to call the put method. And because every implementation conforms to the same API, I can rest assured that there's a put method. I can rest assured the first argument will be the file path, and the second argument will be whatever contents we want it to be. I'll return that to Hello World. And now, this is what we get. So let's give it one final test.
And now, this is what we get. So let's give it one final test. We'll say hello.txt, and in my .env file, we are using the local driver. So if we give it a run, if we open this up, now we have hello.txt. All right, let's return, and I'm going to swap this out with S3. Give it a run, refresh, and there we go, hello.txt. That must have been from a previous test. Open this up, and of course, we still get Hello World.
That must have been from a previous test. Open this up, and of course, we still get Hello World. Finally, what if we set this to gibberish, something that doesn't exist? So we run it, and yeah, we get done. And this may be what you want, or it may not be. This is kind of confusing, isn't it? And of course, the issue is, well, we assumed anything that is not local will default to our S3 implementation. And yeah, like I said, maybe you want that. There's lots of APIs you use where the value doesn't really matter.
And yeah, like I said, maybe you want that. There's lots of APIs you use where the value doesn't really matter. If it's not such and such value, then it will always fall back. But yeah, maybe that could lead to bugs, and you don't want to allow for that. If that's the case, here's what you do. You could say, well, else if the storage method is S3, then handle it like this. Finally, if you reach down here, we should throw an exception. And we're going to talk more about exceptions in a future episode.
Finally, if you reach down here, we should throw an Exception. And we're going to talk more about exceptions in a future episode. But yeah, this is a way to say, wait a minute, I take exception to what's going on here because I don't know how to proceed, and I'm going to alert you. And yeah, we'll talk about exceptions quite a bit more in the future. So check this out. If we simplify that and give it a run, now this time we will, if I can type, this time we will see a exception,
now this time we will, if I can type, this time we will see a exception, and that's what we would want in this case. We're trying to use some kind of storage method that we don't support. So what else can we do except throw a fit? Okay, we covered so much in this video, and I hope you enjoyed it. It gave us the opportunity to just review countless things. Good guidelines. Good guidelines. That's a good way to say it, Jeffrey.
Good guidelines. Good guidelines. That's a good way to say it, Jeffrey. Good guidelines for protecting your code, for hiding and tucking away sensitive data, how to load that sensitive data, how to configure your application, how to build specialized factories of sorts, how to create interfaces and implementations of these interfaces. It really is kind of a cool approach, and I would highly recommend if this felt super over your head,
It really is kind of a cool approach, and I would highly recommend if this felt super over your head, like we went from zero in the last episode to 60 in this episode, that's okay. Watch the video again, and then, of course, of course, ask questions below the video at larrycas.com. I'll see you later.
