What you'll learn
Treat behavior as data with callables and process large sequences without loading them all into memory. Closures customize operations; generators produce one item at a time with a remarkably small API.
By the end of this lesson, you'll be able to:
- Capture values safely in closures
- Accept first-class callables
- Stream large inputs with generators
Core mental model
Professional PHP is less about memorizing APIs and more about choosing a clear boundary for each responsibility. Use this table as a decision guide while reading the examples.
| Concept | What it protects | Decision rule |
|---|---|---|
| Closure | Configurable behavior | Pass a small operation into a general algorithm. |
| Iterable | Uniform sequence contract | Accept it when arrays and lazy sources should both work. |
| Generator | Bounded memory | Yield items when the full collection is unnecessary. |
Professional workflow
Build the feature in small, verifiable steps. Each step leaves the system in a state you can test.
- Describe the lazy data pipeline boundary: its inputs, outputs, invariants, and expected failures.
- Implement the smallest happy path behind an explicit contract.
- Add validation and translate low-level failures into language the caller understands.
- Exercise the boundary with realistic data, then inspect output, logs, and resource cleanup.
- Refactor only after behavior is protected by a repeatable check.
Make the boundary visible
Guided code lab
Build a configurable pipeline
Arrow functions capture by value and keep short transformations readable.
<?php
$taxRate = 0.11;
$prices = [1200, 800, 2500, 400];
$billable = array_filter($prices, fn (int $cents): bool => $cents >= 800);
$withTax = array_map(
fn (int $cents): int => (int) round($cents * (1 + $taxRate)),
$billable,
);
print_r($withTax);Stream a CSV file
The file remains open only while iteration is active; each row is yielded as needed.
<?php
/** @return Generator<int, array<int, string>> */
function rows(string $path): Generator
{
$file = new SplFileObject($path);
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY);
foreach ($file as $row) {
if (is_array($row)) yield $row;
}
}
foreach (rows('students.csv') as $row) {
echo $row[0] . PHP_EOL;
}Production practice
Contract
Document callable signatures and iterable key/value types so tools can verify the pipeline.
Verification
Test empty, one-item, and early-termination cases; assert generators do not eagerly consume the source.
Operations
Stream network or file sources with explicit timeouts and ensure resources close when iteration stops.
Common failure mode
Independent workshop
Create a lazy log processor that filters a date range and yields aggregated error counts.
Your finished workshop must include:
- Generator reading one line at a time
- Injected parsing and filtering callables
- A memory comparison against file()
Definition of done
Recap & quick check
Key takeaways
- Closures pass behavior into algorithms.
- Arrow functions capture outer values by value.
- iterable accepts arrays and Traversable objects.
- Generators make streaming code read like a loop.
Quick check
1. Which keyword produces a generator value?
2. Why accept iterable?
3. What can destroy lazy memory savings?
Keep the workshop: later phases deliberately build on these boundaries, so today's small example can become part of your portfolio architecture.