php
57 lines · 7 steps
Streaming CSV imports in Laravel
An Artisan command imports a CSV of any size using lazy iteration and batched upserts to keep memory flat.
Explained by
highlit
1<?php
2
3namespace App\Console\Commands;
4
5use Illuminate\Console\Command;
6use Illuminate\Support\Facades\DB;
7use Illuminate\Support\LazyCollection;
8
9class ImportProductsCsv extends Command
10{
11 protected $signature = 'products:import {file : Path to the CSV file}
12 {--chunk=500 : Rows to insert per batch}';
13
14 protected $description = 'Import products from a CSV file into the products table';
15
16 public function handle(): int
17 {
18 $path = $this->argument('file');
19
20 if (! is_readable($path)) {
21 $this->error("Cannot read file: {$path}");
22
23 return self::FAILURE;
24 }
25
26 $rows = LazyCollection::make(function () use ($path) {
27 $handle = fopen($path, 'r');
28 $header = fgetcsv($handle);
29
30 while (($record = fgetcsv($handle)) !== false) {
31 yield array_combine($header, $record);
32 }
33
34 fclose($handle);
35 });
36
37 $imported = 0;
38 $now = now();
39
40 $rows->chunk((int) $this->option('chunk'))->each(function ($chunk) use (&$imported, $now) {
41 $payload = $chunk->map(fn ($row) => [
42 'sku' => trim($row['sku']),
43 'name' => trim($row['name']),
44 'price_cents' => (int) round(((float) $row['price']) * 100),
45 'created_at' => $now,
46 'updated_at' => $now,
47 ])->all();
48
49 DB::table('products')->upsert($payload, ['sku'], ['name', 'price_cents', 'updated_at']);
50 $imported += count($payload);
51 });
52
53 $this->info("Imported {$imported} products.");
54
55 return self::SUCCESS;
56 }
57}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1LazyCollection with a generator lets you process arbitrarily large files without loading them all into memory.
- 2Chunking rows and upserting in batches trades round-trips for constant memory and idempotent imports.
- 3Artisan signatures declare arguments and options with defaults and descriptions in one string.
Related explainers
ruby
class InactiveAccountCleanup BATCH_SIZE = 500 INACTIVITY_THRESHOLD = 18.months
Batch-processing dormant users in Rails
batch-processing
activerecord
service-object
Intermediate
9 steps
php
<?php namespace App\Services;
Resilient HTTP retries with Laravel's client
http-client
retry-logic
error-handling
Intermediate
7 steps
php
<?php namespace App\Services;
Building signed, expiring download URLs in PHP in Laravel
hmac
url-signing
authentication
Intermediate
7 steps
rust
use std::path::PathBuf; use clap::{Parser, Subcommand, ValueEnum};
Building a CLI with clap's derive macros
cli
derive-macros
argument-parsing
Intermediate
10 steps
ruby
def collatz_lengths Enumerator.new do |y| n = 1 loop do
Building infinite sequences with lazy enumerators
lazy-evaluation
enumerators
infinite-sequences
Intermediate
8 steps
php
<?php declare(strict_types=1);
A single-action JSON user controller in PHP
invokable-class
json-api
validation
Intermediate
7 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-csv-imports-in-laravel-explained-php-b0ac/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.