PHP triky

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

Abstrakce nad abstrakcí

Zapojil jsem se do vývoje jednoho webu postaveného na Nette. Nad některými změnami, které by mi obvykle zabraly minutu, jsem strávil třeba hodinu. Aplikace je podle mě udělaná dobře, logické celky jsou oddělené, kód se neopakuje, takže v tom to není. Problém je, že kvůli několika úrovním abstrakce musím ty abstrakce nejdřív pochopit a naučit se, jak se v nich dělá to, co potřebuji. Přesně vím, co chci udělat, i jak se to ve finále udělá, ale oříšek je to přes ty abstrakce protlačit.

Příkaz po připojení k databázi

Kupříkladu jsem chtěl po připojení k databázi provést nějaký SQL příkaz. Ve starých dobrých časech jsem si ze skriptů includoval connect.inc.php, ve kterém jsem zavolal mysql_connect, za který bych si přidal to mysql_query, které jsem potřeboval. V této aplikaci je do prezenteru injectován repozitář – o to se asi nějak stará samo Nette, fajn. Repozitáře mají rodičovskou třídu, do jejího konstruktoru jsem tedy zkusil doplnit volání toho příkazu, což ale samozřejmě vedlo k tomu, že jednotlivé repozitáře příkaz volaly nezávisle na sobě a ten se tak provedl opakovaně. Konstruktor repozitářů přijímá instanci Dibi. Kde ta se bere? Mohl bych příkaz doplnit tam. Ale v kódu se přímo nikde nevytváří, asi ji zase injectuje Nette. Přímo do existujícího kódu si tedy příkaz nikam nedoplním, musím nějak přesvědčit Nette nebo Dibi, aby to udělaly za mě. Říkám si, že asi nejsem první, kdo něco takového v Dibi potřebuje. Koukám se do dokumentace, ale tam o tom nic nenacházím. V API dokumentaci projíždím seznam metod a taky tam nic nevidím. Nakonec se koukám do zdrojáku a zjišťuji, že to Dibi skutečně podporuje. Ono to je nakonec i v té API dokumentaci, ale ne moc přehledně:

Ukázka dokumentace

Pak už na první dobrou hádám, že stačí tenhle parametr přidat do sekce dibi v config.neon (syntaxe NEON je další abstrakce, ale naštěstí jednoduchá) a kód skutečně funguje.

Co když by Dibi něco takového nepodporovalo? Jsem jeden z těch, kdo by poslal pull request, ale co když by ho autor nepřijal? Tuším, že i to by se dalo vyřešit. V Nette bych si jistě mohl udělat nějakou DibiFactory, která by ten příkaz zavolala tam. Ale je to další abstrakce, kterou bych si musel nejdřív nastudovat.

Doplňování jména

V jednom formuláři je políčko name, které značí název místa, a políčko email, do kterého se zadává e-mail člověka, který název zadává. Chrome (a asi i další prohlížeče) při vyplňování name nabízí jméno člověka, což samo o sobě moc nevadí. Co je horší, že při automatickém doplnění e-mailu se přepíše i to, co je zadané v názvu. Ten je navíc o stránku výš, takže si toho člověk ani nevšimne. Můj první nápad bylo políčko prostě přejmenovat. Ve starých dobrých časech by to bylo triviální. Políčko bych změnil na <input name="place" value="<?php echo htmlspecialchars($row["name"]); ?>"> a kód, který ho zpracovává, na $row["name"] = $_POST["place"]. Přejmenovávat sloupec v databázi by bylo velmi složité, protože se v aplikaci používá na mnoha různých místech. Jak tohle udělat v aplikaci postavené nad Nette? Nepřišel jsem na to. Aplikace (nebo Nette?) automaticky provazuje formulářová políčka s daty v databázi a formulářová data zase posílá podle názvu do databáze. Asi by někam šlo doplnit tohle přejmenování, ale minimálně by to působilo jako hack.

Napadla mě jednodušší věc – políčku přidat atribut <input autocomplete>, což by v běžném formuláři byla práce na 10 sekund. Ve fóru jsem našel, jak se to dělá pro celý formulář s tím, že „pro jednotlivé inputy to není problém“. Pro mě to problém je. V dokumentaci jsem nic o nastavování vlastních atributů elementům nenašel. V API dokumentaci jsem našel metodu getControl, která vrací HTML element pro políčko. To mi přišlo dost podobné jako getElementPrototype doporučované pro nastavení autocomplete celému formuláři, tak jsem ji zkusil použít. Bohužel to ale nezabralo kvůli tomu, že metoda generuje kód pokaždé znovu a změny v dříve vygenerovaném HTML přijdou vniveč. Naštěstí jsem našel metodu setAttribute (později přejmenovanou na setHtmlAttribute), která vedla ke kýženému cíli. Povedlo by se mi to i pomocí metody getControlPrototype, která ale bohužel není v dokumentaci TextBase, kde jsem našel getControl. Je to proto, že pochází z rodičovské třídy a TextBase ji na rozdíl od getControl nepřepisuje. Navíc rozdíl mezi těmito metodami není nijak zevrubně popsán – čekal bych odkaz z jedné na druhou s vysvětlením rozdílu.

Proměnná na devu

Na vývojovém serveru jsem si chtěl nastavit nějakou proměnnou a použít ji v šabloně. Ve starých dobrých časech bych do config_local.php přidal define("MEDIA_ROOT", "https://...") a kdekoliv v kódu ji potom prostě vypsal. Jak to udělat v Nette? V config.local.neon si můžu vlastní proměnnou přidat do sekce parameters, jak ji ale pak použít v šabloně? Nepřišel jsem na to, tak jsem zvolil velmi krkolomné řešení poslání parametru do App\Model\Configuration (který se injectuje do presenterů), tam jeho uložení a následné ruční předání z prezenteru do šablony. Tuším, že tohle je úplně špatné řešení a že existuje mnohem jednodušší, ale v dokumentaci se o sekci parameters vůbec nepíše, natož aby tam bylo vysvětlené, jak ty parametry použít v jiných částech aplikace.

Závěr

Tímhle článkem rozhodně nechci říct, že abstrakce jsou špatné nebo že použité části (aplikace samotná, Nette, Dibi) nejsou dobré. Chci říct to, že každá abstrakce může něco zjednodušit, ale nejdřív se ji člověk musí naučit. Je jistě velmi pohodlné nestarat se o vytváření všech objektů použitých v aplikaci, ale pokud to nějaká abstrakce dělá za mě, tak je najednou mnohem pracnější to přizpůsobit. Stejně tak je pohodlné nemuset každé formulářové políčko vytvářet ručně, ale když ho potřebuji změnit, tak je to s abstrakcí opět složitější. Nebo vlastně ani ne, ale nejdřív tu abstrakci musím podrobně znát.

Dokumentace je v takovém případě klíčová. V uživatelské dokumentaci Dibi a Nette jsem třikrát nenašel to, co jsem potřeboval (onConnect, setAttribute, parameters). V API dokumentaci dvakrát ano, ale jednou v nepřehledné formě (onConnect) a jednou jsem sešel na scestí (getControl místo getControlPrototype). Jednu věc jsem nenašel vůbec (správné použití parameters), ale možná jen nevím, kde hledat. Na to, abych dokumentaci sám vylepšil, se necítím dost erudovaný – např. BaseControl má ještě nedokumentovanou metodu getControlPart, jaký je její smysl?

Kolem Nette je velmi aktivní komunita, takže když bych se zeptal, asi bych se dobral často k lepšímu řešení, než jaké jsem našel já. Ale už jen dobře zformulovat dotaz je těžká práce a čekat na odpověď se mi nikdy nechce – mám nějaký problém a chci ho vyřešit hned. Radši budu hodinu hledat v dokumentaci (a u toho třeba dozvím i něco dalšího) než deset minut formulovat dotaz a pak hodinu nebo den čekat na odpověď.

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

Adminer 4.7.5

Adminer 4.7.4 opravil XSS, ke kterému došlo naštěstí pouze v případě, že Adminer byl dostupný na URL ve tvaru /data:.

Adminer 4.7.5 zlepšuje kompatibilitu s PostgreSQL 12, které odstranilo několik systémových sloupců (bug #719). Další změny jsou drobnější:

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

Adminer 4.7.3

Adminer 4.7.3 přináší tyto změny:

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

Adminer 4.7.2

Nová verze Admineru opravuje několik drobnějších chyb.

Také jsem přesunul hosting a demo na Váš hosting.

Jakub Vrána, Adminer, 18.7.2019, diskuse: 2 (nové: 2)

Váš hosting

Roky jsem hostoval tento blog a další své stránky u kamaráda na serveru, hlavně kvůli svým bývalým projektům mysms.cz (posílání SMS přes GSM modul připojený k serveru) a matfyz.cz (e-mailové a webové aliasy pro studenty MFF UK). Teď jsem se rozhodl přestěhovat na něco standardnějšího a vybral jsem Váš Hosting. Líbilo se mi jejich VPS Centrum, kde si lze server snadno nastavit, neomezený počet domén na jednom hostingu, slušná cena a celkově dobré parametry (automatické aktualizace, dobrý uptime). Ještě před koncem sedmidenního zkušebního období jsem dospěl k závěru, že je to ono. Dobré reference jsem měl třeba i na WebSupport, tam mě ale odradila platba za počet hostovaných domén. (Účtuje se php.vrana.cz a vrana.cz jako dvě různé domény nebo jedna? Nenašel jsem to a nechci to řešit.) Některé hostingy mě vyloženě odradily svým zpátečnickým přístupem. (Např. Český hosting má na stránkách návod, jak se vyhnout placení za více hostingů a hacknout jejich systém tak, abych platil jen jednou. To by se to neobešlo bez nějakého nastavování aliasů a poddomén?)

Hlavní přednosti Váš Hostingu z mého pohledu:

Byl jsem trochu překvapen tím, že jsem na serveru dostal i roota, ale maximálně mi to vyhovuje. Většinu věcí si můžu naklikat ve webovém rozhraní, ale složitější věci (např. instalace VPN) můžu udělat z konzole. Ocenil jsem i návod na migrace – díky rsync jsem weby, maily a databázi převedl pohodlněji, než jsem se bál.

Párkrát jsem s něčím potřeboval pomoc (převod .com domény) nebo jsem si něco rozbil (sebrání práv pro zápis webovému uživateli do adresáře se sessions) a podpora byla ráno i večer pohotová a dokázala problém rychle identifikovat a vyřešit.

Pár drobností se mi nelíbilo:

Celkově jsem ale velmi spokojen. I poměrně složité operace jsem byl schopen si sám naklikat (přesun domény demo.adminer.org na druhý virtuální server a získání jeho certifikátu, i když adminer.org zůstal na prvním serveru). Oceňuji hlavně pohodlnost správy, rychlost serveru a široké možnosti (např. instalace vlastní VPN).

Za tuto recenzi jsem nedostal nic zaplaceno, nejsou v ní žádné referral odkazy a hosting si normálně platím, za takovéto služby mi není líto peněz.

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

Starší články naleznete v archivu.

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