Načtení neexistujícího souboru

Školení, která pořádám

Chceme načíst soubor, který nemusí existovat. To je běžné třeba u keše, kde pokud soubor neexistuje, tak data načteme z primárního zdroje. Soubor může přestat existovat znenadání, třeba když jiná část aplikace usoudí, že data už jsou zastaralá a soubor smaže. Na Twitteru jsem dal uživatelům vybrat ze dvou možností, jak tento problém řešit, v odpovědích mi přišly ještě další návrhy:

  1. if (file_exists($filename)) $file = file_get_contents($filename);
  2. if (is_readable($filename)) $file = file_get_contents($filename); // nebo is_file()
  3. if (!is_readable($filename)) throw new IOException('...'); $file = file_get_contents($filename);
  4. $file = @file_get_contents($filename);

Zadání nehovoří o tom, co má být ve $file v případě neúspěchu, takže to nechme stranou. Rozeberme si jednotlivé možnosti:

  1. Řešení je problematické v tom, že provedení dvou operací není atomické. Takže se klidně může stát, že mezi testem na existenci a načtením někdo soubor smaže, takže kód vyvolá chybu.
  2. Řešení má stejný problém jako jednička, kromě toho nám ale nenahlásí chybu, pokud soubor existuje, ale není čitelný (třeba proto, že někdo místo souboru omylem vytvořil adresář).
  3. Kromě stejného problému jako jednička toto řešení zbytečně eskaluje chybu. Neexistence souboru je stav, se kterým v zadání počítáme, není to žádná výjimečná situace. Výjimka se samozřejmě pro indikaci tohoto stavu dá použít, ale bez popisu jejího ošetření jde o nejhorší řešení, protože očekávaný vstup převede na pád aplikace.
  4. Zavináčové řešení jako jediné nemá problém s atomicitou operací, nenahlásí nám ale chybu ani v případě, že soubor existuje, ale je nečitelný (což se chceme dozvědět).

Z uvedených řešení tedy není správně žádné. Řešení, které bude bez řečí fungovat i při neexistujícím souboru, ale přitom nás upozorní na všechny ostatní chyby a zároveň nebude mít problém s atomicitou, je překvapivě složité a ošklivé. Musíme chybu zachytit, prozkoumat její text a podle toho buď poslat dál nebo zahodit. K tomu jde od PHP 5.2.0 využít funkce error_get_last, ve starších verzích to je ještě krkolomnější (pomocí set_error_handler).

<?php
$file = @file_get_contents($filename);
if ($file === false) {
    $error = error_get_last();
    if (!strpos($error["message"], "No such file or directory")) {
        trigger_error($error["message"]); // nebo klidně throw
    }
}
// v Nette se dá použít Debug::tryError() a Debug::catchError()
?>

Obvykle je ale mnohem lepší tyto chyby při čtení neřešit a ošetřit je až při zápisu.

Jakub Vrána, Řešení problému, 3.11.2010, diskuse: 71 (nové: 0)

Diskuse

Jakub Jarabica:

Error_get_last nenarusi atomicitu? T.j. jej scope je v rámci aktuálne vykonavaneho skriptu?

ikona Jakub Vrána OpenID:

Funkce error_get_last() se vztahuje k aktuálně spuštěnému skriptu, zvenku ji tedy nijak změnit nejde.

ikona Martin Malý:

Odkud bere, Jakube, error_get_last() text toho hlášení? Pokud od systému, tak se obávám (ale teď to nemohu vyzkoušet), že české verze Windows vrací text "Neexistující soubor nebo adresář". Navíc hezky s diakritikou... Ale jak říkám - nejsem si jist, jen mám podezření, a jestli můžeš, tak vyzkoušej.

Navíc připomenu, že hodnota té funkce není resetovatelná a zůstává tam stále to, co v ní jednou bylo, takže při jejím použití je třeba s tímhle počítat (tedy ověřit si, že opravdu došlo k chybě - což tam máš).

Michal Prynych:

Na windows dostávám stejnou hlášku jako v Ubuntu: [message] => file_get_contents(testjjeee): failed to open stream: No such file or directory

ikona Jakub Vrána OpenID:

Dobrý postřeh! Chybová hláška se bere ze systémové funkce strerror(): http://svn.php.net/viewvc/php/php-src/tags/…=markup#l168

Jak moc se na ní dá spolehnout, nevím, ale na českých Windows (které jsou asi nejrizikovější) vrací anglický text.

ikona Jakub Vrána OpenID:

Ono by úplně stačilo, když by v PHP byla nějaká funkce vracející systémové strerror(). Bohužel ale taková neexistuje.

Pavel Campr:

abychom se vyhnuli jakemukoliv testovani textu z OS, ktery nevime, jaky bude, co treba toto? (netestovano, jen nastin reseni)

<?php
function hash($data) {
  return md5(serialize($data));
}

$previous_error = error_get_last();
$file = @file_get_contents($filename);
if (
$file === false) {
    $new_error = error_get_last();
    // zmenila se chyba v $new_error oproti predchozi $previous_error ?
    if (hash($previous_error)!=hash($new_error)) {
        trigger_error($error["message"]);
    }
}

ikona Jakub Vrána OpenID:

Takhle by to nešlo. Chyba se změní vždy už jen proto, že k ní došlo na jiném řádku. Navíc předchozí chyba mohla být úplně jiná nebo taky žádná.

Muselo by se to udělat tak, že bychom otevřeli soubor, o kterém víme, že neexistuje, poznamenali si část chybové hlášky (část proto, že hláška obsahuje i název souboru) a tu pak porovnali s tím, co jsme dostali. Způsobilo by to ale zbytečné zpomalení.

Funkce hash() je úplně zbytečná, protože v PHP lze porovnávat i pole, navíc koliduje s vestavěnou funkcí http://php.net/function.hash.

ikona David Grudl:

U některých funkcí, např. rename, to skutečně píše české hlášky (Systém nemůže nalézt uvedený soubor), u file_get_contents() vidím anglickou - ale klid do duše to rozhodně nevnáší ;-)

error_get_last() je jak píšeš problematická funkce - je závislá na nastaveném error handleru (via set_error_handler) a chybí jistota, že ji posledně volaná funkce skutečně nastavila na novou hodnotu. Teoreticky ji lze resetovat přes trigger_error('');

ikona Jakub Vrána OpenID:

O nutnosti za určitých okolností resetovat chybu pomocí <?php trigger_error(''); ?> vím, ale v tomto konkrétním případě by to nemělo být potřeba.

Ohledně set_error_handler() to je dobrá připomínka. Doplním, že pokud registrovaná funkce nevrátí false, tak zůstane error_get_last() původní. Takže kód by chtěl předělat na set_error_handler() i pro novější verze PHP.

Jiri Malek:

Takze jsem byl jediny, kdo atomicitu zminil? Musim se pochvalit.. :)

Andrew:

Zrovna jsem řešil podobný problém, ale měl jsem to ještě o poznání složitější, protože jsem používal vlastní streamwrapper, který sám o sobě prováděl neatomické operace. Tam pak zavináč nepomůže.
Zatím jsem úspěšně nedořešil. :-(

Radek Salac:

a co neco takoveho?
<?php
$file
= @file_get_contents($filename);
if (
$file === false) {
    if (!is_readable($filename)) {
        trigger_error($error["message"]); // nebo klidně throw
    }
}
?>
A nemusim spolehat na zadny text.

ikona Jakub Vrána OpenID:

Když soubor neexistuje, tak není ani čitelný, tohle by tedy vyvolalo chybu skoro vždy. Pokud bychom to změnili na <?php if (file_exists($filename) && !is_readable($filename)) ?>, tak to má zase problém s atomicitou, ale ne tak závažný jako jiná řešení.

Martin:

Spoléhání se na text chyby je neuvěřitelný nesmysl, skoro bych až řekl, že to nemohl ani navrhnout Jakub. Co když se text změní třeba s novou verzí prostředí? To bude autor aplikace pořád hlídat, zda je toto chybové hlášení v každé verzi prostředí stejný? To snad ne...

ikona Jakub Vrána OpenID:

Pokud máš lepší řešení, tak sem s ním! V článku je uvedeno, že je řešení ošklivé, jiné ale bohužel neznám.

Martin:

Tady nejde o to, že neznám lepší řešení, ale o to, že je snad nejhloupější možné. Jakube promiň, ale to jsi snad ani nemohl myslet vážně...

ikona Jakub Vrána OpenID:

Řešení má potenciální slabinu, ale lepší neznáme. Takže ho použijeme i přes tuto slabinu nebo se na všechno vykašleme a půjdeme péct koláče?

Martin:

Jenomže to řešení prostě může KDYKOLIV přestat fungovat! Je úplně stejně nebo i více nespolehlivé, než ty další uvedené.

ikona Jakub Vrána OpenID:

Omyl. Řešení je na rozdíl od ostatních řešení stoprocentně spolehlivé za daných podmínek, není ale robustní v tom smyslu, že může přestat fungovat při změně těchto podmínek.

Navíc i když řešení přestane fungovat, tak tím lepším způsobem – začne nás upozorňovat i na chyby, které chybami nejsou, takže můžeme snadno zjednat nápravu.

ikona v6ak:

Proč péct koláče? Radši vařit kávu ;-)

http://download.oracle.com/javase/6/docs/…/IOException.html
http://download.oracle.com/javase/6/docs/…/FileNotFoundException.html

ikona David Grudl:

Souhlasím, že je to "nejhloupější možné", ale na PHP je signalizace chyb taková katastrofa a bída, že nic lepšího tam nejspíš napsat nejde. A to je stále slabý čajíček proti ověření, zda funkce preg_replace_callback skončila chybou nebo ne...

Martin:

BTW pro zhodnocení smysluplnosti tohoto článku doporučuji provést pár testů na časové rozpětí mezi příkazem file_exists a načtením samotného souboru. To je snad i časově nezměřitelné. Neboli jak velká je šance, že dojde k odstranění souboru mezi zjištěním jeho existence a načtením? 1:10000000000?

ikona Jakub Vrána OpenID:

Tenhle alibismus nesnáším, odpovím tedy jedním konkrétním naměřeným číslem: šance je 13 %.

Záleží ale samozřejmě na konkrétních podmínkách, především na počtu zároveň prováděných požadavků. Pokud na aplikaci přijde deset požadavků za den, tak je šance samozřejmě skoro nulová. Ale pokud je aplikace vytíženější, tak se šance radikálně zvyšuje.

Vytvořil jsem primitivní skript:

<?php
touch
("a");
if (
file_exists("a")) {
    if (file_get_contents("a") === false) {
        mysql_query("UPDATE test.conflicts SET number = number + 1");
    }
}
unlink("a");
?>

A spustil ho pomocí `ab -n 100 -c 10` (ab je součást Apache). Ze sto požadavků (z nichž se paralelně provádělo vždy maximálně deset) došlo ke 13 konfliktům.

Martin:

Až na to, že těch 13 % platí pouze pro případ, že se ten soubor neustále vytváří a maže. Což se nikdy nestává. Ještě tam připočítej pravděpodobnost smazání souboru v okamžiku průběhu scriptu. U většiny webových aplikací se takto soubory obměňují řádově jednou za měsíc (napr. samotné scripty) až jednou za několik minut (cache). A z tvých 13 % je reálná nula nula nic...

ikona Jakub Vrána OpenID:

A přesně kvůli tomuto způsobu uvažování vznikají mysteriózní problémy, které se vyskytují jen jednou za čas a nikdy je nikdo nedokáže reprodukovat.

Václav Novotný:

Přesně tak. U valné většiny těžko odhalitelných chyb je na začátku věta: "K tomu nikdy nedojde."
Je třeba si dávat na tyhle "nikdy" velký pozor :)

JakubS:

Doufám že podobně neuvažují i konstruktéři mostů :-)

Martin:

Je rozdíl mezi spadnutím mostu a zobrazením jedné "blbé" webové stránky ;-)

Václav Novotný:

Pokud děláš blbé stránky, tak tě to asi trápit nemusí. Nicméně ne každý děla pouze blbé stránky :)

Martin:

Myslíš, že např. tvůrci Facebooku nebo webu Microsoftu řeší nezobrazení stránky jednomu uživateli z x milionů jednou za x let? :-)

ikona Jakub Vrána OpenID:

To je alibismus na druhou. Právě tvůrci velkých aplikací tohle musí řešit bezpodmínečně. Protože čím je provoz vyšší, tím je větší šance, že problém tohoto typu nastane.

ikona Ladislav Prskavec:

Ja jsem si to pustil na linuxu s ab -n 1000 -c 50 a pravdepodobnost konfliktu mi vysla 0.7% coz neni tak moc. Ale Jakub ma pravdu, ze je to problem.

Je pravda, ze se tomu da zabranit vhodnou architekturou aplikace, ale @ se me moc nelibi a celou aplikaci dost zpomaluji.

ikona Jakub Vrána OpenID:

Na Windows to s těmito parametry způsobilo kolizi ve 42.9 % případů.

Co se té pomalosti týče, tak <?php if (file_exists("a")) file_get_contents("a"); ?> mi trvá 68 µs, <?php @file_get_contents("a"); ?> 60 µs (pokud soubor existuje). Zkus to ty.

ikona Ladislav Prskavec:

U me to je opacne u file_exists 6E-5 a u @ 8E-5, ale u toho prvniho zpusobu. Samozrejme je dulezite zda ten soubor existuje nebo neni. Pokud se zprumeruje tak mi to pri 1000 opakovani vyhazi v prvnim pripadne o neco rychlejsi pokud soubor neexistuje (nevykona se file_get_contents)).
Pokud existuje je to skoro stejne i kdyz mi vyslo ze verze se @ je o neco rychlejsi 8E-5 vs 9E-5.

Stanislav:

Že k tomu neodjte? Proto třeba na http://forum.zdrojak.root.cz/index.php?topic=234.0 se ptám, jak to vyřešit. V error logu mi to uvízne cca 6x denně.
Hned mě napadlo, že snad Jakub četl můj dotaz...

Martin:

A o tom to přesně je. Tahle kolize vznikne třeba jednou za rok, možná jednou za deset let. Tzn. např. každému miliontému uživateli se jednou za x let zobrazí nekorektně naše stránka. Což je naprosto bezvýznamné s ohledem na další okolnosti (pády a údržby serveru, atdatd.). Nic víc jsem tím nechtěl říct.

ikona Jakub Vrána OpenID:

Ale ono zase záleží na kontextu. Pokud si ze souboru načtu nějaká data, nějak je upravím a zase zapíšu, a nepočítám při tom s neatomicitou, tak se mi může stát, že jednou za čas o ta data prostě přijdu.

Martin:

To máš pravdu. Ale o ta data přijdeš v tomhle případě prostě vždy. Tomu nezabrání žádný ze zde uvedených scriptů.

ikona Jakub Vrána OpenID:

Jistě, je to jiná situace, tenhle článek zápis vůbec neřeší. Chtěl jsem jen ukázat, že tento způsob uvažování způsobuje velké nepříjemnosti.

Denis:

Osobně bych se snažil vyvarovat testování na nějaký řetězec, tak, jak to uvádí pan Vrána. Pokud tento řetězec nelze získat z nějaké systémové funkce samozřejmě, potom by to bylo OK.

Takže situaci výše bych řešil obráceně a to takto:

1. Načti soubor pomoci <?php file_get_contents() ?>
2. Pokud se nepodařil načíst, pak teprve zjisti proč. <?php file_exists() ?> atd.

Ano, samozřejmě soubor může "někdo" mezitím vytvořit, ale tady se přikláním k Martinovi a myslím si, že tato skutečnost by nemusela nikomu vadit vzhledem k pravděpodobnosti.

Nejhorší případ co může nastat je "Soubor se nepodařilo načís protože soubor existuje a je čitelný" :)

Toto řešení bych volil ale jen proto, protože subjektivně mi přijde více "v pohodě" než testovat ten řetězec.

ikona Jakub Vrána OpenID:

Tento návrh tady už padl: http://php.vrana.cz/nacteni-neexistujiciho-souboru.php#d-10949

Bohužel má slabinu v tom, že může produkovat ne-chyby, které nejdou nijak opravit, čehož výsledkem je to, že chybový log přestane být kritický a začne být spíše otravný.

Já si třeba všechny chyby nechávám posílat mailem a když by docházelo k takovýmto ne-chybám, tak by mě to asi přestalo bavit.

Denis:

Mohl byste mi, prosím, uvést nějakou ne-chybu, která by takto vznikla a nešla by opravit? Asi tomu přestávám rozumět :)

ikona Jakub Vrána OpenID:

Sám o ní mluvíte: „Soubor se nepodařilo načíst, protože soubor existuje a je čitelný“. Pokud by mi takovéto chyby chodily do mailu, asi bych je začal brzy ignorovat, protože s nimi nemohu nic udělat.

Denis:

Prosím o trochu nadhledu a méně slovíčkaření :)
Nikdo Vám přeci nebrání nechávat si zasílat informace z <?php error_get_last() ?> .

Nehledě na to, že pokud by soubor někdo z nenadání vytvořil (problém neatomicity, ale o vytváření souboru není v zadání ani zmíňka), pak dostanete do LOGu chybu "No such file or directory", kterou v logu sice nechcete, ale to bych mohl říct jako argument na Váš výrok výše: "Navíc i když řešení přestane fungovat, tak tím lepším způsobem – začne nás upozorňovat i na chyby, které chybami nejsou, takže můžeme snadno zjednat nápravu."

Mi jde jen o to porovnávání řetězce. Upřímně, kdybych měl distribuovat svou aplikaci mnoha klientům, potom bych měl z toho porovnáváni žetězce obavu, že jakmile se změní, nastanou potíže.

Hodně také záleží na kontextu v jakém by tato řešení měla být nasazena, jelikož pokud se bude jednat o přetížený server, kde se bude soubor vztekle vytvářet a mazat, potom Bude na nic jakékoliv řešení zde uvedené, jelikož LOGy budou přetékat neřešitelnými problémy, protože než se na server dostaneme, bude situace úplně jiná :D

ikona Jakub Vrána OpenID:

Žádného slovíčkaření si nejsem vědom.

Já chybový log udržuji prázdný, všechno z něj si nechávám posílat e-mailem a co nejdříve to řeším (protože jinak mi chodí další a další maily). Takže jakákoliv ne-chyba (false negative) je pro mě problém.

Rozdíl proti mému řešení je zcela zásadní. U něj když začnou chodit zprávy o ne-chybách, tak je to z důvodu změny systémové hlášky. A to se vyřeší jednoduše – prostě se nový řetězec přidá do porovnání. U vašeho řešení s tím neudělám nic.

S posledním odstavcem se nedá souhlasit. V článku představené řešení se vyznačuje právě tím, že žádné problémy na daném systému při jakémkoliv druhu provozu nezpůsobuje. K žádnému přetékání logů tedy rozhodně nedojde.

Martin Hruška:

Testování nějaké chyby OS na nějaký řetězec je naprostý nesmysl a do aplikace bych si to rozhodně nezatahoval. Ne každá aplikace totiž jede v jediné instanci na vašem stroji. V okamžiku kdy máte desetitisíce instalací u zákazníků na různých strojích, pak se můžete dostat do řádného průseru.

Základním problémem rozhodně není neatomicita operací, ale obyčejný souběžný přístup ke sdílenému prostředku. Takových situací každý opravdový programátor řeší desítky denně. A místo uchylování se k nějakým hackům na to existují ověřená a prokazatelně funkční řešení.

Bohužel bezradnost některých programátorů je způsobena tím, že jsou to patlalové v PHP a nic jiného nikdy neviděli. Natož nějaké formální vzdělání v oboru.

Takže všem zoufalcům z diskuse doporučuji nejdřív načíst nějakou literaturu o "concurrent programming" a pak se sem vrátit. Děkuji za pozornost.

ikona Jakub Vrána OpenID:

A já bych chtěl místo bezobsažných blábolů vidět nějaké konkrétní řešení v PHP!

ikona Miloslav Ponkrác:

Ten člověk nad Vámi má pravdu.

Mimochodem, já kešuji na stránkách hodně, a vím, co v keši mám, aniž bych to musel ex post zjišťovat tím, jestli existuje soubor.

Základní problém je programovat čistě – tedy žádné parsování chybové zprávy, pokud není 100%ní záruka, že bse nezmění. Takové záruky jsou třeba u formátů příkazů unixových utilit. Ale v PHP taková záruka není. Tudíž nepoužívat.

Základem programování není „chci a chci a chci to takhle“, ale vědět, co PHP umí a používat to tak, abych dosáhl to co chci.

Situací, kdy skutečně potřebujete atomicky zjišťovat a načíst soubor mnoho není, a v praxi se řeší tak, že si aplikace vytváří někde soukromě své vlastní soubory a ví o nich. Stejně tak jako nevytváříte PHP scripty tak, že Vám je systém kdykoli může smazat, ale logicky předpokládáte, že do určitých souborů php zasahujete jenom Vy. Pak nepotřebuji nic atomického.

Zároveň, ale zde jsem na slabé půdě, takže nemusím mít pravdu, jsem nenašel v manuálu záruku, že file_get_contents() je atomická. Pouze, že se převádí na operaci s paměťově mapovanými soubory.

ikona Jakub Vrána OpenID:

Všechno se to moc hezky poslouchá, ale já bych raději chtěl vidět nějaké konkrétní řešení. Formalizuji zadání:

Ze souboru si chci načítat keš nějakých dat. Ze souboru proto, že je to rychlé. Tento soubor nemusí existovat, pokud jsem ještě nestáhl aktuální data. Zároveň může soubor jiný proces smazat v případě, že by v něm byla zastaralá data (nebo třeba když je málo místa na disku). Smazat proto, aby se mi soubory na disku zbytečně nehromadily. Data nechci při zastarání rovnou stáhnout znovu, protože dost možná nebudou potřeba a stahovaly by se tak zbytečně. Pokud soubor neexistuje, tak to nemá být nahlášeno jako chyba, je to běžný stav aplikace. Pokud dojde k jiné chybě (např. nějaký blbec místo souboru vytvoří adresář stejného jména), tak se o této chybě chci dozvědět stejně jako o jakékoliv jiné chybě v aplikaci.

Teď jsem dost podrobně popsal, co chci udělat, i důvody, proč to takto chci udělat. A rád bych viděl nějaké konkrétní řešení (kód!).

Denis:

Zaprvé, aby někdo vytvořil adresář stejného jména jako je cachovycí soubor, je nepřípustné. Pokud připustíme toto, pak můžeme rovnou polemizovat nad tím, co když nám někdo upraví PHP scripty.

Prostě je nějaký adresář a v něm jsou cache soubory. Klidně zahešované :)

Potom není třeba tak úzkostlivě testovat, k jaké chybě došlo při načítání souboru z cache, ale podstatnější je proč se příslušný cache soubor nepodařilo vytvořit nebo proč se do cache souboru nepodařilo zapsat. Tzn. přesunout testování do části vytváření cache souborů.

Část, která cache soubory "jen" načítá by v podstatě mohla být "tupá". Podařilo se mi načíst nebo nepodařilo? Pokud ne, jede aplikace dál, jelikož existence tohoto cache souboru není pro chod aplikace kritická. Problém ale je, pokud tyto cache soubory nelze z nějakého důvodu ani vytvořit/upravit.

Ještě k tomu porovnáváni řetězce: Pokud budete mít cache vytíženou a dojde ke změně tohoto řetězce, tak se doslova "vytriggerujete" :D

ikona Jakub Vrána OpenID:

Přesun kontroly chyby do zápisu je dobrý nápad. Úplně to tedy stačí takhle:

<?php
$file
= @file_get_contents("safe://$filename"); // Nette\SafeStream
if ($file === false) {
    $file = "..."; // zde bude získání dat z primárního zdroje
    file_put_contents("safe://$filename", $file);
}
?>

Doplnil jsem to do článku.

ikona Jakub Vrána OpenID:

Jen ještě doplním, že tohle řešení nemusí jít použít vždy. Někdy prostě můžu chtít pro neexistující soubor vrátit 404 (a nechci se o tom dozvědět) a v případě jiného problému 500 (a chci se o tom dozvědět).

A co se toho vytriggerování týče, tak před nasazením aplikace (nebo změnou systému) bych ji samozřejmě měl otestovat, takže tuhle chybu bych měl dostat jen jednou.

Martin Hruška:

Pořád žijete v utkvělé představě, že vaše aplikace běží jen v jediné verzi na jediném systému. To nemusí být pravda. Můžete mít desetitisíce instalací na různých systémech, od Windows po Mac. Můžete i potom garantovat stoprocentní funkčnost? Budete to testovat při instalaci? Nepokazí se to upgradem systému?

Mě to je nakonec úplně jedno, jak si to děláte. Ale vadí mi, že vaše řešení na tomto blogu předkládáte čtenářům jako jediná pravá a pletete tak začátečníkům hlavu. Místo aby se dozvěděli, že parsování textového hlášení (určeného pro lidi) je zásadní pitomost, tak to začnou používat, protože jejich guru to tak doporučil.

Nebudete tomu věřít, ale už jsem se v praxi setkal s opravdu "originálními řešeními", která byla založena na vašich radách. A nebyl to hezký pohled.

ikona Jakub Vrána OpenID:

Ano i v takovém případě lze garantovat stoprocentní funkčnost. Může to generovat zbytečné chybové hlášky, ale alespoň to bude fungovat. Parsování chybové hlášky považuji samozřejmě za ošklivé a v článku to je uvedeno, ale lepší řešení bohužel neznám. Vy ostatně taky ne, protože na mou výzvu o dodání kódu jste nijak nereagoval.

Martin Hruška:

Jak vám můžu dodat kód, když netuším, co tím svým chcete říct. V diskusi se sice snažíte o jakousi specifikaci zadání, ale vašemu kódu to rozhodně neodpovídá.

Víte například, co file_get_contents vrátí, když "nějaký blbec místo souboru vytvoří adresář stejného jména"? Vy si asi myslíte, že false. Kdepak, vrátí string o nulové délce. Takže na vaše zpracování chyby vůbec nedojde. A co se stane, když v $error['message'] nebude "failed to open stream: No such file or directory", ale jen "No such file or directory"? Test strpos vrátí výskyt na pozici 0 a dojde na trigger_error, protože netestujete na false pomocí === Opravdu jste to tak chtěl, anebo je to klasická začátečnická chyba?

Teď si představte, že stejným stylem napíšete ne 10, ale 10 000 řádků!

ikona Jakub Vrána OpenID:

Na Windows vrátí file_get_contents() pro adresář false, na Linuxu skutečně prázdný řetězec. To jsem netušil a je to moje chyba.

Ale s $error['message'] jste zase úplně vedle vy. Když byste se podíval do zdrojáků, tak byste snadno zjistil, že "failed to open stream" přidává PHP a před tím je ještě název funkce. Takže to vnější prostředí nemůže nijak ovlivnit.

Přestaňte se prosím vymlouvat na můj kód, který se vám stejně nelíbí, a předveďte nám svůj vlastní. Chci prostě vidět kód, další řeči jsou nezajímavé.

Denis:

Jen doplním, že odvolávat se na zdrojáky v tomto případě není zcela oprávněné, jelikož zdrojáky nejsou důležité. Důležitá je dokumentace výrobce, kterou by měl garantovat.

Pokud je v dokumentaci, že v klíči "message" pole vráceného funkcí error_get_last() je nějaká textová podoba chybové hlášky, pak toto je jediné, na co lze spoléhat.

Výrobci (maníkům, co dělají PHP) tak nebrání nic v tom změnit tuto hlášku kdykoliv aniž by porušili informace z dokumentace. Takže dokumentace je základ. Zkoumání zdrojových kódů je jen dodatečné rozšíření si obzorů a nahlédnutí pod pokličku (někdy bohužel nezbytné).

ikona Jakub Vrána OpenID:

Máte úplnou pravdu: http://svn.php.net/viewvc?view=revision&revision=305079

ikona v6ak:

Nemusí jít jen o adresář, může mít třeba špatná práva, k čemuž při nasazení dojít prostě může. A pak se bude člověk divit, proč je to pomalé...

Denis:

Ano a i přesto jsem přesvědčen, že o toto všechno by se měla starat v případě implementace cache ta část, která soubor vytváří a připravuje ho.

ikona v6ak:

... a při čtení budeme předpokládat, že je to bezchybné...

Denis:

Při čtení nebudeme nic předpokládat, při čtení to budeme vědět, jelikož o bezchybnost se stará ten, kdo soubor vytváří.

Dále nic nebrání třídě, která se o cache soubory stará, aby sem tam zkontrolovala, zda jsou soubory čitelné. Záleží na vytíženosti cache a na frekvenci potřeby obnovovat cache soubory (nebo jakkoli s nimi manipulovat). Tedy opět: záleží na konkrétních požadavcích při implementaci.

ikona Pekelník:

Skutečně mě udivuje jaký flejm dokáže takováhle prkotina způsobit :)

Samozřejmě za daných podmínek je nejsprávnější soubor načíst a pokud se to nepodaří zjistit proč.

Jakékoliv zjišťování předem nejen, že brzdí aplikaci, ale je navíc k ničemu.

jos:

chci přečíst soubor omg.wtf ?

file_get_contents(/cesta/k/omg.wtf)

chci vyrobit soubor /cesta/k/omg.wtf ?

vyrobim /tmp/omg.wtf.nejaky.guid

mv -f /tmp/omg.wtf.nejaky.guid /cesta/k/omg,wtf

a pokud tohle nejde u někoho dobastlit na widlích (unixutils, mingw, cygwin), tak dobře mu tak

cucací potřeby:

pro první část vašeho příspěvku viz ad 4) v článku.

jos:

no buď mi file_get_contents nic nevrátilo a pak zjišťuju proč a co s tim a nebo sem v pohodě

cucací potřeby:

Můžu se zeptat:
1. co všechno za chyby může vrátit funkce file_get_contents()? (neexistující soubor a pokus o přístup k souboru pouze bez práv pro jeho čtení už máme vyjmenované…)
2. jak by se řešil případ, kdy kešovaný soubor může být větší než paměť vyhrazená pro skript?

ikona Jakub Vrána OpenID:

1. Těch chyb je celá řada, viz (odkazy časem zastarají):
http://lxr.php.net/opengrok/xref/PHP_5_3/…/streams.c#1812
http://lxr.php.net/opengrok/xref/PHP_5_3/…/file.c#522
Nevím, co všechno navíc může vracet OS.

2. Asi podobně – @fopen(). Ale příliš se mi nad tím hloubat nechce.

luki:

a co tak to prepojit s db?

chcem nieco robit so suborom.. pripojim do db
nastavim read/write lock na konkretny riadok
porobim si co chcem so suborom
updatnem tabulku aby aj ostatni vedeli co som snim porobil
uvolnim lock

v takomto pripade by bol kde problem ?

msx:

Ja neviem, či autor sleduje aj také staré články, ale v kontrole až po zápise vidím chybu. Čo ak niekto vytvorí súbor v čase medzi jeho načítaním (ktoré neprejde) a jeho zápisom? A ešte tam budú práva, ktoré neumožnia súbor vymazať pri zápise? Keď atomicita, tak by sa malo brať do úvahy aj toto, či to už preháňam?

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.