PHP jako silnik do przetwarzania streamów danych
PHP zwykle kojarzy się z aplikacjami webowymi, ale bardzo dobrze sprawdza się też w zadaniach strumieniowych uruchamianych z CLI. W praktyce oznacza to przetwarzanie danych „w locie”, bez ładowania całego zbioru do pamięci, co jest szczególnie użyteczne przy dużych plikach, logach, CSV, JSONL i integracjach API.
Kiedy stream ma sens
Podejście strumieniowe warto stosować wtedy, gdy dane są duże, napływają stopniowo albo trzeba je od razu transformować i przekazywać dalej. Zamiast budować ogromne tablice pośrednie, pracujesz na pojedynczych rekordach, co ogranicza zużycie pamięci i pozwala szybciej reagować na pierwsze wyniki. To rozwiązanie dobrze pasuje do:- importu i eksportu dużych plików.
- przetwarzania logów.
- prostych pipeline’ów ETL.
- integracji z API zwracającymi dane partiami.
- narzędzi CLI odpalanych przez cron.
Dlaczego PHP?
PHP ma wbudowany model wejścia i wyjścia oparty o strumienie, a to daje naturalną bazę do takiego stylu pracy. Dodatkowo jest łatwo dostępny, prosty do wdrożenia na typowym hostingu i znany wielu zespołom backendowym, więc bywa rozsądnym wyborem tam, gdzie liczy się szybkie dostarczenie działającego rozwiązania. Warto też pamiętać, że PHP ma ekosystem bibliotek wspierających pipeline’y i lazy processing, więc nie trzeba budować wszystkiego od zera.Minimalny pipeline
Najprostszy pipeline ma trzy etapy: odczyt, transformację i zapis. Każdy rekord przechodzi przez te same kroki, a program nie musi pamiętać całego wejścia naraz.
<?php
$input = fopen('php://stdin', 'r');
$output = fopen('php://stdout', 'w');
while (($line = fgets($input)) !== false) {
$line = trim($line);
if ($line === '') {
continue;
}
fwrite($output, strtoupper($line) . PHP_EOL);
}
fclose($input);
fclose($output);
Ten przykład pokazuje klasyczny model streamowy: czytasz linię, obrabiasz ją i od razu wypisujesz wynik. Taki kod dobrze działa w potokach UNIX-owych, np. gdy jedno narzędzie filtruje dane, a kolejne je agreguje.
Przetwarzanie plików liniami
Najczęstszy realny przypadek to analiza dużego pliku tekstowego albo logu. Zamiast robić file() i wczytywać wszystko do tablicy, lepiej użyć fgets() i iterować po liniach.
<?php
$handle = fopen('app.log', 'r');
if (!$handle) {
throw new RuntimeException('Nie można otworzyć pliku.');
}
while (($line = fgets($handle)) !== false) {
if (str_contains($line, 'ERROR')) {
echo $line;
}
}
fclose($handle);
To podejście jest proste, ale bardzo skuteczne. W praktyce można je łatwo rozbudować o parser, filtr daty, zapis do pliku wynikowego albo wysyłkę dalej do systemu analitycznego.
CSV bez bólu pamięci
Przetwarzanie CSV to jedno z najlepszych miejsc dla streamów w PHP. Przy dużych plikach różnica między "wczytaj wszystko" a "czytaj po wierszu" potrafi być ogromna.
<?php
$input = fopen('users.csv', 'r');
$output = fopen('clean-users.csv', 'w');
$header = fgetcsv($input);
fputcsv($output, $header);
while (($row = fgetcsv($input)) !== false) {
if (count($row) < 3) {
continue;
}
$row[1] = strtolower(trim($row[1]));
$row[2] = trim($row[2]);
fputcsv($output, $row);
}
fclose($input);
fclose($output);
Ten wzorzec łatwo rozszerzyć o walidację danych, deduplikację, mapowanie kolumn czy wzbogacanie rekordów z zewnętrznego API. Dzięki temu jeden mały skrypt może działać jak pełnoprawny etap pipeline’u danych.
Stream z API
PHP nadaje się też do pracy z odpowiedziami HTTP, zwłaszcza jeśli API zwraca dane partiami. W takim przypadku warto przetwarzać elementy od razu po odebraniu, zamiast czekać na całą odpowiedź.
<?php
$json = file_get_contents('https://example.com/api/events');
$data = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
foreach ($data['items'] as $item) {
if (!isset($item['type'])) {
continue;
}
echo $item['type'] . ':' . $item['id'] . PHP_EOL;
}
To jeszcze nie jest pełny streaming w sensie „chunked response”, ale pokazuje praktyczny punkt wyjścia. Przy większej skali można przejść na iteracyjne pobieranie stron API, batch processing albo biblioteki wspierające bardziej zaawansowane przetwarzanie strumieniowe.
Lazy processing
Jeśli chcesz budować bardziej elegancki pipeline, warto sięgnąć po lazy evaluation. W PHP można to osiągnąć np. przez generatory, które zwracają dane element po elemencie zamiast budować całą kolekcję naraz.
<?php
function readLines(string $file): Generator
{
$handle = fopen($file, 'r');
if (!$handle) {
throw new RuntimeException('Nie można otworzyć pliku.');
}
try {
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
} finally {
fclose($handle);
}
}
foreach (readLines('app.log') as $line) {
if ($line === '') {
continue;
}
echo strtoupper($line) . PHP_EOL;
}
Taki wzorzec jest wygodny, bo oddziela źródło danych od logiki przetwarzania. Dodatkowo dobrze wspiera testowanie i składanie większych pipeline’ów z mniejszych funkcji.
Ograniczenia
PHP nie zastąpi wyspecjalizowanych platform stream processingowych, jeśli potrzebujesz rozproszenia, gwarancji dostarczenia, replikacji i zaawansowanej orkiestracji. Przy danych o bardzo dużej skali szybciej dojdziesz do granic pojedynczego procesu niż w systemach zbudowanych specjalnie pod ten cel. Warto też uważać na:- przechowywanie zbyt dużych struktur w pamięci.
- brak checkpointów przy długich zadaniach.
- słabą obsługę błędów i retry.
- brak metryk i logowania kroków pipeline’u.