Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Le funzioni di controllo dell'output

Come ottimizzare le nostre pagine php grazie alle ob_() functions
Come ottimizzare le nostre pagine php grazie alle ob_() functions
Link copiato negli appunti

Premessa

In questo articolo esamineremo alcune funzioni di php4 che permettono di ottimizzare l?esecuzione degli scripts e velocizzare sensibilmente il caricamento delle pagine WEB da parte dei navigatori.

Passiamole in rassegna brevemente:

  • ob_start -- attiva il buffering dell?output
  • ob_flush -- invia al browser il contenuto del buffer
  • ob_end_flush -- invia al browser il contenuto del buffer e interrompe l? immagazzinamento
  • ob_implicit_flush -- attiva o disattiva l?invio automatico dei dati
  • ob_gzhandler -- funzione di compressione gzip dei dati nel buffer
  • ob_clean -- cancella il contenuto del buffer
  • ob_end_clean -- cancella il contenuto del buffer e interrompe l? immagazzinamento
  • ob_get_contents -- restituisce il contenuto dei dati immagazzinati nel buffer
  • ob_get_length -- restituisce la lunghezza del buffer
  • ob_get_level -- restituisce il livello di annidamento del meccanismo di buffering
  • ob_get_status -- restituisce lo status del buffer (in fase sperimentale)

I nomi di tutte le funzioni appena descritte presentano il prefisso "ob_" che sta per "output buffering".

Il buffering consiste nell'accumulo in una memoria temporanea, detta appunto "buffer", di ogni dato che lo script altrimenti invierebbe immediatamente all?output attraverso echo() o print().

A partire dal momento della chiamata ad ob_start() tutti gli output verranno deviati verso il buffer e spediti al client soltanto al termine dell' esecuzione dello script, od ogniqualvolta vi sia una chiamata ad una funzione *_flush() (vedi pagine successive).

I casi in cui le funzioni "ob_xxx" si rivelano utili sono essenzialmente 3:

1) quando le pagine presentano un numero notevole di funzioni di stampa queste, attraverso le funzioni "ob", verranno fatte convergere in una sola riducendo il traffico di dati dal server al client.

2) quando si intende facilitare lo scaricamento delle pagine da parte degli utenti spedendo loro dati in formato compresso.

3) quando l'invio di headers crea problemi: in script corposi può essere utile poter progettare la logica del codice liberamente, senza la preoccupazione di aver inserito degli headers (cookie, redirezione etc. etc.) successivamente a delle operazioni di output.

Per dimostrare l?effettiva utilità delle funzioni di buffering dell?output nelle situazioni appena descritte sono state effettuate alcune prove, sia in locale che remoto.

Caso 1: l'output tutto in una volta

Ho testato i tempi di esecuzione in 3 condizioni differenti, ricorrendo in tutti i casi alla classica funzione di benchmark

<?php

/*
Definizione della funzione benchmarck, da INSERIRE IN OGNI SCRIPT che si vuole testare.
Registra il momento iniziale e quello finale dell'esecuzione dello script in secondi (frazioni di secondo)
*/
function getmicrotime(){
list($usec, $sec) = explode(" ",microtime()) ;
return ((float)$usec + (float)$sec) ;
}

?>

1.1 Numerose istruzioni echo() o print()

<?php

/*
momento iniziale
*/
$time_start = getmicrotime();

/*
decommenta la riga sottostante per attivare il buffering
*/
//ob_start() ;

/*
Ciclo di 1000 operazioni echo()
*/
for($i=0 ;$i>1000; $i++){
echo("Visita il forum php di html.it e www.freeephp.it

n" ) ;
}

/*
Registra il momento in cui lo script termina
*/
$time_end = getmicrotime();

/*
Misura la differenza tra start e stop
*/
$time = $time_end - $time_start;
echo (">div style=" position:absolute;top:0;left:200;height:20;background-color:red "<
Eseguito in $time secondi
>/div<");

?>

Tempo medio di esecuzione: 0.0111249685287 secondi

1.2 Eseguiamo lo script precedente dopo aver attivato il buffering dell'output

Tempo medio di esecuzione : 0.00333297252655 secondi

Le 1000 operazioni echo() vengono ridotte ad una soltanto

2.1 Invio al browser di 10000 caratteri annidati tra due sezioni

<?php ... ?>

...testo...

<?php ... ?>

<?php

/*
Momento iniziale
*/
$time_start = getmicrotime();

/*
decommentiamo la riga sottostante per attivare il buffering
*/
//ob_start() ;

/*
Inseriamo qui sotto, dopo la chiusura del tag php, 10000 caratteri di testo
*/

?>

qui i 10000 caratteri

<?php
/*
Fine testo e riapertura tag php
*/

/*
Registra il momento in cui lo script termina
*/
$time_end = getmicrotime();

/*
Misura la differenza tra start e stop
*/
$time = $time_end - $time_start;

/*
Stampa il risultato in un livello con position absolute per non costringerci
a scorrere la pagina intera
*/
echo(">div style=" position:absolute;top:0;left:200;height:20;background-color:red "<
Eseguito in $time secondi
>/div<");

?>

Tempo medio di esecuzione: 0.141275048256 secondi

2.2 Come nello script precedente ma questa volta decommentiamo la riga che attiva il buffering dell'output

Tempo medio di esecuzione: 0.00213897228241 secondi.

Una bella differenza vero? Ma non finisce qui...

3.1 Invio al browser di 10000 caratteri attraverso un'unica istruzione echo()

<?php

/*
Momento iniziale
*/
$time_start = getmicrotime();

/*
decommentiamo la riga sottostante per attivare il buffering
*/
//ob_start() ;

/*
un unico "grande" echo di 10000 caratteri
*/
echo($contenuto_10000_caratteri) ;

/*
Registra il momento in cui lo script termina
*/
$time_end = getmicrotime();

/*
Misura la differenza tra start e stop
*/
$time = $time_end - $time_start;

echo (">div style=" position:absolute;top:0;left:200;height:20;background-color:red "<
Eseguito in $time secondi>/div<") ;

?>

Tempo medio di esecuzione: 0.188348889351 secondi.

3.2 Come l'esempio precedente, ma attiviamo il buffering decommentando la riga ob_start()

Tempo medio di esecuzione: 0.000921964645386 secondi.

Molto inferiore rispetto all'esempio 1.2.1 e leggermente migliore persino a quello dell'esempio 1.1.2

Dalle prove illustrate fino a questo momento credo si possa trarre la conclusione che è utile inserire ob_start() all'inizio delle nostre pagine non soltanto quando desideriamo ridurre più operazioni di output ad un' unica emissione (ed è il caso degli esempi 1.1.1 e 1.1.2), ma sempre e comunque quando vi sia una certa mole di dati da inviare al browser.

Cosa accade esattamente? A cosa dobbiamo questo miglioramento nell'esecuzione?

In Php le operazioni di emissione dell'output attraverso echo() o print() sono operazioni costose in termini di prestazioni in quanto comportano ogni volta l'invio di pacchetti TCP/IP ai client: ecco perchè, ad esempio, sarebbe preferibile riunire più stringhe in una variabile da inviare all'output in un'unica soluzione anzichè ricorrere a tanti echo().

Ad esempio meglio

<?php

$output_string=$str1.$str2.$str3.$str4.$str5 ;
echo($output_string) ;

?>

rispetto a

<?php

echo($str1) ;
echo($str2) ;
echo($str3) ;
echo($str4) ;
echo($str5) ;

?>

Il buffering dell'output rappresenta un metodo interno a Php (e quindi ancora più efficiente) per ottenere questo risultato, e le funzioni "ob" andrebbero applicate seza esitazione quando abbiamo molte operazioni di output che si susseguono annidate nel codice html: penso ad esempio all'aggiunta di variabili nella query-string di tutti i links presenti in una pagina.

Questi vantaggi hanno comunque un costo, ecco alcuni aspetti da tenere in considerazione:

1) Anche ob_start(), come tutte le funzioni, richiede un certo tempo di esecuzione, pertanto bisogna valutare il suo impiego in rapporto alla quantità di dati da inviare. Non ha senso ricorrere ad una funzione del genere per sole poche righe di output

2) Latenza: qualora la mole di dati sia davvero enorme, i visitatori del nostro sito non vedranno nulla fino al momento in cui lo script risulterà completamente eseguito, quindi, anche se nel complesso il ricevimento dei dati da parte del browser sarà notevolmente velocizzato, non si avvertirà il classico caricamento progressivo della pagina.
Questo fatto, in determinate condizioni, potrebbe far pensare erroneamente ad un crash e spingere il visitatore a passare oltre. Qualora si presentasse questo inconveniente potremmo ricorrere di tanto in tanto al flush (vedi più avanti, "Quando ricorrere alle funzioni *_flush()?")

3) Ricordiamo che in un ambiente multiutente la velocità di esecuzione è soltanto uno dei parametri da cosiderare nell'ottimizzazione degli script, l'altro è la quantità di memoria che l'esecuzione stessa richiede: dobbiamo tenere presente che il buffer viene immagazzinato nella RAM.

Anche se probabilmente troverete sempre conveniente ricorrere alle possibilità offerte dal buffering, è bene testare caso per caso.

Caso 2: ob_gzhandler(): inviare i dati in formato compresso

Probabilmente si tratta dell'aspetto più utile dell'impiego di queste funzioni: ob_start() può accettare come argomento una funzione di callback che operi sul buffer immagazzinato (il buffer non è altro che una stringa), tale argomento può essere una funzione predisposta da noi (vedi più avanti) oppure ob_gzhandler(), la quale ha come unico impiego pratico proprio l'essere passata ad ob_start().

ob_gzhandler opererà una compressione del buffer prima che questo venga inviato al browser velocizzando notevolmente(fino al 60%!) il flusso di scaricamento della pagina Web.

Ecco come ho cercato di individuare i tempi di scaricamento con Javascript:

<?php

/*
Decommenta per attivare la compressione dell' output
*/
//ob_start("ob_gzhandler") ;

?>

<html><head>

<script Language="JavaScript">
/*
Useremo Javascript per misurare il tempo di caricamento della pagina
in frazioni di secondo
*/
function getsecs(){
var oggi= new Date();
var secondi=(oggi.getTime())/1000 ;
return secondi ;
}

/*
inizio della ricezione del flusso di dati e del caricamento della pagina
*/
var start=getsecs();

</script>

</head>

<!-- L'evento onLoad corrisponde al momento del completo caricamento -->

<body bgcolor="#ffffff" onLoad="alert('Inizio: '+start+' Fine: '+(getsecs())+'n Tempo intercorso: '+((getsecs())- start));">

<!-- Output 10000 caratteri -->

</body>
</html>

Dalle prove effettuate, questa volta prevalentemente in remoto, i tempi di scaricamento si sono perfettamente dimezzati con Internet Explorer 5 e 6, mentre non ho potuto riscontrare alcun miglioramento con Netscape e Opera versioni 6.x (eppure, sulla carta, dovrebbero riconoscere e accettare vari formati di compressione).

L'ottimo Mozilla 1.0 e, soprendentemente, Netscape 4.5 hanno fatto riscontrare miglioramenti intorno al 30%.

Php dovrebbe essere in grado di rilevare quali tra i tipi di compressione il browser supporta (es. gzip, deflate) o se non ne sia supportata alcuna: sta al browser inviare le corrette intestazioni, ob_gzhandler opererà di conseguenza.

Caso 3: inviare headers anche dopo che lo script abbia già cominciato ad emettere output

Normalmente non è una cosa possibile, e la presenza anche di un semplice carattere "n" (newline) prima del tag di apertura "<?php" è sufficiente a produrre il seguente errore:

"Warning: Cannot add header information - headers already sent by (output started at g:wwwprovasessionick.php:1) in c:wwwprovesessionibadcookie.php on line 3"

Chi non l'ha mai incontrato almeno una volta alzi la mano!

Ebbene in origine le funzioni ob_xxx erano state introdotte proprio per ovviare a questo problema: nella maggior parte dei casi basterebbe prestare un po' di attenzione ma, specialmente quando il codice è molto complesso, questa può essere per il programmatore una fastidiosa limitazione nella logica di scrittura del codice.

Attraverso il buffering dell'output, ovunque si trovino nello script, le operazioni di output verranno comunque posposte e inviate soltanto al termine dell'esecuzione.

Provare per credere:

<?php

ob_start();
echo ("Questo normalmente non si potrebbe fare!");
setcookie ("miocookie", "dati");

?>

Quando usare le funzioni *_flush?

Flush significa "flusso" e corrisponde all'emissione del buffer: fino a questo momento non abbiamo utilizzato esplicitamente alcuna funzione di flush, infatti uno script che abbia attivato il buffering dell'output effettua sempre l'emissione al termine dell'esecuzione.

Se vi è una sola chiamata a ob_start() il buffer è unico, quindi non serve forzare l'output.

In alcuni casi possiamo decidere di svuotare il buffer senza attendere la conclusione dello script, ad esempio al raggiungimento di tot. byte nel buffer, dopo aver misurato la sua lunghezza con ob_get_length.

ob_end_flush() e buffer annidati

Un'altra occasione in cui potremmo decidere di forzare il flush prima della conclusione dello script, potrebbe presentarsi quando abbiamo annidato diversi buffer gli uni negli altri.

Ho già accennato al fatto che ad ob_start() può avere come argomento una funzione che
effettui delle operazioni sulle stringhe, ecco cosa potremmo fare:

<?php

function changeNames($buffer){
return (str_replace("red","blue",$buffer)) ;
}

ob_start("changeNames");

echo('<div style="position:absolute; height:100;width:100; top:50;left:50;background-color:red"></div>') ;

?>

Otteremo un livello con sfondo blu.

Questo è un esempio banale, ma potremmo applicare una funzione di sostituzione di stringhe (magari una che supporti le espressioni regolari) su un buffer di migliaia di caratteri prima di inviarlo al browser: ob_start applica all'intero buffer la funzione inserita come argomento, nello stesso modo in cui effettuava la compressione attraverso la funzione di callback predefinita ob_gzhandler().

E se volessimo operare sostituzioni e compressione al tempo stesso?

ob_start() accetta un solo argomento alla volta, ma questo è uno dei casi in cui possiamo annidare le chiamate.

<?php

function changeNames($buffer){
return (str_replace("red","blue",$buffer)) ;
}

/*
Prima chiamata, applicherà la compressione al buffer finale
*/
ob_start("ob_gzhandler") ;

/*
Seconda chiamata, effettuerà le sostituzioni nel buffer compreso prima della prossima chiamata al flush
*/
ob_start("changeNames");

echo('<div id="manipolato" style="position:absolute; height:100; width:100; top:50; left:50; background-color:red"></div>') ;

/*
Chiude la seconda chiamata e passa il contenuto (un livello il cui colore di sfondo è stato sostituito) dal secondo buffer al primo
*/
ob_end_flush();

/*
In questo caso invece nessuna modifica dei colori, subisce solo la compressione
*/
echo('<div id="intatto" style="position:absolute; height:100;width:100;top:200;left:200;background-color:red"></div>') ;

/*
Chiude la prima chiamata ed emette l'intero buffer
*/
ob_end_flush();

?>

Otteniamo 2 livelli di colore diverso, dato che in uno dei due lo sfondo è stato cambiato "al volo".
Tutto il codice html viene inviato in formato compresso.

Ogni "ob_end_flush()" trasferisce il buffer che racchiude in quello principale in cui si trovava annidato.

Ci dovranno essere tante chiamate ad ob_end_flush quante sono le chiamate ad ob_start(), altrimenti si verifichereanno errori imprevedibili: solo l'ultimo flush è facoltativo, ma per ragioni di leggibilità è preferibile inserirlo comunque.

Conclusioni

Spero di essere riuscito a dimostrare l'utilità di queste funzioni, vi invito a sperimentare a vostra volta e a comunicarmi eventuali osservazioni.

Se avete accesso al server potrete anche intervenire sul php.ini (cioè il file di configurazione di Php) per rendere il buffering dell'output e la compressione dei dati opzioni di default, sarà sufficiente cercare l'apposita sezione e seguire le indicazioni tra i commenti.

Ti consigliamo anche