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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Yielding from a reader keeps memory flat regardless of file size, since records are produced one at a time on demand.
- 2A declarative column layout separates the file format from the parsing logic, so changes stay in one place.
- 3Constructor-promoted readonly properties turn a parsed row into a validated, immutable value object in a single expression.
Related explainers
php
<?php namespace App\Console\Commands;
Releasing stale document locks in Laravel
artisan-command
transactions
row-locking
Intermediate
6 steps
php
<?php namespace App\Providers;
Subdomain multi-tenancy routing in Laravel
multi-tenancy
service-container
route-binding
Advanced
7 steps
ruby
class KeyTransformer def self.camelize(data) new.camelize(data) end
Recursively camelizing nested Ruby data
recursion
data-transformation
pattern-matching
Intermediate
7 steps
php
<?php namespace App\Broadcasting;
Authorizing presence channels in Laravel
broadcasting
authorization
presence-channels
Intermediate
3 steps
php
<?php namespace App\Http\Middleware;
Resolving the current team in Laravel middleware
middleware
multi-tenancy
cookies
Intermediate
8 steps
php
<?php namespace App\Http\Controllers;
Broadcasting typing indicators in Laravel
websockets
authorization
presence-channels
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/streaming-a-fixed-width-file-into-typed-objects-explained-php-2d8a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.