Your next chapter / PHP iterable data, generators and failure timing

Intermediate · 50 MINUTES · VERSION 1

PHP iterable data, generators and failure timing

Aggregate validated rows in one pass while distinguishing lazy work, source memory and one-shot traversal.

Reviewed 23 Oct 2026

Your setup may differ. Windows, Ubuntu, and optional tool bundles →

Log in or create an account to keep private notes, bookmarks, and learning progress. Reading is free without an account.

What you will learn

  • Consume arrays and Traversable objects through iterable.
  • Explain lazy execution and one-shot generator state.
  • Validate rows and return success only after complete consumption.

Theory: produce values when requested

An iterable can be traversed with foreach. PHP accepts an array or Traversable object for this type. A generator uses yield to produce values incrementally. Calling its factory creates an object; its body starts working as iteration advances.

This can avoid building a second complete output collection. It cannot erase a large input array already built by the caller. A file/database source also has its own buffering, resource lifetime and failure rules. This fixture is not evidence that a database cursor streams without buffering.

A generator has traversal state. A consumed object is not a fresh dataset; create a new producer when another pass is required. A fresh external read can also see changed data, so define snapshot needs deliberately.

Worked example: validated aggregation

Required: PHP 8.4+ CLI. Save as study_stream.php. No framework, server, database or paid service. Laragon/XAMPP are optional.

<?php

declare(strict_types=1);

function sampleStudyRows(): Generator
{
    yield ['minutes' => 15];
    yield ['minutes' => 30];
    yield ['minutes' => 0];
}

function sumStudyRows(iterable $rows): int
{
    $total = 0;
    foreach ($rows as $row) {
        $minutes = is_array($row) ? ($row['minutes'] ?? null) : null;
        if (! is_int($minutes) || $minutes < 0 || $minutes > 480) {
            throw new InvalidArgumentException('Each row needs whole minutes from 0 to 480.');
        }
        if ($minutes > PHP_INT_MAX - $total) {
            throw new OverflowException('Total is too large.');
        }
        $total += $minutes;
    }

    return $total;
}

if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') === __FILE__) {
    echo 'Array total: '.sumStudyRows([['minutes' => 15], ['minutes' => 30]]).PHP_EOL;
    echo 'Generator total: '.sumStudyRows(sampleStudyRows()).PHP_EOL;
    echo 'Empty total: '.sumStudyRows([]).PHP_EOL;
}

Run and explain the result

Windows PowerShell:

php ./study_stream.php

Ubuntu Bash:

php ./study_stream.php
Array total: 45
Generator total: 45
Empty total: 0

PHP must be on PATH. Both shells share source. Windows PHP 8.4.26 was executed; Ubuntu and memory benchmarks were not.

The consumer requires each row to contain whole integer minutes from 0 to 480, ignores extra fields and checks integer capacity before adding. It returns only after every row succeeds. Empty input returns 0. A later invalid row or producer exception prevents a completed return. Arrays remain unchanged, but iterators are consumed. The guard keeps require-based tests quiet.

Guided lab

  1. Predict both totals and test empty input; compare the array before/after.
  2. Try 0, 480, missing/text/null minutes, -1, 481 and a non-array row.
  3. Make a generator log an event before each yield. Factory creation should do no body work; advancement should.
  4. Yield one valid row and then throw. The consumer must report failure, not a completed partial total.
  5. Compare two fresh generators, then try to traverse the same exhausted object again. Do not assume rewind always works.

Cost and operational boundaries

For n valid rows this consumer uses O(n) work and O(1) additional aggregate space. Source memory is separate; materializing it with iterator_to_array adds a full collection. Accepting iterable does not remove either cost.

Lazy execution changes error timing. A successful factory call does not mean every row is valid. Do not display final saved/success until required work completes. Real persistence may need transactions, idempotent chunks and cleanup; this pure sum provides none of those guarantees.

Independent exercise

Write maxStudyMinutes(iterable $rows): ?int using the same row rules, one pass and no materialization. Return null for no rows but integer 0 for a single zero-minute row.

Test empty/zero/two values, invalid late rows and a producer failure. Hint: track whether a valid row was seen; a falsey zero check loses a valid result. Use a new producer for a second traversal.

Common mistakes and interview

Avoid assuming factory creation validates everything, ignoring source memory, coercing numeric strings, catching every exception and returning partial success, or treating an exhausted generator as a repeatable list.

Interview: “When does a generator help with large data?” State producer/consumer contracts, failure timing, memory boundaries, one-pass behavior and the integration checks needed for the actual source.

English practice and glossary

Iterable: accepted traversable input. Generator: an iterator from a yielding function. Yield: produce a value and suspend. Lazy execution: work when requested. Consumer: code requesting values. Materialization: building a collection from a stream.

Say: “The producer starts when __. If row __ fails, the consumer __. Memory also depends on __.” Record actual observations in your journal.

Recap and next step

Separate producing values from consuming them. Describe source memory and failure timing honestly. Next connect PHP service boundaries to framework and transactional database work, without executing learner code on this website.

Reveal exercise hints and self-check answers

Generator creation does not execute the full producer. A consumer completes only after the traversal succeeds. A later exception means failure, not a valid partial total. Source memory is independent of the consumer’s aggregate variables.

Exercise outline: start with null maximum, validate each row and update the maximum with a real integer comparison. Propagate source failures. Empty yields null; valid zero stays 0. Use a fresh producer for another pass.

Official references

Practise on your own computer. The exercise self-checks are not graded. This version also has a scored quiz for verified accounts. Signed-in learners can save reading, self-reported practice, private notes, and bookmarks.