PHP triky

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

Verze PHP v ČR – únor 2010

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

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: 19 (nové: 19)

HipHop

Možná už jste slyšeli zvěsti o tom, že „Facebook přepisuje PHP“. Ve skutečnosti se jedná o projekt HipHop for PHP, který vezme zdrojový kód PHP, převede ho do C++ a ten zkompiluje. Motivace tohoto postupu je jasná – zvýšení výkonnosti, který prý dosahuje až 50 %. HipHop není jediný projekt, který něco takového dělá, z konkurenčních projektů zmíním Phalanger s původem v Česku, který PHP kompiluje do .NET. HipHop je výjimečný tím, že se už teď v praxi využívá na jednom z největších serverů na Internetu.

HipHop si nedokáže poradit s libovolným kódem:

Z těchto důvodů ani Facebook nepoužívá HipHop exkluzivně, ale obsluhuje pomocí něj jen 90 % svých požadavků.

Obří aplikace provozované na více strojích HipHop jistě rády časem využijí (byl uvolněn jako open-source). Menší servery si vystačí s akcelerátory (např. eAccelerator), ještě menší se zaměří třeba raději na optimalizaci databáze.

Další zajímavé informace v angličtině: Facebook, Ilia Alshanetsky.

Jakub Vrána, Ze zákulisí, 3.2.2010, diskuse: 15 (nové: 15)

phpMyAdmin VS Adminer

Článek vyšel na serveru Zdroják.

Aplikace phpMyAdmin je nepsaný standard pro správu databáze MySQL z prostředí webového prohlížeče a mnoho uživatelů pro správu této databáze ani žádný jiný nástroj nepoužívá. phpMyAdmin ale není jediný nástroj svého druhu a jiné aplikace se mu snaží konkurovat. Jednou z těchto aplikací je Adminer, který je stejně jako phpMyAdmin napsaný v PHP, ale některými vlastnostmi se snaží odlišit.

Poznámka: Autor článku je zároveň autorem aplikace Adminer, článek je proto pochopitelně zaujatý, i když se snaží být férový.

Instalace

phpMyAdmin 3.2.4 lze stáhnout ve dvou verzích:

Adminer 2.2.1 zabírá 174 kB v jednom souboru a na hosting se zkopíruje prakticky okamžitě. Jednojazyčná anglická verze má 119 kB, ke stažení je i jednojazyčná česká a slovenská verze.

phpMyAdmin používá licenci GPL 2, Adminer o něco vstřícnější licenci Apache 2, která není nakažlivá.

Co se požadavků na server týče, phpMyAdmin od verze 3 vyžaduje PHP 5.2+ a MySQL 5, pro práci se staršími verzemi je nutné použít starší, stále udržovanou řadu 2. Adminer se spokojí s PHP od verze 4.3 a s MySQL od verze 4.1. phpMyAdmin od uživatele vyžaduje zapnuté cookies, Adminer se obejde i bez nich. Adminer pro práci s databází dokáže využít i extenzi PDO, phpMyAdmin ne.

phpMyAdmin je k dispozici v 57 jazycích, Adminer ve 12, základní světové jazyky a čeština jsou k dispozici v obou.

phpMyAdmin dovoluje nastavit celkem 143 konfiguračních direktiv, mezi jinými třeba i tu určující, kam povede odkaz u ikony ve výpisu tabulek. Výchozí hodnota této konfigurační direktivy se navíc v průběhu času změnila, takže uživatelé, kteří se ikonu naučili používat (což není zcela intuitivní), bývají zmateni. Adminer žádnou možnost konfigurace nenabízí, všechny instalace se tudíž chovají stejně.

phpMyAdmin obsahuje instalační skript, který dovoluje nastavit některé konfigurační direktivy (jiné je potřeba přímo zadat do PHP kódu). Tento skript je obvykle nezbytné spustit, protože ve výchozí konfiguraci nedovoluje phpMyAdmin zadat název serveru nebo se přihlásit pod uživatelem bez hesla. Adminer vzhledem k absenci konfigurace žádnou instalaci nevyžaduje.

Vzhled

phpMyAdmin nabízí uživateli výběr mezi dvěma vzhledy, dalších 12 vzhledů lze navíc stáhnout, samostatně lze kromě toho nastavit barvu pozadí. Jeden vzhled má kolem 120 souborů. Adminer vedle vestavěného vzhledu dovoluje stáhnout 5 dalších, každý vzhled je tvořen jediným souborem.

phpMyAdmin používá pro zobrazení stránek rámy a samostatná okna. To uživateli znemožňuje ukládat si jednotlivé stránky do oblíbených položek a otvírat je do samostatných panelů. Adminer na druhou stranu rámy nevyužívá, díky čemuž si uživatel každou stránku může uložit do oblíbených položek nebo otevřít do samostatného panelu.

phpMyAdmin používá interní zvýrazňovač syntaxe SQL dotazů. Ten ve výchozím nastavení dotaz zároveň i přeformátuje, což může být někdy užitečné a jindy na škodu. Adminer používá externí JavaScriptový zvýrazňovač syntaxe, který zároveň vytváří odkazy do dokumentace použitých příkazů. Adminer navíc detekuje verzi MySQL a odkazuje do dokumentace používané verze.

Možnosti rozšíření

phpMyAdmin možnosti svého rozšíření staví na bohaté konfiguraci a na vytvoření pomocných tabulek v databázi. Pomocí nich lze např. ke sloupcům zadat informace o typu dat, takže phpMyAdmin pak místo binárních dat zobrazuje obrázek. Adminer dovoluje vytvořit objekt, pomocí kterého lze kompletně změnit jednotlivé části aplikace (např. si vytvořit vlastní způsob autorizace nebo libovolným způsobem upravit výpis nebo editaci dat). Tento způsob je o něco náročnější (vyžaduje programování), ale nabízí podstatně širší možnosti přizpůsobení.

Import a export

phpMyAdmin i Adminer dovolují importovat data ve formátu SQL a CSV, volitelně komprimovaná. Adminer navíc umožňuje načíst SQL soubor z disku serveru, což dovoluje spustit i velké soubory, jejichž přenos je v konfiguraci PHP zakázaný (soubor lze nahrát např. pomocí FTP). phpMyAdmin tuto možnost nabízí také, ale je potřeba ji nakonfigurovat. Pro CSV import dovoluje phpMyAdmin použít příkazy INSERT, INSERT IGNORE nebo REPLACE. Adminer používá příkaz INSERT s klauzulí ON DUPLICATE KEY UPDATE, která jako jediná provede import správně. Při CSV importu lze v phpMyAdminu ignorovat několik prvních řádek, Adminer si na druhou stranu dokáže z prvního řádku načíst seznam sloupců, které se pro zpracování souboru použijí.

phpMyAdmin dovoluje exportovat data ve 12 formátech jako např. LaTeX nebo Word. Adminer nabízí jen formáty SQL a CSV, u SQL ale nabízí i možnost tzv. ALTER exportu, který slouží primárně k synchronizaci vývojového a produkčního serveru (vypíše příkazy, které je nutné spustit, aby stav cílové databáze odpovídal stavu zdrojové databáze, obvykle ALTER TABLE).

phpMyAdmin a Adminer se liší ve způsobu hromadné editace záznamů. phpMyAdmin při hromadné editaci zobrazí pro každý záznam vlastní formulář, které uživatel edituje zvlášť. Adminer nabízí skutečně hromadnou editaci, kdy se všechny záznamy editují najednou (u každého sloupce lze nechat původní hodnotu, nastavit novou hodnotu nebo provést relativní operaci jako je třeba přičtení čísla). Pro stejnou úpravu třeba všech záznamů v tabulce phpMyAdmin využít nelze.

Cizí klíče

phpMyAdmin má velmi slabou podporu pro cizí klíče, které se dají vytvářet v tabulkách typu InnoDB. Na speciální stránce lze vytvořit pouze jednosloupcové cizí klíče, s kterými se dále nikde nepracuje. Vícesloupcové cizí klíče nelze vytvořit vůbec. Adminer naproti tomu dovoluje jednosloupcové cizí klíče vytvořit přímo při vytváření nebo úpravě struktury tabulky, podle názvu sloupce navíc vytvoření cizího klíče automaticky nabízí. Pro vytvoření vícesloupcového cizího klíče a jejich úpravy slouží samostatná stránka.

V Admineru se z cizích klíčů při výpisu dat automaticky vytváří odkazy. V phpMyAdminu se tyto odkazy vytváří pouze po vytvoření a konfiguraci speciálních tabulek. Ve verzi Adminer Editor se navíc vytváří i zpětné odkazy (např. na všechny knihy daného autora).

V phpMyAdminu lze schéma databáze zobrazit jen po konfiguraci speciálních tabulek ve formátu PDF. Pozici tabulek ve schématu lze upravit pouze ručně zadáním souřadnic. V moderních prohlížečích lze použít také návrhář využívající značku <canvas>. Adminer schéma databáze včetně propojení tabulek zobrazuje přímo v HTML stránce s možností tabulky přetahovat pomocí myši.

Použitelnost

Při vytvoření tabulky v phpMyAdminu je nutné předem stanovit počet sloupců. Pokud si teprve při jejím zadávání uvědomíme, že potřebujeme ještě nějaké sloupce navíc, je nutné odeslat formulář. phpMyAdmin navíc tabulky do tří polí zobrazuje po sloupcích a od čtyř polí po řádcích, takže se při přidání čtvrtého sloupce tabulka celá přeskládá. Adminer naproti tomu do tabulky přidává další pole automaticky podle potřeby. Obdobný rozdíl ve způsobu práce je vidět třeba i u vícesloupcových indexů.

Při změně struktury tabulky je v phpMyAdminu nutné nejprve vybrat, kam chceme jednotlivé sloupce přidat. Pokud je chceme přidat na různá místa, je nutné to udělat ve více krocích. Adminer naproti tomu dovoluje sloupce přidávat přímo v editaci struktury tabulky. Kromě toho umožňuje změnit i pořadí sloupců, což phpMyAdmin vůbec neumožňuje.

Pro editaci výčtového typu enum a set nabízí Adminer komfortní editor, kde se každá položka píše na jeden řádek textového políčka. Uživatelé phpMyAdminu se musí řídit následujícím návodem:

Pokud je sloupec typu „enum“ nebo „set“, zadávejte hodnoty v následujícím formátu: 'a','b','c'... Pokud potřebujete zadat zpětné lomítko („\“) nebo jednoduché uvozovky („'“) mezi těmito hodnotami, napište před ně zpětné lomítko (příklad: '\\xyz' nebo 'a\'b').

V phpMyAdminu i v Admineru lze existující záznamy duplikovat, nástroje ale používají opačný postup operací. V phpMyAdminu se vyvolá běžným způsobem editace záznamu a po dokončení úprav uživatel vybere, že se má záznam uložit jako nový. To může vést k tomu, že uživatel delší dobu upravuje záznam určený k duplikování, ale pak ho po paměti uloží a tím přepíše původní záznam. Adminer proto volí opačný postup úkonů – nejprve se vybere operace klonování a pak se nový záznam uloží běžným způsobem.

phpMyAdmin i Adminer nabízí výpis proměnných na serveru, Adminer k nim ale přidává i odkazy do dokumentace každé z nich (najít je v dokumentaci MySQL dá totiž trochu práce). phpMyAdmin na druhou stranu obsahuje popis všech stavových informací.

phpMyAdmin zobrazuje velikost tabulek spolu s jednotkami, Adminer ji zobrazuje v bajtech. Díky tomu jsou na první pohled snadno rozpoznatelné velké tabulky od malých, v phpMyAdminu se dá podle velikosti zase třídit.

Při zadávání binárních dat dovolují oba nástroje nahrát soubor z disku, phpMyAdmin ale tato binární data nedovoluje stáhnout (možné to je pouze při vytvoření pomocných tabulek a explicitního určení všech sloupců, jejichž obsah má jít stáhnout). Adminer stažení binárních dat nabízí vždy, zobrazitelná data (např. text ve sloupci typu blob) navíc také zobrazuje.

Funkční výbava

phpMyAdmin i Adminer drží krok s vývojem MySQL a dovolují pracovat i s moderními objekty jako jsou triggery, uložené procedury nebo události. Zatímco ale Adminer nabízí komfortní uživatelské rozhraní, kde uživatel vyplňuje jednotlivé parametry do připravených kolonek a stejným způsobem může všechny objekty i vytvářet, phpMyAdmin se omezuje na výpis existujících objektů a vypsání SQL příkazu pro jejich vytvoření. Uživatelské rozhraní k těmto objektům tedy vůbec neexistuje a uživatel musí přesně znát syntaxi SQL dotazů, pomocí kterých je může vytvářet. Stejně tak phpMyAdmin neumožňuje uložené procedury zavolat jinak než pomocí ručního sestavení SQL příkazu.

phpMyAdmin dovoluje vytvořit pohled z libovolného SQL příkazu (pohled se ale potom automaticky nezobrazí v seznamu tabulek), neumožňuje pohledy ale měnit. V přehledu tabulek databáze také chybí odkaz pro výpis záznamů pohledů. Adminer pro vytvoření a správu pohledů nabízí vlastní uživatelského rozhraní.

Adminer dovoluje při výpisu tabulky na jednotlivé sloupce aplikovat funkce (dokonce i agregační), phpMyAdmin nic takového nenabízí. Adminer dovoluje tabulku třídit podle více sloupců (např. nejdřív podle data a potom podle času), phpMyAdmin obdobnou funkci nabízí jen komplexním databázovým dotazem.

U SQL příkazu nabízí oba nástroje různou funkčnost. phpMyAdmin dovoluje dotaz profilovat, Adminer dovoluje vypsat více výsledků v rámci jednoho zavolání příkazu. Adminer navíc zobrazuje odkazy pro editaci záznamů i u dotazů spojujících více tabulek. Oba nástroje dovolují příkaz vysvětlit, phpMyAdmin k tomu otevře novou stránku, Adminer tuto informací zobrazí přímo pod dotazem. Oba nástroje ukládají historii provedených příkazů, Adminer do této historie ale ukládá i příkazy provedené přes uživatelské rozhraní.

Bezpečnost

Adminer za dobu své existence opravil dvě bezpečnostní chyby, phpMyAdmin za stejnou dobu dvacet (za celou dobu celkem 47). phpMyAdmin navíc nenabízí spolehlivou ochranu proti útoku ClickJacking (kvůli využívání rámů by to šlo jen obtížně).

Jiné chování je vidět i u externích odkazů, které phpMyAdmin odkazuje přímo (po patřičné konfiguraci) a tím jim přes hlavičku Referer předává URL, na kterém správce databáze běží. Adminer externí odkazy směruje přes speciální stránku, která hlavičku Referer skryje.

Výkonnost

Adminer je při práci díky jednodušší architektuře o něco rychlejší než phpMyAdmin. Podle testu Juraje Hajdúcha je to průměrně o 28 %.

Kromě toho Adminer velmi obezřetně pracuje s dlouhotrvajícími operacemi. Jednak před provedením delší operace explicitně posílá dosavadní výstup do prohlížeče, čímž uživateli dovolí pracovat s dosud poslanými daty. Zásadním rozdílem je ale to, že Adminer před provedením dlouhotrvající operace odemkne session, čímž uživateli dovolí s aplikací pracovat v jiném panelu prohlížeče. phpMyAdmin je během dlouhé databázové operace jednoduše zablokován.

Důraz na rychlost práce je u Admineru vidět také v přehledu všech databází. phpMyAdmin v něm zobrazuje počet tabulek v jednotlivých databázích, získání této informace ale může trvat až desítky vteřin. Adminer proto tuto informaci raději zobrazuje až u jednotlivých databází.

Závěr

Přestože je phpMyAdmin zavedený standard pro správu databáze MySQL z webového prohlížeče, řadu funkcí kupodivu nenabízí nebo je nutné je zvlášť konfigurovat. Kromě toho má překvapivé mezery v použitelnosti, často prováděné operace nejsou intuitivní nebo k nim vede zdlouhavá cesta. Padesátkrát menší Adminer nabízí v mnoha oblastech použitelnější uživatelské rozhraní a na řadě míst také více funkcí.

Jakub Vrána, Adminer, 12.1.2010, diskuse: 21 (nové: 21)

ORM z druhé strany

Martin Malý píše o tom, co mu vadí na ORM (především nutnost synchronizace mezi kódem a databází) a nepřesně mě zmiňuje:

Chvíli jsem přemýšlel o řešení Jakuba Vrány, který si, jestli se dobře pamatuju, parsuje SQL a z něj si generuje strukturu pro třídu, ale furt se mi to zdálo příliš složité.

Já se ve svých myšlenkách (žádné ORM jsem nenapsal ani nepíšu) ubírám jiným směrem. Jde o to, že v kódu vůbec žádný seznam polí v jednotlivých tabulkách být nemusí. K čemu by mi tam vlastně měl být?

  1. Když uvedu neexistující název sloupce, tak se o tom dozvím v momentě, kdy se do databáze posílá sestavený dotaz nebo když se přistupuje k neexistující vlastnosti. To je sice o maličko později než když bych měl seznam sloupců k dispozici, ale je to ve stejné fázi (konkrétně při spuštění kódu, protože při kompilaci PHP přístup k neexistujícím vlastnostem nijak neověřuje).
  2. Datové typy mě prakticky nemusí zajímat, data mohu ošetřovat podle jejich PHP typu. Když třeba přešvihnu délku řetězce nebo pošlu NULL do NOT NULL sloupce, tak se o tom opět dozvím chybou nebo varováním databáze.
  3. Hlavním motivem tak může být to, aby IDE napovídalo názvy sloupců při práci s objektem. To ale řešíme problém IDE a nikoliv kódu, takže bych to raději řešil na úrovni IDE. Jistě by šel alespoň do některých IDE dopsat plugin, který by třeba při anotaci @table dokázal seznam vlastností načíst ze struktury tabulky.

Stručně řečeno: Bez seznamu sloupců v kódu se dá bez problémů obejít, takže synchronizaci mezi kódem a databází nemusíme vůbec řešit. Když bych informace o sloupcích mermomocí chtěl (třeba kvůli včasnější kontrole), tak bych je spíše získával reflexí (SHOW COLUMNS) při spuštění.

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

Architecture of Adminer

Adminer is a feature complete MySQL administration tool with the aim of compactness. The whole application consists of a single file as small as possible. This request influences the code design of Adminer and this article describes the way of reaching that target.

As a freelance PHP programmer, I often get a task to check some problem or implement new functionality at a strange site. I usually get only FTP credentials without further documentation about the application or about the client's hosting. If the task consists of some database manipulation then I have to discover or modify the database structure without any tools or remote access to the database server. I can usually install phpMyAdmin in the web space but it currently consists of 11 MB in 666 files and I became quickly tired of uploading this package with every new task (and removing it after the task completion).

Therefore, I started to create a compact alternative of phpMyAdmin consisting of a single file easy to upload and delete anywhere. Initially, it was a simple tool with basic commands such as the table structure, select, SQL command and export. Later, I have added more and more features so now it is a feature complete MySQL administration tool including even the MySQL 5.1 features (events and table partitioning). Adminer implements some features (such as triggers or stored routines) in a more comfortable way than the bigger competitor but the basic goal of a single file is still valid.

Note: The project's original name was phpMinAdmin. I chose that name as a joke to emphasize the fact that phpMinAdmin is a minimalistic version of the famous PHP MySQL tool. (By the way do you use Lynx or Links?) This name was however confusing and invoked the feeling that phpMinAdmin is only a weaker alternative of phpMyAdmin.

Adminer Table Structure

Compilation

When the size of adminer.php started to grow then I have realized that it would be wise to split the file for manageable development. Nevertheless, I still wanted the all-in-one file so I have created a compilation script that joins the files back together.

Because everything lies in one file, Adminer cannot use the usual URL alter.php?table=X and the action must be passed in the query string. Instead of common ?action=alter&table=X, I have used the more compact alternative ?alter=X. The central script is then a sequence of if (isset($_GET["..."])) instead of one big switch ($_GET["action"]). There is an include of the specific functionality inside the if and these includes are compiled to a one file in the build process:

<?php
function include_file($match) {
    $file = file_get_contents($match[1]);
    $token = end(token_get_all($file));
    $php = (is_array($token) && in_array($token[0], array(T_CLOSE_TAG, T_INLINE_HTML)));
    return "?>\n$file" . ($php ? "<?php" : "");
}
$file = preg_replace_callback('~include "([^"]+)";~', 'include_file', $file);
?>

This code is not universally usable because it finds only the include "" variant. It would be necessary to process the file with token_get_all function and find all include variants. The _once variants would be more difficult. There can be also a global level return.

Adminer uses also some external files – style sheet, shortcut icon and couple of images. There are several possible approaches of accessing these files:

  1. They can be integrated in the main HTML code – style sheet by <style> tag, images by utilizing the data: protocol. The problem with this approach is that Internet Explorer < 8 does not support this protocol. The second problem is that the browser would need to transfer these data with each page repeatedly.
  2. The files can be downloaded from an external server. It would be problematic if the server is unreachable or if the computer running Adminer is without the Internet connection. This approach is however used with syntax highlighting of SQL queries, which is an optional feature – Adminer uses the JavaScript Syntax Highlighter JUSH for this task.
  3. The files can be integrated in the source code and a special parameter would serve them. Did you ever notice the PHP logo in the output of phpinfo? PHP downloads it from the local server with a special query string ?=PHP…. Adminer uses a similar approach and saves the files Base64 encoded in the source code (the encoding is not necessary but it simplifies the script editing in common text editors). This approach allows the HTTP caching of the external files which Adminer utilizes.

Note: PHP 5.3 comes with a support for PHP archives through the phar extension. With this extension, it is possible to pack several files to one archive and access the files inside the archive from PHP. Usage of the webPhar method allows packing most of the PHP applications to one archive and there are instructions for creating phar version of phpMyAdmin in the PHP manual. Adminer does not use this extension and works with PHP >= 4.3 and PHP >= 5.0.

Minification

The compilation script also minifies the code – it removes comments and trims white space. The minification code is based on a work of David Grudl who uses it for his brilliant libraries Texy, dibi and Nette. This function is more effective than the internal function php_strip_whitespace.

<?php
function php_shrink($input) {
    $set = array_flip(preg_split('//', '!"#$&\'()*+,-./:;<=>?@[\]^`{|}'));
    $space = '';
    $output = '';
    foreach (token_get_all($input) as $token) {
        if (!is_array($token)) {
            $token = array(0, $token);
        }
        if ($token[0] == T_COMMENT || $token[0] == T_WHITESPACE) {
            $space = "\n";
        } else {
            if (isset($set[substr($output, -1)]) || isset($set[$token[1][0]])) {
                $space = '';
            }
            $output .= $space . $token[1];
            $space = '';
        }
    }
    return $output;
}
?>

Current version also shortens the names of user variables and functions. It finds the variables and functions in the source code with token_get_all function and replaces them by shorter identifiers. The shortening process skips the internal PHP variables and functions. It would be possible to shorten them too but it would be more complicated and it would have a performance penalty. Some PHP variables have their super global feature so it would be necessary to globalize their aliases inside the functions.

The internal functions can be enclosed in envelope functions with shorter names. This approach is however problematic because the PHP functions can have optional parameters without the default values (example of this function is fwrite which detects the number of passed arguments instead of their values). Universal function func_get_args does not work with the references on the contrary.

Alternative approach would be to define a variable for an internal function (e.g. $fw = 'fwrite') and call the function through this variable: $fw($fp, $string). Adminer however does not use neither approach because it would slow the application down.

The minification trims white space also from CSS and PHP version of JSMin minifies the JavaScript code too.

The file would be much smaller if it would be compressed. PHP supports several compression algorithms but only through an extension. I was thinking about a simple decompression function written in PHP which will unpack the rest of the file but it will need to eval the decompressed code and everybody knows that eval is evil (one reason is that it is not compatible with PHP accelerators). Therefore, the code is not compressed but only minified.

Translations

I develop Adminer in English. There is also a Czech version (which is my native language) from the beginning and Adminer is currently available in 11 languages. All messages are served by a simple function, which checks the current language and returns the correspondent translation.

A simple function detects the language from the Accept-Language header:

<?php
function acceptable_language($translations) {
    $accept_language = array();
    preg_match_all('~([-a-z]+)(;q=([0-9.]+))?~', strtolower($_SERVER["HTTP_ACCEPT_LANGUAGE"]), $matches, PREG_SET_ORDER);
    foreach ($matches as $match) {
        $accept_language[$match[1]] = (isset($match[3]) ? $match[3] : 1);
    }
    arsort($accept_language);
    foreach ($accept_language as $lang => $q) {
        if (isset($translations[$lang])) {
            return $lang;
        }
        $lang = preg_replace('~-.*~', '', $lang);
        if (!isset($accept_language[$lang]) && isset($translations[$lang])) {
            return $lang;
        }
    }
    return false;
}
?>

The caller of this function saves the language to a cookie after the first detection. A user can change the language anytime while staying on the same page. Saving the page language to a cookie is a very bad practice on a public site because the search engines then can index only one version of the page. It is however acceptable in Adminer because the search engines do not index the pages and the main content of the page (e.g. the table structure) is the same in all languages. The reward for storing the language in a cookie is a simpler URL while it is still possible to enforce the language by passing the language identifier in the query string.

Translations are stored in a simple array where the key is a message identifier (which is an English version of the message and it is used if the translation does not exist) and the value is a string with the translation. The value is more interesting with messages containing a number (e.g. “1 row” or “2 rows”). Most languages have different singular and plural form but some languages (e.g. Czech or Russian) use more plural forms depending on the number (e.g. Czech “1 pivo”, “2 piva” or “5 piv” for beers). The messages with numbers are stored in an array instead of string and Adminer contains a simple logic for each language choosing the right form depending on the number.

By default, Adminer comes with all languages. The compilation converts the translation array to values only and changes the identifiers to numbers to save some bytes so each translation takes about 4 KB. The compilation script is able to create also a single language version, which removes the translating function and the language detection completely. This file takes only 111 KB for the English version of Adminer 1.10.1.

Database extension abstraction

PHP allows connecting to a MySQL database through three extensions: mysql, mysqli and pdo_mysql. Adminer supports all of them and contains a simple abstraction layer for these extensions. The abstraction layer emulates the subset of mysqli and mysqli_result classes for all three extensions.

PDO support is little tricky because PDO uses exceptions for reporting connection errors. Adminer cannot use the usual try block to catch these exceptions because it runs also on PHP 4. Everything is in a single file so the PDO support cannot be conditionally included only in PHP 5. The solution is to use set_exception_handler function to process the connection errors. PHP 4 also does not support class constants on the syntax level so the source code uses the numerical values of the constants instead.

Security

The most important part of the security in Adminer is surprisingly the defense against Cross-site Request Forgery. CSRF allows an attacker to perform actions in the name of a logged-in user. Adminer stores the login information in a standard session so PHP can send the session identifier in a cookie and the defense against CSRF is necessary. Adminer sends a token with each form and checks this token before performing the operation of the form. A token protects even the logout action which is often forgotten in other web applications.

Very important is also the protection against Cross-site Scripting. Outputting the HTML code stored in the database could reveal sensitive information to the attacker so htmlspecialchars function escapes every output.

Adminer has also a protection against the SQL injection but it is not in the name of security – if the attacker already logs-in to the Adminer then she could execute any SQL code by the SQL command page. Therefore, the protection against the SQL injection is only a side effect of proper manipulation with the user input – a user can pass any data to the database. If Adminer detects the magic_quotes_gpc then it neutralizes this directive and uses a proper escaping function only when passing the data to MySQL.

<?php
if (get_magic_quotes_gpc()) {
    $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][stripslashes($k)] = $v;
                $process[] = &$process[$key][stripslashes($k)];
            } else {
                $process[$key][stripslashes($k)] = stripslashes($v);
            }
        }
    }
    unset($process);
}
?>

Calling of session_regenerate_id after the login prevents session fixation.

Every page of Adminer contains the robots: noindex meta information to hide the page from search engines.

Login form

Adminer saves the database credentials to a session and checks them on every page. If the database cannot authorize the user then the login form is displayed directly on the page. This approach is better then redirecting the user to a login page for several reasons:

  1. It saves one request, the login form is sent directly to the target page.
  2. User can bookmark any page, which displays directly after the login. The redirection to a login form approach can achieve this too but it is much more complicated (the login form needs to get the returning URL, check it for validity and redirect back after the login because the Referer header is not reliable).
  3. If the session expires before sending any application form then the posted data prepopulates the login form with the hidden fields.
<?php
function hidden_fields($process) {
    while (list($key, $val) = each($process)) {
        if (is_array($val)) {
            foreach ($val as $k => $v) {
                $process[$key . "[$k]"] = $v;
            }
        } else {
            echo '<input type="hidden" name="' . htmlspecialchars($key) . '" value="' . htmlspecialchars($val) . '" />';
        }
    }
}
hidden_fields($_POST);
?>

The sent files are also stored to hidden fields and there is a little layer serving the files from the $_FILES variable or from the posted hidden fields.

It is possible to log in without filling the form by passing the ?username= parameter. A user can use this feature in a secure environment where he authenticates by some other mean (e.g. on localhost without a remote access or with HTTP authentication). However, it is not possible to pass the password in a GET parameter which would be very insecure. Adminer uses the value of mysql.default_password configuration directive in this case. The demo application uses this approach to automatically log in the users.

Form processing

Adminer sends all action forms by the POST method. If the action is successful then it redirects the browser to a page showing the result with a confirmation message. This message is stored to a session variable after performing the action and the beginning of each HTML page processes this session variable. There is a theoretical chance that other Adminer page opened simultaneously in a different browser tab can catch the message and display it instead of the original page. However, the period between setting the message and displaying it is very short so this probability is low. Besides, the consequence of this coincidence would be small – the user would get informed about the result, just in a different tab.

An alternative to this approach would be to send a message ID or the message itself in the URL but it would clutter the browser history. In addition, the page refresh would maintain the message on the page, which is not desirable.

If the form action produces an error then the page directly displays the error message with the form prefilled by the sent data. The code does not redirect the browser in this case, which allows a user to refresh the page with the sent data if the error was only temporary (for example if it could be fixed in other browser tab).

Database schema

Simple database schema Adminer contains a simple interface for visualization of the database schema. It displays all tables with their columns. Colors differentiate the column types and arrows symbolize the table references. Tables can be moved around the screen with a mouse, which is achieved by JavaScript and CSS. The database schema page uses a cookie to save the table placement.

Export

Adminer supports SQL and CSV import and export. The most interesting variant of export is the one using the ALTER command. There is often a different database on the production and the development server. If you have made some changes in the database structure which should go to the production server during a release then dropping the old table and creating the new one is not an option because there are live data in this table. Adminer is able to generate a set of ALTER commands synchronizing the databases. It utilizes a stored procedure which explores the production database and alters it to conform to the development version. New tables are created as usual, old tables are dropped, missing fields and indexes are added.

Performance

Adminer always queries all data directly from the database with one exception. Getting the database list could take a very long time if there are many databases on the server even if the user has access only to couple of them. Because each page displays the database list then it is cached to a session variable. This variable is refreshed if a user creates or drops a database either from the user interface or by the SQL command.

Closing a session by session_write_close is a performance optimization with a little impact. The default PHP session handler locks the file with a session data, which prevents the web server to serve multiple requests of the same user at the same time. This is very important on sites using frames where it is a common situation. Adminer does not use frames but a user can still open several browser tabs with Adminer at the same time. Adminer writes all the session data and closes the session as soon as possible to allow the maximum concurrency.

Most users feel the smoothness of Adminer and one user even created the test suite, which measures the performance with comparison to phpMyAdmin:

Demonstration

The demo is one of the best ways to present the features of some product. I have created some sample database and made it accessible from the website through Adminer but users have started corrupting it quickly. I was thinking about periodically restoring the database but it would not work because in the meantime the database would be still corrupted. On the other side, users working with the database during the reset would be surprised what is happening. Restricting the actions would cripple the demo, so it was not an option either. Other possibility was to emulate the database access in a session for each user but it would be a very complex task and it will not present all the features of Adminer.

Finally, I have ended with creating a separate database for each user entering the demo. The script initializes this database by a sample data after login to the demo and destroys it after logout. A cron job drops the databases of users who did not log out. The demo user has powerful privileges to present the most features of Adminer so it was necessary to restrict the list of databases to disallow accessing databases of other users. Each MySQL user can also modify her password and users of the demo application quickly discovered it. Thus, the demo disables also this feature.

I did not want to create a separate version of Adminer just for the demo so I have made all customizations by auto_prepend_file. The prepended script restricts the database list, disallows changing of password, initializes the demo databases and drops them.

Tests and code coverage

Nearly all parts of the application have tests. I have created the tests in Selenium IDE, which is a very convenient Firefox plug-in for creating complex web applications tests. The main advantage of Selenium is that it can test the whole application – from PHP on the server side to HTML on the client side, it can test even the JavaScript interactions. Adminer works well without JavaScript but some features are more comfortable with the JavaScript enabled – for example adding a field in a table does not require sending the page to the server.

Selenium IDE can create the tests very easily. It is possible to just push the record button and work in a browser as usually. The recorded test can be modified afterwards so if you are checking some feature in the application then why not to record a test for it?

I was curious how much code is tested and I was interested in which parts of Adminer need tests that are more thorough. It can be find out by the code coverage. I have used the Xdebug extension, which has the ability to measure the code coverage. It is well suited for running unit tests because the code coverage is computed for the whole run of the tests. But the web application tests are different as they compose from several requests and the code coverage is computed for each request separately. The solution was quite easy – I have registered a shutdown function saving the code coverage to a session and joining it with the previous results.

The code coverage of Adminer is around 75 %, which is satisfactory. For comparison, the code coverage of PHP source codes is around 70 %. I was very glad that the tests exist when I refactored some parts of the application. I was quite sure that the refactoring did not break anything when all the tests have passed.

Version checker

There is a RSS feed with the project news and e-mail subscriptions for the new releases. Some users however just install the current version and forget about it. For this reason, the Adminer contains a version checker.

The version checker must be as unobtrusive as possible because sometimes the Adminer is run in an environment without the Internet connection or the server could be unreachable. So checking the current version from PHP code was not an option because it is blocking. Furthermore, an administrator can disable the remote connections in PHP by allow_url_fopen configuration directive. Thus, the version checker runs from JavaScript. A simple <script src> is blocking too (a user can work with the page but the page is still in the loading state) which is the same for images too. So the script is loaded in an onload event which is non-blocking. The server part is quite simple:

<?php
header("Content-Type: text/javascript");
if (version_compare($_GET["version"], $VERSION) < 0) {
    echo "document.getElementById('version').innerHTML = '$VERSION';\n";
}
echo "document.cookie = 'adminer_version=$VERSION';\n";
?>

If the current version is old then the script modifies the document to display the new version number. The client side checks the sent cookie so the communication with the server is only occasional.

Design

Design from Vlasta Neubauer I am not an artist so the style sheet comes from freelance graphic designer Ondřej Válka. The design is simple and tidy as the whole Adminer so I like it. Other users however like more fancy designs so they created their own. The design can be easily integrated to the single file by own compilation but users like to test several designs so Adminer checks if the external file adminer.css exists. If it does exist then the script uses it instead of the integrated CSS.

Conclusion

This article is about the Adminer architecture, not about its features. However, it should be clear that Adminer supports all MySQL features from a simple table editing, through multiple-columns foreign keys, triggers, stored routines, export, user and processes management to the MySQL 5.1 events and table partitioning. There is of course a universal SQL command for non-standard tasks.

Adminer is not just a compact version of phpMyAdmin, it tries to be a fully competitive alternative for the MySQL administration and the small footprint is only a pleasant bonus.

About the author

Jakub Vrána is one of the authors of official PHP manual, and he uses MySQL for developing web applications in PHP. He also teaches the PHP and MySQL basics at Charles University and conducts commercial trainings. He is the founder of compact MySQL management tool Adminer. Jakub lives in Prague, Czech Republic.

Jakub Vrána, Adminer, 28.12.2009, diskuse: 9 (nové: 9)

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.