Introduzione
Quando si ottimizza un´applicazione PHP, l´attenzione viene spesso concentrata esclusivamente sul tempo necessario per eseguire il codice lato server. In realtà, una parte importante della latenza dipende anche dalla quantità di dati trasferiti tra server e client. Una risposta HTML, JSON o XML molto grande richiede più tempo per essere inviata e più banda per essere scaricata.
Due tecniche semplici ed efficaci sono la compressione delle risposte HTTP e il caching condizionale. La prima riduce le dimensioni dei dati trasferiti. La seconda evita di inviare nuovamente una risorsa quando il client possiede già una versione aggiornata.
In questo tutorial realizzeremo un endpoint PHP che restituisce un documento JSON. L´endpoint:
- controlla se il client supporta la compressione gzip;
- genera un identificatore ETag per la risposta;
- risponde con
304 Not Modifiedquando il contenuto non è cambiato; - invia il JSON compresso quando è necessario trasferirlo.
Queste tecniche sono particolarmente utili per API, cataloghi, dashboard e pagine con contenuti pubblici che cambiano raramente.
Codice completo
<?php
declare(strict_types=1);
// Risposta in formato JSON.
header(´Content-Type: application/json; charset=utf-8´);
// In un´applicazione reale questi dati potrebbero provenire
// da una sorgente già ottimizzata o da un livello applicativo.
$payload = [
´status´ => ´ok´,
´generated_at´ => ´2026-09-04T10:00:00+00:00´,
´products´ => [
[
´id´ => 101,
´name´ => ´Tastiera meccanica´,
´price´ => 89.90
],
[
´id´ => 102,
´name´ => ´Mouse wireless´,
´price´ => 39.90
]
]
];
// JSON_UNESCAPED_UNICODE evita di trasformare i caratteri UTF-8
// in sequenze Unicode più lunghe e leggibili.
$json = json_encode(
$payload,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
);
// L´ETag identifica la versione corrente del contenuto.
// sha256 è più che sufficiente per questo esempio.
$etag = ´"´ . hash(´sha256´, $json) . ´"´;
header(´ETag: ´ . $etag);
header(´Cache-Control: public, max-age=300, must-revalidate´);
// Se il client ha già questa versione, non inviamo il corpo.
if (isset($_SERVER[´HTTP_IF_NONE_MATCH´])
&& trim($_SERVER[´HTTP_IF_NONE_MATCH´]) === $etag
) {
http_response_code(304);
exit;
}
// Compressione lato PHP come fallback.
// È preferibile configurarla a livello di web server o reverse proxy,
// ma questa soluzione funziona anche in ambienti semplici.
$acceptEncoding = $_SERVER[´HTTP_ACCEPT_ENCODING´] ?? ´´;
$canUseGzip = str_contains($acceptEncoding, ´gzip´)
&& function_exists(´gzencode´);
if ($canUseGzip) {
$compressedJson = gzencode($json, 6);
if ($compressedJson !== false) {
header(´Content-Encoding: gzip´);
header(´Vary: Accept-Encoding´);
header(´Content-Length: ´ . strlen($compressedJson));
echo $compressedJson;
exit;
}
}
// Risposta non compressa se gzip non è disponibile o fallisce.
header(´Content-Length: ´ . strlen($json));
echo $json;
Spiegazione
Compressione della risposta
Il browser o il client HTTP comunica i formati supportati tramite l´header Accept-Encoding. Un valore comune è gzip, deflate, br. Il codice verifica la presenza di gzip e utilizza la funzione gzencode() per comprimere il JSON.
Il livello di compressione utilizzato nell´esempio è 6, un compromesso tra velocità della CPU e dimensione finale. Un livello maggiore può ridurre leggermente il file, ma richiede più elaborazione. Per risposte dinamiche generate frequentemente, valori intermedi sono generalmente più equilibrati.
L´header Content-Encoding: gzip è indispensabile: informa il client che il contenuto deve essere decompresso. Anche Vary: Accept-Encoding è importante quando esiste una cache intermedia, perché indica che la risposta può variare in base alle capacità dichiarate dal client.
ETag e risposta 304
L´header ETag rappresenta una versione del contenuto. In questo caso viene calcolato applicando SHA-256 al JSON prodotto. Il client salva l´ETag e, alla richiesta successiva, lo invia nell´header If-None-Match.
Se il valore coincide, il server risponde con 304 Not Modified. Questa risposta non contiene il corpo della risorsa: il client utilizza la copia già presente nella propria cache. In questo modo si riduce sia il tempo di risposta sia il traffico di rete.
Cache-Control
max-age=300 comunica che la risposta può essere considerata valida per 300 secondi. L´opzione public consente anche a cache condivise, come proxy e CDN, di conservarla. La direttiva must-revalidate indica che, dopo la scadenza, la cache deve verificare nuovamente la validità.
Best practice
- Preferisci la configurazione del web server: Apache, Nginx o un reverse proxy possono comprimere le risposte in modo più efficiente rispetto a PHP, evitando di occupare risorse del processo applicativo.
- Non comprimere contenuti già compressi: immagini JPEG, PNG, file ZIP e PDF spesso non ottengono vantaggi significativi dalla compressione gzip.
- Imposta soglie minime: per risposte di pochi byte, il costo degli header e della compressione può superare il risparmio. I web server permettono di comprimere solo file oltre una determinata dimensione.
- Usa ETag stabili: l´ETag deve cambiare quando cambia il contenuto, non a ogni richiesta. Evita di includere timestamp casuali nel payload.
- Valuta Last-Modified: per file o risorse basate su una data di modifica, l´header
Last-Modifiedpuò essere più semplice di un hash. - Non cacheare dati personali: risposte contenenti informazioni private devono usare direttive come
privateono-store, secondo il caso. - Misura prima e dopo: confronta dimensione della risposta, tempo al primo byte, tempo complessivo e numero di richieste realmente servite dal server.
- Considera Brotli: se l´infrastruttura lo supporta, Brotli può offrire una compressione migliore per contenuti testuali. La negoziazione deve però rispettare l´header
Accept-Encoding.
Riepilogo
Ottimizzare le risposte HTTP permette di migliorare le performance senza riscrivere necessariamente la logica dell´applicazione. La compressione riduce la quantità di dati trasferiti, mentre ETag e risposte 304 eliminano trasferimenti ripetitivi.
Una soluzione robusta deve sempre inviare gli header corretti, gestire i client che non supportano gzip e distinguere tra contenuti pubblici e dati riservati. In produzione, la compressione dovrebbe normalmente essere gestita da Nginx, Apache, un CDN o un reverse proxy, lasciando a PHP la responsabilità di produrre contenuti corretti e cacheabili.
