PHP triky

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

Adminer 5.1.0 - autoloading pluginů

Jsem velký zastánce convention over configuration, takže Adminer ani žádnou konfiguraci nemá. Jsem za to strašně rád. S konfigurací se člověk časem dostane do bodu, kdy by do ní potřeboval začít přidávat minimálně nějaké ify a pomalu se z toho stává programovací jazyk. Dále je konfigurace noční můra řešení problémů – uživatel si stěžuje na nějaké chování a teprve po hodině zjistíte, že si něco zapnul v konfiguraci. V horším případě spolu konfigurační direktivy můžou všelijak kolidovat a jejich udržování stojí hodně energie. Jenže uživatelé si chování prostě měnit chtějí, tak jsem jim dal do ruky mocnější nástroj – pluginy. Ty nejsou omezeny tupostí konfiguračního jazyka a můžou dělat vše, co jejich autor zvládne vymyslet. Adminer jen musí poskytnout dostatečné API.

Zapnutí pluginů v Admineru nebylo úplně nejjednodušší: bylo potřeba vytvořit soubor s definicí funkce adminer_object, ručně vložit soubory s pluginy (nebo je autoloadnout) a vytvořit jejich objekty. Dále vytvořit další objekt, který to všechno pospojoval, a ten z funkce vrátit. Člověk také nesměl zapomenout vložit původní adminer.php a také soubor plugin.php, který pluginy vůbec dovoloval používat. Říkal jsem si, že to každý programátor zvládne – v praxi to je copy/paste příkladu a jeho upravení. Ale někdy člověk musí řešit i další věci – třeba pokud plugin dělá dodatečnou autentizaci (např. podle IP adresy nebo podle jiného hesla než do DB), tak také musíme znemožnit přístup k původnímu adminer.php, kde tyto kontroly neproběhnou.

adresářová struktura Tohle všechno je teď pryč. V Admineru 5.1.0 stačí pluginy prostě zkopírovat do adresáře adminer-plugins/ a Adminer si je tam odtud sám nahraje. Pokud pluginy potřebují nějakou konfiguraci, tak je možné ji udělat v souboru adminer-plugins.php pouhým vrácením pole s vytvořenými pluginy:

<?php
// zde nemusí být žádné include_once "login-password-less.php"

return array(
    new AdminerLoginPasswordLess('$2y$07$Czp9G/aLi3AnaUqpvkF05OHO1LMizrAgMLvnaOdvQovHaRv28XDhG'),
);
?>

Dobrá zpráva je, že předchozí způsob vytváření pluginů i všechny stávající pluginy nadále fungují – není potřeba dělat žádnou úpravu. Další možnost je všechny pluginy vytvořit z libovolného zdroje v adminer-plugins.php a adresář adminer-plugins/ ani nemusí existovat. Tohle celé funguje i pro pluginy databázových ovladačů.

Já už tento způsob nahrávání pluginů nějakou dobu používám a je to neskutečně pohodlné. Když chci nějaký plugin, tak si na něj v adresáři adminer-plugins/ prostě jen vytvořím symlink. To jde příkazem mklink i na Windows a ve správci souborů na to mám klávesovou zkratku. A když plugin nechci, tak symlink zase jen smažu.

Další změny

Kromě této zásadní změny jsem udělal i několik dalších oprav a úprav:

  • V přehledu serveru se nyní zobrazuje i seznam nahraných pluginů.
  • V přehledu tabulky se zobrazuje collation u sloupců, pokud se liší od tabulky. Existuje i plugin, který zobrazuje více informací vždy.
  • Ctrl+klik ve výpisu dat vyvolá rychlou editaci dané buňky. Když jsem tuto funkci před 14 lety přidával, tak se kurzor automaticky i přesunul na místo, kam uživatel kliknul. Ale bylo to v turbulentních dobách, kdy tato API teprve vznikala, každý prohlížeč používal jiné a postupně z nich všechny odešly na nové. Na něj jsem Adminer přehodil teď, takže tato vychytávka zase funguje.
  • V PostgreSQL se zobrazuje auto_increment vloženého řádku. PostgreSQL nemá funkci obdobnou mysql_insert_id, ale musím říct, že jeho způsob je celkem elegantní. Stačí na konec příkazu INSERT INTO přidat RETURNING "id" a hodnota tohoto sloupce se vrátí.
  • V PostgreSQL se u systémových proměnných kromě odkazu do dokumentace zobrazuje i popis.
  • Přidání detekce CockroachDB zahltilo logy PostgreSQL chybami o neexistenci proměnné crdb_version. Nyní tuto detekci dělám pomocí version() (bug #924, regrese z 5.0.5).
  • Do PostgreSQL jsem přidal podporu PROCEDURE, dostupnou od PostgreSQL 11.
  • MS SQL mi z nějakého důvodu začalo vracet chyby s collations u defaultních sloupců, tak jsem jejich získávání změnil. Ale přiznám se, že collations v MS SQL jsou pro mě trochu španělská vesnice – jen pro češtinu jich má 70 a co znamená třeba Czech_100_CS_AI_KS_WS, to mi není úplně jasné.
  • Do vzhledu jsem dodělal změnu, kterou jsem chtěl už dávno – hlavičky sloupců zůstávají při scrollování vidět. Dřív to ve Firefoxu způsobilo zmizení rámečků v hlavičce tabulek, ale ani s moderními prohlížeči to nebylo úplně jednoduché. Nad řádkem s hlavičkou zůstávala 1px díra, kterou byl vidět obsah pod ní. Takže jsem musel konstrukci rámečků trochu změnit (bug #918).
  • Kromě souboru adminer.css lze vzhledy konfigurovat třeba i podle názvu serveru, což využívají pluginy. Pro ně jsem upravil detekci přehazování na tmavý režim (bug #925).
  • Zvětšil jsem maximální šířku políčka pro editaci řetězců ze 40 na 60 (bug #930), mezeru za výsledkem SQL dotazu (bug #937) a mezeru nad odhlášením na mobilu (bug #938).
  • Přidal jsem plugin pro sestavování SQL dotazů umělou inteligencí Google Gemini, který funguje naprosto neuvěřitelně. Více v samostatném článku.
  • Další plugin kontroluje dostupnost nových verzí na GitHubu místo na adminer.org/version. Někdo může být háklivý na leaknutí IP adresy, kde instalace Admineru běží. Ale třeba se takoví uživatelé smíří s tím, že leakne pouze na GitHub.
  • screenshot Přidal jsem plugin pro ruční přepínání tmavého a světlého vzhledu.
  • Přidal jsem plugin zobrazující zpětné odkazy stejně, jako to dělá Adminer Editor.
  • Po editaci hodnot ve formulářích se prohlížeč nyní zeptá, jestli chcete stránku skutečně opustit. Nejdřív jsem udělal pěknou implementaci, která i vracela změněná formulářová políčka, a pak jsem si teprve všiml, že text vrácený obsluhou události beforeunload všechny moderní prohlížeče ignorují (asi kvůli phishingu). Nejprve jsem dal tuto funkci přímo do Admineru, ale pak mě to samotného občas trochu štvalo, tak jsem to přesunul do pluginu.
  • Přibyl uzbecký překlad. Uzbečtina používá docela dost apostrofy, ale Adminer překlady neescapuje, protože v některých situacích oprávněně používají HTML. Tak jsem udělal takovou fintu, že ' měním na . Lepší by bylo překlady plaintextu od překladů HTML rozdělit, ale to se mi dělat nechtělo.

Ovladač pro IMAP

Jen tak pro legraci jsem vytvořil Adminer pro IMAP. Koukám do poštovního klienta a po intenzivní práci na Admineru najednou všude vidím tabulky s daty. Tak jsem si řekl, jak by asi bylo složité to udělat, a ukázalo se, že zase až tolik ne. Schránky se zprávami se zobrazují jako tabulky, hlavičky zpráv se zobrazují jako jejich obsah. Pokus o editaci zprávy o ní zobrazí nějaké další údaje. Vložení záznamu nic nedělá, ale s trochou úsilí by to asi šlo předělat na odeslání nové zprávy. Mazání označí zprávy jako smazané, ale ze schránky je fyzicky neodstraní, to se dá udělat příkazem TRUNCATE. A to je asi tak všechno, co to umí. Funkce support(), kterou Adminer zjišťuje, co všechno daná databáze podporuje, vrací pro všech 25 featur prostě false.

Testy pro PDO

Přidal jsem parametr URL ext=pdo, který vynutí použití extenze PDO místo nativních, které Adminer preferuje. Je to hlavně kvůli snadnějšímu ladění chyb, protože PDO ovladač se chová trochu jinak (např. pro boolean sloupce v některých databázích vrací skutečný PHP bool). Pro PDO se také nově generuje kompletní sada end-to-end testů. Díky tomu se mi podařilo ošetřit pár chyb a nekonzistencí, které Adminer při použití PDO měl.

Interní změny

Změna, která se navenek doufám nijak neprojeví (end-to-end testy nic neodhalily), je modernizace JavaScriptu:

  • Změna všech var na let/const. Využil jsem k tomu eslint --fix s pravidly no-var a prefer-const. letlogičtější chování než var.
  • Změna anonymních funkcí, které nepoužívají this, na () => {}. Adminer this docela hodně používá pro události, takže se to zase tolika funkcí nedotklo.
  • Použití for...of. To kód zpřehledňuje a zkracuje.
  • Odstranil jsem několik obstarožních kontrol, jako třeba jestli prohlížeč podporuje JSON.
  • Místo className se teď většinou používá classList.

V PHP jsem zapnul chyby E_NOTICE a E_STRICT kromě přístupu k neexistujícímu prvku pole. To se hádám navenek v některých okrajových případech projevit může, ale co odhalily testy a na co jsem sám narazil, to jsem opravil.

Na závěr zmíním, že jsem sepsal poznámky pro vývojáře, v podstatě takový můj braindump.

Jakub Vrána, Adminer, 24.3.2025, diskuse: 0 (nové: 0)

Google Gemini: API

Odborník na umělou inteligenci a vynikající PHP programátor David Grudl před časem zveřejnil sneak peek pluginu do Admineru, který dovoluje sestavovat SQL dotazy pomocí AI. Navázal tak na svůj SQL Wizard. Plugin ale nikdy nepublikoval, tak si možná část lidí myslela, že to je fake. Řekl jsem si, jak by asi bylo složité to doopravdy udělat, a ukázalo se, že úplně triviální! Použil jsem Google Gemini a z jednoduchosti jeho nasazení jsem byl velmi mile překvapen. Nemusíte chodit do žádné Google Cloud Console (která je neuvěřitelně nepřehledná), ale prostě přímo v AI Studiu vygenerujete klíč a hned vidíte URL, na kterém ho můžete použít. Implementace v PHP je pak už jednoduchá:

<?php
/** Položení dotazu Google Gemini
* @param string Dotaz v přirozeném jazyce
* @param string Klíč získáte na https://aistudio.google.com/apikey
* @param string Dostupné modely: https://ai.google.dev/gemini-api/docs/models#available-models
* @return string Odpověď umělé inteligence
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function gemini($prompt, $apiKey, $model = "gemini-2.0-flash") {
    $context = stream_context_create(array("http" => array(
        "method" => "POST",
        "header" => array("User-Agent: PHP", "Content-Type: application/json"),
        "content" => '{"contents": [{"parts":[{"text": ' . json_encode($prompt) . '}]}]}',
    )));
    $response = json_decode(file_get_contents("https://generativelanguage.googleapis.com/v1beta/models/$model:generateContent?key=$apiKey", false, $context));
    return $response->candidates[0]->content->parts[0]->text;
}
?>

V pluginu pak dotazu od uživatele předřadím schéma databáze a přidám nějakou omáčku, aby to vracelo skutečně jen SQL dotaz, který pak prostě zobrazím. Funguje to naprosto neskutečně:

Poradí si to dokonce i s češtinou, umí to vytvářet i INSERT, přidávat indexy a já nevím, co ještě:

Plugin vyžaduje Adminer 5.1.0, kam jsem přidal hook pro zobrazení promptu na tom správném místě, jinak si to žádnou změnu nevyžádalo.

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

GitHub API: Získání času modifikace Gistu

Dobrovolník ke všem pluginům v jejich seznamu přidal popis a datum poslední aktualizace – díky! Já jsem všechny autory pluginů obeslal pull-requesty s úpravou na Adminer 5. A tím se většina datumů poslední aktualizace stala neplatná.

Aktualizovat to celé ručně by byl nesmysl, tak jsem využil toho, že většina pluginů je k dispozici na GitHubu, některé na Gistu. Tím pádem se dá využít GitHub API a datum aktualizace získat přes něj. Např. pro Gist se to dá udělat takhle:

<?php
// zdrojem je normální URL s Gistem
if (preg_match('~^https://gist.github.com.*/(.+)$~', $url, $match)) {
    $gist = github_api("gists/$match[1]");
    echo $gist->history[0]->committed_at; // $gist->updated_at includes comments
}

function github_api($path) {
    $context = stream_context_create(array('http' => array(
        'header' => array("User-Agent: PHP"),
    )));
    return json_decode(file_get_contents("https://api.github.com/$path", false, $context));
}
?>

Při kladení požadavků musíme nastavit hlavičku User-Agent (na libovolnou hodnotu), což je takový chyták. Pro lepší zpracování chyb je vhodné přidat do kontextu ještě ignore_errors, díky čemuž získáme i stránky s chybami místo pouhého false. Hlavičky zprávy potom získáme v proměnné $http_response_header. Z nich se dozvíme třeba to, že u veřejných API (jako je tohle) má GitHub docela přísné limity – nějakých 60 požadavků za hodinu. Pokud chceme víc, dá se to nejjednodušeji udělat posláním tokenu, který si vygenerujeme v nastavení. Pak jen do pole s hlavičkami přidáme Authorization: Bearer ghp_... (nezapomenout na Bearer).

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

Adminer 5.0.6

Vyšel Adminer 5.0.6, který přináší tyto změny:

  • Možná trochu kontroverzní bude změna v zarovnávání čísel. Adminer Editor je odjakživa zarovnává doprava, Adminer je dával doleva. Ale zarovnání doprava je čitelnější, tak jsem to změnil i v Admineru (bug #912).
  • Pokud na sloupec v editaci najedete myší, zobrazí se jeho typ. Teď jsem tam přidal i komentář, na což dřív byl potřeba plugin.
  • Při exportu z SQL příkazu se nezapamatovalo jeho nastavení, takže ho bylo příště potřeba vybírat znovu. Nyní jsem ho synchronizoval s nastavením exportu ve výpisu dat.
  • Formulář s SQL příkazem Adminer pochopitelně odesílá metodou POST, ale JavaScriptem se ukládá i do URL, aby byl snadno k dispozici v historii prohlížeče. Tam se ukládal, jen pokud byl kratší než 2000 znaků, to ale pro některé servery bylo moc. Proto jsem to zkrátil na 500 (bug #917).
  • V SQL příkazu lze pomocí Ctrl+klik na klíčová slova otevřít jejich dokumentaci.
  • Opravil jsem i jednu bezpečnostní chybu, ve většině případů ale asi ne moc dobře zneužitelnou. Adminer si vytváří nějaké soubory v dočasném adresáři. Pokud do něj mají přístup i další uživatelé serveru (běžné např. na školách), tak můžou pod názvem souboru používaným Adminerem vytvořit symlink vedoucí na jiný soubor uživatele, pod kterým Adminer běží. Adminer pak přepíše tento soubor svými daty. Jednak v dnešní době virtuálních serverů nebývá zvykem, aby se o jeden server dělilo více uživatelů. Jednak webový server obvykle běží pod vyhrazeným uživatelem, který žádná práva zápisu kromě dočasných souborů nemá. I tak jsem chybu ale opravil. Jedno použití dočasných souborů je při kontrole nové verze – tam se soubor nejprve prostě smaže. V ostatních případech se nově kontroluje, jestli to není symlink. Do dočasného souboru se ukládají i počty neúspěšných pokusů o přihlášení jako obrana proti brute-force útokům. Pokud by někdo vytvořil symlink adminer.invalid, tak by tuto kontrolu vypnul. Adminer proto v takovém případě zkusí použít soubor s náhodnou koncovkou (bug #855).
  • V přihlašovacím formuláři se nově zobrazuje „MySQL / MariaDB“, aby uživatelům MariaDB bylo jasné, že to můžou použít. Po přihlášení se to změní buď na MySQL nebo na MariaDB. Stejnou změnu jsem mohl udělat i pro CockroachDB, ale text „PostgreSQL / CockroachDB“ byl prostě příliš dlouhý. Vzhledem k nižší známosti této databáze jsem nechal jen PostgreSQL, i když Adminer s CockroachDB plnohodnotně funguje.
  • Přidal jsem tmavý styl pro zvýrazňování syntaxe. Ten se zapíná podle nastavení operačního systému nebo pro tmavé vzhledy.
  • V tmavém vzhledu jsou nově tmavá i formulářová políčka. Měl jsem za to, že by to operační systém měl dělat automaticky, ale je potřeba mu to říct pomocí color-scheme.
  • Jak už jsem popisoval, Adminer nyní hledá vzhled i v souboru adminer-dark.css.
  • screenshot Pro pluginy jsem přidal metodu syntaxHighlighting, kterou lze změnit zvýrazňování syntaxe z výchozího JUSH. Použil jsem ji v novém pluginu CodeMirror, který zvýrazňování syntaxe včetně editace SQL příkazu přepne na CodeMirror. Adminer je zdá se navržen celkem dobře, protože tato změna si vyžádala jen minimální zásahy do zdrojáku. Killer feature CodeMirroru je napovídání klíčových slov, tabulek a sloupců. Časem chci tuto funkci dodělat i do JUSH.

Všechny autory pluginů jsem také obeslal pull requesty s doplněním jmenného prostoru. Většina už tuto změnu přijala, takže by pluginy měly jít přímo používat i v Admineru 5.

Funkci phpSrink jsem vyčlenil do samostatného repozitáře. Udělal jsem k tomu i demo, které díky Php-Wasm běží v prohlížeči uživatele. O tom zase někdy příště.

Na web jsem přidal stránku se seznamem útoků, proti kterým se Adminer explicitně brání.

Jakub Vrána, Adminer, 17.3.2025, diskuse: 4 (nové: 4)

Tmavý vzhled v CSS

V operačním systému si uživatel může nastavit, že preferuje tmavý vzhled. Ne webu to můžeme zohlednit ve stylu:

body { background: white; color: black; /* plus všechny další styly */ }
@media (prefers-color-scheme: dark) { /* měníme jen barvy */
	body { background: #111; color: #eee; }
}

No tak to bylo jednoduché! Od roku 2024 jde použít i funkce light-dark:

body { background: light-dark(white, #111); color: light-dark(black, #eee); }

Když se k tomu přidají CSS proměnné, tak je to úplná pohádka:

:root {
	--bg-color: white;
	--text-color: black;
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg-color: #111;
		--text-color: #ccc;
	}
}

/* teď máme styl pěkně pohromadě a stejnou barvu můžeme použít na víc místech */
body { background: var(--bg-color); color: var(--text-color); }

Ovšem poněkud se to komplikuje, pokud máte nějaké závislosti, které na tmavý vzhled připravené nejsou, nebo pokud někdo závisí na vás. Zvýrazňovač syntaxe je jako mezi mlýnskými kameny. Potřeboval by si nastavit barvu pozadí, což je v pohodě u bloků, ale vypadá to blbě u inline kódu – barvu pozadí je v takovém případě lepší zachovat. V takové situaci je lepší styl rozdělit do dvou souborů:

/* default.css */
body { background: white; color: black; /* plus všechny další styly */ }

/* dark.css - měníme jen barvy */
body { background: #111; color: #eee; }

A stránka si pak řekne, který styl chce použít, a zda natvrdo, nebo podmíněně:

<link rel="stylesheet" href="default.css">
<link rel="stylesheet" href="dark.css"><!-- natvrdo -->
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="dark.css"><!-- podmíněně -->

Pokud má uživatel nastavený světlý vzhled, tak se stahuje míň dat. Ale pokud má nastavený tmavý, tak se kladou dva požadavky.

Pomocí color-scheme bychom operačnímu systému měli říct, jaké vzhledy podporujeme, a on nám pak dodá odpovídající formulářová políčka: <meta name="color-scheme" content="light dark">.

Adminer má situaci zapeklitou. Chci, aby sám od sebe fungoval při světlém i tmavém nastavení včetně zvýrazňování syntaxe. Ale uživatelé si pomocí adminer.css můžou definovat vlastní styl. V takovém případě na výchozí tmavý vzhled přepínat nechci, protože uživatelské styly na to nejsou připravené (mění většinou jen některé barvy, ne všechny). No pak ale zase existují tmavé styly, pro které chci zapnout aspoň tmavé zvýrazňování syntaxe. Jak jsem to vyřešil? Kromě adminer.css se nově hledá i soubor adminer-dark.css, který by měl používat tmavé styly. Dále je rozhodování takovéto:

  1. Nalezl se adminer.css i adminer-dark.css? Použij automatické přepínání podle operačního systému se všemi styly.
  2. Nalezl se pouze adminer.css? Použij jako základ světlý styl Admineru a světlý styl zvýrazňování syntaxe.
  3. Nalezl se pouze adminer-dark.css? Použij jako základ tmavý styl Admineru a tmavý styl zvýrazňování syntaxe.
  4. Nenalezl se ani jeden? Použij automatické přepínání podle operačního systému.

Takový vedlejší efekt toho je, že pokud vytvoříte prázdný soubor adminer-dark.css, tak bude Adminer v tmavém vzhledu nehledě na nastavení OS. Manuální přepínání vzhledu jsem přidal jako plugin.

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

Starší články naleznete v archivu.

avatar © 2005-2025 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.