Introduzione
La modalità CLI di PHP è molto utile quando è necessario eseguire operazioni automatiche senza utilizzare un browser. Un caso pratico è la validazione di un file CSV ricevuto da un gestionale, da un fornitore o da un sistema esterno.
In questo tutorial realizzeremo uno script PHP da terminale che legge un file CSV contenente dati di utenti, controlla la correttezza delle colonne principali e genera un report finale. Lo script individuerà righe valide, email non corrette, campi obbligatori mancanti e duplicati.
L’esempio è volutamente indipendente da database e librerie esterne. Utilizzeremo esclusivamente le funzioni native di PHP, come fopen(), fgetcsv(), filter_var() e fputcsv(). In questo modo il comando può essere eseguito su quasi ogni ambiente PHP.
Il file CSV utilizzato avrà questa struttura:
name,email,age
Mario Rossi,[email protected],35
Anna Bianchi,[email protected],28
Luca Verdi,email-non-valida,abc
,[email protected],40
Mario Rossi,[email protected],35 Codice completo
Salviamo il seguente codice nel file validate-users.php:
<?php
declare(strict_types=1);
const REQUIRED_COLUMNS = [´name´, ´email´, ´age´];
$inputFile = $argv[1] ?? null;
$outputFile = $argv[2] ?? ´validation-report.csv´;
if ($inputFile === null) {
fwrite(
STDERR,
"Uso: php validate-users.php <file-input.csv> [report-output.csv]n"
);
exit(2);
}
if (!is_file($inputFile) || !is_readable($inputFile)) {
fwrite(
STDERR,
"Errore: il file ´$inputFile´ non esiste o non è leggibile.n"
);
exit(2);
}
$handle = fopen($inputFile, ´rb´);
if ($handle === false) {
fwrite(STDERR, "Errore: impossibile aprire il file CSV.n");
exit(2);
}
// Legge la prima riga, che deve contenere le intestazioni.
$header = fgetcsv($handle);
if ($header === false) {
fwrite(STDERR, "Errore: il file CSV è vuoto.n");
fclose($handle);
exit(2);
}
// Rimuove un eventuale BOM UTF-8 dalla prima colonna.
$header[0] = preg_replace(´/^xEFxBBxBF/´, ´´, $header[0]);
$header = array_map(
static fn (string $column): string => strtolower(trim($column)),
$header
);
$missingColumns = array_diff(REQUIRED_COLUMNS, $header);
if ($missingColumns !== []) {
fwrite(
STDERR,
´Errore: colonne mancanti: ´ . implode(´, ´, $missingColumns) . "n"
);
fclose($handle);
exit(2);
}
$reportHandle = fopen($outputFile, ´wb´);
if ($reportHandle === false) {
fwrite(
STDERR,
"Errore: impossibile creare il report ´$outputFile´.n"
);
fclose($handle);
exit(2);
}
fputcsv($reportHandle, [
´line´,
´status´,
´name´,
´email´,
´message´
]);
$emailSeen = [];
$lineNumber = 1;
$validRows = 0;
$invalidRows = 0;
$duplicateRows = 0;
while (($row = fgetcsv($handle)) !== false) {
$lineNumber++;
// Ignora righe completamente vuote.
if ($row === [null] || count(array_filter($row, ´strlen´)) === 0) {
continue;
}
$data = array_pad($row, count($header), null);
$record = array_combine($header, array_slice($data, 0, count($header)));
$name = trim((string) ($record[´name´] ?? ´´));
$email = trim((string) ($record[´email´] ?? ´´));
$age = trim((string) ($record[´age´] ?? ´´));
$errors = [];
if ($name === ´´) {
$errors[] = ´nome mancante´;
}
if (
$email === ´´ ||
filter_var($email, FILTER_VALIDATE_EMAIL) === false
) {
$errors[] = ´email non valida´;
}
if (
$age === ´´ ||
filter_var($age, FILTER_VALIDATE_INT) === false ||
(int) $age < 0 ||
(int) $age > 120
) {
$errors[] = ´età non valida´;
}
$emailKey = strtolower($email);
if ($email !== ´´ && isset($emailSeen[$emailKey])) {
$errors[] = ´email duplicata´;
$duplicateRows++;
} elseif ($email !== ´´) {
$emailSeen[$emailKey] = true;
}
if ($errors === []) {
$status = ´valid´;
$message = ´riga valida´;
$validRows++;
} else {
$status = ´invalid´;
$message = implode(´; ´, $errors);
$invalidRows++;
}
fputcsv($reportHandle, [
$lineNumber,
$status,
$name,
$email,
$message
]);
}
fclose($handle);
fclose($reportHandle);
echo "Elaborazione completata.n";
echo "Righe valide: $validRowsn";
echo "Righe non valide: $invalidRowsn";
echo "Duplicati rilevati: $duplicateRowsn";
echo "Report creato: $outputFilen";
exit($invalidRows > 0 ? 1 : 0); Per rendere lo script eseguibile direttamente sui sistemi Unix-like, è possibile aggiungere come prima riga:
#!/usr/bin/env php Successivamente si assegnano i permessi:
chmod +x validate-users.php
./validate-users.php users.csv report.csv Spiegazione
Lettura del file CSV
La funzione fgetcsv() legge una riga e la converte in un array di colonne. La prima chiamata viene utilizzata per ottenere le intestazioni. Il codice normalizza i nomi trasformandoli in minuscolo e rimuovendo gli spazi superflui.
Il controllo delle colonne è importante perché impedisce allo script di elaborare dati con una struttura inattesa. Se manca una colonna obbligatoria, il comando termina con codice di errore 2.
Validazione dei dati
Il nome deve essere presente, l’email viene controllata con FILTER_VALIDATE_EMAIL e l’età deve essere un numero intero compreso tra 0 e 120. Gli errori della singola riga vengono raccolti nell’array $errors, così il report può mostrare più problemi contemporaneamente.
Individuazione dei duplicati
L’array $emailSeen funziona come un insieme di email già incontrate. Prima di registrare una nuova email, lo script la converte in minuscolo. In questo modo [email protected] e [email protected] vengono considerati lo stesso indirizzo.
Codici di uscita
Il comando restituisce 0 se tutte le righe sono valide, 1 se sono stati trovati errori nei dati e 2 per errori tecnici, come file mancante o intestazioni errate. Questa distinzione è molto utile negli script automatici:
php validate-users.php users.csv report.csv
echo $? Un sistema esterno può quindi capire se l’elaborazione è terminata correttamente, se il file contiene dati da correggere oppure se il comando non è riuscito ad avviarsi.
Best practice
- Usare STDERR per gli errori: i messaggi tecnici vengono inviati a STDERR, mentre il riepilogo normale viene scritto su STDOUT.
- Controllare sempre i file: prima di aprire un file è opportuno verificare che esista e sia leggibile.
- Non caricare tutto il CSV in memoria: la lettura riga per riga consente di gestire anche file molto grandi.
- Normalizzare i dati: usare trim() e conversioni coerenti evita falsi errori e duplicati non riconosciuti.
- Definire codici di uscita documentati: gli strumenti di automazione possono reagire in modo diverso a seconda del valore restituito.
- Proteggere il file di output: in un ambiente reale bisogna verificare i permessi della directory e scegliere un percorso controllato.
- Gestire il delimitatore: alcuni CSV usano il punto e virgola. In quel caso è possibile chiamare fgetcsv($handle, 0, ´;´) e usare lo stesso delimitatore con fputcsv().
Riepilogo
Abbiamo creato un comando PHP CLI capace di validare un file CSV senza dipendere da un database o da pacchetti esterni. Lo script controlla le intestazioni, legge le righe progressivamente, valida nome, email ed età, rileva duplicati e produce un report CSV dettagliato.
Questo approccio può essere esteso facilmente per controllare numeri di telefono, codici fiscali, date, importi monetari o regole specifiche del proprio dominio applicativo. Può inoltre diventare una fase di pre-validazione prima dell’importazione dei dati in un database.
