Pod kapotou systému

Jak funguje framework uvnitř Bloguje? Souvisí nějak s API? Třeba to někoho inspiruje...

Po prvotní "ad hoc" verzi Bloguje přišel čas (a potřeba) systém poněkud překopat. Jak to tedy uvnitř Bloguje vypadá?

Systém obsahuje (kromě drobností) dvě základní části: Zobrazovací jádro (engine) Blox a redakční systém. Zobrazovací jádro vyšlo z původního jádra Převážně neškodného, které bylo od začátku koncipováno jako víceautorské a "víceblogové" jádro se šablonovým systémem kompatibilním s Blogger.com. (Pro zajímavost dodám, že původně jsem počítal s tím, že systém nabídne i možnost blogu na Bloxxy.) Toto původní jádro jsem upravil tak, aby generovalo statické HTML stránky (od počátku se na Bloguje počítalo s FTP uploadem stránek) a stalo se základem pro Bloguje. Postupem času bylo rozšiřováno a rozšiřováno a jak rostl počet blogů, začaly se projevovat jeho slabiny, především pomalost.

Nové jádro Blox2 jsem napsal v roce 2005. Bylo moderní, voňavé a objektové, bohužel se ukázalo, že je líné jako vandrácká veš v srpnu. Udělal jsem si pár testů a zjistil jsem, že režie objektů v PHP není tak strašná jak jsem si myslel. Zjistil jsem že je mnohem strašnější. Během čtrnácti dnů jsem napsal od základů nové jádro Blox3, které je "smradlavě-procedurální" a neobsahuje jediný řádek vysoce kvalitního objektového kódu, ale je rychlé a snadno rozšiřovatelné. Kdyby měl někdo zájem, mohu zveřejnit API pro psaní vlastních PHP modulů do Bloguje. ;)

Druhou částí je redakční systém. To je ta část, co obhospodařuje psaní příspěvků a komunikaci s bložérem. To je ta na které dělám teď. Ale není to jen tak... Většina práce navíc zůstává skryta pod kapotou a tvoří tzv. framework. (Framework je slovo, které pochází ze staré programátorské latiny a sestává ze dvou částí: "Fra", což znamená "Funkce", a "mework", což znamená "které se budou ještě někdy hodit".)

Framework na Bloguje nemá jméno, říkám mu "framework". Je postaven z několika vrstev. Nejnižší vrstva je jádro. (Jádro taky nemá jméno, říkám mu "core".) Jádro se stará o ty nejzákladnější funkce systému.

Každý skript, který na Bloguje běží, začne tím, že si includuje core.php. Core.php začne tím, že si načte konfigurační soubor pro server. Protože mi běží doma na serveru identická kopie Bloguje kvůli ladění a z té kopie dělám update serveru Bloguje, tak core.php rozlišuje konfigurační soubory podle IP adresy na které běží ($_SERVER['SERVER_ADDR']). Na svém PC doma tak mám soubor 127_0_0_1.cfg.php a v něm údaje pro připojení k domácí databázi a domácí absolutní cestu k souborům, na Bloguje si core.php načte konfiguraci tamního serveru. Nemusím proto přepisovat neustále nějaký config.inc.php nebo dávat pozor, abych si při změně v db.php nepřepsal ten na serveru...

Core.php si definuje z absolutní cesty k souborům některé své cesty – k syslib, k sysinc, k ap a k cache.

Sysinc je adresář, který obsahuje "vždy includované" soubory. Core si je načte vždycky a všechny.

Syslib je adresář, který obsahuje soubory, které jsou includovány pouze tehdy, když je volána funkce v nich obsažená. Dělám to jednoduchým trikem, prozradím později.

Ap je adresář, který obsahuje "aplikaci", tedy funkce, které nejsou součástí jádra, ale jsou specifické pro daný web. Na Bloguje to jsou funkce spojené s uživateli, příspěvky a všemi těmi věcmi okolo.

Cache je systémový cache adresář. Kvůli výkonu si cachuju kde co. Co? No, například funkce ze syslib (jsme u toho triku). Dejme tomu,že mám funkci "udělej z názvu příspěvku hezké URL", co se jmenuje niceurl a má jeden parametr (title). U takové funkce je zbytečné, aby byla vždy includována, na druhou stranu je pohodlné, když ji mám po ruce vždy když potřebuju, nemusím přemýšlet KDE JE a nemusím kvůli ní načítat velký include soubor. Tak si vytvořím soubor "niceurl.php", který obsahuje právě jen tuto funkci, a umístím si ho do syslib. Ptáte se kde je ten trik? :)

V souborech v SYSLIB jsou funkce pojmenovány jako "sl__***", tedy např. "sl__niceurl". Core.php si vždycky includuje z cache adresáře soubor syslib.cache.php. V tomto souboru jsou všechny funkce ze všech souborů v syslib dohromady a jsou v podobě:

function niceurl($title){
 include_once('niceurl.php');
 sl__niceurl($title);
}


(Lze použít i verzi s if function_exists.)

No a v jádru je integrován cache manager, který čas od času zkontroluje, zda se v syslib něco nezměnilo. Pokud se změnilo, tak přegeneruje soubor syslib.cache.php. Já mám tak ve skriptech k dispozici funkci niceurl až tehdy, když ji zavolám, a systém si může navíc syslib.cache.php předpřipravit a "předparsovat". Zpoždění je podle měření minimální, úspora času programátora veliká. :)

Část, která s touto vrstvou frameworku souvisí, je tvořená "application-specific" skripty, uloženými v adresáři ap. Zde je aplikační sysinc, aplikační syslib, aplikační cache a adresář fn, kde jsou funkce, které manipulují s daty aplikace. Aplikační funkce jsou, podobně jako syslib funkce, cachovány a pojmenovány.

Důležitou zásadou je "nesahat na data aplikace jinak než funkcemi v ap/fn".

Tak mohu na jedné doméně spustit několik více či méně oddělených služeb, které budou využívat společné jádro.

Kromě jádra obsahuje framework i další části, např.:

- lokalizační modul. Stačí mi tak ve skriptu uzavřít český text do dohodnuté sekvence znaků. Framework je před výstupem odstraní a pokud má nastavenou jinou jazykovou mutaci, tak nahradí řetězec ekvivalentem z jiného jazyka. Nemusím se starat o nějaké speciální řetězce, textové konstanty ani nahrazovat každý řetězec voláním funkce.

- uživatelskou cache. Kde to má smysl, tam si cachuji data nebo výstup skriptu do souboru. Data si ukládám jako serializovaná pole, načítám je zpět pomocí $data=unserialize(join('',file($souborcache))); Necachované skripty měním na cachované tím, že na jejich začátek umístím if (need_recache("jmenocache",3600)){ a na konec  ob_end_flush();}. Funkce need_recache vrátí 0, pokud je cache "ještě čerstvá", a při té příležitosti pošle uložená data na výstup. Pokud je cache už stará, tak funkce zaHOOKuje obsluhu pro OB (ob_handler) a vrátí 1. Obsluha OB pak uloží zachycená data do cache pro budoucí použití a pošle je na výstup.
 
- API modul. Díky oddělení aplikačních funkcí a jejich přesné specifikaci mohu velmi jednoduchým způsobem poskytnout webové API založené na RESTu. V podstatě pouze ošetřím autorizaci klienta, ostatní volání mapuju rovnou na aplikační funkce v ap/fn. Mám to o to jednodušší, že funkce v ap/fn se jmenují tak, jak by to API očekávalo, tedy např. "post.delete", "post.create" apod.

- HTML helpery. Funkce, které mi pomáhají s generováním HTML kódu. Např. onen modul na formuláře, který jsem zmínil v diskusi u DGX. Ve skriptu mám nadefinovaný formulář, jeho prvky, obsah těchto prvků, povolené hodnoty, kde to má smysl tak i GET a SET aplikační funkce s mapováním položka<=>parametr a vůbec všechno, co s formulářem souvisí. Skript se tak zjednoduší na definici formuláře a zavolání funkce "form_gen_html" s touto definicí jako parametrem. form_gen_html se postará o vygenerování formuláře, o jeho provázání s AJAX JS částí, o přidání JS kódu na kontrolu povolených hodnot, o případné úvodní načtení GET funkcí, o správnou definici submit AJAX funkce apod. Submit skript pak zavolá form_get_data (podle definice předpřipraví pole s daty převzatými z formuláře) nebo rovnou form_process (podle definice formuláře převezme data z POST a zavolá patřičnou SET funkci; já se už ve skriptu nemusím o nic starat).

Tak, toto byla blesková procházka po frameworku Bloguje se zastávkami u věcí, o nichž si myslím, že by se jimi mohl někdo inspirovat.

--- 

A na závěr tohoto drobného nakouknutí pod pokličku rovnou zodpovím na vybrané komentáře:

Kde se to dá stáhnout?
Zapomeňte!

Proč jsi o tom tedy napsal?
Protože si říkám, že by se někdo mohl inspirovat nebo že by naopak mohl inspirovat mne.

Viděls framework XYZ? Je mnohem lepší...
Nezajímá mne. Potřebuju svůj, na míru svým aplikacím.

Podle mne bys měl... Podle mne je chyba dělat... Proč jsi to neudělal jinak...
Je mi jedno co si o tom myslíte. Nesnažím se nikoho přesvědčit o tom, že je to nejlepší možné řešení, jen jsem chtěl pustit pár námětů, které se možná někdy budou někomu hodit.

Já ve svém systému dělám ještě .... a dělám to tak a tak...
Do komentářů s tím! Děkuji. :)

Jak děláš ****? Podělíš se?
Ano,  proč ne?

Autor by se měl zamyslet... Autor si tu hraje na... což vypovídá o jeho charakteru.
Jsi zapráskaný idiot.

Dne 16.02.2007

Twittni

Přidej do: asdf.sk StumbleUpon Toolbar Stumble It!

Komentáře

[1] (dond ) 16.02.2007, 14:04:11 [X] [D]
Já ve svém systému dělám (dělal jsem, když jsem ho dělal) ještě to, že jsem si připravil rozhraní (ehm, tedy tři funkce) pro práci s URL - docela často se stránky přesouvají z hlavního obsahu do archivu (zprávy z akcí atp.), takže jsem si na AP úrovni vymyslel mapovací tabulku mezi jednotlivými URL (která je kešovaná) a pomocí těch tří funkcí snadno zařídím mapování ze "staré" URL na novou (nebo výjimečně obráceně). A ta tabulka se samozřejmě může průběžně sama měnit (řadit) podle toho, na jaké "špatné" URL mi uživatelé lezou.

Funguje to samozřejmě i pro různé časově omezené formuláře, neexistující stránky a tak podobně, ale vlastně se to moc netýká PHP a v případě Bloguje to navíc nebude příliš zajímavý případ.

[2] (Arthur Dent ) 16.02.2007, 14:09:00 [X] [D]
[1] "vlastně se to moc netýká PHP a v případě Bloguje to navíc nebude příliš zajímavý případ" - to je jedno, třeba se inspiruje i někdo jiný.

Jinými slovy: Tvůj systém nemá perzistentní URL (trvalé odkazy), tak je takto přemapováváš?

[3] (dond ) 16.02.2007, 14:14:48 [X] [D]
[2] Ano, v podstatě to tak je. Většina URL je perzistentních, tj. jakmile stránka jednou vznikne, už se URL nezmění. Ale principiálně se přesunout může, pak vznikne nová URL a z té staré se stane alias pro novou, přičemž uvnitř systému se automaticky používá ta nová.

[4] (johno - Mail - WWW) 16.02.2007, 15:15:14 [X] [D]
Skvelá finta na cache je ešte toto:

$raw = '';

Toto potom stačí zapísať do nejakého PHP súboru a ten načítavať cez include();

Super na tom je to, že to parsuje rýchly PHP a automaticky fungujú všetky tie PHP akselerátorz a optimajzerz.

Ja takto includujem pri každom requeste na môj ngram generátor asi 300KB PHP súbor a je to bleskové.

[5] (johno - Mail - WWW) 16.02.2007, 15:16:52 [X] [D]
Nejako mi to zozralo môj skvelý PHP kód. Sakra Arthure!


[6] (dgx - Mail - WWW) 16.02.2007, 15:44:11 [X] [D]
johno, zjistil jsem, že načítat struktury přes include() je kupodivu velmi pomalé. Rychlejší je serializace. Avšak úplně bleskové je parse_ini_file, což se samozřejmě hodí jen na jednodušší struktury, a (opět kupodivu!) DOMDocument::loadXML(). To je drak.

[7] (Havran - WWW) 16.02.2007, 20:31:07 [X] [D]
Během čtrnácti dnů jsem napsal od základů nové jádro Blox3, které je "smradlavě-procedurální" a neobsahuje jediný řádek vysoce kvalitního objektového kódu, ale je rychlé a snadno rozšiřovatelné.

Heh, pripomina mi to Drupal - tiez je napisany proceduralne. http://api.drupal.org/api/5/file/developer/topics/oop.html

[8] (Johnny ) 16.02.2007, 23:46:21 [X] [D]
Pánové Programátoř, promiňte, že vám vpadám do diskuse já, laik, ale nechtěl by už někdo konečně vymyslet UPJPB (Univerzální Programovací Jazyk Pro Blbce), který by měl asi takovouhle syntaxi:

--začátek kódu--
Prosím vás pěkně, já bych chtěl aby tenhle program udělal todle, todle a tamhléto a to asi tak, aby to vypadalo třeba nějak takhle. díky.
--konec kódu--

??

[9] Žiadny problém (Kozo - WWW) 17.02.2007, 09:02:47 [X] [D]
[8] Takých jazykov sú mrte. (Námatkovo napr.brainfuck). Problem trochu je, že napísať v tom nieco väčšie je už o hubu. :-{)

[10] CAPTCHA (#13 - Mail - WWW) 17.02.2007, 16:13:43 [X] [D]
Arthure, tohle bude OT, ale týká se to blguje ;). Pokud po odeslání komentáře vyplňuju CAPTCHA v Opeře, vždycky projdu. Když to ale vyplňuju v IE 7, napíše mi to ERR a stránka divně problikavá. A komentář není vložen.

[11] (Arthur Dent ) 17.02.2007, 17:20:25 [X] [D]
[10] Odesílej kliknutím na tlačítko, nikoli ENTERem... Taky jsem si všiml, důvod nechápu

[12] Submit button vs. ENTER (Roman Pištěk - Mail ) 18.02.2007, 01:53:12 [X] [D]
Nebude to tím, že odeslání formuláře testuješ na hodnotu submit prvku v $_POST? Kdysi jsem narazil na to, že IE submit prvek a jeho hodnotu do POST dat odešle, jen pokud je na tlačítko kliknuto - při odenterování nikoliv :-/

[13] (Arthur Dent ) 18.02.2007, 01:59:03 [X] [D]
[12] Díky, to bylo ono! :)

[14] ( - WWW) 18.02.2007, 09:22:43 [X] [D]
já bych uvítal v bloguje nějakou funkci pro generování náhodných čísel, stejně jednoduchou jako je v php, že bych mohl při každém znovunačtení blogu, například zobrazovat jiný obrázek (z několika), mít několik sloganů a ty rotovat atd...

[15] OOP vs procedural (Hds ) 18.02.2007, 10:14:13 [X] [D]
Smím ze zeptat, jak zhruba velký byl (je) rychlostní rozdíl mezi OOP verzí a novou procedurální verzí, kterou jste napsal? Sám si píšu takový malý framework v OOP a tohle mě docela vylekalo :-)

[16] (dgx - Mail - WWW) 18.02.2007, 11:57:28 [X] [D]
[12] zajímavé. IE tlačítko neodešle, pokud je formulář odeslán enterem v textovém poli a zároveň pokud je formulář tvořen pouze jedním textovým políčkem a odesílacím tlačítkem.

Evidentně formulář může být tvořen i hidden prvky, to jsem netušil a proto jsem tuto úvahu zamítl (v Captcha formuláři jsou tuším dva hidden prvky).

[17] (dgx - Mail - WWW) 18.02.2007, 12:12:52 [X] [D]
[15] PHP4 a PHP5 se v tomhle dost liší, například volání statických metod je v PHP5 stejně rychlé jako volání funkcí, $this je v PHP5 jakoby-klíčové slovo, nepoužívají se reference u objektů, atd. To vše OOP značně pomohlo. Rychlostní bariéry mohou být skryty úplně jinde a velmi nenápadné - http://www.dgx.cz/trine/item/php-cerna-magie-optimalizace

[18] (Arthur Dent ) 18.02.2007, 12:15:43 [X] [D]
[14] Bude, Vincente, bude. Rotátor jest připraven.
[15] Asi tak: Úkoly, které dělá nové jádro 15ms dělala objektová verze 30-100ms v závislosti na složitosti úlohy. (PHP5)
[16] Ano. Udělal jsem jednoduchou úpravu, kdy jsem prvek SUBMIT přejmenoval a jeho původní jméno jsem dal dalšímu HIDDEN prvku

[19] (Arthur Dent ) 18.02.2007, 12:31:13 [X] [D]
[17] Ani toto není článek "OOP vs procedurální". :) Takže stručná odpověď, poslední na toto téma zde, prosím!

Když jsem zjistil, že je cosi shnilého v OOP FW, tak jsem si udělal jednoduché měřičské skripty. Třeba 100.000x vytvořit instanci jednoduchého objektu a zrušit ji. Pak 100.000x vytvořit jednoduché pole a zrušit. A měřit čas. Když jsem to viděl, tak jsem hlavou pokýval a zkoušel jsem dál: 100.000x vytvořit instanci objektu, zavolat jeho metodu a zrušit. 100.000x vytvořit pole, zavolat na něj funkci, zrušit. A další a další. Přepisoval jsem kousky reálných kódů do OOP a vice versa. Plodné dopoledne strávené psaním naprosto neužitečných skriptů, ale na konci jsem věděl, že na věci, kde je potřeba rychlost, rychlost a rychlost použiju:

a) OOP (1 bod)
b) Procedurální přístup (5 bodů)

[20] (dgx - Mail - WWW) 18.02.2007, 13:52:51 [X] [D]
> Ani toto není článek "OOP vs procedurální"

O tom také vůbec není řeč. Nezmínil jsi totiž podstatou skutečnost, jestli jsi testy dělal v PHP 4 nebo PHP 5. Soudím, že šlo o verzi 4, a dodávám, že ve verzi 5 je to zase trošku jinak. Ok?

[21] (Arthur Dent ) 18.02.2007, 14:16:46 [X] [D]
[20] Davide, já vím že máš ooPHP rád, ale - soudíš špatně. Šlo o verzi 5.0.4, ty testy jsem dělal loni na jaře. :-/

Pro pořádek doplním, že šlo o build z 31.3.2005, 02:44:34, systém WinXPpro/build 2600 a Apache/2.0.50 ;)

Jinak samosebou mezi PHP4 a PHP5 je v rychlosti objektových operací opravdu VELKÝ rozdíl, o tom sporu není.

A už nechci o OOP/PHP ani slyšet, debata nechť pokračuje na tom odkazu ze [7]. Děkuji!

[22] lokalizačný modul (jules - Mail - WWW) 18.02.2007, 18:56:19 [X] [D]
lokalizačný modul (LM) - to znie zaujimavo... zaujimali by ma podrobnosti.... LM nahradzuje statické texty a aj texty z db ?
vacsinou to riesim práve konstantami ;-), alebo zvlast stlpacom alebo zanamom v tabulke ....

[23] (Arthur Dent ) 18.02.2007, 19:02:16 [X] [D]
[22] Ano. LM vezme výslednou HTML stránku a nahradí všechny výskyty označených řetězců (zhruba něco jako ) svými. Je jedno, jestli se ten řetězec objevil na stránce proto, že tam je staticky napsaný nebo z databáze (pokud je už v DB označen) :)

[24] (Wu - WWW) 19.02.2007, 09:50:47 [X] [D]
Ještě tu chybí pochvalný komentář, tak ho přihodím. Zajímavý článek plný dobrých tipů, díky. Nejvíc mě zaujala cache.

[25] (llook - Mail - WWW) 19.02.2007, 12:19:41 [X] [D]
[23] Nemůže se pak stát, že nějaký uživatel něco v článku označí stejným způsobem a pak se mu to nahradí? Rozhodne se upravit nějaký článek a ve formuláři najde něco jiného než napsal?

[26] (Arthur Dent ) 19.02.2007, 12:54:47 [X] [D]
[25] Může. Proto jsem zvolil jako značku velmi nepravděpodobnou kombinaci znaků. Ale uživatelé jsou vynalézaví a už se stalo, že ji kdosi použil. Řešení 2 je zabránit uživatelům ve vložení podobné značky.

[27] (dgx - Mail - WWW) 19.02.2007, 13:35:18 [X] [D]
[26] pro ISO-8859-2 je osvědčená kombinace trojice znaků chr(262)+chr(257)+chr(-1)

[28] (Arthur Dent ) 19.02.2007, 13:55:58 [X] [D]
[26] Používám chr(67214)+chr(3.1415)

[29] (rony - WWW) 19.02.2007, 15:59:27 [X] [D]
[14] navrhy k novym funkciam Bloguje uvitame v systeme Mantis, ja viem, objavili ste, ze Arthur znovu vnima nase kecy a vyuzivate to na to, aby ste to tu zasypali podnetnymi myslienkami :-) ale mame tu Mantis, kde vas zmrska naraz viac ludi :-)

[30] (rony - WWW) 19.02.2007, 16:03:49 [X] [D]
mna osobne z clanku zaujal prave lokalizacny modul, hodne odlisne riesenie od roznych poli, premennych ci podobnych zalezitosti.

co urobi v pripade, ze mu znacky vyhovuju ale to, co je medzi nimi uz nie? Ponecha to tak ako to je?

[31] (Arthur Dent ) 19.02.2007, 16:25:12 [X] [D]
[30] Ano. Funkce je zhruba takováto:

1. Regulárním výrazem najdu řetězce "BEG xxxx END" (BEG a END jsou ty značky)
2. Kouknu se zda to mezi nima není fráze ze slovníku. Pokud ano, nahradím ji, pokud ne, tak vrátím původní obsah.

[32] (matejcik - WWW) 20.02.2007, 00:14:00 [X] [D]
a to api pro uživatelské moduly na bloguje.cz bude ? jestli "pokud je zájem", tak tímto zájem projevuji.

[33] (Arthur Dent ) 20.02.2007, 00:29:14 [X] [D]
[32] OK, napíšu vo tom článeček a budeme ho brát jako "referenční příručku" :)

[34] (LLook - Mail - WWW) 20.02.2007, 00:45:28 [X] [D]
[26] Jedná se vlastně o klasickou injekci, řešení 3 je stejné jako u ostatních injekcí.

Prostě by se v uživatelském vstupu tyto značky nahradily za odkazy do slovníku. Před výstupem by se s pomocí slovníku zase zpátky přeložily, stejný princip jako u slashování nebo HTML entit:

"Řetězec BEG od uživatele END" by se před uložením přeložil na "Řetězec BEG počáteční značka END od uživatele BEG koncová značka END".

[35] (Kvakoš - Mail ) 08.06.2008, 22:51:05 [X] [D]
Velmi pěkný a zajímavý článek, autorovi dík, překvapila mne velká režie objektů v PHP. Já píši PHP procedurálně, na OOP/PHP5 se chystám,. Zatím jsem rychlost PHP exaktně neměřil, jenom u svých procedurek párkrát a vždy jsem byl s celým skriptíkem do 2 tisícin sekundy. U některých OpenSource slavných implementací jsem se divil, proč na úvodní stránku musím čekat i několik sekund. Menším měřením jsem zjistil, že chyba není v PHP, ale kombinaci dvou nepříznivých faktorů -> CSS soubor generovaný PHP skriptem měl 114 KB a hosting, kde jsme systém instalovali měl omezenu rychlost odesílání CSS souborů, což zapříčinilo tu velkou prodlevu.

Je ale fakt, že rychlé namíru ušité PHPO skriptíky zaberou množství programátorova času. Takže se asi vyplatí koupit silnější počítač a jít do OOP.

Ještě jednou chválím autora za pěkný článek a připojuji, že to je na PHP i CSS pěkné, že vždy existuje mnoho cest jak dojít k cíli.