International PHP Conference Berlin 2025

Сравнение генераторов с объектами класса Iterator

Главное преимущество генераторов — простота. Требуется написать гораздо меньше шаблонного кода по сравнению с реализацией объекта класса Iterator, и этот код гораздо более простой и понятный. Например, эта функция и класс делают одно и то же.

<?php

function getLinesFromFile($fileName)
{
if (!
$fileHandle = fopen($fileName, 'r')) {
return;
}

while (
false !== $line = fgets($fileHandle)) {
yield
$line;
}

fclose($fileHandle);
}

// Против...

class LineIterator implements Iterator
{
protected
$fileHandle;

protected
$line;
protected
$i;

public function
__construct($fileName)
{
if (!
$this->fileHandle = fopen($fileName, 'r')) {
throw new
RuntimeException('Невозможно открыть файл "' . $fileName . '"');
}
}

public function
rewind()
{
fseek($this->fileHandle, 0);
$this->line = fgets($this->fileHandle);
$this->i = 0;
}

public function
valid()
{
return
false !== $this->line;
}

public function
current()
{
return
$this->line;
}

public function
key()
{
return
$this->i;
}

public function
next()
{
if (
false !== $this->line) {
$this->line = fgets($this->fileHandle);
$this->i++;
}
}

public function
__destruct()
{
fclose($this->fileHandle);
}
}

?>

Однако за эту гибкость приходится платить: генераторы — однонаправленные итераторы и их нельзя перемотать после начала итерации. Это также означает, что один и тот же генератор нельзя повторять несколько раз: генератор необходимо пересоздавать каждый раз, снова вызвав функцию генератора.

Смотрите также

Добавить

Примечания пользователей 2 notes

up
107
mNOSPAMsenghaa at nospam dot gmail dot com
11 years ago
This hardly seems a fair comparison between the two examples, size-for-size. As noted, generators are forward-only, meaning that it should be compared to an iterator with a dummy rewind function defined. Also, to be fair, since the iterator throws an exception, shouldn't the generator example also throw the same exception? The code comparison would become more like this:

<?php
function getLinesFromFile($fileName) {
if (!
$fileHandle = fopen($fileName, 'r')) {
throw new
RuntimeException('Couldn\'t open file "' . $fileName . '"');
}

while (
false !== $line = fgets($fileHandle)) {
yield
$line;
}

fclose($fileHandle);
}

// versus...

class LineIterator implements Iterator {
protected
$fileHandle;

protected
$line;
protected
$i;

public function
__construct($fileName) {
if (!
$this->fileHandle = fopen($fileName, 'r')) {
throw new
RuntimeException('Couldn\'t open file "' . $fileName . '"');
}
}

public function
rewind() { }

public function
valid() {
return
false !== $this->line;
}

public function
current() {
return
$this->line;
}

public function
key() {
return
$this->i;
}

public function
next() {
if (
false !== $this->line) {
$this->line = fgets($this->fileHandle);
$this->i++;
}
}

public function
__destruct() {
fclose($this->fileHandle);
}
}
?>

The generator is still obviously much shorter, but this seems a more reasonable comparison.
up
23
sergeyzsg at yandex dot ru
10 years ago
I think that this is bad generator example.
If user will not consume all lines then file will not be closed.

<?php
function getLinesFromFile($fileHandle) {
while (
false !== $line = fgets($fileHandle)) {
yield
$line;
}
}

if (
$fileHandle = fopen($fileName, 'r')) {
/*
something with getLinesFromFile
*/
fclose($fileHandle);
}
?>
To Top