data types and standard library

Iterables

An iterable is any value PHP can loop over with foreach, including arrays, generators, and objects that implement iterator interfaces.

This matters in real projects because not every collection should be loaded into memory at once. A database export, CSV import, log reader, or API paginator may contain thousands of rows. Accepting iterable lets a function process those values without caring whether they came from an array or a lazy stream.

Accepting anything loopable

Use iterable when a function only needs to loop over values.

PHP example
<?php

declare(strict_types=1);

function countActiveProducts(iterable $products): int
{
    $count = 0;

    foreach ($products as $product) {
        if ($product['active'] === true) {
            $count++;
        }
    }

    return $count;
}

$products = [
    ['sku' => 'KB-101', 'active' => true],
    ['sku' => 'MS-202', 'active' => false],
    ['sku' => 'MN-303', 'active' => true],
];

echo countActiveProducts($products) . PHP_EOL;

// Prints:
// 2

The function does not require array functions such as array_filter(). It just needs a sequence it can loop over.

Returning values lazily with yield

A generator function uses yield to return values one at a time.

PHP example
<?php

declare(strict_types=1);

function paidOrderIds(): iterable
{
    yield 101;
    yield 104;
    yield 109;
}

foreach (paidOrderIds() as $orderId) {
    echo 'Paid order: ' . $orderId . PHP_EOL;
}

// Prints:
// Paid order: 101
// Paid order: 104
// Paid order: 109

The function looks like it returns a collection, but PHP does not build a full array of values first. Each value is produced when the loop asks for it.

Reading a file line by line

Generators are useful when the source could be large.

PHP example
<?php

declare(strict_types=1);

function linesFromString(string $contents): iterable
{
    foreach (explode("\n", $contents) as $line) {
        yield rtrim($line, "\r");
    }
}

$csv = "sku,status\nKB-101,active\nMS-202,inactive";

foreach (linesFromString($csv) as $line) {
    echo $line . PHP_EOL;
}

// Prints:
// sku,status
// KB-101,active
// MS-202,inactive

A real file reader would use fopen() and fgets(), but the idea is the same: process one line, yield it, then move to the next. This keeps memory usage predictable.

Generators are usually one pass

Many iterables can only be consumed once. If you need to loop over the same values multiple times, convert them to an array deliberately.

PHP example
<?php

declare(strict_types=1);

function numbers(): iterable
{
    yield 1;
    yield 2;
    yield 3;
}

$values = iterator_to_array(numbers());

echo array_sum($values) . PHP_EOL;
echo count($values) . PHP_EOL;

// Prints:
// 6
// 3

Do not call iterator_to_array() automatically. It is a tradeoff: you gain repeatable array operations, but you also load every value into memory.

Preserving keys

Generators can yield keys as well as values.

PHP example
<?php

declare(strict_types=1);

function stockBySku(): iterable
{
    yield 'KB-101' => 12;
    yield 'MS-202' => 0;
}

foreach (stockBySku() as $sku => $stock) {
    echo $sku . ': ' . $stock . PHP_EOL;
}

// Prints:
// KB-101: 12
// MS-202: 0

This is useful when callers need the identity of each item, not only the value.

When to use array instead

Use array when the function depends on array-specific behaviour: random access by index, sorting in place, counting before processing, or using array functions. Use iterable when the function only needs to loop.

PHP example
<?php

declare(strict_types=1);

function firstProductName(array $products): string
{
    if ($products === []) {
        throw new InvalidArgumentException('At least one product is required.');
    }

    return $products[0]['name'];
}

echo firstProductName([
    ['name' => 'Keyboard'],
]) . PHP_EOL;

// Prints:
// Keyboard

This function should ask for an array because it directly reads index 0.

What to remember

iterable is a promise that code can loop over values. It is not a promise that values can be counted, sorted, indexed, or reused. That distinction is important when you write code for imports, exports, paginated APIs, and database results.

Practice

Task: Process active SKUs from an iterable

Write a function that accepts an iterable of product records and returns the SKUs for active products.

Requirements

  • Use declare(strict_types=1);.
  • The function should accept iterable $products.
  • Each product record should contain sku and active.
  • Return a plain array of active SKUs.
  • Include one example that passes an array.
  • Include one example that passes a generator.
  • Print both results.
  • Include the expected output as comments in the same PHP code block.

The point is to prove that the processing function does not care whether the records came from an array or from a lazy source.

Show solution
PHP example
<?php

declare(strict_types=1);

function activeSkus(iterable $products): array
{
    $skus = [];

    foreach ($products as $product) {
        if ($product['active'] === true) {
            $skus[] = $product['sku'];
        }
    }

    return $skus;
}

function productStream(): iterable
{
    yield ['sku' => 'BK-100', 'active' => true];
    yield ['sku' => 'CP-200', 'active' => false];
    yield ['sku' => 'DS-300', 'active' => true];
}

$arrayProducts = [
    ['sku' => 'KB-101', 'active' => true],
    ['sku' => 'MS-202', 'active' => false],
    ['sku' => 'MN-303', 'active' => true],
];

echo implode(', ', activeSkus($arrayProducts)) . PHP_EOL;
echo implode(', ', activeSkus(productStream())) . PHP_EOL;

// Prints:
// KB-101, MN-303
// BK-100, DS-300

The processing function only uses foreach, so iterable is the right parameter type. It returns an array because the caller receives a finished list of SKUs that can be printed, counted, or passed to another function.