Vysypání výstupu
Školení, která pořádám
Při provádění dlouhotrvajících operací je vhodné uživatele informovat o tom, že se něco děje. Pokud operace sama neprodukuje žádný výstup, je možné v nějakých intervalech např. vypisovat tečky nebo posílat klientovi JavaScript, který zajistí aktualizaci ukazatele průběhu. Kvůli šetrnosti ale výstup skriptu neposílá PHP a webový server klientovi okamžitě, ale až když se ho nashromáždí větší množství. Pokud výstup chceme poslat ihned, musíme zavolat dvě funkce – ob_flush a flush. ob_flush zajistí vyprázdnění interního bufferovacího mechanismu PHP, flush vysype buffer webového serveru. Volání ob_flush si můžeme ušetřit při zapnutém ob_implicit_flush.
<?php
echo "Průběh: <span id='counter'></span>\n";
$total = 20;
$printed = 0;
for ($i=0; $i < $total; $i++) {
sleep(rand(0, 2)); // dlouhotrvající operace
if (time() >= $printed + 3 || $i+1 == $total) { // aktualizace počítadla nejdříve za tři vteřiny
echo "<script type='text/javascript'>document.getElementById('counter').innerHTML = '" . round(100 * ($i+1) / $total) . "%';</script><noscript>.</noscript>\n";
ob_flush();
flush();
$printed = time();
}
}
?>
Skript v cyklu provádí dlouhotrvající operace a klientovi posílá JavaScriptový kód pro aktualizaci ukazatele průběhu. Aby se data neposílala zbytečně často, tak se kontroluje, jestli od posledního výpisu uběhly alespoň tři vteřiny – to se může hodit u operací, které ve skutečnosti tak dlouho netrvají, ale je jich třeba hodně.
Diskuse
Michal:
Co kdyz je to jedina operace ktera trva tuze dlouho? Pustis ji ve threadu a hlavni vlakno bude nadale zobrazovat tecky? Jdou v PHP vubec delat thready? Mozna by bylo lepsi tu operaci nejak pustit na pozadi, skript skoncit a pres AJAX se nejak rozumne dotazovat na prubeh. Shrnuto - tvoje reseni se mi vubec nelibi ;-)
nebo prostě pustit na výstup skript se setInterval, který bude zobrazovač průběhu aktualizovat sám, nezávisle na PHP, jenž si v klidu dokončí úkol.
Leo:
Presne podle hesla - co muze udelat klient necham udelat klienta. Jsem vsema dvaceti pro. Leo
Ještě mi řekni, jak to chceš technicky realizovat.
Jak je z naznačeného příkladu patrné, dlouhotrvající operace obvykle netrvá konstantní dobu a JS bez PHP netuší, jak je daleko.
Michal Molhanec:
smysl ukazatelu prubehu/cinnosti je informovat o tom, ze se neco provadi, ze to nezmrzlo. generovat tuto generaci na klientovy nezavisle na prubehu PHP skriptu je tudiz dobre maximalne jako graficka atrapa
pepper:
zobrazeni prubehu je opravdu jen graficka atrapa protoze jeho smyslem vetsinou neni ukazovat stav prubehu. Takze tupa animace navodi uzivateli pocit ze se neco deje a klient se v intervalech taze na vysledek a zachova se podle toho.
IMO je tedy sparvna kombinace obojiho.
Thready v PHP nejsou a oficiálně nikdy brzy ani nebudou. Pouštění na pozadí je také problematické.
Mordae:
A nedal by se zobrazovat prubeh pres ticks?
<?php
function status ( )
{
echo '.';
flush();
}
register_tick_function('status');
declare(ticks=1)
{
// slozita operace
}
unregister_tick_function('status');
?>
Samozrejme by to chtelo vyladit, nekde si skladovat status a nepsat tecky po kazdem prikazu.
Šlo by to taky, ale se dvěmi výhradami. Ticks neurčují počet vteřin, ale počet elementárních operací, takže hodnota 1 je příliš malá. Hlavní pointa článku byla, že samotné flush() při vypnutém ob_implicit_flush() nestačí, takže by to chtělo doplnit ještě ob_flush().
lukas:
A neni pri prenosu ten cely HTML soubor nejak komprimovan? V takovem pripade bych totiz rekl, ze Apache stejne vsechna data podrzi az do skonceni skriptu a odesle je az cela (male datove objemy se spatne komprimuji). Pak by se OB funkce hodily jen na premluveni PHPka ve stylu "jeste ten kod nevypisuj, budu si nekdy ve prostred behu skriptu chtit poslat par cookies klientovi".
Data se při přenosu komprimovat mohou. Většina kompresních algoritmů je ale proudových - z dat na vstupu ihned produkují výstup a problém je tak maximálně se zarovnáním nedokončených bajtů.
S obvyklým ob_gzhandler vysypání výstupu bez problémů funguje.
Paja5:
Je to zajimavy clanek. Nemate prosim nekdo navrh jak to rozjet pod Operou?
Gimli2:
Kdysi na Builder.cz psali:
... Výsledkem je provedení náročné akce, ovšem uživatel musí čekat do konce a jím případně vyvolané přerušení vše ukončí.
Druhou použitou funkcí je ignore_user_abort(). Ta zajistí ignoraci přerušení vyvolaného uživatelem. Resp. stránka se přestane natahovat, ale skript na straně serveru běží dál. (1 - zapne ignoraci, 0 - vypne, bez parametru - vrátí aktuální stav)
<?php
/* STOP tlacitko z prohlizece ignorujeme ..*/
ignore_user_abort(1);
/* timeout vypnut */
set_time_limit(0);
/* fork na jinou stranku */
Header("Location: /nekam_jinam.html");
Header("Connection: close\n\n");
/* donutime jej poslat hlavicku.. */
echo "neco";
flush();
/*** fork proveden */
/* co se ma provadet dal .. */
/* narocne vypocty :) */
for($i=0; $i<3000000; $i++) {/* */};
/* kontrola ze vse provedeno.. */
mail("spam@spam.spam","test","TEST, i=$i");
?>
pa3k:
Vyriešené pomocou .htaccess:
<Files "script.php">
SetEnv no-gzip
</Files>
querystring ma až tak moc netrápi, ak by bolo treba už si kompresiu nejako ošetrim (zapnutím) na úrovni php
armin:
Dobry den,
co se tyce funkcnosti i zde popsaneho prikladu v prohlizeci Opera, je nutno uvest ze je potrebne na konec (cyklicky vypisovaneho "prirastku") pridat zalomeni radku: <br />. A aby nedoslo k rozhozeni designu, staci doplnit styl:
<br style="line-height: 0px;" />
Nox:
V Opeře 9.52 mi to bohužel nefuguje vůbec, až při zastavení uživatelem se vypíše aktuální stav. V nastavení mám dáno "vykreslovat okamžitě". Ve FF3 funguje v pohodě.
Kajman:
Webový štít od avg dá prohlížeči až celou stránku.
Diskuse je zrušena z důvodu spamu.