Iterator apply: примеры (PHP)

Использование iterator_apply для обработки итераторов в PHP
Раздел: Итераторы
iterator_apply(Traversable $iterator, callable $function, ?array $args = null): int

Основы функции iterator_apply

Функция iterator_apply применяет пользовательскую callback-функцию ко всем элементам итератора. Эта функция полезна для обработки больших наборов данных, которые представлены в виде итераторов, без загрузки всех элементов в память одновременно.

Когда используется

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

Аргументы функции
iterator_apply(Traversable $iterator, callable $callback, ?array $args = null): int

$iterator - объект, реализующий интерфейс Traversable (обычно Iterator или IteratorAggregate).

$callback - callback-функция, которая вызывается для каждого элемента. Функция принимает два параметра: текущий элемент итератора (первый параметр) и вторым параметром - переданные аргументы из $args.

$args - массив аргументов, которые будут переданы в callback-функцию в качестве второго параметра. Необязательный параметр, по умолчанию null.

Возвращает количество примененных итераций (количество вызовов callback-функции).

Базовые примеры использования

Пример 1: Простой перебор массива
function printItem($item, $args) {
    echo $item . $args[0];
    return true;
}

$array = ['apple', 'banana', 'cherry'];
$iterator = new ArrayIterator($array);

$count = iterator_apply($iterator, 'printItem', [', ']);
echo "\nОбработано элементов: $count";
apple, banana, cherry, 
Обработано элементов: 3
Пример 2: Остановка обработки
function processWithLimit($item, $args) {
    static $counter = 0;
    $counter++;
    
    echo "Элемент $counter: $item\n";
    
    // Останавливаем обработку после 2 элементов
    if ($counter >= 2) {
        return false;
    }
    return true;
}

$iterator = new ArrayIterator(['A', 'B', 'C', 'D']);
$result = iterator_apply($iterator, 'processWithLimit');
echo "Обработано: $result элементов";
Элемент 1: A
Элемент 2: B
Обработано: 2 элементов
Пример 3: Использование с аргументами
function multiplyItem($item, $args) {
    $result = $item * $args['multiplier'];
    echo "$item × {$args['multiplier']} = $result\n";
    return true;
}

$numbers = new ArrayIterator([1, 2, 3, 4, 5]);
$args = ['multiplier' => 3];

iterator_apply($numbers, 'multiplyItem', [$args]);
1 × 3 = 3
2 × 3 = 6
3 × 3 = 9
4 × 3 = 12
5 × 3 = 15

Альтернативы в PHP

array_walk и array_walk_recursive

Функции для обработки массивов. array_walk применяет функцию к каждому элементу массива. Работает только с массивами, а не с итераторами.

foreach цикл

Наиболее распространенная альтернатива. Проще в использовании, но не возвращает количество обработанных элементов автоматически. Предпочтительнее для простых операций без необходимости подсчета применений.

array_map

Создает новый массив, применяя callback-функцию к каждому элементу. Возвращает новый массив, а не модифицирует существующий.

Выбор функции

iterator_apply выбирают для работы именно с итераторами, особенно при обработке больших данных. Для обычных массивов удобнее использовать foreach или array_walk.

Аналоги в других языках

Python: map() и for
# Аналог через map()
def process_item(x):
    return x * 2

items = [1, 2, 3]
result = list(map(process_item, items))
print(result)  # [2, 4, 6]

# Через цикл
for item in items:
    print(item * 2)
[2, 4, 6]
2
4
6
JavaScript: forEach()
const items = [1, 2, 3];
let count = 0;

items.forEach((item, index) => {
    console.log(item * 2);
    count++;
});

console.log(`Обработано: ${count} элементов`);
2
4
6
Обработано: 3 элементов
MySQL: Курсоры

В хранимых процедурах MySQL используются курсоры для построчной обработки результатов запросов, что аналогично итераторам в PHP.

Отличия

В отличие от PHP функции iterator_apply, аналоги в других языках обычно работают с коллекциями и не требуют отдельного итераторного объекта. PHP подход более низкоуровневый и гибкий.

Типичные ошибки

Ошибка 1: Неверный тип итератора
$not_iterator = 'some string';
iterator_apply($not_iterator, 'some_function');
TypeError: iterator_apply(): Argument #1 ($iterator) must be of type Traversable, string given
Ошибка 2: Callback-функция не существует
$iterator = new ArrayIterator([1, 2, 3]);
iterator_apply($iterator, 'non_existent_function');
Warning: iterator_apply() expects parameter 2 to be a valid callback, function 'non_existent_function' not found or invalid function name
Ошибка 3: Изменение итератора внутри callback
function modifyIterator($item, $args) {
    echo $item;
    // Попытка изменения итератора может привести к неожиданному поведению
    $args['iterator']->next();
    $args['iterator']->next();
    return true;
}

$iterator = new ArrayIterator([1, 2, 3, 4, 5]);
iterator_apply($iterator, 'modifyIterator', [['iterator' => $iterator]]);
1 3 5 или неопределенное поведение
Ошибка 4: Неправильное количество аргументов в callback
function wrongCallback($item) {  // Нет второго параметра
    echo $item;
    return true;
}

$iterator = new ArrayIterator([1, 2, 3]);
iterator_apply($iterator, 'wrongCallback', ['extra_arg']);
Предупреждение и неожиданное поведение

Изменения в версиях PHP

PHP 8.0

Добавлена строгая типизация параметров. Параметр $iterator теперь имеет тип Traversable, что вызывает TypeError при передаче неверного типа. Ранее в PHP 7 принимался любой тип с генерацией предупреждения.

PHP 8.1

Улучшены сообщения об ошибках. Callback-параметр теперь проверяется более строго.

PHP 8.2

Изменений в самой функции не было, но улучшена работа с итераторами в целом.

PHP 8.3

Нет значительных изменений для iterator_apply.

Расширенные примеры

Пример 1: Обработка больших файлов
class LargeFileIterator implements Iterator {
    private $fileHandle;
    private $currentLine;
    private $lineNumber = 0;
    
    public function __construct($filename) {
        $this->fileHandle = fopen($filename, 'r');
        $this->next();
    }
    
    public function current() { return $this->currentLine; }
    public function key() { return $this->lineNumber; }
    public function next() { 
        $this->currentLine = fgets($this->fileHandle);
        $this->lineNumber++;
    }
    public function rewind() { 
        fseek($this->fileHandle, 0);
        $this->lineNumber = 0;
        $this->next();
    }
    public function valid() { return $this->currentLine !== false; }
}

function processFileLine($line, $args) {
    if (trim($line) !== '') {
        $args['lines'][] = 'Строка ' . $args['counter'] . ': ' . trim($line);
        $args['counter']++;
    }
    return true;
}

// Использование
$iterator = new LargeFileIterator('large_file.txt');
$context = ['lines' => [], 'counter' => 1];
$processed = iterator_apply($iterator, 'processFileLine', [$context]);

print_r($context['lines']);
echo "Обработано строк: $processed";
Пример 2: Фильтрация итератора
function filterCallback($item, $args) {
    if ($item % 2 === 0) {
        $args['result'][] = $item;
    }
    return true;
}

$numbers = new ArrayIterator(range(1, 100));
$filtered = ['result' => []];

iterator_apply($numbers, 'filterCallback', [$filtered]);

echo "Четные числа: " . implode(', ', array_slice($filtered['result'], 0, 10)) . '...';
echo "\nВсего четных: " . count($filtered['result']);
Четные числа: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20...
Всего четных: 50
Пример 3: Пакетная обработка
function batchProcessor($item, $args) {
    static $batch = [];
    
    $batch[] = $item;
    $args['processed']++;
    
    if (count($batch) >= $args['batch_size']) {
        echo "Обрабатываю пакет из " . count($batch) . " элементов\n";
        // Здесь может быть запись в БД или другая операция
        $batch = [];
    }
    
    return true;
}

$data = new ArrayIterator(range(1, 25));
$stats = ['processed' => 0, 'batch_size' => 5];

$count = iterator_apply($data, 'batchProcessor', [$stats]);
echo "\nВсего обработано: {$stats['processed']} элементов";
echo "\nВызовов callback: $count";
Обрабатываю пакет из 5 элементов
Обрабатываю пакет из 5 элементов
Обрабатываю пакет из 5 элементов
Обрабатываю пакет из 5 элементов
Обрабатываю пакет из 5 элементов
Всего обработано: 25 элементов
Вызовов callback: 25
Пример 4: Работа с DirectoryIterator
function processFile($fileInfo, $args) {
    if ($fileInfo->isFile() && $fileInfo->getExtension() === 'txt') {
        $size = $fileInfo->getSize();
        echo "Файл: {$fileInfo->getFilename()}, Размер: {$size} байт\n";
        $args['total_size'] += $size;
        $args['file_count']++;
    }
    return true;
}

$dirIterator = new DirectoryIterator(__DIR__);
$stats = ['total_size' => 0, 'file_count' => 0];

$processed = iterator_apply($dirIterator, 'processFile', [$stats]);

echo "\nПроанализировано элементов: $processed";
echo "\nНайдено txt файлов: {$stats['file_count']}";
echo "\nОбщий размер: {$stats['total_size']} байт";

PHP iterator_apply function comments

En
Iterator apply Call a function for every element in an iterator