مرور الگوی PRG (و فلش جلسه)0:00
All right, so let's see. Welcome back, everybody, by the way. For our next refactor, we're still working in the same controller. But yeah, for this episode, I want to point your attention right here, where we return a view. All right, so at first glance, you might think, well, what's the problem with that? It may make more sense if we break it down. When the User visits the login page, they provide their credentials, they submit the form, and that will then hit this controller, right? All right, we all understand that. But now, in this example, if the form validation fails, we return a view and pass through the errors. And yeah, if we try it out, that's exactly what's happening. So again, you're probably thinking, well, what's wrong with that, Jeffrey? Well, there's actually a couple issues. I want you to notice that the form validation failed, but we didn't redirect back to the login page. That's not what happened here.
Introducing PRG Pattern2:25
page. But instead, I see document expired. Yeah, so all of this is a result of our current implementation, which is in response to a POST request, simply returning a block of HTML. And again, that's just something we don't want to do. So instead, we're going to follow a very, very common pattern known as, I'm going to have to break it down. What is it? P for POST, R for redirect, and G for GET. I don't say this acronym very much. What is it? PRG, is that right? POST, redirect, GET. It just refers to, I'm sorry, I should have looked that up before the video. It just refers to a common approach, especially for hitting controllers and performing form validation. All right, so what that means is, P for POST, you're going to make a POST request to submit the form. And then if, in this example, the validation fails, we don't return a view, as you see here, we perform a redirect. That's the R. That redirect will send us to a new page.
Storing Errors in Session4:57
point, we perform a whole new page refresh, and we don't have those errors anymore. So, how do we grab it? Or in other words, how can we literally pass these validation errors on to the next page request? And yeah, if you're thinking, well, we learned about sessions. Can we use sessions for this? Absolutely, we can. And that is the standard practice for things like this. All right. So, we got our work cut out for us. Let's take this in a few steps. First, we could do something like this, where I say session, and create a new key called errors, and make that equal to the formErrors. This might be your first stab at it. Okay. So, then, when we redirect to the login page, that, of course, will take us into our HttpController sessions.create view. And yeah, right here, I could pass through the errors, like this, by saying, give me the errors key, if you have anything. Otherwise, we will default to an empty array. And yeah, this would be an
remember, that was the whole point of our authentication system. We store details about the User in the session, and then it will persist across every page load for the lifetime of the user's session. And, of course, the same thing is going to be true for these validation errors. So, yeah, it's almost like I want this particular session key, the errors, to have an expiration date, right? So, yes, put the errors in the session, perform the redirect, load the HTML, and then expire that key from the session so that we don't run into this problem where even five or ten minutes later, I return to the login page and I see all of this stale validation data that doesn't apply anymore. All right. How could we do that? Well, maybe with a little bit of trickery, we can get it to work. So, let's start by going back to our StoreController where we handle the login form submission. So, hmm, it's almost like we need a way to distinguish between
Creating Flash Session Data7:29
trickery, we can get it to work. So, let's start by going back to our StoreController where we handle the login form submission. So, hmm, it's almost like we need a way to distinguish between data that should live in the session indefinitely and then data that should instead be flashed to the session for one page request and then it immediately expires, so to speak. So, here's what I'm thinking. Why don't we use a custom key to make this explicit? And maybe we'll call it something like flashed. But you always want to be careful that you don't accidentally interfere with a different key that the user or me or I am trying to put into the session. So, you will often see libraries or frameworks precede the key with an underscore or even two underscores. And again, they do that just to remove the possibility of interfering with a key that you provide as part of your application. I think one is fine. And why don't we call it flash? Okay. So, now, think about...
everything still works the way it did before. So, now, I need to figure out where do I expire that session data? Let's see. If we come back into our entry point into the application, yeah, this is a little messy. And that's going to be one of our refactors. But you can see this is where we route the request. So, could we route a request? And, remember, that's going to figure out which view to load or which controller to load. It will require the controller. The controller will load the view that will present the HTML to the user. And then we would hit this point. So, maybe, yeah, just right here, we could clear out any flashed session data. And, again, later, we will refactor that and organize things a bit more. But for now, would that work, you think? And in that case, would we just do something like session_flash equals an empty array or even just unset it entirely? And if you're not familiar with unset, just think of it as delete. Delete that key from the session
do something like session flash equals an empty array or even just unset it entirely? And if you're not familiar with unset, just think of it as delete. Delete that key from the session super global array. All right. Would that work? Well, let's give it a shot. So, once again, we'll go to home. We'll go back to login. I will fail the validation. We perform a redirect. We flash the errors to the session. And then after, again, after we loaded this whole page, we hit that point. So, hopefully, if I give this a refresh, we won't see the errors anymore. And, yes, that's what we want. So, again, now we've solved that problem where, for some reason, the validation errors just persisted for the whole session, which, of course, we didn't want. All right. So, this is good. Now, I actually want to perform a refactor. Because at this point, notice I had to know what the name of the flash key is. And that's not a huge concern. And yet, still, it exists in multiple areas of our application.
Building Session Helper Class10:57
want to perform a refactor. Because at this point, notice I had to know what the name of the flash key is. And that's not a huge concern. And yet, still, it exists in multiple areas of our application. And if at some point we were to, for example, right here, change this, well, then I would have to remember to go back and then update this to flashed. And then, again, with our current implementation, what if for another form, I don't know, like notes create, maybe right down here, we want to flash something like this? Well, again, I would have to manually write that. So, what I'm saying is the opportunity for these things to become out of sync is pretty high. So, that tells me I need to consolidate. I need to encapsulate just a little bit more. Okay. Let's see what we can do. In our core directory, why don't we add a new class? And it's going to be really, really simple. In fact, these are going to be mostly, like, global functions. They will be static.
can do. In our core directory, why don't we add a new class? And it's going to be really, really simple. In fact, these are going to be mostly, like, global functions. They will be static. But, yeah, I will have a class called Session that can have some simple helpers for us. So, for example, put something in the session. We'll call it put. You give us a key, you give us a value, and then we put it in the session. And, like I said, I'm going to make this stupidly simple for the initial implementation. So, something like this. sessionKey equals value. Done. All right. So, what else would we need to do with the session? Well, we put things into it. We want to take things out of it or get things out of it. All right. Let's do another one. Get me something from the session. You give us the name of the key, and we will return session and then the key. But then, what if the user or we pass a key that doesn't exist in that super
Get me something from the session. You give us the name of the key, and we will return session and then the key. But then, what if the user or we pass a key that doesn't exist in that super global? We should handle that and maybe give them the opportunity to provide a default. So, maybe something like this. Give me a default, and we'll set that to null. There is no default, but you can override that when you call this method. And then I can do something just like this. So, if the provided key exists in the session, return it. Otherwise, if it's null, then return the default. Okay. What else? Put things into the session. Get things out of the session. What about, like, determine if something is in the session? Does the session have this key? Well, maybe a method like has. has. And then the key. And we could manually write this, but maybe I can just defer to the get method. So, this is almost like a helper. Like this.
Well, maybe a method like has. has. And then the key. And we could manually write this, but maybe I can just defer to the get method. So, this is almost like a helper. Like this. So, think about it. If I return that, if the key exists, we would return that value. If it doesn't exist, we would return null. So, we're dealing with truthy or falsy values. Why don't I just convert those into a Boolean? So, now the has method will return true or false, depending upon whether the session has the given key. All right. has, put, get. Well, what about the other thing we talked about, where we want to flash something to the session? Well, let's go back into our controller. Into sessions. Into store. And right here, I want to abstract that away. That was the main motivation for this class in the first place. So, let's add another one called flash. You will give us a key and you will give us a value. And I'll paste what we had.
that away. That was the main motivation for this class in the first place. So, let's add another one called flash. You will give us a key and you will give us a value. And I'll paste what we had in earlier for reference. Okay. So, we're gonna put it into _flashed and then replace this with the key. And then make that equal to the value you provide. So, now we have a function or a method that explicitly puts something into the session, but then marks it as being flashed. We originally called it flash. It marks it as something that should only live for a single request. Okay. That's looking good. Let's go back here. So, now I can say session, import that. And there we go. flash. The errors. Like so. Okay. But now, if I come back to public/index, yeah, right down here, remember, we were talking about, well, you keep repeating the same key. There's no need to do that. Let's just call a method on the session class that does the
index, yeah, right down here, remember, we were talking about, well, you keep repeating the same key. There's no need to do that. Let's just call a method on the Session class that does the opposite. Right? So, like, on session, and I don't know. I might think for a minute what is a good name? We had been using expire, but maybe even something like unflash it to the session. Do the opposite of this. Maybe that's fine. So, if I did that, I could say session()->flash. And then, yeah, let's unset it. Cool. So, now if I come back into our index.php file, yeah, I no longer or this file no longer needs to know what the name of the identifier we use to handle flashing session data. I can just say session()->unflash. And that'll take care of it. And again, yeah, we might want to take a few moments to think of a better method name. But for now, it's clear enough. Okay. So, are we all on the same page? Let's do a quick recap. Now, when the User visits
we might want to take a few moments to think of a better method name. But for now, it's clear enough. Okay. So, are we all on the same page? Let's do a quick recap. Now, when the User visits the login form, they provide their credentials, they submit it, we validate it, and if validation fails or the authentication attempt fails, we now use our handy dandy session flash method to flash the validation errors to the session, and then we perform our redirect. Now, when the User redirects to the login page, that will hit this controller, where we're still manually fetching the errors from the session. But, yeah, now I can update this like so. session get. But, yeah, now we have another issue. If I did this, it wouldn't work, right? Think about it. If we try this out, so it'll redirect back, and actually, we think it's working, but it's not. Like, let's do session equals an empty array. And actually, what I'm doing here is flushing the session. That's probably
Updating Get for Flash17:03
so it'll redirect back, and actually, we think it's working, but it's not. Like, let's do $session equals an empty array. And actually, what I'm doing here is flushing the session. That's probably another method we should add to that class. But, yeah, if I manually just clear out the session, so I can start all over. So, we try it, it redirects back, and, yeah, this time, I don't see the validation errors. And, yeah, it makes sense. There is no key in the session called errors. What's in the session is _flash, and within that array is an errors key. So, yeah, again, I don't want that to leak out. So, I don't want to do something like _flash, and then even if we added something like this, like dot notation, well, that unique key has once again leaked out of the session class, and I don't want that. So, why don't we just do this? When you get a key, let's first see if you have flashed that key to the session. And if you have, that very likely is the
the session class, and I don't want that. So, why don't we just do this? When you get a key, let's first see if you have flashed that key to the session. And if you have, that very likely is the one you want. And if it's not flashed to the session, then we will fall back and look for it at the top level of the session super global. So, for example, I could say, well, if session_flash_key, and then I'd have to say, well, if that is set, then that's the one you want. So, yeah, this is getting a little annoying to write, but we can refactor it like that. And I think that would do the trick. So, yeah, if errors exist within the flashed key, then that's what we want. So, return them. Otherwise, see if errors exist in this example. See if errors exist at the top level. Okay. Fail it, and I hope this works, and it does. Okay. So, now we can perform just a very quick refactor here. Or really, it's fine if you want to keep it as it is. But if you
Adding Flush and Destroy19:33
at the top level. Otherwise, fall back to the provided default. I think it's fine. So, one more time, let's check our work. There we go. I'm pretty happy. All right. And, yeah, I think that mostly does the trick. So, finally, back in our Session class, remember we talked about adding a helper method to flush the session entirely? Let's add that now. And maybe that would just set the session super global to an empty array. And, yeah, then we can make use of that elsewhere. For example, you'll remember in our Authenticator class right here, at the bottom where the User logs out, well, immediately, I see that. So, I could replace it with session()->flush(). But also, I see more stuff related to sessions. So, maybe all of this could move to our Session class. So, in that case, well, that's not the same as flushing the session. All of this code is destroying the session. So, maybe I will select all of this and replace it with session() and a new method. destroySession().
that's not the same as flushing the session. All of this code is destroying the session. So, maybe I will select all of this and replace it with session and a new method. Destroy the session. And now all of that logic can instead live here, if you prefer. Okay. So, I will paste all of that in. And then we're already in the session class. So, I can replace that with self or static. And, yeah, I think it's fine for this code to live here. All right. So, again, this is a basic initial implementation. Everything's static, which means they are effectively global functions. But for our current implementation, it will work just fine. All right. So, we'll keep working on this in the next video.
