An introduction to

Iterators & Generators

in PHP


By @JeroenDeDauw
www.EntropyWins.wtf


Use "page down" and "page up" to navigate

  • Iterators
  • Collections in PHP
  • Generators
  • PHP is fun

Iterators


function doStuff(array $things) {
    foreach ($things as $thing) {
        /* ... */
    }
}
					

function doStuff(Iterator $things) {
    foreach ($things as $thing) {
        /* ... */
    }
}
					

Find the cat




  • Directory with .txt files
  • One contains ~=[,,_,,]:3




function findFileWithNyanCat(string $path): ?string {
    foreach (glob($path . '*.txt') as $file) {
        $fileContent = file_get_contents($file);

        if (in_string('~=[,,_,,]:3', $fileContent)) {
            return $fileContent;
        }
    }
    return null;
}
					

function getContentsOfTextFiles($path): array {
    // glob and file_get_contents
}

function findTextWithNyanCat(array $texts): ?string {
    foreach ($texts as $text) {
        if (in_string('~=[,,_,,]:3', $text)) {
            return $text;
        }
    }
    return null;
}

findTextWithNyanCat(getContentsOfTextFiles($path));
					

class TextFileIterator implements Iterator {
    /* ... */
    public function current() {
        // return file_get_contents
    }
    /* ... */
}
					

function findTextWithNyanCat(Iterator $texts): ?string {
    foreach ($texts as $text) {
        if (in_string('~=[,,_,,]:3', $text)) { /*...*/ }
    }
    return null;
}

findTextWithNyanCat(new TextFileIterator($path));
					

findTextWithNyanCat(new DatabaseIterator(/*...*/));
findTextWithNyanCat(new ArrayIterator(['test text', '~=[,,_,,]:3']));
					
  • iterable
    • array
    • Traversable
      • Iterator
        • Generator
      • IteratorAggregate


  • iterator_count(Traversable $t)
  • iterator_to_array(Traversable $t)

Generators


function newNyanGenerator(): Generator {
    yield '~=[,,_,,]:3';
    yield 'Nyan!';
}

$generator = newNyanGenerator();

foreach ($generator as $text) {
    echo $text . ' ';
}

// ~=[,,_,,]:3 Nyan!
					
  • new LimitIterator(Iterator $i)
  • new CachingIterator(Iterator $i)

IteratorAggregate + Generator = <3


function newNyanGenerator(): Generator {
    yield '~=[,,_,,]:3';
    yield 'Nyan!';
}

$generator = newNyanGenerator();

foreach ($generator as $text) {
    echo $text;
}
foreach ($generator as $text) {
    echo $text; // BOOM!
}
					

interface IteratorAggregate extends Traversable {
    public function getIterator(): Traversable;
}
					

class NyanNyan implements IteratorAggregate {
    public function getIterator(): Traversable {
        yield '~=[,,_,,]:3';
        yield 'Nyan!';
    }
}

$traversable = new NyanNyan();

foreach ($traversable as $text) {
    echo $text;
}
foreach ($traversable as $text) {
    echo $text; // works fine
}
					

Real world example


private function getMailTemplatesOnDisk(array $mailTemplatePaths): array {
    $mailTemplatesOnDisk = [];

    foreach ($mailTemplatePaths as $path) {
        $mailFilesInFolder = glob($path . '/Mail_*');
        array_walk($mailFilesInFolder, function(&$filename) {
            $filename = basename($filename);
        });
        $mailTemplatesOnDisk = array_merge($mailTemplatesOnDisk, $mailFilesInFolder);
    }

    return $mailTemplatesOnDisk;
}
					

class MailTemplateFilenameTraversable implements \IteratorAggregate {
    public function __construct(array $mailTemplatePaths) {
        $this->mailTemplatePaths = $mailTemplatePaths;
    }

    public function getIterator() {
        foreach ($this->mailTemplatePaths as $path) {
            foreach (glob( $path . '/Mail_*') as $fileName) {
                yield basename($fileName);
            }
        }
    }
}
					

Generators also can...


yield "Iterators" => "are useful";
yield "Generators" => "are awesome";
// ["Iterators" => "are useful",
// "Generators" => "are awesome"]
						

yield from [1, 2, 3];
yield from new ArrayIterator([4, 5]);
// 1, 2, 3, 4, 5
						

// Flattens iterable[] into Generator
foreach ($collections as $collection) {
    yield from $collection;
}	

class SeriousBiznessTest extends PHPUnitSomething {
    public function testContainsNyan(string $text) {
        $this->assertContains('~=[,,_,,]:3', $text);
    }

    public function nyanTextProvider(): array {
         return [
             [ 'Foo ~=[,,_,,]:3' ],
             [ 'Bar ~=[,,_,,]:3' ],
             [ 'message' => 'Baz ~=[,,_,,]:3' ],
         ];
    }
}
						

public function nyanTextProvider(): iterable {
    yield [ 'Foo ~=[,,_,,]:3' ];
    yield [ 'Bar ~=[,,_,,]:3' ];
    yield [ 'message' => 'Baz ~=[,,_,,]:3' ];
}
						

function doStuff(iterable $things) {
    foreach ($things as $thing) {
        /* ... */
    }
}
					

function doStuff(iterable $things) {
    foreach (array_chunk($things, 2) as $twoThings) {
        /* ... */
    }
}
					

function iterable_to_array(iterable $iterable): array {
    if (is_array($iterable)) {
        return $iterable;
    }
    return iterator_to_array($iterable);
}
					

iterable → Iterator


function iterable_to_iterator(iterable $iterable): Iterator {
    if ($iterable instanceof Iterator) {
        return $iterable;
    }
    if (is_array($iterable)) {
        return new ArrayIterator($iterable);
    }

    // TODO: IteratorAggregate case
}
					

    return new IteratorIterator($iterable);
					

interface IteratorAggregate extends Traversable {
    public function getIterator();
}
					

    return new \WMDE\TraversableIterator\TraversableIterator($iterable);
						

github.com/wmde/iterable-functions

Cattribution

Questions?

www.EntropyWins.wtf

@JeroenDeDauw