Introducing LazyCollection0:00
First up, and easily one of my favorite additions to Laravel 6, is LazyCollection, that were contributed by Joseph Silber. Now, you're already familiar with the standard Collection class in Laravel, which wraps an array. However, we also have a new LazyCollection class that wraps an iterator. And when necessary, this comes with the benefit of massively decreased memory usage. So let me show you a handful of examples. Let's perform a query here to fetch all projects from my database. Now, before I run that, if I switch over to SQL Pro, you can see there's about 56,000 generic records in here.
Demonstrating Memory Limits0:34
Now, before I run that, if I switch over to SQL Pro, you can see there's about 56,000 generic records in here. So let's run that query, and then simply return done. All right. If I switch to Chrome, give it a refresh, it doesn't even work. It blows up because of memory limits. So think about it. In this case, we not only run a query to fetch 56,000 records from the database, but we also populate a collection of 56,000 items, and PHP is just not up for the job. However, instead, what if we switch to a LazyCollection?
Using Cursor and Generators1:00
populate a collection of 56,000 items, and php is just not up for the job. However, instead, what if we switch to a LazyCollection? So I'm going to use the cursor method here that will run our query and then return a generator. Now, on that note, before we run this, we will talk about generators a decent amount in this episode. But if you want a more dedicated lesson, search Laracasts for the Explain PHP Generators episode. All right. So now, if I give this page a refresh, it does work for a number of reasons.
All right. So now, if I give this page a refresh, it does work for a number of reasons. Let's take a look, though. If I were to dump this projects variable, what you would normally expect if you did project all is an Eloquent\Collection instance. However, now you're going to get a LazyCollection. Now, a very important thing to understand is at this point, no database query has been executed. And again, that's because we're using a generator. We're not going to run it until we pull items out of that collection.
And again, that's because we're using a generator. We're not going to run it until we pull items out of that collection. So for example, if I were to say, well, give me the first item, only then will we trigger a query. And there we go. We get our first project. So let's make it a little more clear because this is pretty important. I'm going to do a quick DB::listen here to catch any SQL queries, and all I'm going to do is dump it to the page. So let's dump the SQL itself to the page.
do is dump it to the page. So let's dump the SQL itself to the page. OK. So once again, at this point, no query is being executed. We're only returning a generator. So if we run it, nothing is dumped to the page. But if we come back and pull an item out of the collection, run it again, there is your query. So you'll see the benefit here is, yes, we're performing the query, but we're not loading it all up in memory.
So you'll see the benefit here is, yes, we're performing the query, but we're not loading it all up in memory. And that's why the code was breaking before. Now if you'd like to take a look at this cursor method, cursor, where is it? It's on the query builder. Not seeing it. QueryBuilder. All right. And let's go to the cursor method. OK.
Inspecting Cursor Implementation2:58
And let's go to the cursor method. OK. So you'll see right here, it is in fact returning a new LazyCollection, and it's yielding from a different generator or iterator. And to illustrate that, let me quickly boot up php artisan tinker, and then I will whip up a Illuminate\Support\LazyCollection, and then we'll pass a closure here. And all I'll do here is just yield multiple times. yield one, yield two, yield three. You get the idea. Or you can even do things like yield from, and then you could pass an iterator or something.
You get the idea. Or you can even do things like yield from, and then you could pass an iterator or something like that. OK. So if we run that, sure enough, we do have a collection. And if I were to fetch all of the items out of the collection, you'll see our three items there. And to prepare it, we only had to pass a closure to the constructor. So yeah, basically, that's what's happening here. We're building up a collection, and then we're yielding from a different generator.
So yeah, basically, that's what's happening here. We're building up a collection, and then we're yielding from a different generator. But it's the same basic thing. So anyways, that will defer to the connections cursor method. Here's the contract. Let's go down to the implementation. OK. So take a look. We are running a SQL query. Just come along for the ride.
We are running a SQL query. Just come along for the ride. We're preparing a PDO statement. We're binding the values. We're executing it. And then right down here, you can see we're iterating over the results and yielding each one. And actually, in fact, as I record this, I wonder if this is even necessary because statement would already be iterable. So if somebody wants to check me on this, I wonder if they could just instead return
This is just a simple explanation with no code.
Lazy File Processing Example4:54
to run into memory issues related to building up some massive array of items. All right. What else? Let's go back to the PR because Joseph has a number of really cool examples. Here's one on building up a collection of infinite items because, again, we're not generating any of these items here until they're pulled out of the collection. But now he had one other example I thought was pretty neat. Yeah, let's take a look at this one here. So we're making a lazy collection that will open a file, read each line, and then yield it for processing.
So we're making a lazy collection that will open a file, read each line, and then yield it for processing. So again, this would be preferred to something like using the file function to load a very large file that will instantly blow up your memory. Instead we use fopen and fgets to read it incrementally, one line at a time. But also because we're dealing with a lazy collection here and a generator, this means we'll call yield for as many lines as are in that file. And I know this can be confusing, but again, the advantage is you're not building up an array all at once. So at no point here are we constructing an array for however many lines are in that file.
array all at once. So at no point here are we constructing an array for however many lines are in that file. Instead, we incrementally read and yield. So as it turns out, a generator is nothing more than syntactic sugar for creating an iterator. And when it's wrapped in this lazy collection, we can then chain it with the usual set of methods you're used to. So let's try that out. We're going to fetch all of the projects and then iterate over them. And then for each one, we're simply going to log the name of the project to a file.
We're going to fetch all of the projects and then iterate over them. And then for each one, we're simply going to log the name of the project to a file. All right, so think about what's happening here. We're calling a cursor method that returns a lazy collection. Then when we iterate over the items, for each one we yield the project and then we log it to a file. Now why don't we limit this? We'll just say take 500 items there. And if we come back to Chrome, give it a refresh. Now 500 items should be in my log file.
Building Lazy Log Reader6:43
And if we come back to Chrome, give it a refresh. Now 500 items should be in my log file. And there it is. All of the generic names of the projects. Okay, next, let's read from it. I'll create a little helper function up here. We'll call it readFile. It accepts the file. It opens the file for reading. This will give us a file pointer.
It opens the file for reading. This will give us a file pointer. And then we can just say while the current line, and then we'll say fgets read the current line from the file pointer. Then we will simply yield the current line. All right, so think about it. This function readFile returns a generator. We've also learned a generator is just syntactic sugar for whipping up an iterator. Or in other words, something that you can call forEach on. So now if I were to say forEach readFile, and we're just going to read that storage
Or in other words, something that you can call forEach on. So now if I were to say forEach readFile, and we're just going to read that storage directory we just created. So it should be in the storage path within logs/laravel/2019/September/3rd. I think that's right. So we'll run that as the line. Now we can once again dump it back to the page. So now we're just kind of doing it in reverse. We are reading from the log file. That's returning a generator that we can iterate over.
We are reading from the log file. That's returning a generator that we can iterate over. And then we run a forEach and dump it to the page. So this time if we come back to Chrome, oh, whoops, readFile must already be a thing. Change it to readLogFile. Okay, give it a refresh, and there we go. So we read the log file. It returned a generator. We looped over it. And each time we do that, we yield the next line that then is dumped to the page.
We looped over it. And each time we do that, we yield the next line that then is dumped to the page. So while we're on this path, what we could do is turn it into a lazy collection. I could say create a new lazy collection like so. And then let's move all of this within here. Now I'll just grab that whole file there. Read the file, get the pointer, loop over it, and yield each line. So again, we're going to yield for as many lines as are in that file. So now that should do it. We have our lazy collection of lines.
So now that should do it. We have our lazy collection of lines. And then actually let me import that real quick. But yeah, at this point, you have your lines. You can then use any of the collection methods you're used to. So for example, maybe, I don't know, for each line, for some reason you want to know how many characters are in each line. Just remember though, at this point, if you were to wrap it, once again, you're getting a lazy collection, which means we haven't even opened the file yet at this point. That will only take place as soon as we pull out of the collection.
a lazy collection, which means we haven't even opened the file yet at this point. That will only take place as soon as we pull out of the collection. So if we run it, there's the character count for each line within that file. Kind of cool, right? So and by the way, actually, if you want, you could instead call lazy collection make. And this would allow you to continue chaining directly off of it. Okay, so that does it. But I guarantee you're still going to have questions. It takes a while to really understand php generators. It's a bit of a mindset shift.
