PHP Iterables
PHP Iterables — это массивы и объекты Traversable, по которым можно итерироваться с помощью foreach.
Введение в PHP Iterables
В PHP итерируемое (iterable) — это всё, по чему можно пройтись с помощью foreach. Сюда относятся два вида значений:
- Массивы — обычная структура данных, хранящая упорядоченную коллекцию пар ключ/значение.
- Объекты
Traversable— объекты, по которым PHP умеет проходить, потому что они реализуют встроенный интерфейсTraversable(на практике черезIteratorилиIteratorAggregate, либо в виде генератора).
В PHP 7.1 был добавлен псевдотип iterable, чтобы можно было объявить «дай мне что угодно, по чему можно foreach», не задумываясь, передаёт ли вызывающий код массив или объект. Эта страница рассказывает, что считается итерируемым, как создавать и обходить каждый вид, и когда стоит использовать ленивые итерируемые объекты, которые предоставляют генераторы.
Эта глава опирается на PHP Arrays и цикл foreach. Если вы ещё не знакомы с ними, прочитайте их сначала.
Массивы: наиболее распространённый итерируемый тип
Массивы бывают двух видов, и единственное различие — тип используемых ключей:
- Индексированный массив хранит значения под автоматическими целочисленными ключами, начиная с
0. См. Indexed Arrays. - Ассоциативный массив использует строки (или выбранные вами целые числа) в качестве ключей. См. Associative Arrays.
Один массив может сочетать оба стиля, а значения могут быть любого типа данных.
Создание и доступ к массивам
Создайте массив с помощью квадратных скобок, затем прочитайте значение по его ключу:
PHP define and access an array
<?php
$fruits = ["apple", "banana", "cherry"]; // indexed
$student = ["name" => "John Doe", "age" => 25]; // associative
echo $fruits[0] . "\n"; // apple (first element, index 0)
echo $student["name"] . "\n"; // John DoeВывод:
apple
John DoeОбратите внимание, что индексированные массивы нумеруются с нуля, поэтому $fruits[0] — это первый элемент.
Перебор массива с помощью foreach
foreach — это идиоматический способ обхода итерируемого значения. Для индексированного массива обычно нужно только значение; для ассоциативного — как правило, оба: ключ и значение:
PHP iterate over arrays
<?php
$fruits = ["apple", "banana", "cherry"];
$student = ["name" => "John Doe", "age" => 25];
foreach ($fruits as $fruit) {
echo $fruit . "\n";
}
foreach ($student as $key => $value) {
echo "$key: $value\n";
}Вывод:
apple
banana
cherry
name: John Doe
age: 25Полезные функции для работы с массивами
PHP поставляется с десятками функций для массивов. Вот несколько, которые вы будете использовать постоянно:
array_keys($arr)— возвращает все ключи в виде нового массива.array_values($arr)— возвращает все значения, переиндексированные с0.count($arr)— возвращает количество элементов.sort($arr)— сортирует значения по возрастанию на месте, возвращаяtrueпри успехе (не возвращает отсортированный массив).in_array($needle, $arr)—true, если значение существует.
PHP array functions in action
<?php
$scores = [40, 10, 30];
echo count($scores) . "\n"; // 3
print_r(array_keys($scores)); // [0, 1, 2]
sort($scores); // modifies $scores in place
print_r($scores); // [10, 30, 40]Вывод:
3
Array
(
[0] => 0
[1] => 1
[2] => 2
)
Array
(
[0] => 10
[1] => 30
[2] => 40
)Псевдотип iterable
iterable — это не класс, а подсказка типа, означающая «массив или Traversable». Используйте его в параметре или возвращаемом типе, когда функция лишь выполняет перебор и вы не хотите принуждать вызывающий код конвертировать данные в обычный массив.
PHP iterable type hint
<?php
function sumAll(iterable $numbers): int
{
$total = 0;
foreach ($numbers as $n) {
$total += $n;
}
return $total;
}
echo sumAll([1, 2, 3]) . "\n"; // works with an array
function countToThree(): iterable { // a generator is also iterable
yield 1;
yield 2;
yield 3;
}
echo sumAll(countToThree()) . "\n"; // works with a Traversable tooВывод:
6
6Преимущество: sumAll() принимает и обычный массив, и лениво генерируемый поток без лишнего кода. Подробнее о подсказках типов см. в PHP Functions.
Генераторы: ленивые итерируемые объекты
Генератор — это функция, которая использует yield вместо return. Он производит значения по одному, только когда цикл запрашивает следующее, поэтому никогда не строит всю коллекцию в памяти. Это идеально для больших или бесконечных последовательностей.
PHP generator example
<?php
function range_lazy(int $start, int $end): iterable
{
for ($i = $start; $i <= $end; $i++) {
yield $i; // pauses here and resumes on the next iteration
}
}
foreach (range_lazy(1, 5) as $value) {
echo $value . " ";
}
echo "\n";Вывод:
1 2 3 4 5Поскольку ничего не сохраняется, range_lazy(1, 1_000_000) использует столько же памяти, что и range_lazy(1, 5).
Пользовательские итерируемые объекты с Iterator
Когда нужен полный контроль над обходом объекта, реализуйте интерфейс Iterator. Он требует пяти методов, которые foreach вызывает за кулисами: rewind(), valid(), current(), key() и next().
PHP custom Iterator
<?php
class EvenNumbers implements Iterator
{
private int $position = 0;
public function __construct(private array $items) {}
public function rewind(): void { $this->position = 0; }
public function valid(): bool { return isset($this->items[$this->position]); }
public function current(): mixed { return $this->items[$this->position]; }
public function key(): mixed { return $this->position; }
public function next(): void { $this->position++; }
}
$evens = new EvenNumbers([2, 4, 6]);
foreach ($evens as $n) {
echo $n . " ";
}
echo "\n";Вывод:
2 4 6В большинстве случаев генератор проще полного класса Iterator — прибегайте к Iterator только когда нужно нестандартное поведение rewind/key или вы хотите сделать итерацию частью публичного API объекта.
Проверка, является ли значение итерируемым
Используйте is_iterable(), чтобы во время выполнения проверить, можно ли передать значение в foreach:
PHP is_iterable check
<?php
var_dump(is_iterable([1, 2, 3])); // bool(true)
var_dump(is_iterable("a string")); // bool(false)
var_dump(is_iterable((function () { yield 1; })())); // bool(true)Вывод:
bool(true)
bool(false)
bool(true)Заключение
«Итерируемое» в PHP означает просто можно перебрать с помощью foreach — и это охватывает массивы, объекты Iterator/IteratorAggregate и генераторы. Используйте обычные массивы для обычных коллекций, подсказку типа iterable, чтобы писать функции, принимающие любой из них, и генераторы, когда важна память или последовательность велика. С этими инструментами вы сможете эффективно моделировать данные и сохранять гибкость API.