Weblog o elegantním programování v PHP pro mírně pokročilé
Prošvihl jsem pravidelný měsíční interval pro vydávání nových verzí Adminera, takže se změn nakupilo trochu více.
Výchozí limit platnosti session proměnných v PHP je 24 minut. Vzhledem k tomu, že Adminer informace o přihlášení ukládá právě sem, tak po této době dochází k automatickému odhlášení. Adminer s tím nemůže dohromady nic dělat, protože by bylo potřeba změnit buď globální hodnotu direktivy session.gc_maxlifetime nebo lokální hodnotu session.save_path, Adminer ale nemusí mít nikam právo zápisu. Nová verze proto přihlašovací jméno a heslo ukládá volitelně do cookie.
Kvůli bezpečnosti je ovšem neukládá v otevřeném textu, ale šifrovaně. K tomu se nakonec používá šifra XXTEA a ne mnou popsaný způsob. Aby šifrování k něčemu bylo, tak nesmí být heslo společné, ale musí být jedinečné pro každou verzi Adminera. Toho je dosaženo jednoduchým přizpůsobením – stačí definovat metodu permanentLogin vracející libovolný řetězec. Ukázkový kód vrací každému uživateli jiný řetězec, takže ho lze přímo použít, heslo si nicméně samozřejmě můžete zvolit i vlastní.
Chápu, že definice rozšíření může být nepohodlná a povede k tomu, že většina uživatelů bude o trvalé přihlášení ochuzena, jiný bezpečný způsob uložení přihlašovacích údajů ale neznám.
Při správě cizích databází se může hodit nová funkce, která dovoluje vyhledat text v libovolné tabulce. Ta je dostupná v Admineru i v Adminer Editoru.
Tabulky se prohledávají dotazem SELECT 1 FROM tab WHERE field1 LIKE '%$query%' OR field2 LIKE '%$query%' LIMIT 1, který by měl být pro tuto úlohu nejrychlejší. V Admineru lze zvolit tabulky, které se mají prohledávat (takže se dají přeskočit obrovské tabulky, o kterých víme, že tam hledaný řetězec nebude, a jen by zdržovaly).
Vedle proměnných MySQL, které Adminer už nějakou dobu zobrazuje, se nově zobrazují i stavové proměnné (SHOW STATUS). Proti ručnímu zavolání příkazu to má výhodu, že jsou z názvů proměnných vytvořeny odkazy do dokumentace.
Při optimalizaci databáze je velmi užitečné vědět, kolik je v databázi celkem dat a jak jsou velké indexy. Adminer proto nově zobrazuje součty velikostí v přehledu tabulek. Za zmínku stojí součet sloupce Volné místo, který zobrazuje pouze místo, které lze uvolnit příkazem OPTIMIZE TABLE. Tabulky typu InnoDB totiž volné místo mohou sdílet, každopádně ho neuvolňují.
Ve verzi 2.0.0 došlo k odstranění tlačítka Smazat z detailu záznamu, protože záznam lze smazat z výpisu dat. Protože si ale odkaz na detail záznamu posílám e-mailem, tak mi vadilo, že z této stránky nejde přímo smazat, tak jsem tlačítko zase vrátil. Stejně tak možná vrátím tlačítka pro odstranění tabulky a databáze k jejich detailu (když je zakázaný výpis seznamu databází, tak se k jejímu smazání teď nedá doklikat).
Několik uživatelů volalo po tom, aby v SQL příkazu bylo možné zobrazit jen příkazy, které skončily chybou. Místo toho se nyní nově pod více než jedním SQL příkazem zobrazuje odkaz na ty chybové. Jedním pohledem tak lze zkontrolovat, zda všechny příkazy uspěly a není nutné stránku prohledávat. Výhoda oproti skrytí bezchybných dotazů je v tom, že někdy je u chybného potřeba znát kontext, v jakém byl příkaz proveden.
Nevím jak vás, ale mě hrozně štve hláška You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use. Chvíli jsem váhal, pak jsem ji ale nahradil za prosté Syntax error.
U příkazů modifikujících záznamy vrací MySQL počet záznamů, které byly změněny. Někdy se ale hodí vědět i to, kolik záznamů strefila podmínka. To Adminer nově zobrazuje v bublině výsledkové řádky.
Přemýšlel jsem také o možnosti zobrazovat varování, která příkaz vyvolal, nenašel jsem pro to ale vhodné umístění, tak jsem to dal k ledu.
Když se v editaci struktury tabulky mění typ, tak obvykle nedává smysl zachovat délku. Adminer ji proto nově zachovává jen při zachování typu *char/*binary a enum/set.
V exportu se někdy může hodit označit víc tabulek, ale přitom ne všechny. Platí to hlavně v situaci, kdy máme v jedné databázi spatlané tabulky více projektů. David Grudl navrhoval změnit zaškrtávátka u tabulek na <select multiple>, ten ale uživatelé neumí moc ovládat, navíc se komplikuje tím, že Adminer dovoluje zvlášť označit export struktury a dat. Nakonec jsem proto doplnil detekci prefixů tabulek (oddělených podtržítkem) a z těch, které se ve výpisu opakují, jsem pod seznam tabulek umístil odkazy.
Kromě funkčních změn bylo opraveno také několik chyb, hlavně nefunkčnost na některých verzích IIS.
O fulltextovém vyhledávání v MySQL jsem už psal, dokonce i o jeho základním nastavení. Pro možnost hledat krátká slova je vhodné nastavit ft_min_word_len (minimální délka indexovaného slova) nejvíc na trojku.
Pro hledání v českých textech je vhodné nastavit také ft_stopword_file (neindexovaná slova). Výchozí seznam totiž obsahuje i slova, která v češtině mají význam (např. let nebo most) a naopak neobsahuje česká bezvýznamová slova, která se indexují zbytečně a tím zdržují. Z předložek a spojek jsem sestavil seznam pro češtinu, odstranil jsem slova, která význam mít mohou (např. bez nebo místo), a nabízím ho ke stažení (soubor musí být v kódování character_set_server). Někomu by se mohl hodit také zabudovaný seznam anglických slov – pro prohledávání českých i anglických textů lze tyto seznamy spojit a naopak z nich odstranit slova, která v druhém jazyce mají význam.
Při vyhledávání mi vadí, že se najdou všechny texty obsahující libovolné slovo oddělené mezerou. Vyhledávače se přitom obvykle chovají tak, že hledají jen texty se všemi slovy. Kupodivu i toto se dá změnit pomocí strašidelného nastavení ft_boolean_syntax. Když se nastaví na ' |-><()~*:""&^' (začíná mezerou), tak se budou hledat texty se všemi slovy oddělenými mezerou a pouze slova s operátorem | budou volitelná. Volba se dokonce dá nastavit i při běhu pomocí příkazu SET GLOBAL, k tomu je ale potřeba oprávnění SUPER.
Pro praktické využití chybí jen schopnost skloňování. To se dá částečně řešit přes pravostranné rozšíření (operátor hvězdička), to ale samozřejmě není dokonalé. Rychlejší vyhledávání s podporou českého skloňování nabízí Sphinx Search, s jeho zprovozněním je ale o něco víc práce.
Přijďte si o tomto tématu popovídat na školení o návrhu a používání MySQL databáze.
Řekněme, že na stránce chceme zobrazovat deset článků s největším počtem diskusních příspěvků. K tomu můžeme použít zhruba takovýto dotaz:
SELECT clanky.nadpis, COUNT(diskuse.id) AS pocet_komentaru FROM clanky LEFT JOIN diskuse ON clanky.id = diskuse.clanek GROUP BY clanky.id ORDER BY pocet_komentaru DESC LIMIT 10
Problém s tímto dotazem je v tom, že se pro setřídění nemůže použít index, což je obzvlášť bolestné kvůli klauzuli LIMIT – při existenci indexu by stačilo prozkoumat 10 záznamů, bez indexu je potřeba prozkoumat všechny. Takže když článků bude přibývat, dotaz bude pomalejší a pomalejší. Jak z toho ven?
MySQL nedovoluje vytvářet materializované pohledy, takže řešením může být si sloupec do tabulky přidat ručně a vytvořit nad ním index (třeba v komentáři je vhodné naznačit, že jde o dopočítávaný sloupec). Naplnění tohoto sloupce můžeme zajistit dotazem UPDATE clanky SET pocet_komentaru = (SELECT COUNT(*) FROM diskuse WHERE clanek = clanky.id). Pokud si chceme být jisti jeho konzistencí, tak musíme nadefinovat celkem 5 triggerů:
CREATE TRIGGER `diskuse_ai` AFTER INSERT ON `diskuse` FOR EACH ROW UPDATE clanky SET pocet_komentaru = pocet_komentaru + 1 WHERE id = NEW.clanek; CREATE TRIGGER `diskuse_ad` AFTER DELETE ON `diskuse` FOR EACH ROW UPDATE clanky SET pocet_komentaru = pocet_komentaru - 1 WHERE id = OLD.clanek; -- pro případ, že se změní diskuse.clanek CREATE TRIGGER `diskuse_au` AFTER UPDATE ON `diskuse` FOR EACH ROW UPDATE clanky SET pocet_komentaru = pocet_komentaru + IF(id = NEW.clanek, 1, -1) WHERE id IN (OLD.clanek, NEW.clanek) AND OLD.clanek != NEW.clanek; CREATE TRIGGER `clanky_bu` BEFORE UPDATE ON `clanky` FOR EACH ROW SET NEW.pocet_komentaru = IF(NEW.id = OLD.id, OLD.pocet_komentaru, (SELECT COUNT(*) FROM diskuse WHERE clanek = NEW.id)) -- není potřeba, pokud je dodržena referenční integrita CREATE TRIGGER `clanky_bi` BEFORE INSERT ON `clanky` FOR EACH ROW SET NEW.pocet_komentaru = (SELECT COUNT(*) FROM diskuse WHERE clanek = NEW.id);
Dotaz se pak zjednoduší na prosté SELECT nadpis, pocet_komentaru FROM clanky ORDER BY pocet_komentaru DESC LIMIT 10.
V MySQL je potřeba dát pozor na to, že triggery se nespustí při kaskádové aktualizaci záznamů cizím klíčem.
Jde o poměrně pracnou techniku zlepšení efektivity dotazů, takže bychom ji měli používat jenom tam, kde měření výkonnosti ukáže, že je co zlepšovat. Dotaz pro získání dat se nicméně podstatně zjednoduší, takže ji na druhou stranu můžeme někdy využít i místo definice pohledu.
Technika jde použít na prakticky libovolné dopočítávané sloupce, ať už jde třeba o výpočet prodejní ceny podle nastavené marže nebo o ID uživatele momentálně nejvýše přihazujícího v aukci.
Přijďte si o tomto tématu popovídat na školení o konfiguraci a výkonnosti MySQL.
Výbornou vlastností pro začátky s PHP je, že lze kód přímo kombinovat s HTML výstupem. U větších projektů je to ale spíše na škodu, takže se sahá k oddělení PHP a HTML kódu. Ten se obvykle umisťuje do šablony, která může být napsaná také v PHP – pak se ale kodér musí hlídat, aby nepoužíval nepovolené konstrukce, a navíc se všechen výstup musí ručně ošetřovat. Proto se častěji používají speciální šablonovací jazyky (např. Smarty), které jsou funkčně omezené a které se o ošetřování dat mohou starat samy. Pak se ale zase člověk musí učit nový jazyk.
Zajímavou třetí možnost přinesl Facebook s projektem XHP. Ten dovoluje zapisovat XML kód přímo do PHP podobně jako je to možné ve Visual Basicu nebo v některých implementacích JavaScriptu.
<?php require './core.php'; require './html.php'; if ($_POST['name']) { echo <span>Hello, {$_POST['name']}.</span>; } else { echo <form method="post"> What is your name?<br /> <input type="text" name="name" /> <input type="submit" /> </form>; } ?>
Všimněte si, že XML elementy nejsou uzavřeny do řetězce, což je právě smysl XHP. Kromě trochu jednoduššího zápisu je možné díky tomu XML validovat a automaticky ošetřovat vypisovaná data, o což se XHP stará.
Rasmus Lerdorf se podíval na výkonnost tohoto řešení a zjistil, že proti čistému PHP je asi čtvrtinová. To je dost žalostný výsledek, musíme ale vzít v úvahu následující:
I tak je výkon stále horší, Facebook už ale XHP vesele používá. Mohou si to dovolit myslím hlavně díky převaděči PHP do C++ HipHop, který velkou část režie pojme.
Což o to, přístup to je zajímavý, ale osobně zůstanu u šablon Nette, které mají velmi dobře použitelnou syntaxi a díky převodu šablon do PHP také výkon srovnatelný s čistým PHP kódem. Nezanedbatelnou výhodou je také to, že jsou použitelné i bez speciální extenze.
Jako již tradičně jsem začátkem roku prozkoumal, jak moc se v ČR používá PHP. Na rozdíl od předchozích ročníků jsem tentokrát stahoval celý obsah titulních stránek všech webů v doméně .cz, což umožnilo výzkum rozšířit.
| IP | domén | změna | |||
|---|---|---|---|---|---|
| celkem | 32473 | 100 % | 634064 | 100 % | +26 % |
| nedostupné | 29795 | 5 % | 0 % | ||
| neuvedeno | 4540 | 14 % | 24236 | 4 % | +2 % |
| Apache | 21318 | 66 % | 450556 | 71 % | –3 % |
| Apache 1 | 1682 | 5 % | 58009 | 9 % | –2 % |
| Apache 2 | 13350 | 41 % | 208635 | 33 % | 0 % |
| IIS | 5666 | 17 % | 96295 | 15 % | –2 % |
| nginx | 442 | 1 % | 22258 | 4 % | +3 % |
Do přehledu serverů jsem nově zařadil nginx, který si ukousl už 4 % domén a z nenáročných webových serverů je jednoznačně nejpoužívanější.
Zajímavé mi přijde také to, že 19 % domén provozovaných na IIS se hlásí k podpoře PHP.
| IP | domén | změna | |||
|---|---|---|---|---|---|
| PHP | 15581 | 48 % | 240917 | 38 % | –3 % |
| .NET | 5153 | 16 % | 90390 | 14 % | +1 % |
| Perl | 1291 | 4 % | 14227 | 2 % | –1 % |
| Python | 1029 | 3 % | 12208 | 2 % | 0 % |
| Ruby | 148 | 0 % | 4627 | 1 % | 0 % |
| Java | 263 | 1 % | 1314 | 0 % | 0 % |
Kromě PHP a .NET mají ostatní programovací jazyky používané na webech zanedbatelný podíl.
| IP | domén | změna | |||
|---|---|---|---|---|---|
| PHP 3 | 24 | 0 % | 136 | 0 % | 0 % |
| PHP 4.0 | 17 | 0 % | 97 | 0 % | 0 % |
| PHP 4.1 | 88 | 1 % | 1316 | 1 % | 0 % |
| PHP 4.2 | 66 | 0 % | 157 | 0 % | 0 % |
| PHP 4.3 | 995 | 6 % | 8301 | 3 % | –4 % |
| PHP 4.4 | 1551 | 10 % | 29628 | 12 % | –7 % |
| PHP 5.0 | 197 | 1 % | 874 | 0 % | –1 % |
| PHP 5.1 | 1482 | 10 % | 15251 | 6 % | –11 % |
| PHP 5.2 | 11000 | 71 % | 183112 | 76 % | +19 % |
| PHP 5.3 | 283 | 2 % | 2159 | 1 % | +1 % |
PHP 4 dále pozvolna umírá, už je jen na 16 % domén, celkově nejpoužívanější je s drtivým náskokem verze 5.2. PHP 5.3 se za sedm měsíců od svého uvedení zatím příliš neprosadilo.
Suhosin používá 25 % instalací PHP. Z českého pohledu může být zajímavé, že framework Nette běží na 592 českých doménách. Ostatní frameworky detekovat neumím.
Obrovský počet serverů tají, jestli používá PHP. Všimněte si třeba toho, že Apache 1 a 2 mají v součtu 42% zastoupení, ale Apache celkem má 71%, přitom se žádné jiné verze nepoužívají. Rozdíl vyjadřuje správce, kteří tají informace o používaných verzích. Přítomnost PHP se ale dá poznat i podle dalších indícií. Kupříkladu pokud server pošle cookie s názvem PHPSESSID, tak je téměř jisté, že na serveru běží PHP (jde o výchozí session identifikátor). Nebo pokud se odkazuje na soubor s koncovkou .php na stejné doméně, tak opět nejspíš půjde o server s podporou PHP.
Alespoň jednu z těchto indícií vykazuje 11 % domén, které se k podpoře PHP nehlásí. Indície ale naopak samozřejmě nevykazují všechny PHP servery – mohou se obejít bez session proměnných a pro odkazy mohou používat pěkná URL (nebo se bez interních odkazů mohou úplně obejít). Když se tedy vezme poměr domén, které indície nevykazují a přitom se k podpoře PHP hlásí a zohlední se ve výpočtu, tak vyjde, že PHP používá celkem asi 58 % českých domén. Můžete to brát jako číslo vycucané z prstu, o reálném zastoupení PHP ale podle mě vypovídá docela dobře.
Starší články naleznete v archivu.