تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Register Blade directives0:00

Here's where we left off in the previous video. So, we introduced this concept of a cache directive, but we haven't registered that with Blade yet, so we see the text on the page. Let's get started. Open up my editor, and if I browse to app/Providers/AppServiceProvider.php, down here within the boot method is a good place to register any custom Blade directives, like this. Now, if I switch to my note, we decided we want to call it cache, and then we have another one called ncache. Okay. So, let's register this first one, cache. That will accept any kind of expression if we pass one through. And now here, we can return any bit of PHP, and that's important to remember. As part of the compile process, Laravel is going to basically use a regular expression and swap in whatever we return here. So, for example, if we were to return a PHP block that simply echoes out,

Laravel is going to basically use a regular expression and swap in whatever we return here. So, for example, if we were to return a php block that simply echoes out, hello world, like this, well, let me show you what you get. Now, right now, if I give that a refresh, it'll look identical. Yeah. And that's because, well, whenever you update your directive, we need to clear out our view cache. So, we can use php artisan view:clear for that. So, refresh, and yeah, now we can see for that directive, we're swapping it out with a call to echo hello world. So, that means we can create one more for ncache, and then do something like goodbye world, and you have your basic 101 for creating a custom Blade directive, as you can see right here. All right. So, now we have a way to hook up our cache. And in fact, why don't we delegate to a dedicated simple class here, almost like a procedure. So,

Delegate to caching class1:30

blade directive, as you can see right here. All right. So, now we have a way to hook up our cache. And in fact, why don't we delegate to a dedicated simple class here, almost like a procedure. So, why don't we call it, within our app directory, I don't know, RussianCaching, and you can put that within any directory we want. But now, I do want to pass to it, well, real quick, if we go back to our card, notice that we're trying to pass the card object to it. So, I need to make sure I include that here. But how do we do that? Let me show you something. Your directive is going to receive an expression, which contains anything you pass through to it. So, if I clear up my view cache, come back to Chrome, and give this a refresh, notice you're not getting the card object itself, you're just getting a regular expression match. So, that includes the parentheses here. Okay. So, here's what we could do. If I undo a couple steps

notice you're not getting the card object itself, you're just getting a regular expression match. So, that includes the parentheses here. Okay. So, here's what we could do. If I undo a couple steps and bring back the expression. Yeah, we could do something like this. We could call maybe a setup method, and then do this number right here. And if you think about it, what's going to happen is this, we make a call to a setup method. And if I just hit paste, that's what you're going to get. And that's what we want. We want our view to call a setup method and pass through the card object. Okay, cool. So, I'm going to remove all of that, copy this or yank it and paste it in here. And then finally, for any teardown work, I'm just using testing terms here. We could also new up a class. But my main concern for that would be, could we end up in a situation where we new up 500 different objects. And that's definitely not what we want. So, we could

We could also new up a class. But my main concern for that would be, could we end up in a situation where we knew up 500 different objects. And that's definitely not what we want. So, we could maybe register a singleton, or at least right now, we might be able to get away with just a couple calls to a static method. Anyways, our teardown, we'll take a look at this, we're not passing anything there. So, that means it will not receive an expression, in which case I can call the method per usual. Next, I'm going to bring those back to regular php blocks. All right, so yeah, if we run this, it's going to fail, right? We haven't created this class yet. So, let's get to it. App, Russian caching, save it. And let me add a bit of boilerplate here. All right, so we're going to have a static method called setup that receives the model. And then we have another one called teardown that doesn't receive anything. Okay, so let's go through and maybe just outline what we want to happen.

Implement fragment caching3:49

method called setup that receives the model. And then we have another one called teardown that doesn't receive anything. Okay, so let's go through and maybe just outline what we want to happen. So, let's act like it's a first request. Well, what I want to say here is essentially this. If we already have something in the cache, there's really no reason to execute any of this code. So, I want to ignore it completely. So, we know that this will need to ultimately return a condition that checks whether or not something is in the cache. If it is, ignore this, we don't need to process it. If it isn't, well, we're going to turn on output buffering, process this, and then save it to a variable. And then that way, we can take that compiled HTML and cache it. And then ultimately, down here, we will echo it out. Okay, so with that in mind, let's come back up. Let's write this out. We need to, like I said, turn on output buffering. And then finally, return a boolean that indicates

down here, we will echo it out. Okay, so with that in mind, let's come back up. Let's write this out. We need to, like I said, turn on output buffering. And then finally, return a boolean that indicates if we have cached this model yet. Okay, well, output buffering, very easy with vanilla php. I can say ob_start, start output buffering. And when you're doing this, anything here is not being echoed out immediately. It's basically being stored in a buffer. So, make sure you remember that. Okay, next, return a boolean that indicates if we have cached this model yet. So, it'll be something like this, right? Return cache has and then some kind of key that we'll need to generate. And I will go ahead and import this, by the way. Okay, so we're almost done with this section. But I do need to figure out, well, what will the cache key be? And here's what we'll do. We're going to make the model responsible for telling us what its cache key should be. So, we'll do something like

need to figure out, well, what will the cacheKey be? And here's what we'll do. We're going to make the model responsible for telling us what its cacheKey should be. So, we'll do something like this, $key = model::getCacheKey(). Now, at the time of this recording, Laravel doesn't offer this out of the box. So, we will need to add that to each of our models. I'll show you how to do that in just a minute. All right, so we return our boolean. If we go back here, this is either going to return true or false. So, now we can say, if we do not have anything in the cache, only in that case should we execute the code that follows. And then down here, I will close that out. Yeah, it's a little funky, I have to admit. But it's the only way I know to get this to work at the moment. So, think about it. This is what we're saying. We're saying right here to use pseudocode, if not model::hasBeenCached, only in that case should we continue on and process all of this

So, think about it. This is what we're saying. We're saying right here to use pseudocode, if not model has been cached, only in that case should we continue on and process all of this here. Now, if the model has been cached, we won't execute anything here. We won't render any of these partials. We won't lazy load any database queries. None of that will be triggered. Okay, hope that makes sense. All right, so now we've taken care of this first block. Actually, in fact, why don't we go through it from scratch? We load the page for the first time. There is no cache, there's no nothing. And we get to this section. Well, that will hit this, where we make a call to a setup method, and we pass through the model name. Now, that will figure out a unique cache key, it'll turn on output buffering, and it's just going to check, have we cached anything with this key yet? Well, it's the very first time, so the answer is no, right? So, here we say,

to save the output buffer contents to a variable called html. So, we'll take that compiled html and save it to a variable. And then, finally, cache it, if necessary, and return the html. Now, really, what we want to say is echo out the html. So, let's make sure we do that. Whatever we return from that method should be echoed out right here to the page. And, by the way, these methods, I think we'll probably end up changing those to something, I don't know, a little more readable resolve. I don't know. I'd think about that for a minute. Anyways, it doesn't matter at this point. So, let's figure this out. Fetch the cache key. Well, already, that's kind of confusing because we don't have access to the model in this case. And now, what we could do is something like this, where you're just explicit. You pass through the card every time. Not a fan. I don't like that. So, here's what we're going to do instead. I'm going to store it. I'm going to create a protected.

where you're just explicit. You pass through the card every time. Not a fan. I don't like that. So, here's what we're going to do instead. I'm going to store it. I'm going to create a protected static variable called keys. And then, each time we call the setup method, I will write to that or append to that. So, each time we open up a cache block, I'm going to write to this array and make it equal to the unique key for that specific model. That way, when we call teardown, we can just pop off the most recently added key to this array, and we have our cache key. Okay. So, key equals array_pop(static keys). Okay. So, we have that step done. Next, save the output buffer contents to a variable. Okay. ob_get_clean is the function we want, and we'll save that to HTML. So, in fact, why don't I show you this? It's pretty cool. Now, in this case, it's squawking because I haven't used that variable yet.

and we'll save that to HTML. So, in fact, why don't I show you this? It's pretty cool. Now, in this case, it's squawking because I haven't used that variable yet. So, let's clear everything out. I'm going to come back to Chrome, give this a refresh. And in this case, we haven't set up our model key yet. So, what I'm going to do here is say foo for the time being. Once again, clear the view cache, refresh, and there you go. So, we turned on output buffering and we rendered everything here until we turned it off. So, we get the compiled HTML right there. Okay. So, that gets saved to a variable and ultimately gets cached. So, I'm going to hit you a couple times to undo, and good. So, we have that section. Cache it if necessary, and echo out the HTML. So, return, and LayerHelper makes this pretty easy for us. I can say rememberForever key, and for our function, well, we just want the HTML, right?

and echo out the HTML. So, return, and LayerHelper makes this pretty easy for us. I can say rememberForever key, and for our function, well, we just want the HTML, right? So, I could say use HTML, and then return that. Okay. If you're not familiar with this cache, rememberForever, it's pretty useful. What we're saying is find an item in the cache that has this key. And if you find it, just return it to me right now. However, if you don't have it in the cache, I'm going to trigger this closure, and you're going to write or put to the cache, and then also return it in the process. So, yeah. In other words, if this is not in the cache, put to it, and return the response. Otherwise, if it is in the cache, just give me the value associated with it. And really, that's maybe all there is to this. There's not that much going on. So, now, take a look at all of this. I'm going to clear our view cache, and also our regular cache.

Generate model cache keys11:13

it. And really, that's maybe all there is to this. There's not that much going on. So, now, take a look at all of this. I'm going to clear our view cache, and also our regular cache. And if I go back to Chrome and give this a refresh, ah, yeah, we are on to the next step. So, we need to figure out how to generate a unique cache key for each model. Here's how we're going to do that. We could begin by, well, let's say we go to our Card model and add a getCacheKey method. And now, the convention we're going to use is this. It's going to return a string that basically has the class name, followed by a slash, followed by the ID of the instance, so the Card with the ID of one, followed by the lastUpdatedAt timestamp for this instance. And this is a crucial part of this approach. Because we are linking the cache key to the timestamp, whenever you update the model, that timestamp will change, and you effectively generate a new

crucial part of this approach. Because we are linking the cache key to the timestamp, whenever you update the model, that timestamp will change, and you effectively generate a new cache key, and the page refreshes. You're not even responsible for clearing out that cache. It'll just happen automatically, which is really cool. So, let's do this. I'm going to return sprintf, and it looks like our format will be a $variable, slash, a $variable, dash, and then another $variable. Okay, the values for that will be this, the class. So, I can do getClass for this object. Next, I need the ID for this instance, like so. And then finally, I need the lastUpdatedAt timestamp. So, this, updatedAt, and that will be a Carbon instance with Eloquent by default. So, I can call a timestamp property off of that, and that should do it. Let's see this in effect. I'm going to run php artisan tinker. I'm going to fetch the card, the very first one. And now,

So, I can call a timestamp property off of that, and that should do it. Let's see this in effect. I'm going to run php artisan tinker. I'm going to fetch the Card, the very first one. And now, I will say card.getCacheKey, and we should see a unique string or unique identifier for this key. And now, here's what I was saying. If you were to update this, my Card has been updated, and if I save it, once again, Eloquent will, as part of that process, refresh your updatedAt timestamp. So, if I were to now, let's make sure we have a fresh copy. If we were to now call this getCacheKey method, it's different. Notice up here, 7656, down here, 0136. So, because we have a new cache key that we're using in our views, well, obviously, that key doesn't exist yet, so we will display fresh data and then cache it when we're done. Now, though, what about the old cache? So, what about the item in the cache with this string? Well, the nice thing about

exist yet, so we will display fresh data and then cache it when we're done. Now, though, what about the old cache? So, what about the item in the cache with this string? Well, the nice thing about something like memcached is you don't have to do anything. It will automatically be flushed out as things fill up, so you can practically ignore it altogether. Okay, so that's cool, but one thing is you would need to add this method to any model that should have a cache key. Instead, we don't want to duplicate our code. This is a very good use case for a trait like this. Let's instead say useCacheable, and then I will create that, cacheable.php, and now, right up here, we could say trait Cacheable, and I will paste that in, like so, and finally, set the namespace. Yeah, so now, any model that should be cacheable can implement this trait, or what you could even do is have all of your models extend a custom parent model that will

set the namespace. Yeah, so now, any model that should be cacheable can implement this trait, or what you could even do is have all of your models extend a custom parent model that will use this trait, and that way, you'd only have to write that out one time. Anyways, we'll keep it like this for now. So, Card, and then Note should be cacheable. So, let's come back to Chrome, give this a refresh, and now, it looks like what it did at the beginning of the video, but we're doing some different things at this point. So, right off the bat, let me show you something. If I were to come back to, how about our Note itself, and let's just change it to like an h1. If I were to come back and give it a refresh, you're not going to see that reflected here because we are, in fact, pulling from the cache. So, in fact, you could delete it altogether, and it wouldn't make a difference because it's not being executed at all, and further, that'll

here because we are, in fact, pulling from the cache. So, in fact, you could delete it altogether, and it wouldn't make a difference because it's not being executed at all, and further, that'll apply to your database queries, too. So, and this is like one nice benefit to the approach. Let's go into my log file, and why don't we set it up just temporarily so that for any query that's executed, we log it to this file. Okay. We can do that in our routes file since it's temporary, and I could say, listen for any database queries, and as part of that workflow, I want to log the queries SQL to this file here. All right. So, let's do php artisan cache:clear and php artisan view:clear so that I can start from scratch. Okay. So, close this out, back to our log file, and if we now load the page, if I then switch back, we can see we're triggering a bunch of queries here, and one reason, even outside of the caching, is because we didn't use any eager

and if we now load the page, if I then switch back, we can see we're triggering a bunch of queries here, and one reason, even outside of the caching, is because we didn't use any eager loading, and what I mean by that is in your CardsController, yeah, you could say cards with notes, and that way, you don't have to worry about the n plus one problem, but anyways, we're not doing that in this case, so yeah, we end up with a lot of queries, but here's the cool thing. We've now cached that view logic, which means at any point where we trigger those queries, we won't anymore. We just pull from the cache instead, like this. Give it a refresh, and now you see exactly one query, and incidentally, that is the query associated with this call right here, and that will, in fact, run every time. So, yeah, pretty cool, right? That's a nice side effect to this. If you're not thoughtful about eager loading everything, yeah, you can end up in a situation

will, in fact, run every time. So, yeah, pretty cool, right? That's a nice side effect to this. If you're not thoughtful about eager loading everything, yeah, you can end up in a situation where maybe, unbeknownst to you, a hundred different queries are being executed, but if we use this technique, it sort of protects you in a weird way. Anyways, let's move on to something else. Back to my routes file. Get rid of that. So, we do have one other problem, though. If we give this a refresh, what about when we update an instance? Well, we do want to make sure that we clear the cache, so to speak, for that item. Now, we're part of the way there, but it's still not going to work like we want just yet. Let me demonstrate this. I will go back to our notes, and then let's bring that back to a list item, okay? Well, right now, you're not going to see that happen, because it's all cached, so we don't even execute any of that code. But now,

and then let's bring that back to a list item, okay? Well, right now, you're not going to see that happen, because it's all cached, so we don't even execute any of that code. But now, didn't we decide that if we update the model, its timestamp will be refreshed as well, and so that should, in effect, clear the cache? Well, yes, but then also no. We're missing one step. Let me demonstrate this for you. php artisan tinker. We'll say $card equals app('Card') ::first(), and then let's find this note here. So, $note equals $card->notes [1]. There we go. Okay, so we have the note that corresponds to this section right here. I'm going to update it. So, a new update here, and I will save the note. So, it's true. We do know that we have changed the timestamp and the cache key is different. However, if I were to come back and refresh, you're not going to see that reflected here. Why not? Can you figure it out? Here's the answer.

Touch parent timestamps18:26

the timestamp and the cache key is different. However, if I were to come back and refresh, you're not going to see that reflected here. Why not? Can you figure it out? Here's the answer. Yes, this section would refresh, but we're never getting to that point, because right here, this is all still cached. So, remember when I said if it's cached, we don't even execute anything in here? Well, that's the case. So, we never had an opportunity to get to this section and fetch new data. So, it sounds like we need a way for these child caches, these child fragment caches, to notify the parent, because basically what I want to do is say, when this note is updated, I also want you to touch the parent's timestamps so it can refresh as well. And you'll often have to do this. Imagine if right here, this heading also displays the number of notes. Well, if you didn't clear that parent cache, but you changed or added more notes, well, none of that would

to do this. Imagine if right here, this heading also displays the number of notes. Well, if you didn't clear that parent cache, but you changed or added more notes, well, none of that would sync up, right? Unless you're using JavaScript, none of it would sync up. So, yeah, we need a situation where updating this note will, quote unquote, touch the parent as well. Here's how we do that in Laravel, and it's an essential piece of Russian caching. I'm going to go to my note, and I'm going to say, protected touches, and then reference any relationships like this: card. So, here's what this is saying. When this particular note is updated, as part of that save process, we want to touch any relationships here and specifically increment their updated_at timestamps in the process. I'll show you this in effect. php artisan tinker. Let's say note equals App\Card::first()->notes, and grab that one again. I will now update the note. One more update,

stamps in the process. I'll show you this in effect. php artisan tinker. Let's say $note equals app()->card->first()->notes, and grab that one again. I will now update the note. One more update, and I will save it. However, though, before we do that, let's just echo out the card, and we can see here's the last updated at time stamp. Okay, so I'm going to save the note, but this time, because we added this section, we said, well, when you save it, please touch the parent's time stamps as well and update those. All right, so let's try it out now. If I say $note, of course, this time stamp has been updated, and if we also fetch the card again, I'm going to get a fresh copy, so we have current data. Well, let's take a look. If we scroll up, before it was 1848.56, but now it's 1859, so that's specifically what this section does right here. Now, think about it. This is all we really needed. We updated the note. Its time stamp was refreshed, so we have a new cache key.

Flush cache in development22:35

well, for local development, that should always just work, right? But right now, I would do this, and I would get annoyed. I'd have to clear the cache, come back. Yeah, we don't want to do that. Now, I think there's a couple ways around this. I know the way it works in Rails is they work the template itself into the cache key. So imagine something like in pseudocode, you do an MD5 of this entire template, you get a unique string, and that becomes linked to your cache key for the model. In Rails, that's not too hard. With Laravel and php, I think that might be kind of tough. I don't know. We could play around with it in a future video. But for now, another approach is to just figure out a way to clear out the view-specific cache for local development. And here's something we could do. Let's add a middleware. Let's go to our HTTP Kernel. Let's add a new one, maybe something like this, app/Http/Middleware, and we'll call it

And here's something we could do. Let's add a middleware. Let's go to our HTTP kernel. Let's add a new one, maybe something like this, app/Http/Middleware, and we'll call it FlushViewCache. Now, remember, this section, these are global middleware that will be triggered one by one for every single request. So let's create it, app/Http/Middleware, FlushViewCache. And let's build this up. Okay, so a middleware needs a method called handle that will receive the current request as well as a function that will pass it on to the next request. So now, like ultimately, I could say, I'm done with this section, so I'm going to pass the request on to the next middleware that has a chance to receive and respond to the request. So now we could do something as simple as this. If the current environment is local, well, in that case, we want to clear out the view cache, clear the view-specific cache.

So now we could do something as simple as this. If the current environment is local, well, in that case, we want to clear out the view cache, clear the view-specific cache. Now, one option would be to clear out the cache entirely, but we don't necessarily have to do that. We just need to clear out any cache for our views. So one way we could tackle this is to use cache tags like this. And I'm sorry, it looks like I have a mistake up there. Anyways, if we go back to our RussianCaching class, we could do this. We could tag every key here with views. Then down here, I could do the same thing, views. So now think of this as sort of like a hook. If I ever wanted to clear out all the cache that has this tag, I can just say cache:flush views. For example, let me just show you this in real time. Let's manually clear the cache and also the view cache. Okay, so we're going to run chrome, give it a refresh, and instantly we're

flush views. For example, let me just show you this in real time. Let's manually clear the cache and also the view cache. Okay, so we're going to run php artisan cache:clear, give it a refresh, and instantly we're told this cache store does not support tagging. Now, the problem is, if I go to my .env file, right now we're using the default cache driver of file, and that doesn't support tagging. We'd need to use something like Redis or Memcached. So let's switch that over. And luckily, because we're using Homestead, that's already set up for us out of the box, which is really nice. Okay, so back to Chrome. If we give this a refresh, we grab fresh data. So on subsequent clicks, we're pulling from the cache. If we now go to any note, how about this one, and we bring that back to a list item. And if we go back to Chrome and refresh, well, yeah, it's not updating, right? Because we're pulling from the cache. So we now, because we're using cache tags, we now have a

that back to a list item. And if we go back to Chrome and refresh, well, yeah, it's not updating, right? Because we're pulling from the cache. So we now, because we're using cache tags, we now have a way to clear out all of that. Because remember, in production, you don't really want to do this. That could be disastrous in some cases, to just empty out everything. You could get killed. So we just want to clear out the view-specific cache. So we can now do that. php artisan tinker, cache, tags, views, flush. And in this case, I'm sorry, it's failing. That's because we're doing this on our main machine. I actually need to go into my VM. So let's go there, php artisan tinker, and then run the command here. Okay, that's done. So if I come back to Chrome, everything should be updated. And you can see the bullet there to prove it. Okay, cool. So yeah, this is one way we can tackle it. Let's come back to our middleware. And we could say cache tags,

everything should be updated. And you can see the bullet there to prove it. Okay, cool. So yeah, this is one way we can tackle it. Let's come back to our middleware. And we could say cache tags, views, flush. And I will make sure that I import that. And that's it. So yeah, it's true. I honestly think this is kind of, it's not messy, but I feel like it's kind of gross to write. I would rather have that work automatically. And I think what we're gonna have to do in a follow up lesson together is figure out a way to say, yeah, the template structure itself factors into the cache key. So that way, if we change this, well, the MD5 of the template will change as well. So we get fresh data, and we cache that new data. And that way, incidentally, when we push to production, that all works automatic, you don't have to do anything. With this current approach. Well, this takes care of local development. But you would also need to add a command for your deployment.

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