PHP triky

Weblog o elegantním programování v PHP pro mírně pokročilé

Adminer 2.3.0

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

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.

Trvalé přihlášení

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.

Vyhledávání ve všech tabulkách

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).

Zobrazení stavových informací

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.

Součty v přehledu tabulek

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í.

Smazání záznamu v jeho detailu

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).

Zobrazení chyb v SQL příkazu

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.

Zkratky uživatelského rozhraní

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.

Chyby

Kromě funkčních změn bylo opraveno také několik chyb, hlavně nefunkčnost na některých verzích IIS.

Jakub Vrána, Adminer, 26.2.2010, diskuse: 25 (nové: 25)

Nastavení fulltextového vyhledávání MySQL

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.

Jakub Vrána, Řešení problému, 24.2.2010, diskuse: 4 (nové: 4)

Dopočítávané sloupce

Ř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.

Závěr

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.

Jakub Vrána, Dobře míněné rady, 17.2.2010, diskuse: 23 (nové: 23)

XHP – XML přímo v PHP

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á.

Výkonnost

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.

Závěr

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.

Jakub Vrána, Seznámení s oblastí, 12.2.2010, diskuse: 9 (nové: 9)

Verze PHP v ČR – únor 2010

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.

Zastoupení serverů

 IPdoménzměna
celkem32473100 %634064100 %+26 %
nedostupné 297955 %0 %
neuvedeno454014 %242364 %+2 %
Apache2131866 %45055671 %–3 %
Apache 116825 %580099 %–2 %
Apache 21335041 %20863533 %0 %
IIS566617 %9629515 %–2 %
nginx4421 %222584 %+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.

Programovací jazyky

 IPdoménzměna
PHP1558148 %24091738 %–3 %
.NET515316 %9039014 %+1 %
Perl12914 %142272 %–1 %
Python10293 %122082 %0 %
Ruby1480 %46271 %0 %
Java2631 %13140 %0 %

Kromě PHP a .NET mají ostatní programovací jazyky používané na webech zanedbatelný podíl.

Verze PHP

 IPdoménzměna
PHP 3240 %1360 %0 %
PHP 4.0170 %970 %0 %
PHP 4.1881 %13161 %0 %
PHP 4.2660 %1570 %0 %
PHP 4.39956 %83013 %–4 %
PHP 4.4155110 %2962812 %–7 %
PHP 5.01971 %8740 %–1 %
PHP 5.1148210 %152516 %–11 %
PHP 5.21100071 %18311276 %+19 %
PHP 5.32832 %21591 %+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.

Celkové zastoupení PHP

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.

Jakub Vrána, Verze PHP, 5.2.2010, diskuse: 31 (nové: 31)

Starší články naleznete v archivu.

© 2005-2008 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.