Introduzione
La riga di comando è uno strumento molto utile per eseguire attività automatiche, pianificate o ripetibili. Un caso concreto consiste nel creare uno script PHP CLI che interroga un’API REST, legge i dati paginati e li salva localmente in un file JSON.
Questo approccio può essere utilizzato per sincronizzare prodotti da un gestionale, scaricare utenti da un servizio esterno o aggiornare periodicamente una cache locale. A differenza di uno script web, un programma CLI può essere eseguito da cron, da un sistema CI/CD o manualmente da un amministratore.
Realizzeremo un comando chiamato sync-users.php. Lo script:
- accetterà un endpoint API come argomento;
- invierà richieste HTTP usando
cURL; - gestirà la paginazione;
- salverà i risultati in un file JSON;
- mostrerà un riepilogo dell’operazione;
- restituirà codici di uscita utili per l’automazione.
Codice completo
Salviamo il seguente contenuto in un file chiamato sync-users.php:
<?php
declare(strict_types=1);
/**
* Sincronizza utenti da un´API REST paginata.
*
* Esempio:
* php sync-users.php https://api.example.com/users output/users.json
*/
if (PHP_SAPI !== ´cli´) {
fwrite(STDERR, "Questo script deve essere eseguito da PHP CLI.n");
exit(1);
}
if ($argc < 3) {
fwrite(
STDERR,
"Uso: php sync-users.php <endpoint> <file-output>n"
);
exit(1);
}
$endpoint = $argv[1];
$outputFile = $argv[2];
if (!filter_var($endpoint, FILTER_VALIDATE_URL)) {
fwrite(STDERR, "Errore: l´endpoint non è un URL valido.n");
exit(1);
}
try {
$users = fetchAllUsers($endpoint);
$directory = dirname($outputFile);
if (!is_dir($directory) && !mkdir($directory, 0755, true)) {
throw new RuntimeException(
"Impossibile creare la directory: {$directory}"
);
}
$json = json_encode(
[
´synced_at´ => date(DATE_ATOM),
´count´ => count($users),
´users´ => $users,
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
if (file_put_contents($outputFile, $json . PHP_EOL) === false) {
throw new RuntimeException(
"Impossibile scrivere il file: {$outputFile}"
);
}
echo "Sincronizzazione completata.n";
echo "Utenti ricevuti: " . count($users) . "n";
echo "File creato: {$outputFile}n";
exit(0);
} catch (Throwable $exception) {
fwrite(STDERR, "Errore: " . $exception->getMessage() . "n");
exit(2);
}
/**
* Recupera tutte le pagine dell´API.
*
* L´API di esempio dovrebbe restituire una struttura simile:
* {
* "data": [...],
* "current_page": 1,
* "last_page": 3
* }
*/
function fetchAllUsers(string $endpoint): array
{
$users = [];
$page = 1;
do {
$separator = str_contains($endpoint, ´?´) ? ´&´ : ´?´;
$url = $endpoint . $separator . ´page=´ . $page;
echo "Richiesta pagina {$page}: {$url}n";
$response = requestJson($url);
if (
!isset($response[´data´]) ||
!is_array($response[´data´])
) {
throw new RuntimeException(
"Formato API non valido: manca il campo data."
);
}
foreach ($response[´data´] as $user) {
$users[] = $user;
}
$currentPage = (int) ($response[´current_page´] ?? $page);
$lastPage = (int) ($response[´last_page´] ?? $currentPage);
$page = $currentPage + 1;
} while ($currentPage < $lastPage);
return $users;
}
/**
* Esegue una richiesta HTTP e converte la risposta JSON in array PHP.
*/
function requestJson(string $url): array
{
$curl = curl_init($url);
if ($curl === false) {
throw new RuntimeException("Impossibile inizializzare cURL.");
}
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
´Accept: application/json´,
´User-Agent: PHP-CLI-Sync/1.0´,
],
]);
$body = curl_exec($curl);
if ($body === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException("Errore HTTP: {$error}");
}
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException(
"L´API ha restituito il codice HTTP {$statusCode}."
);
}
$decoded = json_decode($body, true);
if (!is_array($decoded)) {
throw new RuntimeException(
"La risposta dell´API non contiene JSON valido."
);
}
return $decoded;
} Spiegazione
Controllare l’ambiente di esecuzione
La costante PHP_SAPI identifica il tipo di interfaccia utilizzata da PHP. Il valore cli indica l’esecuzione da terminale. Il controllo iniziale impedisce che lo script venga richiamato accidentalmente tramite un server web.
Leggere gli argomenti
La variabile $argc contiene il numero degli argomenti, mentre $argv è un array che li contiene. In questo esempio:
$argv[0]è il nome dello script;$argv[1]è l’endpoint API;$argv[2]è il percorso del file di destinazione.
È importante validare sempre gli input. La funzione filter_var() controlla che l’endpoint sia un URL valido prima di avviare la richiesta.
Gestire la paginazione
Molte API non restituiscono tutti i risultati in una sola risposta. Il metodo fetchAllUsers() parte dalla pagina numero uno e continua finché current_page è minore di last_page.
La funzione è volutamente separata dalla gestione della richiesta HTTP. Questa divisione rende il codice più facile da testare e da modificare se l’API utilizza una struttura di paginazione diversa.
Effettuare richieste HTTP
requestJson() utilizza cURL con alcuni parametri importanti:
CURLOPT_CONNECTTIMEOUTlimita il tempo per stabilire la connessione;CURLOPT_TIMEOUTevita che lo script rimanga bloccato troppo a lungo;CURLOPT_RETURNTRANSFERrestituisce il corpo della risposta come stringa;CURLOPT_FOLLOWLOCATIONsegue eventuali reindirizzamenti.
Il codice verifica sia gli errori cURL sia il codice HTTP. Una risposta 404 o 500, per esempio, genera un’eccezione e termina il programma con un errore.
Eseguire il comando
Da terminale è possibile lanciare lo script così:
php sync-users.php
https://api.example.com/users
storage/users.json Prima di provarlo, assicurati che PHP disponga dell’estensione cURL:
php -m | grep curl Se il comando restituisce curl, l’estensione è disponibile. In caso contrario, occorre installare o abilitare l’estensione nell’installazione PHP utilizzata dalla CLI.
Best practice
- Usa codici di uscita coerenti:
0indica successo, mentre valori diversi da zero segnalano un problema. - Scrivi gli errori su STDERR: in questo modo l’output normale può essere reindirizzato separatamente.
- Imposta sempre timeout HTTP: una rete lenta o un servizio irraggiungibile non deve bloccare indefinitamente il processo.
- Non salvare direttamente file importanti: per dati critici puoi scrivere prima in un file temporaneo e poi rinominarlo.
- Limita la memoria: per migliaia di record è preferibile elaborare ogni pagina e scriverla progressivamente, invece di accumulare tutto in un array.
- Gestisci l’autenticazione in modo sicuro: token e chiavi API non dovrebbero essere inseriti direttamente nel codice sorgente.
- Prevedi retry controllati: errori temporanei come timeout o risposte
503possono essere ritentati, ma con un numero massimo di tentativi. - Registra il momento della sincronizzazione: il campo
synced_ataiuta a diagnosticare problemi e a verificare la freschezza dei dati.
Riepilogo
Uno script PHP CLI per sincronizzare un’API REST combina diversi concetti importanti: lettura degli argomenti, validazione degli input, richieste HTTP, decodifica JSON, paginazione, gestione delle eccezioni e codici di uscita.
Il comando può essere eseguito manualmente oppure automatizzato con cron. Per esempio, una sincronizzazione ogni ora potrebbe essere configurata con una riga simile:
0 * * * * /usr/bin/php /var/www/sync-users.php
https://api.example.com/users
/var/www/storage/users.json >> /var/log/user-sync.log 2>&1 Partendo da questo esempio puoi aggiungere autenticazione, filtri, retry, modalità incrementale e scrittura su database.
