Generator
Każdy programista w swojej pracy boryka się z problemami związanymi z użyciem pamięci. Czasami urwanie 1mb jest na wagę złota i od tego może zależeć poprawne wykonanie skryptu.
Najczęstszym problemem jest procesowanie tablic zawierających tysiące rekordów. Na szczęście z pomocą przychodzi Generator. Jest to funkcja, która pozwala na iterowanie po zbiorach danych. W PHP został dodany w wersji 5.5. Aby zacząć używać funkcji Generatora wystarczy zamiast return użyć yield.
function getData()
{
for ($i = 0; $i < 100000; $i++)
{
yield $i;
}
}
$data = getData();
Funkcja getData() zwraca obiekt klasy Generator, która implementuje interface Iterator.

Dzięki temu tablica nie jest od razu budowana w pamięci, a my mamy możliwość iterowania po rekordach.
Test
Jak wygląda kwestia wydajności? Przeprowadziłem 2 testy. Jedno przy użyciu PDO i tabeli zawierającej ok 200k rekordów oraz z tablicą również zawierającą ok 200k rekordów.
<?php
class TestGenerator
{
private $pdo;
public function __construct()
{
$this->pdo = new PDO(
'mysql:host=127.0.0.1;dbname=test;port=3300',
'root',
'root'
);
}
public function pdoWithGenerator()
{
$stmt = $this->pdo->query('select id from `test`');
while ($row = $stmt->fetch())
{
yield $row;
}
echo 'PDO Generator: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
}
public function pdoWithoutGenerator()
{
$stmt = $this->pdo->query('select id from `test`');
$test = [];
while ($row = $stmt->fetch())
{
$test[] = $row;
}
echo 'PDO Brak generatora: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
return $test;
}
public function testArrayWithGenerator()
{
for ($i = 0; $i <= 200042; $i++)
{
yield $i;
}
echo 'Tablica Generator: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
}
public function testArrayWithoutGenerator()
{
$test = [];
for ($i = 0; $i <= 200042; $i++)
{
$test[] = $i;
}
echo 'Tablica brak generatora: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
return $test;
}
}
$test = new TestGenerator();
foreach ($test->pdoWithGenerator() as $item){}
foreach ($test->pdoWithoutGenerator() as $item){}
foreach ($test->testArrayWithGenerator() as $item){}
foreach ($test->testArrayWithoutGenerator() as $item) {}

Podsumowanie
Wnioski z tego testu są dość oczywiste. Jak widać powyżej, używając Generatora można zaoszczędzić znaczną ilość pamięci.
Ciekawostki:
* Można wykonywać operacje po yield, np. za pomocą break.
function getData()
{
for ($i = 0; $i < 100000; $i++)
{
yield $i;
if ($i === 10)
{
break;
}
}
}
$data = getData();
foreach ($data as $i)
{
echo $i. PHP_EOL;
}

* Można zwracać pary klucz-wartość.
function getData()
{
for ($i = 0; $i < 10; $i++)
{
yield $i => $i+1;
}
}
$data = getData();
foreach ($data as $key => $value)
{
echo 'Key: ' . $key . PHP_EOL;
echo 'Value: ' . $value . PHP_EOL;
}

Autorem tekstu jest Łukasz Cieślik.