Что такое итератор (Iterator) в PHP?

Ответ

Итератор — это объект, который позволяет последовательно обходить элементы коллекции (например, массива, объекта, результата запроса) без необходимости знать её внутреннее устройство. В PHP итераторы реализуют встроенный интерфейс Iterator или IteratorAggregate.

Основные методы интерфейса Iterator:

  • current(): mixed — возвращает текущий элемент.
  • key(): mixed — возвращает ключ текущего элемента.
  • next(): void — перемещает указатель вперёд к следующему элементу.
  • rewind(): void — сбрасывает указатель на первый элемент.
  • valid(): bool — проверяет, существует ли текущая позиция.

Практический пример — обход большого файла без загрузки в память:

class LargeFileIterator implements Iterator {
    private $fileHandle;
    private $currentLine;
    private $lineNumber = 0;

    public function __construct(string $filePath) {
        if (!file_exists($filePath)) {
            throw new RuntimeException("File not found: $filePath");
        }
        $this->fileHandle = fopen($filePath, 'r');
        if (!$this->fileHandle) {
            throw new RuntimeException("Could not open file: $filePath");
        }
    }

    public function rewind(): void {
        rewind($this->fileHandle);
        $this->currentLine = fgets($this->fileHandle);
        $this->lineNumber = 0;
    }

    public function current(): string {
        return $this->currentLine;
    }

    public function key(): int {
        return $this->lineNumber;
    }

    public function next(): void {
        $this->currentLine = fgets($this->fileHandle);
        $this->lineNumber++;
    }

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

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

// Использование
$logIterator = new LargeFileIterator('/var/log/app.log');
foreach ($logIterator as $lineNumber => $line) {
    if (str_contains($line, 'ERROR')) {
        echo "[$lineNumber] $line";
    }
}
// Файл читается построчно, не загружаясь целиком в память.

Интерфейс IteratorAggregate упрощает создание итераторов, требуя реализации только одного метода getIterator(), который должен возвращать объект Iterator. Все встроенные классы, реализующие Traversable (как ArrayObject), могут использоваться в цикле foreach.

Ответ 18+ 🔞

Давай разберём эту штуку, а то звучит умно, а на деле — проще пареной репы. Итератор, блядь... Ну представь, что у тесть есть мешок картошки, и ты должен каждую пощупать. Можно, конечно, вывалить всё на пол — но тогда овердохуища грязи будет, да и память жалко. А можно сунуть руку в мешок и таскать по одной, пока не нащупаешь гнилую. Вот итератор — это такая рука, которая лезет в коллекцию и тащит элементы по одному, не загружая всё сразу.

Короче, методы, которые надо реализовать, если ты вдруг решил стать итератором:

  • current(): mixed — что у тебя сейчас в руке? Картошка? Возвращай её.
  • key(): mixed — а под каким номером ты её вытащил? Первая? Вторая?
  • next(): void — выкинул эту картошку, полез за следующей.
  • rewind(): void — оп, всё по новой. Снова лезем с начала мешка.
  • valid(): bool — а там вообще ещё что-то есть? Или мешок пустой?

А вот реальный пример, где это, блядь, спасает жизнь. Допустим, у тесть лог-файл на 500 гигов. Попробуй его в память загнать — комп просто накроется медным тазом, и будет тебе хиросима. А с итератором — читаем построчно, как будто потягиваем пиво, а не выпиваем разом весь ящик.

class LargeFileIterator implements Iterator {
    private $fileHandle;
    private $currentLine;
    private $lineNumber = 0;

    public function __construct(string $filePath) {
        if (!file_exists($filePath)) {
            throw new RuntimeException("File not found: $filePath");
        }
        $this->fileHandle = fopen($filePath, 'r');
        if (!$this->fileHandle) {
            throw new RuntimeException("Could not open file: $filePath");
        }
    }

    public function rewind(): void {
        rewind($this->fileHandle);
        $this->currentLine = fgets($this->fileHandle);
        $this->lineNumber = 0;
    }

    public function current(): string {
        return $this->currentLine;
    }

    public function key(): int {
        return $this->lineNumber;
    }

    public function next(): void {
        $this->currentLine = fgets($this->fileHandle);
        $this->lineNumber++;
    }

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

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

// Использование
$logIterator = new LargeFileIterator('/var/log/app.log');
foreach ($logIterator as $lineNumber => $line) {
    if (str_contains($line, 'ERROR')) {
        echo "[$lineNumber] $line";
    }
}
// Файл читается построчно, не загружаясь целиком в память.

А ещё есть IteratorAggregate — это для ленивых, ёпта. Не хочешь городить все пять методов? Реализуй только один getIterator(), который вернёт тесть готовый итератор, и всё, ты красавчик. Все встроенные штуки вроде ArrayObject уже умеют в foreach, потому что они внутри — хитрая жопа, которая умеет возвращать того самого невидимого работягу, который и таскает за тебя данные.