Není pochyb o tom, že se PHP s každou novou verzí posouvá dopředu. Některé vlastnosti si nese z minulosti a proto se jich nemůže tak rychle zbavit. Avšak tam, kde to hlasování a zpětná kompatibilita dovolí, tam se snaží držet krok s vyspělejší konkurencí. Minulý rok vydaná verze 5.5 přinesla mimo jiné jednu důležitou novinku: generátory. Obejdete se bez nich, ale o hodně se připravujete.

The old-fashioned way

Začnu jednoduchým příkladem bez použití generátoru:

<?php

function squares($from, $to)
{
  $result = [];

  for (; $from < $to; $from++) {
    $result[] = $from * $from;
  }

  return $result;
}

foreach (squares(1, 10) as $value) {
  echo $value;
}

// 1, 4, 9, 16, 25, 36, 49, 64, 81

Funkce přijme interval čísel a na každém z nich postupně vypočítá hodnotu jeho druhé mocniny. Na první pohled na této funkci není nic podezřelého, nic co by šlo napsat lépe. Pojďmě přidat nějakou podmínku, filtr. Jednoduše odebereme z výsledné množiny ta čísla, která jdou dělit třemi:

<?php

function removeDivisibleBy3($values)
{
  $result = [];

  foreach ($values as $value) {
    if ($value % 3 != 0) {
      $result[] = $value;
    }
  }

  return $result;
}

foreach (removeDivisibleBy3(squares(1, 10)) as $value) {
  echo $value;
}

// 1, 4, 16, 25, 49, 64

Na první pohled zase nic špatného, ovšem na pohled druhý je patrné, že jak ve funkci squares, tak ve funkci removeDivisibleBy3 vytváříme “zbytečné” pole. Kdybych navíc chtěl vypsat například jen první tři hodnoty, pak je mrhání zdroji ještě markantnější.

<?php

$i = 0;
foreach (removeDivisibleBy3(squares(1, 10)) as $value) {
  echo $value;

  if (++$i > 2) {
    break;
  }
}

// 1, 4, 16

Můžete namítnout, že kdyby se všechno vykonalo v jednom for kroku, tak by to bylo mnohem efektivnější - máte sice pravdu, ale cílem je mít nejen efektivní program, ale i znovupoužitelné funkce.

První generátor

Pojďme přepsat předchozí příklad tak, abychom použili generátor:

<?php

function squares($from, $to)
{
  for (; $from < $to; $from++) {
    yield $from * $from;
  }
}

foreach (squares(1, 10) as $value) {
  echo $value;
}

// 1, 4, 9, 16, 25, 36, 49, 64, 81

Hlavní změnou oproti původní implementaci je použití použití operátoru yield, který nedělá nic jiného, než že výsledky funkce posílá průběžně, tedy bez potřeby uložit výsledek do paměti. Stejně tak můžeme upravit implementaci funkce removeDivisibleBy3:

<?php

function removeDivisibleBy3($values)
{
  foreach ($values as $value) {
    if ($value % 3 != 0) {
      yield $value;
    }
  }
}

Generátory jsou skvělé a měli byste je použít v případě, že se chystáte pracovat s obrovskými sety a zároveň nechcete/nemůžete alokovat potřebné množství paměti pro získané výsledky, pracovat s jakoukoliv posloupností nebo když nevíte, jestli získané výsledky budete potřebovat všechny. Díky tomu, jak generátory fungují, můžete stáhnout paměťovou náročnost vašeho programu jenom na to, co je potřeba k získání jednoho výsledku: