SafeStream: atomické operace
Nette\Utils\SafeStream pro práci se soubory garantuje, že každá operace proběhne atomicky a izolovaně.
K čemu je to vlastně dobré? Začněme jednoduchým příkladem, který opakovaně zapisuje do souboru a následně z něj čte tentýž řetězec:
$s = str_repeat('Long String', 10000);
$counter = 1000;
while ($counter--) {
file_put_contents('soubor', $s); // write it
$readed = file_get_contents('soubor'); // read it
if ($s !== $readed) { // check it
echo 'řetězce se liší!';
}
}
Může se zdát, že volání echo 'řetězce se liší!'
nikdy nemůže nastat. Opak je pravdou. Schválně si
zkuste tento skript spustit ve dvou oknech zároveň. Chyba se dostaví prakticky okamžitě.
Uvedený kód totiž není bezpečný, pokud se zároveň provádí vícekrát (tedy ve více vláknech). Což na internetu není nic neobvyklého, často se v tentýž okamžik připojuje více lidí k jednomu webu. Takže starat se o to, aby vaše aplikace fungovala spolehlivě i při provádění ve více vláknech (thread-safe), je velmi důležité. Jak totiž vidíte, nativní PHP funkce atomické nejsou. Jinak musíme počítat se ztrátou dat a vznikem těžko odhalitelných chyb.
Jak zajistit, aby se funkce file_put_contents
nebo file_get_contents
chovaly atomicky? Řešení
nabízí bezpečný protokol SafeStream, pomocí něhož můžeme atomicky manipulovat se soubory prostřednictvím standardních
PHP funkcí. Protokol se zpřístupní automaticky při načtení Nette Framework. A pak stačí jen uvést safe://
před jménem souboru:
$handle = fopen('safe://test.txt', 'x'); // před jméno souboru dáme safe://
fwrite($handle, 'Nette Framework'); // zatím se píše do pomocného souboru
fclose($handle); // a teprve teď se přejmenuje na test.txt
Lze samozřejmě používat všechny známé funkce, například:
file_put_contents('safe://test.txt', $content);
$ini = parse_ini_file('safe://autoload.ini');
SafeStream vám garantuje:
- atomicitu: soubor se zapíše buď celý, nebo vůbec
- isolaci: nikdo nezačne číst soubor, který ještě není celý zapsán
Pokud zapisujete do existujícího souboru v režimu a
(append), SafeStream vytvoří jeho kopii a
teprve po úspěšném zápisu jí přejmenuje na původní název. Zapisovat v tomto režimu má tedy vyšší režii, než
zápis v ostatních módech.