PHP: What are generators in PHP, and how do they compare to arrays?

Table of contents

Generators are like little functions that let you iterate over it (for example with a foreach) and it
will 'yield' a value on every iteration.

If you do foreach(range(1,10000000) as $i) {...} then PHP will probably crash, as it has to first
'create' the array (with items from 1 to 10000000 in it). This isn't terribly memory efficient.

A better way to do it is using a generator. It will 'output' (yield) one value at a time, so uses much less
memory.

Here is an example of a generator - take note of the yield line.

function range_generator($from,$to){

for($i=$from;$i<=$to;$i++){

yield $i;// here!

}

}

The yield $i is sort of the same as return $i, however the code that called range_generator()
(i.e. the foreach loop) can keep asking it to yield again and again and again...

This range_generator() function is often called xrange() in other languages that have this function built in, BTW

The function above can be used like this:

foreach(range_generator(1,10000000)as$i){

echo$i;

}

The foreach loop starts, and gives the generator two parameters, 1 and 10000000.

The generator 'beings', and starts its own for loop. In its first iteration, it will yield 1.

This is then passed back to the foreach ( as $i), and the code within the foreach loop is executed
(echo $i).

Once the first iteration of the foreach is complete, it will go back to the range generator, which will then
finish its for loop (nothing else for it to do), and go into its own second iteration of that loop,
until it reaches the yield $i again, where the pattern repeats.

This carries on until the range_generator stops yielding a variables back to the foreach loop.

If you used the standard range(1,10000000) function then the array of 10000000
elements has to be created and stay in memory. Using this generator means that only one $i exists at a time,
massively reducing memory load. If I use the standard range(1,10000000) function, I get a
Allowed memory size of 134217728 bytes exhausted (tried to allocate 536870920 bytes) exception.

The memory usage with a generators is constant, no matter how many times it iterates. It isn't really even fair to
compare an array and a generator, as they aren't really the same thing. But you can loop them in things like a
foreach loop.

How to send data to the generator

When you work with generators, you are actually working with the Generator class. There are a number of
methods available to you:

public mixed current() - Get the yielded value

public mixed getReturn() - Get the return value of a generator. This is used after you have finished
using the generator - as soon as you return anything (null, a value) the generator will stop yielding

public mixed key() - Get the yielded key

public void next() - Resume execution of the generator (same as calling Generator::send() with NULL as
argument)

public void rewind() - Rewind the iterator. N.B. If iteration has already begun, this will throw an
exception.

public mixed send( $value ) - Sends the given value to the generator as the result of the current yield
expression and resumes execution of the generator.(see below for more details)

public mixed throw( Throwable $exception ) -- Throw an exception into the generator, and then resumes execution of the generator. The behavior will be the
same as if the current yield expression was replaced with a throw $exception statement

public bool valid() - Check if the iterator has been closed

public void __wakeup() - not really used - it will just throw an exception as you cannot serialise
generators.

I think the most important one to know is the send($val) method. Here is an example:

<?php

function printer(){

echo"I'm printer!".PHP_EOL;

while(true){

$string= yield;

echo$string.PHP_EOL;

}

}

$printer= printer();

$printer->send('Hello world!');

$printer->send('Bye world!');

?>

This will output the following:

I'm printer!
Hello world!
Bye world!

When to use generators and their yielding feature

If you are ever doing something with a large amount of data and iterating over all of it, then your first thought should always be if you should be using a generator.

You don't want to be storing huge amounts of data in memory, especially if you are actually only working on one part (one line, one element, one document, one row) at a time.

Good times to use generators:

Dealing with things like database rows. It is fine to process a few hundred (even a few thousand) in a normal array based loop. But if you have a big data set then you will very quickly run out of memory. Put it in a generator and handle one row at a time.

When working with log files. Log files can easily be many GBs of text. Loading it all into memory at once, again, would be a bad idea. But cycling through it line by line means you won't face any memory issues.

I am a 29 year old backend web developer from London, mostly focusing on PHP and Laravel lately. This (webdevetc.com) is my blog where I write about some web development topics (PHP, Laravel, Javascript, and some server stuff). contact me here.

More...

Comments and discussion about What are generators in PHP, and how do they compare to arrays?

Search for something

Welcome to my web dev blog! I'm a 29 year old guy from London, UK. I've loved programming since I was around 9 years old, and have run websites since being an early teenager.

For the last 12 or so years I've been working full time as a backend web developer (mostly with PHP). This is my site where I write up about various web development topics (including Laravel stuff, some JS articles, Vim tips, server stuff, etc.)

Welcome to my site about all topics related to webdev (website development), but mostly focusing on PHP and Laravel.

I mostly work with PHP/Laravel nowadays - my time is split between working for some UK firms (handling their website stuff and coding their web based projects), and my own personal projects, sites and services.