Zrádné regulární výrazy v PHP
V PHP jsou k dispozici tři knihovny pro regulární výrazy: PCRE, Oniguruma a POSIX Regex. Druhá jmenovaná nemusí být vždy k dispozici a třetí je zavržená, proto byste měli používat výhradně šikovnější a rychlejší knihovnu PCRE. Bohužel implementace trpí docela nepříjemnými nedostatky, a to ve všech verzích PHP.
Činnost jednotlivých funkcí preg_* lze rozdělit do
dvou kroků:
- kompilace regulárního výrazu
- exekuce (hledání, záměna, filtrování, …)
Sympatické je, že PHP zkompilovanou podobu regulárních výrazů udržuje v cache a tudíž se kompilují vždy jen jednou. Proto je vhodné používat statické regulární výrazy, tj. negenerovat je parametricky.
Teď k těm nepříjemným záležitostem. Pokud se během kompilace odhalí
chyba, PHP na ni upozorní chybou úrovně E_WARNING, avšak
návratová hodnota funkce je nejednotná:
preg_filter,preg_replace_callback,preg_replacevracíNULLpreg_grep,preg_match_all,preg_match,preg_splitvracíFALSE
Dobré je vědět, že funkce vracející skrze referenci pole
$matches (tj. preg_match_all a
preg_match) při kompilační chybě argument nevynulují, tudíž
testovat návratovou hodnotu má opodstatnění.
PHP od verze 5.2.0 disponuje funkcí preg_last_error vracející kód
poslední chyby. Avšak pozor, týká se to pouze chyb vzniklých během
exekuce! Pokud dojde k chybě během kompilace, hodnota
preg_last_error se nevynuluje a vrací předchozí hodnotu. Pokud
tedy návratová hodnota preg_* funkce není NULL
resp. FALSE (viz výše), rozhodně nepřihlížejte
k tomu, co preg_last_error vrací.
K jakým chybám může dojít během exekuce? Nejčastějším případem
je překročení pcre.backtrack_limit nebo nevalidní UTF-8 vstup
při použití modifikátoru u. (Poznámka: neplatné
UTF-8 v samotném regulárním výrazu se odhalí již při kompilaci.)
Nicméně způsob, jak PHP s takovou chybou naloží, je naprosto
neadekvátní:
- nevygeneruje žádnou zprávu (silent error)
- návratová hodnota funkce může naznačovat, že je vše v pořádku
- chybu lze zjistit až následným zavoláním
preg_last_error
Zastavím se u té návratové hodnoty, což je asi největší zrada.
Proces se totiž vykonává do chvíle, než se chyba objeví a poté se vrátí
částečně zpracovaný výsledek. A to v naprosté tichosti. Jenže ani
tohle neplatí vždy, třeba trojice funkcí preg_filter,
preg_replace_callback, preg_replace umí i při
exekutivních chybách vracet NULL.
Zda došlo během exekuce k chybě lze zjistit jedině voláním
preg_last_error. Ale jak už víte, tato funkce vrací nesmyslný
výsledek v případě, že došlo naopak k chybě kompilace, musíme tedy
obě situace rozlišit přihlédnutím k návratové hodnotě funkce, zda-li je
NULL resp. FALSE. A jelikož funkce vracející
NULL při chybě kompilace umí vracet NULL i při
chybě exekuce, lze konstatovat asi jen tolik, že PHP je nadevší pochybnost
zkurvený jazyk.
Jak by vypadalo korektní a bezpečné použití PCRE funkcí? Například takto:
function safeReplaceCallback($pattern, $callback, $subject)
{
// callback musíme ověřit sami
if (!is_callable($callback)) {
throw new Exception('Neplatny callback.');
}
// vynulujeme preg_last_error
preg_match('##', '');
// zavoláme PCRE
$result = preg_replace_callback($pattern, $callback, $subject);
// chyba exekuce?
if (preg_last_error()) {
throw new Exception('Chyba zpracovani regularniho vyrazu.', preg_last_error());
} elseif ($result === NULL) { // chyba kompilace?
$error = error_get_last();
throw new Exception($error['message']);
}
return $result;
}
Uvedený kód transformuje chyby do výjimek, nesnaží se však potlačit výpis varování.
Bezpečné zpracování regulárních výrazů je implementováno ve třídě Nette\String.


#1 Ondřej Mirtes http://ondrej.mirtes.cz/ nový
Jak by řekl klasik – „peklíčko!“
#2 K. http://k47.cz nový
Že by počátek \Nette\Regex?
#3 Dušan Janošík dusan@djanosik.cz nový
A teď tu, jak pejsek s kočičkou pekli dort :)
#4 PHX nový
Ale jaký jiný jazyk, ketrý je jednoduchý na nasazení do Apache, široce dostupný na hostingu a podporovaný na Linuxu? Osobne to vidím na Javu, ale zatím jsem neměl čas se do ni pořádně ponořit a v uvedených požadavcích si nevede extra dobře.
#5 Jan Tichý http://www.medio.cz nový
Díky za zajímavý článek, i když byl určitě napsán jen a pouze kvůli té google bombě
#6 Majkl578 nový
#4 PHX: Spíš než na Javu bych to viděl na Python. :)
#7 Aichi http://www.czechdesign.cz/blogs/aichi nový
#6 Majkl578: Spíš než Python nebo Javu bych viděl JavaScript
#8 IDDQD jakub.v.maly@gmail.com nový
#6 Majkl578: Sorry, ale budoucnost není v Pythonu, ani v Javě, ani v Javascriptu, natož v PHP, ale v ASP.NET a .NET Frameworku… Samozřejmě na IIS a Windows Serveru.
#9 Rychta lukas.rychtecky@gmail.com nový
#8 IDDQD: To chce celkem dost kuráže tvrdit, že budoucnost je v .NETu. Souhlasím, že to není PHP ani Python. Viděl bych to na boj mezi Javou a .NETem;o)
#10 Majkl578 nový
#8 IDDQD: Možná by bylo vhodné, kdyby sis přestal léčit komplexy. Tvé nepodložené názory nejsou vhodné. Dvanáctiletý bývalý spolužáku.
#11 makrobiotik koukam@nikde.nic nový
S těmi neléčenými komplexy má Majkl578 pravdu, ale platí to bohužel i na autora článku. Bez těch dem… by totiž nebylo ani nette ani samozvaný institut školení php a mnoho dalšího, autor by si na php nemohl postavit svou živnost atd.
Další věc je, že p. Grudl se v afektu spletl (nebo to možná vůbec neví?), že jsou v php minimálně tři extenze nad knihovnami pro regexp. Ta třetí je mbstring.
Doporučuji dietnější stravu, ty mraky indie v kombinaci s nutelou evidentně dráždí ta nepravá místa.
#12 makrobiotik koukam@nikde.nic nový
A samozřejmě php není skur.. jazyk, je to především glue language s velmi svobodným (místy trochu anarchistickým) pojetím komunitního vývoje se všemi důsledky, s tím je nutné se smířit a uvědomit si to.
#13 petr petr.match@gmail.com nový
díky, já jsem článek pochopil a pobavil se při neděli. teď ještě aby nám vyšel ten hokej!
#14 David Grudl http://davidgrudl.com nový
Jakub Vrána mě taktně upozornil, že odkaz „dementí čuráci“ vede na stránku, kde je i jeho jméno, takže jsem jej taktně odstranil. Tedy nikoliv Jakuba, ale odkaz
#15 David Grudl http://davidgrudl.com nový
#11 makrobiotik: díky za upozornění, odkaz na třetí knihovnu jsem doplnil.
(a taktéž doporučuji krotit se v projevu)
#16 Dundee http://blog.milde.cz nový
Nemá být místo pcre_* napsáno preg_*? Jinak fakt dobrý článek. Díky!
#17 David Grudl http://davidgrudl.com nový
Naja
#18 vedouci http://tomsik.cz nový
#8 IDDQD: Java je zaprve sracka (vlastni zkusenost – klidne se o tom muzem pobavit nekde jinde a podrobneji, argumenty jsou) a zadruhe ma svuj okruh zakazniku – bankovni a provozni systemy (opet z vlastni zkusenosti) a na male projekty ji nikdo nikdy nenasadi (napsat jednoduchou aplikaci v j2ee resp. jeho subsetu je nesrovnatelne narocnejsi cinnost nez v php) – k zamysleni je treba fakt, ze jedine blogy o jave, ktere bezi na jave jsou ty od sunu.
k .netu ani pythonu se vyjadrovat nebudu – neznam…
javascript nic nenahradi, to by znamenalo rhino a rhino znamena jvm a jvm znamena aplikacni server a nebo minimalne web kontejner – tzn. opet vsechny ty kovadliny z j2ee
no a php – to zustane tam, kde je – protoze je tady spousta vyvojaru, co ho umi, jsou tady hostingy za hubicku a koncoveho zakaznika nezajima, na jake platforme to bezi – zajima ho jenom, aby to bezelo, vypadalo a krasne blikalo – a na to je zatim nejlepsi php a ror
ror par lidi a projektu prebralo jak z javy, tak z php, ale dal uz to podle me nepujde, protoze vetsina killer featur byla hbite zapracovanych do php frameworku…
tot muj skromny nazor – zcela irelevantni ke clanku