jakubův notes – programování a vejšplechty

Píšu o:

Zkušenosti se Zend_Search_Lucene

Jak se praví v dokumentaci, Zend_Search_Lucene je obecný textový vyhledávací engine napsaný pouze v PHP5. Praktické využití pro většinu webů – fulltextové vyhledávání. Osobně jsem si zvykl spíš používat Google a jeho site:domain.tld. Ale mít vlastní fulltextové vyhledávání je lepší, jelikož Google mě nemusí zas tak rychle zaindexovat a uživatelé jsou na zvyklí.

Zend je celkem moloch, takže nedoporučuji stahovat celý archiv, ale namísto toho využít jen SVN repozitář a provést checkout pouze potřebných adresářů a souborů:

$ mkdir -p lib/Zend
$ svn checkout \
http://framework.zend.com/svn/framework/standard/trunk/library/Zend/Search \
lib/Zend/Search
$ svn cat \
http://framework.zend.com/svn/framework/standard/trunk/library/Zend/Exception.php \
> lib/Zend/Exception.php
$ find lib/ -name ".svn" | xargs rm -rf

Zend potřebuje, aby byla správně nastavena include_path. Je-li framework umístěn v podadresáři lib/, pak je potřeba include_path nastavit nějak takto:

set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'lib' .
    PATH_SEPARATOR . get_include_path());

Ještě než se člověk pustí do práce s indexem, je potřeba nastavit locale. Tohle mě docela potrápilo. Na localhostu všechno fungovalo, jak by mělo, ale na produkčním serveru se výsledky hledání nějak podivně omezily jen na pár věcí – jako by se nezaindexovalo posledních pár slov. Zkusil jsem vše přeindexovat asi 3krát, ale pořád to samé. Kdyby mě rovnou napadlo se podívat do chybového logu, mohl jsem serveru ušetřit hodně procesorového času. Nejdříve jsem si logu vůbec nevšímal, ale jak dosáhl velikosti někde kolem 3 MiB, přilákal tím tak trochu mou pozornost. Problém byl v jiném nastavení locale u mě doma a na produkčním serveru, takže iconv() skončilo s Notice o nepovoleném znaku, čímž se zpracovala jen část řetězce. Doma jsem měl nastaveno UTF-8, na produkčním je asi ISO-8859-2, takže setlocale():

setlocale('cs_CZ.UTF-8');

Fulltext jsem potřeboval pro jednu instanci Shopaholic. Nakonec jsem se rozhodl, že indexovat se budou akorát produkty – u e-shopu lidi stejně nic jiného nezajímá (alespoň doufám). (Jelikož jsem na této instanci provedl ještě nějaké úpravy, které by nebylo dobré zahrnout do hlavní větve, bude chvilku trvat, než se fulltext dostane do repozitáře na GitHubu.)

Celé indexování se Zend_Search_Lucene probíhá ve vytváření dokumentů a jejich ukládání do indexu. Každý dokument obsahuje různá pole, pomocí kterých se dá vyhledávat. Jedno pole je pro vyhledávání výchozí, a tak se při psaní dotazu nemusí uvádět jeho jméno.

Pole jsou různých typů a mají různé vlastnosti. Pro aplikace, kde slouží jako persistentní úložiště databáze, je podle mě nejlepší zvolit pro všechna pole typ UnStored – bylo by zbytečné data z databáze ještě replikovat v indexu. Jediné, co Shopaholic ukládá do indexu, je ID produktu, podle kterého se poté při vyhledávání podnikne dotaz na databázi a vyberou skutečná data.

Když se na to podíváme z toho pohledu, že data jsou v databázi, pak možnosti indexování jednotlivých polí ztrácí význam. Řekněme, že název produktu je ukládán do pole name výrobce do manufacturer a popis produktu do description. Jako výchozí se zvolí pole description. Ale teď chce zákazník vyhledat všechny produkty od výrobce foobar. Aby tak mohl učinit, musel by do vyhledávacího políčka zadat manufacturer:foobar. Ale kdo tohle udělá? Zákazník chce prostě zadat foobar a mít výrobky od společnosti foobar.

Takže všechny informace, které by mohl chtít zákazník hledat, je nakonec potřeba dát do jednoho pole a to nastavit jako výchozí. Zvažuji, jestli do hlavní větve ponechat i další pole (kvůli pokročilejším dotazům), nebo se jich úplně zbavit a nechat jen jedno jediné? Spíš zbavit, Shopaholic má směřovat k co největší jednoduchosti – easy yet powerful, a ne powerful yet easy (protože to stejně nikdy pak easy není).

Potom tu vyvstává problém, jak aktualizovat index, když se některý z produktů změní. Jsou dvě možnosti, každá má svá pro a proti:

  1. aktualizovat index hned při jakékoli změně produktu

  2. vyčlenit změny zvlášť

První možnost přidá, a to docela dost, na čase všech operací, takže práce s produkty je pomalejší. Ale zase zachovává index neustále aktuální a zákazník vždycky najde to, co je právě teď v databázi. U druhého řešení je to naopak – práce s produkty je plynulejší, ale zákazník nemusí vždy najít to, co vlastně chtěl, protože se to mezitím mohlo změnit. A taktéž přidává nutnost do rozhraní nějak tu volbu k aktualizaci fulltextu zapracovat. Vzhledem k importu nakonec vyhrála druhá možnost.

Jak ukládat informace o tom, že produkt změnil? Databázové schéma postrádá informace o tom, kdy byl produkt vložen, či upraven (jaké to špatné mé návrhové rozhodnutí!), takže tudy cesta nevede. I když teď mě napadá, že by se to dalo sledovat pomocí změn cen – při každém ukládání produktu je zároveň zapsána cena, s kterou je v ten okamžik ukládán. Proč mě to nenapadlo dříve?

Vyřešil jsem to tedy jinak – bitmapou (a teď myslím tu bitmapu, tu strukturu, ne obrázek) ukládanou do souboru. Kód na konci článku.

Zend_Search_Lucene mě potěšilo – jak svou jednoduchostí, tak rychlostí. Originál Lucene je asi rychlejší, ale na PHP to podle mě vůbec není špatné. Taky mě to utvrdilo v tom, že Zend má své dobré části – Zend_Search_Lucene, různá API –, ale celkově je to moloch a jako základ k psaní aplikací bych ho nepoužil.

function bitmap($filename)
{
    // get all true
    if (func_num_args() === 1) {
        if (($data = file_get_contents($filename)) === FALSE) return NULL;
        $bits = array();
        for ($i = 0, $len = strlen($data); $i < $len; ++$i) {
            $byte = ord($data{$i});
            for ($j = 0; $j < 8; $j++) 
                if ($byte & (1 << (7 - $j))) $bits[] = $i * 8 + $j;
        }

        return $bits;

    // get/set bit
    } else {
        $bit = intval(func_get_arg(1));
        $set = NULL;
        if (func_num_args() > 2) $set = func_get_arg(2);
            
        // open file
        if (!($handle = @fopen($filename, 'r+b'))) return NULL;
        if (!flock($handle, $set === NULL ? LOCK_SH : LOCK_EX)) {
            fclose($handle);
            return NULL;
        }

        // read byte with needed bit
        $offset = intval(floor($bit / 8));
        $byte_offset = 7 - ($bit % 8);
        if (fseek($handle, $offset, SEEK_SET) === -1) {
            fclose($handle); 
            return NULL;
        }
        $byte = ord(fread($handle, 1));

        // get
        if ($set === NULL) {
            fclose($handle);
            return (bool) ($byte & (1 << $byte_offset));
        }

        // set
        $byte = ($a = ($byte >> ($byte_offset + 1) << ($byte_offset + 1))) |
            $byte & ~($a + (1 << $byte_offset)) |
            intval((bool) $set) << $byte_offset;

        if (fseek($handle, $offset, SEEK_SET) === -1) {
            fclose($handle);
            return NULL;
        }

        if (fwrite($handle, chr($byte), 1) !== 1) {
            fclose($handle);
            return NULL;
        }

        fflush($handle);
        fclose($handle);
        return TRUE;
    }
}

vydáno 28. 7. 2009, 15:02:51

2 komentáře (2 nové)

Zařazeno mezi:

Jak jsem skončil s Nette

Když jsem se rozhodoval, na čem postavit Shopaholic, moc jsem neváhal a říkal si, že konečně na něčem vyzkouším to slavné Nette. Nabuzen články ze seriálu Začínáme s Nette Framework jsem se odvážně vrhl na věc. První fáze byla taková, že jsem postupně vytvářel model databáze a v mezičase četl různé věci z dokumentace. Když byl model, vrhl jsem se již k samotnému programování „těch příjemnějších věcí“.

Nejdříve byla cesta dosti strastiplná – člověku, než se v tom všem zorientuje, to chvilku trvá. Dokumentace tomu moc nepomohla, protože přeci jen se mi zdá o hodně chudší než např. u Zendu. Ke konci práce na frontendu jsem už měl povědomí slušné a v administraci to jen sypalo. Nakonec jsem dodělal celý e-shop a říkal si: Paráda, mám to.

Ale v ten moment začalo to nejhorší, deployment z localhostu na produkční server. Nahraju a bílá obrazovka. Nette\Debug v produkčním módu tohle dělá, tak dám logování do souboru a odesílání chyb na e-mail. Načtu, opět bílá stránka, v errorlogu nic a schránka taky prázdná. Pohled na HTTP hlavičky odpovědi je zajímavý: HTTP/0.9 200 OK, to je všechno. Jo, tak tohle nebude dělat Nette\Debug.

Hrál jsem si s tím. A hrál a hrál, začal sahat na Application, Presenter a další a vysledoval, že provádění skončí někde v metodách formatTemplateFiles() a formatLayoutTemplateFiles(). Zkoušel jsem porůznu upravovat atp. Divné bylo, že asi tak jeden z 50 požadavků se náhodně povedl. Nakonec jsem boj vzdal.

Řekl jsem si, že by to mohlo být použitím poslední verze zdrojových kódů z repozitáře, a proto jsem provedl downgrade na verzi 0.8, která je označena za stabilní. Hurá, už to jakžtakž fungovalo. Projedu, jestli všechno jde. A hle, nejde – jeden presenter hlásí: metoda Environment::getVariables() neexistuje. Začnu bádat, na localhostu všechno jde, podívám se tedy do API dokumentace. A tam metoda Environment::getVariables() je. Kouknu do zdrojových kódů a tam opravdu není. Zkontroluji tedy na SVNko a hle, v 0.8 tahle metoda chybí.

Některé Nettí nástroje – Nette\Debug, profiler – jsou opravdu hezké, usnadňují vývoj. Ale se zbytkem je problém. Ne v tom, že by se nejednalo o kvalitní kód, či podobně, to jsem nezkoumal (tedy až na pár řádků při této deployovací slavnosti). Ale dokumentace, dokonce i API dokumentace – kámen úrazu. Framework prostě bez kvalitní dokumentace být nemůže. Může to být mistrovský kousek kódu, ale bez stejně dobré dokumentace je to jen brak, který nestojí za to používat. Snad se to trochu zlepší po srpnu.

Další věcí je zpětná kompatibilita. Jen za těch pár dnů, co jsem Shopaholic psal, se stihlo změnit hned několik věcí: vylepšená továrnička a úprava životního cyklu presenteru. Opravdu hrozný pocit, když se vám něco pod rukama mění. Může to být dobrý ukazatel stavu projektu – je krásně živý, vyvíjí se. Ale čeho je moc, toho je přespříliš. Takovýhle vývoj už neprospívá věci; je cestou do záhuby. Kult osobnosti Davida Grudla mě nezasáhl, ale byla by škoda špatným marketingem a příliš překotným vývojem poslat do kytek takový podle mě docela slibný český framework.

Možná, že se spolu s Nette ještě někdy potkáme, ale v nejbližší době to asi nebude.

vydáno 17. 7. 2009, 20:48:25

2 komentáře (2 nové)

Zařazeno mezi:

HTML za mřížemi aneb CSS grid

Dlouho jsem vzdoroval použít nějaký ten CSS framework. Říkal jsem si, že přece každá stránka (i když většina věcí je přinejmenším podobně dělaných) je ouplně jiná, takže tam nepůjde použít nějaké zobecňující řešení jako v případě aplikačních frameworků. Ale nakonec jsem se rozhoupal a u své poslední práce – e-shopu Shopaholic – zvolil použít některé z ještě nepoužitých technologií. Vlastně ne jen některé, všechny. Místo svých minimalistických „frameworků“ jsem sáhnul po Nette a místo docela osvědčeného PDO zase po dibi. Ale zpět k CSS gridu.

Překlad slova grid zní mříž, či mřížka. A přesně tím stylem se i ubírá kódování designu. Člověk si rozvrhne mřížku a do té si dosadí různé designové elementy.

Grid

Poté pomocí různých zanořování sloupců a řádků do sebe z toho vyleze požadovaný výsledek. Svým způsobem to může připomínat tabulku (sloupce, řádky…), takže někteří tvrdí, že CSS gridy jsou vlastně vracením se k předchozím generacím tvorby layoutu stránek. Ale zase, když si vzpomenu na svoje první stránky, které měly v té době moderní tabulkový layout, tak musím říct, že ono to fungovalo!

A o to právě jde. Sémantičtí maniaci vybili své síly pár roků zpět. Teď sice každý deklaruje, že jeho kódování je to nejsémantičtější vůbec a všem je jedno, jak je to ve skutečnosti. Ze slova „sémantika“ a odvozeného přídavného jména „sémantický“ se staly výrazy, které se běžně používají, nikdo neví, co to vlastně znamená, a neexistuje žádná jednotná definice. (Připomíná mi to MVC architekturu…)

Tvůrci gridů a vůbec ti, co je používají, tvrdí, že názvy CSS tříd jako row a column jsou sémantické, a tak se jich lidé nemusí bát. Souhlasím v tom, že se toho jeden bát opravdu nemusí.

Dalším problémem je, že gridy mohou do HTML zanášet značky navíc (podpůrné <div>y pro sloupce a řádky). V tom nevidím moc problém, protože stejně používám matrjošku, a tak mě nějaký ten bajtík a značka navíc nezabijí (ukamenujte mě).

Trochu větší oříšek je pozicování sloupů jiné než je jejich umístění v dokumentu. Pro věci, kde je tohle stěžejní bych asi grid nepoužil.

Co je vůbec na výběr? Nejznámější asi jsou Blueprint a 960.gs. Ale já jsem použil a hodlám nadále používat The 1KB CSS Grid. Jednoduché, přímočaré, doporučuji.

vydáno 16. 7. 2009, 10:00:00

žádný komentář

Zařazeno mezi:

Srovnání webových frameworků

Na začátek upozorňuji, že já se o to snažit nebudu. Nemyslím si, že bych měl dostatečný rozhled v technologiích a postupech práce s frameworky, takže to radši přenechám někomu jinému. Těsně před obědem (dva dny stará sekaná s bramborem, takže obrázek si můžete udělat sami) jsem se tak toulal v končinách Google Readeru, když tu zavadil můj zrak o kanál zdrojáku a tam trčící „Srovnání webových frameworků“. Jelikož jsem se poslední dobou rozhodoval, či vytáhnout kotvy z vod PHP a přesunout se (byť třebas jen na chvilku) někam jinam, a tam stálo, že se budou srovnávat frameworky hlavně v Javě, pak Ruky on Rails a z PHP Symfony a Zend, neváhal jsem.

Nečekal jsem, že bych se dozvěděl nějakou jednoznačnou informaci, že tenhle a tenhle framework je nejlepší, protože to a to a to. Závěr ve stylu: Každý se hodí na něco jiného, byl více než zřejmý ono to jinak ani nejde. (Stejné to máte, jako když se kohokoli soudného zeptáte, jaká distribuce Linuxu je nejlepší.) To je jedině dobře.

Ale hlavně mě zajímaly kapitoly o těch Javích frameworcích. .NET je pro mě trochu pasé, protože Mono nikdy vývoj od hochů z Microsoftu nedožene (jestli jo, tak si tenhle článek vytisknu a sním (pokud na to nezapomenu)) a mně se nechce mít kvůli vývoji mít Windows (na kterých se mi ke všemu nikdy nevyvíjelo moc dobře), když je tu Java, která je „multiplatformní“.

V tom mě ale tahle diplomka trochu zklamala. Chápu, že to nebylo jejím záměrem, ale popis byl trochu strohý. Srovnání mi nedalo, co jsem potřeboval – rozhodnutí, pro jaký framework se nakonec rozhodnout. A závěr jsem již hodnotil. Čekal jsem, že Javí frameworky na tom budou s dokumentací na dobré úrovni – nakonec dopadl nejlépe Symfony a Zend byl na třetím místě. Bylo by špatné zamlčet, že Spring byl druhý. Ale u něj zase nevidím nic nového, co by mi mohl do vědění přinést. (Dobře, znalosti Javy nejsou jen tak k zahození.) Jsem asi moc líný, abych se konečně odpoutal od PHP.

vydáno 14. 7. 2009, 23:02:53

žádný komentář

Zařazeno mezi:

Limonáda ničí nápady

Jsem pragmatický minimalista se sklony k perfekcionismu. Když se to spojí s programováním obecně, hledám různé cesty, jak něco napsat jednodušeji a lépe. Když se to spojí s PHP, tak jsem napsal už asi 4 „frameworky“, každý z nich jednou použil a potom zahodil jako neskutečný bastl :-). Prošel jsem mnoha stádii, ale s každým jsem se snažil o více jednoduchosti. První z mých výtvorů byl nedokonalou napodobeninou Zendu a bylo utrpení s tím pracovat. Dodělal jsem s tím jednu věc a hned to letělo z disku.

[ pokračovat ve čtení… ]

vydáno 14. 7. 2009, 15:15:43

žádný komentář

Zařazeno mezi: