php 63 lines · 8 steps

Streaming a fixed-width file into typed objects

A generator reads a fixed-width record file line by line and yields immutable Employee value objects.

Explained by highlit
1<?php
2 
3declare(strict_types=1);
4 
5final class Employee
6{
7 public function __construct(
8 public readonly int $id,
9 public readonly string $name,
10 public readonly string $department,
11 public readonly float $salary,
12 public readonly \DateTimeImmutable $hiredOn,
13 ) {}
14}
15 
16final class FixedWidthEmployeeReader
17{
18 private const LAYOUT = [
19 'id' => [0, 6],
20 'name' => [6, 30],
21 'department' => [36, 20],
22 'salary' => [56, 12],
23 'hiredOn' => [68, 8],
24 ];
25 
26 public function read(string $path): \Generator
27 {
28 $handle = fopen($path, 'rb');
29 if ($handle === false) {
30 throw new \RuntimeException("Unable to open {$path}");
31 }
32 
33 try {
34 while (($line = fgets($handle)) !== false) {
35 $line = rtrim($line, "\r\n");
36 if ($line === '') {
37 continue;
38 }
39 yield $this->parse($line);
40 }
41 } finally {
42 fclose($handle);
43 }
44 }
45 
46 private function parse(string $line): Employee
47 {
48 $field = static fn (array $spec): string => trim(substr($line, $spec[0], $spec[1]));
49 
50 $hiredOn = \DateTimeImmutable::createFromFormat('!Ymd', $field(self::LAYOUT['hiredOn']));
51 if ($hiredOn === false) {
52 throw new \UnexpectedValueException("Invalid date in record: {$line}");
53 }
54 
55 return new Employee(
56 id: (int) $field(self::LAYOUT['id']),
57 name: $field(self::LAYOUT['name']),
58 department: $field(self::LAYOUT['department']),
59 salary: (float) $field(self::LAYOUT['salary']),
60 hiredOn: $hiredOn,
61 );
62 }
63}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Yielding from a reader keeps memory flat regardless of file size, since records are produced one at a time on demand.
  2. 2A declarative column layout separates the file format from the parsing logic, so changes stay in one place.
  3. 3Constructor-promoted readonly properties turn a parsed row into a validated, immutable value object in a single expression.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Streaming a fixed-width file into typed objects — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code