PHP triky
Weblog o elegantním programování v PHP pro mírně pokročilé
Rozčarování z AI
Budoucnost celkem jasně vidím v tom, že AI řeknu, co má udělat a jak si to má zkontrolovat, a ona se bude tak dlouho snažit, dokud to neudělá a kontrola neprojde. Možná mám moc velké nároky, ale schopnosti modelů zatím nejsou tak daleko, aby mi ušetřily práci, pokud chci dosáhnout požadované kvality. Často mi ji naopak přidělají.
PHP_CodeSniffer
Na PHP_CodeSniffer je vidět, že byl navržen v „minulém století“ a pravidla pro něj se píšou strašně nešikovně. Když jsem potřeboval napsat celkem jednoduché pravidlo (kolem vlastností tříd může být 0-1 prázdný řádek), tak jsem to milerád přenechal modelu Claude Opus. Na specifikaci chování i v okrajových případech jsem si dal záležet, zadání mělo asi půl stránky. Model přesto přišel s řešením, které zadání nesplňovalo. Navíc bylo dost pomalé – zkoumalo všechny proměnné v souboru a ne jen vlastnosti. Hodinu jsem se s modelem hádal, ukazoval mu příklady, kde kód nesplňuje zadání, a vysvětloval mu, že takhle napsaný kód je navíc pomalý. Všelijak to záplatoval, řešení bylo čím dál ošklivější a zanášelo nové chyby. Nakonec jsem to celé zahodil a za hodinu si napsal pravidlo po svém – správné, rychlé a elegantní.
Upgrade verzí
Jeden projekt mi běžel na prehistorické verzi Nette. Požádal jsem Claude o převod na moderní verzi Nette, čehož se zhostil velmi dobře – pár problémů tam bylo, ale ty jsme rychle vyřešili. Tady jsem byl spokojen.
Stejně tak u převodu jiného projektu na moderní verzi PHP – převedl odstraněné funkce, našel zastaralé obraty a převedl i je. Při plánování jsme se dohodli, že nemá dělat shim, ale funkce má nahradit přímo v kódu. Trochu mě ale překvapilo, jak tupě převod dělá – hledá regulární výrazy a nahrazuje je něčím jiným – kódu vůbec nerozumí. Vím, že existuje napojení na MCP třeba v Idea, nebo LSP, ale to je trochu složitější rozchodit. V tomto případě regulární výrazy stačily.
Třetí převod verze dopadl katastrofálně – některé soubory nebyly v UTF-8 a Claude rozbil kódování. Chvíli jsem se ho snažil donutit je opravit, on původní verze lovil z nějaké své historie, ale nakonec jsem to vzdal, obnovil původní verzi a nechal ho udělat všechno znovu. Tentokrát s explicitní instrukcí, že některé soubory nejsou v UTF-8 a ať si na to dá pozor. Co myslíte? Dopadlo to úplně stejně a soubory jsem si nakonec převedl sám.
Jazykový model
Od té doby, co si David Grudl (velký popularizátor AI) nechává pomocí AI psát články, tak se nedají číst. Dřív měl břitký styl a věřím, že napsání dobrého textu trvalo řádově déle, než nadiktování poznámek AI s instrukcí, ať z toho udělá článek. Ale obraty jako „Jedna uživatelka nahrála hlasové poznámky z procházek s kočárkem“ tento styl prostě nemají. Dřív jsem rád poslouchal i jeho podcast Na vlnách EKG. Nový podcast Uměligence předčítaný robotickým hlasem se poslouchat fakt nedá, přestože mi je zaměřením mnohem bližší.
Windows
Část mých problémů je způsobena asi i tím, že Claude Code pouštím na Windows. Tam se dá spustit buď v PowerShellu nebo ve WSL. Půlka věcí nefunguje tam, druhá půlka tam. Propojení služeb (které vnímám jako největší skok dopředu) je kvůli tomu o dost komplikovanější. Pokud mám např. Claude spuštěný ve WSL a vývojový server mi běží ve Windows, tak bych Clauda ve WSL chtěl naučit koukat se na výsledky na vývojovém serveru. Sám to protunelovat neumím a když jsem to zadal Claudu, tak si s tím taky neporadil.
Integrace
Kde mi AI docela pomáhá, jsou jednoduché úlohy – hledám nějaké nastavení v Idea a ChatGPT ho celkem spolehlivě najde. Proč ale nemůžu vlastními slovy říct přímo Idea, co chci nastavit? Místo toho je tam úplně stupidní AI generování commit zpráv, které se nedá vypnout.
Stejně tak v Google Sheets – nemůžu si vzpomenout, jak se jmenuje nějaká funkce, tak Gemini řeknu, co potřebuji udělat (nejlépe s konkrétním vstupem) a funkci dostanu. Opět – proč kvůli tomu musím z Google Sheets odcházet (nebo za to platit, když ve vedlejším okně to mám zdarma)?
Co mě baví
Nedávno jsem objevil Wikidata, ze které se dají data získávat jazykem Sparql. Prvních pár dotazů jsem si nechal napsat pomocí AI a učící křivka tedy byla velmi strmá – rychle jsem dostával výsledky, které jsem potřeboval. Ale jak se mé požadavky komplikovaly, tak se do toho AI začala zamotávat a já se s ní víc času musel hádat o tom, že má něco špatně nebo pomale (nebo oboje). Organizaci dat ve Wikidata a způsob jejich dotazování jsem pochopil a dotazy si už většinou snadněji napíšu sám. Nebo u těch vygenerovaných aspoň dokážu rychle identifikovat, pokud je na nich něco špatně a buď to opravit, nebo AI říct, co má špatně.
U toho jsem si uvědomil, co mě při práci vlastně baví – často to není jen samotný výsledek, ale i způsob, jak se k němu dostanu. Něco pochopit a dokázat to aplikovat na nové problémy – to mi přináší radost. Ne hádání se s tupou AI, co zase udělala špatně, nebo spokojení se s neefektivním kódem. Nebaví mě ani konfigurace – nastavit robota, aby si dokázal spustit webový server a v prohlížeči si ověřoval výsledky. U každého projektu se to dělá trochu jinak a vždycky je potřeba to nějak nastavit.
Budoucnost
Modely se rychle zlepšují a s čím měly včera problémy, už dnes obstojně zvládají. Pořád mi ale přijde užitečné, když problému sám rozumím a poznám, když AI něco dělá špatně. Nudné práce bude ubývat, ale pro mě je větší nuda vysvětlovat AI, co má udělat, než problém pochopit a udělat to sám.
Adminer 5.4.2
Adminer 5.4.2 opravuje bezpečnostní chybu DoS: útočník může Admineru podstrčit nesprávně formátovanou verzi a Adminer se pak všem uživatelům na tomto serveru odmítne nahrát. Chyba postihuje verze 4.6.2 až 5.4.1. Pokud nemůžete Adminer aktualizovat, dá se chyba potlačit tak, že souboru adminer.version v dočasném adresáři (obvykle hodnota upload_tmp_dir) seberete práva zápisu.
Způsob kontroly nové verze byl dříve dost komplikovaný – z adminer.org se nahrál <iframe>, ten pomocí postMessage poslal informaci zpátky Admineru, který ji uložil do cookie a také do souboru na serveru. Tento soubor se vytvářel kvůli uživatelům s vypnutými cookies, kteří jinak server Admineru bombardují žádostí o novou verzi při každém požadavku. Aby soubor nemohl nikdo podstrčit, tak byl podepsaný – a právě při ověřování podpisu mohlo dojít k DoS.
Kontrolu nové verze jsem teď díky CORS dramaticky zjednodušil – Adminer se ptá adminer.org rovnou přes fetch a novou verzi ukládá jen do cookie. Snad mi to nesloží server. Připomínám, že kontrola nové verze se pomocí pluginu dá přepnout na GitHub, nebo úplně vypnout.
Další změny
- V editačním formuláři se JSON vypisuje hezky.
- Při změně tabulky lze upravovat víceřádkové generované hodnoty.
- Z textů, které začínají
//a vypadají jako URL, se nyní vytváří odkaz stejně jako když začínajíhttps://. - Některé ovladače v pluginech vracejí jako hodnoty tabulky pole. Toto pole se teď vykresluje jako vnořená tabulka.
- Některé databáze mají právo pro řazení podle sloupce. Adminer ho nyní respektuje při vytváření odkazů v záhlaví tabulky.
- V editačním formuláři se nyní zobrazují i hodnoty, které se nedají upravit. To jsou jednak generované sloupce a jednak sloupce bez práva modifikace (u některých databází např. auto increment).
- Při výpisu dat se zkracují textové sloupce. Ale typů, které se zkracovaly, postupně přibývalo, tak jsem podmínku znegoval – nyní se zkracuje vše kromě čísel a datumů.
- Pro ošetření hodnot cookie se používalo
urlencode. To asi někdy může způsobit nějaké komplikace, tak jsem to přehodil narawurlencode. (bug #1208) - V TSV exportu se do uvozovek zavíraly i hodnoty s čárkou. To sice ničemu nevadilo, ale nebylo to pěkné. (bug #1238)
- V MariaDB není název checků unikátní, takže se mohly zobrazit i checky z jiné tabulky. (bug #1135)
PostgreSQL
- Při vytváření tabulky se podle názvu sloupce nabízí vytvoření cizího klíče stejně jako to už 16 let dělá Adminer v MySQL. Změna přitom byla na pár řádek. Těch člověko-hodin, co jsem tím mohl ostatním ušetřit…
- V exportu checků chyběly závorky.
- … a naopak se duplikovalo
DEFERRABLEu cizích klíčů. - Lze vytvářet
NOT DEFERRABLEcizí klíče. - V exportu sekvencí a pohledů se nyní uvádí schéma stejně jako u tabulek.
- Opravil jsem zobrazování složitých generovaných sloupců.
- Unikátní částečné klíče se nyní označují jako unikátní. (bug #1172)
- U zděděných tabulek se správně zobrazují tabulky z jiných schémat. (bug #1221)
- Naopak všude kromě PostgreSQL se zobrazují explicitně vytvořené
NOT NULLchecky. (bug #1237)
Pluginy dostaly metody showVariables() a showStatus() (bug #1157). Také mohou být v jakémkoliv jmenném prostoru, pokud dědí z Adminer\Plugin.
Sponzoři
Mezi sponzory Admineru chci po první vlaštovce PikaPods přivítat také AI-Text-Humanizer.com a Humanize AI Text.
Adminer 5.4.0
Delší dobu se v Admineru neobjevila žádná závažná chyba, tak jsem nemusel vydávat novou verzi, až počet změn hodně nabobtnal.
- Při vyhledávání ve všech tabulkách se dá nově zadat jakýkoliv operátor. Dřív se vždy používalo
=. - Při
GROUP BYselectu (např. vybrání sloupce a k tomu nějaké agregační funkce) se výsledky už neřadí sestupně. - V SQL příkazu lze nově exportovat i zadaný příkaz (bug #1092).
- V přehledu databáze jsem přidal odkazy na sekce (např. na rutiny), což se hodí, když je toho v databázi hodně.
- Při importu se zobrazí varování v případě překročení
max_file_uploads. - V seznamu existujících přihlášení v menu přihlašovací stránky se za uživatelským jménem zobrazuje
@, i pokud je server prázdný. - V přehledu databáze se u materializovaných pohledů zobrazuje velikost dat a indexů.
- Při zvýrazňování syntaxe se nově kromě tabulek odkazují i rutiny.
- Ve změně struktury tabulky se přesune focus na nově přidaný sloupec.
- Do hlášek o provedených SQL příkazech jsem přidal tlačítko na jejich zkopírování.
- Po nahrání více řádek ve výpisu se spouští zvýrazňování syntaxe, hlavně kvůli JSON sloupcům.
- Je možné se připojit k serveru pomocí IPv6 adresy (bug #1095). Dřív se chápala jako server s portem.
- V MySQL jsem opravil ukládání
enums prázdnou hodnotou (bug #1152). - V MySQL 5.0- se nenahrávají informace o rozdělených tabulkách (bug #1099).
- V MariaDB se parsuje
COLLATEv definici rutiny (bug #1104). - V Elasticsearch lze smazat alias.
Další spousta změn je pro PostgreSQL, který jsem začal více používat:
- U zděděných tabulek se zobrazuje struktura.
- Zobrazují se výrazy indexů, např.
lower(filename). - K operátorům jsem přidal
SQL, který dovoluje hledat podle libovolného SQL výrazu. - V menu se skrývají jen oddíly, ne všechny zděděné tabulky.
- Lze porovnávat JSON sloupce (bug #1107).
- Kromě textových sloupců se při výpisu zkracují i hodnoty ve sloupcích
hstore. - S operátorem
intervalse hodnoty předávají jako řetězce. - Opravil jsem volání funkcí s bezejmennými parametry.
- Opravil jsem volání funkcí vracejících tabulky.
- Uživatelské typy obsahující
fileuž se neberou jakoblob(bug #1118). - Při exportu databáze se respektuje požadavek na její
DROPaCREATE(bug #1140). - Ve verzi 11 a nižší jsem opravil duplicitní
oidpři získávání informací o tabulce (bug #1089).
Pluginy:
- Přidal jsem metody
afterConnect(),processList()akillProcess(). - Vytvořil jsem plugin pro zobrazení čísla řádku ve výpisu (bug #1106).
- Vytvořil jsem plugin pro nastavení timeoutu dotazů.
Php-Wasm
WebAssembly je pozoruhodná technologie. Umožňuje běh binárních programů v různých prostředích, především pak v prohlížečích. Do Wasm se dá kompilovat z různých programovacích jazyků, pro PHP existuje Php-Wasm. To umožňuje spouštět PHP kód v prohlížeči na počítači klienta. Výhoda je mnohem rychlejší odezva, než když se data pro zpracování posílají na server a zpátky, dále pak nezatěžování serveru. Další využití je, pokud spouštěnému kódu nevěříte a nechcete si ho pouštět na server.
Php-Wasm jsem si vyzkoušel na demu PhpShrink:
let highlightShrink;
(async function () {
// momentálně je potřeba použít alfa verzi, vydaná 0.0.8 toto nepodporuje
const { PhpWeb } = await import('https://cdn.jsdelivr.net/npm/php-wasm@0.0.9-alpha-29/PhpWeb.mjs');
const php = new PhpWeb({files: [
// namapujeme URL jako soubor v prostředí Wasm; URL musí vrátit PHP kód nezpracovaný serverem
{name: 'phpShrink.php', url: 'https://raw.githubusercontent.com/vrana/PhpShrink/refs/heads/main/phpShrink.php'}
]});
// výstup a chybový výstup nepoužíváme, tak ho jen pošleme do konzole
php.addEventListener('output', event => console.log(event.detail));
php.addEventListener('error', event => console.error(event.detail));
// zpracuje PHP kód z URL zadaného výše
await php.run('<?php include "./phpShrink.php"; ?>');
// největší magie - PHP funkci zpřístupní do JavaScriptu
highlightShrink = await php.x`function ($input) { return highlight_string(phpShrink($input), true); }`;
shrink();
document.getElementById('input').oninput = shrink;
})();
function shrink() {
document.getElementById('output').innerHTML = highlightShrink(document.getElementById('input').value);
}
Php-Wasm má mnohem širší možnosti, kód se dá definovat i rovnou ve <script type="text/php">, existuje také CGI umožňující třeba i běh WordPressu v prohlížeči.
SimpleXML: jmenné prostory
Extenzi SimpleXML mám v oblibě pro velice jednoduchou práci s XML dokumenty. Konec konců jsem se jí nechal inspirovat při tvorbě NotORM. Např. pro získání hodnoty atributu <meta charset> se dá použít jednoduché $html->meta["charset"]. Pro složitější dotazy se dá použít XPath, např. všechny tabulky v dokumentu pomocí $html->xpath("//table").
Když dokument obsahuje jmenné prostory, tak je práce složitější a část magie mizí. Mějme takovýto dokument:
<movies xmlns="http://default" xmlns:a="http://a">
<movie xml:id="movie1" a:link="IMDB">
<a:actor>Onlivia Actora</a:actor>
</movie>
</movies>
Dá se s ním pracovat takhle:
<?php $movies = simplexml_load_string($file); // Namespace http://www.w3.org/XML/1998/namespace is available as "xml". echo $movies->movie->attributes("xml", true)["id"] . "\n"; // Namespaced attributes can be accessed with attributes(). echo $movies->movie->attributes("a", true)["link"] . "\n"; // Using namespace URI allows document to use any namespace alias. echo $movies->movie->attributes("http://a")["link"] . "\n"; // Children can be accessed with children(). echo $movies->movie->children("http://a")->actor . "\n"; // Using xpath() with namespace requires registering it first. $movies->registerXPathNamespace("a", "http://a"); echo count($movies->xpath("//a:actor")) . "\n"; // Even the default namespace must be registered. $movies->registerXPathNamespace("default", "http://default"); echo count($movies->xpath("//default:movie")) . "\n"; ?>
Starší články naleznete v archivu.

