Powered by slightly modified MediaWiki and TED Notepad.

Doc:Dev/Jadro

Autor: Juraj Šimlovič

Contents

Vrstvy systému

Image:Doc - Layers.png

Aplikácia webAlbumizer je rozdelená do štyroch samostatných vrstiev: databáza, vrstva entít, aplikačná vrstva, grafické rozhranie.

Databáza a vrstva entít

Databáza obsahuje tabuľky s výhradne základnými integritnými obmedzeniami a reláciami medzi tabuľkami. Priamo v databáze nie sú žiadne vložené procedúry, funkcie ani triggre.

Procedúry, funkcie a triggre sú implementované až vo vrstve entít, ktoré s databázovými tabuľkami pracujú a oddeľujú ich od daľších vrstiev. Dôvodom tohto prístupu bola predovšetkým slabá podpora procedúr a triggrov v MySQL v čase návrhu riešenia. Vrstva entít navyše poskytuje zapúzdrené API rozhranie k databáze. Aplikačná vrstva tak pristuje k datam vyhradne cez inštancie entít.

Aplikačná vrstva a grafické rozhranie

Aplikačná vrstva je od grafického rozhrania (tj. výstupu do prehliadača) oddelená predošetkým časovo. Pri každom požiadavku uživateľa sa najskôr vykonajú všetky potrebné operácie (napr. načítanie z databáze, úprava v databáze, odoslanie spáv a pod.); až potom sa generuje samotný výstup do prehliadača.

Keďže generovanie výstupu je vždy kompletne oddelené od spracovania požiadavku, je možné spracovávať akékoľvek požiadavky na pozadí a bez zbytočného výstupu. Eventuelne by bolo tiež možné implementovať rôzne druhy výstupných formátov (XHTML, XML, atd.) bez nutnosti akokoľvek upravovať aktuálnu aplikačnú logiku.

Jadro systému

Image:Doc - Core.png

Jadro aplikácie sa skladá zo štyroch základných častí: databáze, vrstvy entít, statickej a nestatickej časti systémových objektov .

Databáza

Aplikácia ukladá všetky (väčšinu) dáta do databáze. Táto databáza môže byť umiestnená i na inom stroji a komunikácia medzi aplikáciou a databázou používa jazyk SQL. Ku komunikácií pritom používa dve spojenia, jedno výhradne na čítanie a druhé na úpravy. Takýto prístup umožňuje napríklad použitie viacerých slave serverov (ktoré by replikovali dáta master serveru). Aplikácia samotná už však nerieši problém výberu vhodného slave/master serveru.

Statická časť

Statická časť jadra obsahuje predovšetkým triedy, ktoré nijak nesúvisia s aktuálne bežiacimi požiadavkami užívateľov. Sú to napríklad triedy obsluhujúce nastavenia systému; podporné triedy určené na validné generovanie výstupného XHTML formátu; ďalej triedy obsluhujúce dotazy do databáz; a nakoniec triedy poskytujúce preklad do rôznych jazykov.

Nestatická časť

Nestatická časť jadra obsahuje triedy, ktoré sú priamo závislé na aktuálne bežiacej požiadavke užívateľa (napr. zobrazenie zoznamu fotiek). Sú to napríklad triedy riadiace spracovanie požiadavky a následného grafického výstupu, ďalej triedy určené k spracovaniu vstupných dát; a v neposlednej rade i triedy určené k spracovaniu výstupných formátov.

Entity

Entity, vzhľadom na svoju úlohu v systéme patria v podstate jednak do nestatickej časti jadra a jednak do aplikačnej logiky. Ich primárnou a jedinou úlohou je oddeliť databázu od zvyšku aplikácie a zapuzdriť SQL volania do databáze v logických metódach.

Databáza

Návrh databázovej štruktúry vyplynul priamo z analýzy a počas vývoja projektu sme v ňom boli nútený robiť len minimálne zmeny. Do databáze sme nevkladali žiadne procedúry ani triggre. Logiku nad možnosti základných dátových obmedzení sme vždy delegovali do vrstvy entít.

Kompletná štruktúra databáze je uvedená prehľadne v kóde triedy InstallMakeDB a je pomerne obšírna, pretože tabuľky obsahujú množstvo stĺpcov nepodstatných atribútov (name, description, location, atd).

Skrátená verzia databázovej štruktúry, v ktorej sú ponechané iba skutočne významné stĺpce (ako sú napríklad primárne a cudzie kľúče, prípadne vyhľadávacie kľúče) je nasledovná:

table_user

Tabuľka všetkých užívateľov.

`uid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`login` VARCHAR UNIQUE KEY NOT NULL,
`state` ENUM('waiting-mail', 'waiting-approval', 'approved', 'banned') NOT NULL DEFAULT 'waiting-mail',
INDEX (`state`),
`session` VARCHAR UNIQUE KEY,
`photo` INT UNSIGNED REFERENCES table_photo (pid) ON DELETE SET NULL,
INDEX (`photo`),

table_user_settings

Tabuľka nastavení a oprávnení užívateľov. Dôvodom na oddelenie užívateľov od ich nastavení bolo veľké množstvo stĺpcov. Tabuľka užívateľov predstavuje atribúty, podľa ktorých sa dá filtrovať a triediť. Naopak tabuľka user_settings obsahuje dáta, ktoré sú podstatné len pre aktuálne prihláseného užívateľa.

`user` INT UNSIGNED PRIMARY KEY REFERENCES table_user (`uid`) ON DELETE CASCADE,

table_password

Tabuľka hesiel užívateľov. Nemá primárny kľúč, pretože užívateľ si môže to isté heslo definovať teoreticky i viac krát.

`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
`passwd` CHAR(32) NOT NULL,
INDEX (`user`, `passwd`),
`once` BOOL NOT NULL,

table_friend

Tabuľka priateľov.

`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
`friend` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
PRIMARY KEY (`user`, `friend`),

table_enemy

Tabuľka nepriateľov.

`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
`enemy` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
PRIMARY KEY (`user`, `enemy`),

table_mail

Tabuľka neodoslaných mailov.

`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
INDEX (`user`),

table_image

Tabuľka obrázkov a ich originálnych dát. Po úspešnom uploade súboru sa vždy nahrajú originálne dáta do tejto tabuľky a zostávajú tak v databáze, nie mimo nej (napr. v súborovom systéme).

`iid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`md5` CHAR(32) NOT NULL,
`md5ex` INT UNSIGNED NOT NULL,
UNIQUE INDEX (`md5`, `md5ex`),
`data` LONGBLOB NOT NULL,

table_photo

Tabuľka fotiek. Tabuľka fotiek replikuje niektoré z informačných stĺpcov (widht, height, size, type), aby nebolo nutné prehľadávať i tabuľku image, keď sa vyhľadáva a filtruje vo fotkách.

`pid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`owner` INT UNSIGNED REFERENCES table_user (`uid`) ON DELETE CASCADE,
INDEX (`owner`),
`image` INT UNSIGNED NOT NULL REFERENCES table_image (`iid`) ON DELETE CASCADE,
INDEX (`image`),

table_photo_tag

Tabuľka tagov fotiek.

`photo` INT UNSIGNED NOT NULL REFERENCES table_photo (pid) ON DELETE CASCADE,
`tag` VARCHAR NOT NULL,
PRIMARY KEY (`photo`, `tag`),

table_photo_preview

Tabuľka zmenšených náhľadov na fotky. Tabuľka obsahuje dáta zmenšených obrázkov, pokial zápis do filesystému nie je možný. Zmenšené obrázky sa v takom prípade vždy ukladajú a čítajú z databáze.

`photo` INT UNSIGNED NOT NULL REFERENCES table_photo (pid) ON DELETE CASCADE,
`watermark` BOOL NOT NULL,
`resolution` INT UNSIGNED NOT NULL,
PRIMARY KEY (`photo`, `watermark`, `resolution`),
`data` LONGBLOB,

table_group_category

Tabuľka kategórií skupín.

`gcid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`parent` INT UNSIGNED REFERENCES table_group_category (`gcid`) ON DELETE RESTRICT,
`name` VARCHAR NOT NULL,
UNIQUE KEY (`parent`, `name`),
`icon` INT UNSIGNED REFERENCES table_photo (`pid`) ON DELETE SET NULL,
INDEX (`icon`),

table_group

Tabuľka skupín.

`gid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`category` INT UNSIGNED NOT NULL REFERENCES table_group_category (`gcid`) ON DELETE RESTRICT,
INDEX (`category`),
`owner` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
INDEX (`owner`),
`icon` INT UNSIGNED REFERENCES table_photo (`pid`) ON DELETE SET NULL,
INDEX (`icon`),
`name` VARCHAR UNIQUE KEY NOT NULL,


table_group_member

Tabuľka členov skupín. Vyjadruje reláciu m:n medzi užívateľmi a skupinami.

`group` INT UNSIGNED NOT NULL REFERENCES table_group (`gid`) ON DELETE CASCADE,
`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
PRIMARY KEY (`group`, `user`),
INDEX (`user`),

table_album_directory

Tabuľka adresárov albumov.

`adid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`parent` INT UNSIGNED REFERENCES table_album_directory (`adid`) ON DELETE RESTRICT,
`name` VARCHAR NOT NULL,
UNIQUE KEY (`parent`, `name`),
`icon` INT UNSIGNED REFERENCES table_photo (`pid`) ON DELETE SET NULL,
INDEX (`icon`),


table_album

Tabuľka albumov.

`aid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`directory` INT UNSIGNED NOT NULL REFERENCES table_album_directory (`adid`) ON DELETE RESTRICT,
INDEX (`directory`),
`owner` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
INDEX (`owner`),
`icon` INT UNSIGNED REFERENCES table_photo (`pid`) ON DELETE SET NULL,
INDEX (`icon`),
`name` VARCHAR NOT NULL,
INDEX (`directory`, `name`),

table_album_photo

Tabuľka fotiek pridaných do albumov. Vyjadruje reláciu m:n medzi fotkami a albumami.

`album` INT UNSIGNED NOT NULL REFERENCES table_album (`aid`) ON DELETE CASCADE,
`photo` INT UNSIGNED NOT NULL REFERENCES table_photo (`pid`) ON DELETE CASCADE,
PRIMARY KEY (`photo`, `album`),

table_album_group

Tabuľka skupín priradených k albumom.Vyjadruje reláciu m:n medzi albumami a skupinami.

`album` INT UNSIGNED NOT NULL REFERENCES table_album (`aid`) ON DELETE CASCADE,
`group` INT UNSIGNED NOT NULL REFERENCES table_group (`gid`) ON DELETE CASCADE,
PRIMARY KEY (`group`, `album`),
INDEX (`album`),

table_photo_comment

Tabuľka komentárov k fotkám.

`cid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`thread` INT UNSIGNED REFERENCES table_photo_comment (`cid`) ON DELETE RESTRICT,
INDEX (`thread`),
`parent` INT UNSIGNED REFERENCES table_photo_comment (`cid`) ON DELETE RESTRICT,
INDEX (`parent`),
`photo` INT UNSIGNED NOT NULL REFERENCES table_photo (`pid`) ON DELETE CASCADE,
INDEX (`photo`),
`user` INT UNSIGNED REFERENCES table_user (`uid`) ON DELETE SET NULL,
INDEX (`user`),

table_group_discussion

Tabuľka diskusných príspevkov v skupinách.

`cid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`thread` INT UNSIGNED REFERENCES table_photo_comment (`cid`) ON DELETE RESTRICT,
INDEX (`thread`),
`parent` INT UNSIGNED REFERENCES table_group_discussion (`cid`) ON DELETE RESTRICT,
INDEX (`parent`),
`group` INT UNSIGNED NOT NULL REFERENCES table_group (`gid`) ON DELETE CASCADE,
INDEX (`group`),
`user` INT UNSIGNED REFERENCES table_user (`uid`) ON DELETE SET NULL,
INDEX (`user`),

table_album_discussion

Tabuľka diskusných príspevkov v albumoch.

`cid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`thread` INT UNSIGNED REFERENCES table_photo_comment (`cid`) ON DELETE RESTRICT,
INDEX (`thread`),
`parent` INT UNSIGNED REFERENCES table_album_discussion (`cid`) ON DELETE RESTRICT,
INDEX (`parent`),
`album` INT UNSIGNED NOT NULL REFERENCES table_album (`aid`) ON DELETE CASCADE,
INDEX (`album`),
`user` INT UNSIGNED REFERENCES table_user (`uid`) ON DELETE SET NULL,
INDEX (`user`),

table_photo_notification

Tabuľka notifikácií na fotkách.

`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
`photo` INT UNSIGNED NOT NULL REFERENCES table_photo (`pid`) ON DELETE CASCADE,
PRIMARY KEY (`user`, `photo`),
INDEX (`photo`),

table_group_notification

Tabuľka notifikácií na skupinách.

`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
`group` INT UNSIGNED NOT NULL REFERENCES table_group (`gid`) ON DELETE CASCADE,
PRIMARY KEY (`user`, `group`),
INDEX (`group`),

table_album_notification

Tabuľka notifikácií na albumoch.

`user` INT UNSIGNED NOT NULL REFERENCES table_user (`uid`) ON DELETE CASCADE,
`album` INT UNSIGNED NOT NULL REFERENCES table_album (`aid`) ON DELETE CASCADE,
PRIMARY KEY (`user`, `album`),
INDEX (`album`),

table_ugly

Tabuľka nevhodného obsahu. Tabuľka je schopná spájať objekty ako nevhodný obsah (napríklad fotka je nevhodná iba v konkrétnom albume).

`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`user` INT UNSIGNED REFERENCES table_user (`uid`) ON DELETE CASCADE,
INDEX (`user`),
`photo` INT UNSIGNED REFERENCES table_photo (`pid`) ON DELETE CASCADE,
INDEX (`photo`),
`group` INT UNSIGNED REFERENCES table_group (`gid`) ON DELETE CASCADE,
INDEX (`group`),
`album` INT UNSIGNED REFERENCES table_album (`aid`) ON DELETE CASCADE,
INDEX (`album`),
`reporter` INT UNSIGNED REFERENCES table_user (`uid`) ON DELETE SET NULL,
INDEX (`reporter`),

table_hash

Tabuľka slúžiaca na ukladanie hodnôt za ich alebo nahodné hashe.

`hash` VARCHAR(255) PRIMARY KEY,
`str` VARCHAR(255)

Entity

Image:Classes_-_Albums.png

Vrstva entít zabezpečuje komunikáciu medzi databázou a aplikačnou vrstvou systému. Okrem dvoch statických entít (Password a PhotoTag) a jednej pomocnej entity (Storage). Sú všetky ostatné koncipované rovnakým spôsobom:

  • Definujú konštanty reprezentujúce podporované hodnoty atribútov; podporované hodnoty filtrov; a podporované hodnoty triedení.
    • napr. APPROVED, BANNED, SUGGESTED ako stavy albumu.
    • napr. FILTER_NAME, FILTER_OWNER ako filtre fotiek.
    • napr. SORTBY_NAME, SORTBY_OWNER ako triedenia fotiek.
  • Ponúkajú atribúty reprezentujúce stĺpce v príslušnej tabuľke.
    • napr. $name, $description, $author u fotky.
  • Ponúkajú zapúzdrený prístup k reláciám nad tabuľkami, tj. k asociovaným entitám.
    • napr. GetParent(), SetParent() u adresáru.
    • napr. HasOwner(), GetOwner(), SetOwner() u fotky.
  • Doplňujú metódy počítajúce dodatočné informácie nad atribútmi s tabuľky.
    • napr. GetDisplayableName() u užívateľa, ktorá hľadá zobraziteľné meno.
    • napr. GetScore() u fotky, ktorá počíta priemer známok.
  • Doplňujú metódy overujúce oprávnenia užívateľa nad danou entitou.
    • napr. CanView(), CanEdit(), CanDelete(), CanComment() u fotky.
  • Poskytujú zapúzdrené a inteligentné metódy na úpravu dát databáze.
    • napr. generické Insert(), Update(), Delete().
    • napr. špecializované UpdateSession(),UpdateState().
  • Poskytujú zapúzdrené metódy na načítanie entít z databáze.
    • napr. LoadById(), LoadByOwner(), LoadByFilter().
  • Poskytujú zapúzdrené metódy na počítanie entít v databáze.
    • napr. CountByOwner(), CountByFilter().
  • Automaticky používajú cache na načítané entity
    • napr. Dve prekrývajúce sa požiadavky na dáta z databáze generujú iba jeden skutočný SQL dotaz. Napríklad pokiaľ LoadByOwner() načíta entitu s nejakým ID, tak LoadById() je potom túto entitu schopné vrátiť z lokálnej cache bez ďalšieho prístupu do databáze.

Dátové štruktúry a zapúzdrennie

Vďaka tomu, že entity jasne definujú štruktúru objektov z databáze, celá aplikačná logika používa tieto entity ako dátové štruktúry na vzájomnú komunikáciu. Vďaka zapúzdrenosti relácií nad tabuľkami do metód je pre aplikačnú logiku možné použiť pomerne jednoduché
$group->GetOwner()->GetProfilePhoto()->GetImage()->GetData();
miesto volania zložitého SQL dotazu, ktorý by cez príslušné tabuľky našiel požadovanú informáciu. Nevýhoda tohto prístupu spočíva v tom, že vrstva entít takto vygeneruje miesto jedného zložitého dotazu štyri jednoduchšie, čo môže mať za následok zvýšený traffic medzi aplikáciou a databázou.

Poznámka: Password a PhotoTag nie sú koncipované ako ostatné entity predovšetkým pre svoju funkčnú odlišnosť od ostatných entít. Password napríklad neposkytuje LoadyBy() ani Update() metódy, ale Verify(), ktorá napríklad automaticky maže jednorazové heslá po ich úspešnom overení. U PhotoTag triedy je zase zbytočné vytvárať entitu ako objekt, keď je tag v skutočnosti iba jednoduchý reťazec znakov.

Poznámka: Trieda Storage sa na entitu v mnohom podobá, neobsahuje však žiadne metódy na úpravu dát v databáze, keďže žiadna tabuľka storage v skutočnosti neexistuje. Storage je len abstrakciou zodpovednosti a funkcionality nad pojmom photo storage, čo je v skutočnosti množina entít v tabuľke fotiek, patriacich rovnakému užívateľovi.

EntryPoint, Body, Page, SubPage

O spracovanie požiadavku užívateľa sa starajú triedy implementujúce interface EntryPoint a Body. Spojenie týchto interface je v triede Page, ktorá tvorí základ každej našej užívateľské požiadavky spracúvajúcej triedy.

EntryPoint, Body

EntryPoint je interface, ktorý definuje tzv. vstupný bod do aplikácie. Jednotlivé vstupné body sú navonok reprezentované URL adresami, na ktoré užívateľ cez prehliadač posiela svoje požiadavky. EntryPoint samotný nemá žiadne metódy, ale predpisuje konvenciu na statické metódy MakeLink(), ktoré majú za úlohu vytvoriť linku na samého seba.

Body je naopak interface, ktorý definuje výstupný bod z aplikácie, tj. miesto, kde sa spracuje užívateľská požiadavka a následne sa vygeneruje výstup do prehliadača. Body definuje dve metódy, a to Run() a Display(). Prvá z nich je určená k spracovaniu požiadavku; druhá zas naopak ku generovaniu výstupu.

Page, SubPage

Tieto dva interface (EntryPoint a Body) sú spojené v jednej abstraktnej triede Page, ktorá pre jednotlivé stránky pripravuje prostredie. Potomkovia triedy Page implementujú jednak statické vytváranie liniek (URL) na seba a jednak implementujú rutiny na obslúženie požiadaviek, ktoré tie linky predstavujú. Inými slovami, každý potomok triedy Page je zodpovedný jak za vytvorenie linky na akcie, ktoré poskytuje a jednak za ich následné obslúženie.

Špeciálnym typom stánok sú tzv. podstránky (obvykle sú to napr. editačné formuláre a pod.), ktoré nemajú vlastný EntryPoint. Tieto fungujú ako samostatné komponenty, ktoré poskytujú volajúcim stránkam istú vopred definovanú funkčnosť (napríklad zobrazenie editačného formuláru a následné uloženie dát do databáze). Tieto podstránky majú spoločného predka SubPage, ktorý implementuje iba interface Body.

Request, Link, PATH_INFO

Základný stavebný prvok pre linkovanie medzi stránkami tvorí dvojica tried Link a Request. Tá prvá ma na starosti vytváranie (na pohľad pokiaľ možno príjemných, ale zároveň) parametrizovaných URL, ktoré využívajú technológie ako PATH_INFO a pod. Request na druhej strane takúto URL po prijatí od prehliadača rozoberie na pôvodné parametre.

Význam použitia týchto objektov spočíva predovšetkým v netriviálnosti používania polí v rámci URL, ktoré pre nás boli často nevyhnutné (napr. formuláre položiek s checkboxami u každej položky). Druhou nie menej významnou úlohou je jednotné skrášľovanie týchto URL použitím dynamických PATH_INFO.

PATH_INFO je rozšírenie web serveru Apache (i ďalších web serverov), ktoré umožňuje za názvom skutočného súboru stránky špecifikovať sub-cestu, ktorá v skutočnosti už neexistuje; a to i bez použitia rozšírení ako mod_rewrite (rovnako rozšírenie Apache, umožňujúce programovateľné prepisovanie URL na iné URL).

Jednoduchým príkladom takejto PATH_INFO je URL http://example.com/dir/script.php, v ktorej /dir/script.php je skutočne existujúca stránka, a /path/info je doplňujúca cesta, presnejšie postupnosť znakov, ktorá v samotnom súborovom systéme neexistuje. Táto technológia je napríklad využitá v známom projekte wikipédia.

Link, Request

Trieda Link dokáže napríklad miesto URI tvaru /index.php?action=list&type=public&page=2 vygenerovať výrazne jednoduchšiu URI v tvare /index.php/list/public/2. K tomu využíva tzv. pathInfoMap, ktoré dodá vždy cieľová stránka (viď interface EntryPoint v predošlej kapitole). Táto pathInfoMap špecifikuje poradie a význam jednotlivých PATH_INFO častí. Prípadné ďalšie parametre, ktoré nie sú v pathInfoMap obsiahnuté sa do linky potom zakomponujú štandardným CGI spôsobom (tj. ...?name=value).

Je očividné, že druhom prípade sa z URI stratili informácie o názvoch parametrov. To však v skutočnosti nie je úplne pravda, pretože z poradia tých parametrov sa dá ich význam v čase potreby spätne rekonštruovať. Preto na druhej strane cieľová stránka použije triedu Request, poskytne mu ten istý pathInfoMap, Request podľa neho rozoberie PATH_INFO a následne vráti stránke k požadovanému parametru skutočnú hodnotu.

pathInfoMap

Jednotlivé stránky si pathInfoMap volia sami. Komunikácia parametrov medzi stránkami i v rámci stránky samotnej je viazaná výhradne na mená parametrov, takže zmena v definícií pathInfoMap by nemala nikdy vyžadovať úpravu ďalšieho kódu.

Na druhú stranu, samotné pathInfoMap sledujú hierarchiu stránok a podstránok. Niektoré stránky napríklad definujú význam prvého PATH_INFO parametru a až jej podstránky sú zodpovedné za definíciu ďalších zložiek výslednej pathInfoMap. Takto je možné, aby vedľa seba koexistovali dve rozdielne stránky a jedna využívala URI typu /index.php/albums/list/$page, a druhá URI typu /index.php/ albums/detail/$id, pričom časť /index.php/albums/ je definovaná i obsluhovaná spoločnou nad stránkou.

Output a Errors

Za riadenie výstupu je zodpovedná predovšetkým trieda Output. Táto ma jednak na starosti prácu z tzv. skinmi (popísané v ďalších kapitolách) a jednak má na starosti dohliadanie na samotný požadovaný formát výstupu.

Je to predovšetkým informačná trieda, obsahujúca množstvo nastaviteľných atribútov, nevynímajúc možnosti nastavenia automatického HTTP presmerovania, nastavovanie cookies, kódovanie stránky či titulok stránky. Obsluhuje i tzv. QuickBoxy (popísané v ďalších kapitolách).

Errors

Súčasťou triedy Output je trieda Errors, ktorá slúži na automatizované zobrazovanie správ pre užívateľa. Takéto správy sa delia na 5 typov: NOTICE, WARNING, ERROR, FATAL a QUESTION. Doplňujúcim typom je typ DEBUG, ktorý okrem samotnej správy prikladá i aktuálny back-trace k danej správe. Typ QUESTION okrem textu obsahuje i tlačítka (napríklad YES, NO, CANCEL).

Automatizované spracovanie užívateľských správ má výhody napríklad v tom, že všetky správy pre užívateľa majú rovnaký (resp. podobný) vzhľad a rovnaké umiestnenie na výstupnej stránke, ktoré je ďalej upravované konkrétnym skinom stránky (popísané v ďalších kapitolách). Samotná trieda Errors poskytuje stránkam tiež automatickú obsluhu prekladu do jazykov (stránka zadá iba číslo správy, ktorá sa má preložiť).

Exceptions

Chyby v systéme, či už chyby užívateľského charakteru, alebo fatálne chyby ako napríklad nedostupná databáza, sú ošetrované systémom typových výnimiek. Tieto výnimky môžu triedy spracovávajúce aktuálnu požiadavku odchytávať sami a reagovať podľa potreby. Neodchytené výnimky sa potom automaticky zachytávajú na úrovni triedy Process, kde sa z nich generujú užívateľsky zrozumiteľné chybové hlásenia.

Hierarchia výnimiek je nasledovná:

  • DevException – najviac všeobecná výnimka obvykle používaná na testovanie.
    • DBException – všeobecná výnimka spôsobená chybou z databáze.
      • DBDuplicateException – výnimka spôsobená chybou na UNIQUE kľúčoch.
    • MsgException – všeobecná výnimka obsahujúca správu chybového hlásenia.
      • ErrorMsgException – výnimka obsahujúca správu o užívateľskej chybe.
        • AccessDeniedException – výnimka obsahujúca správu o nepovolenom prístupe.
        • NotFoundException – výnimka obsahujúca správu o chýbajúcich dátach.
      • FatalMsgException – výnimka obsahujúca správu o fatálnej chybe.
        • InternalErrorException – výnimka obsahujúca správu o fatálnej chybe na úrovni zdrojového kódu (užívateľ pravdepodobne našiel bug v našom kóde).
        • MalformedRequestException – výnimka obsahujúca správu o fatálnej chybe spôsobenej užívateľom a jeho manipuláciou s URL adresami. Túto výnimku sme dali do kategórie fatálnych chýb predovšetkým kvôli navýšeniu bezpečnosti. Fatálne chyby sa totiž odchytávajú výhradne na úrovni procesu. Tým je zaručené, že po fatálnej chybe sa už systém nebude pokúšať niečo vykonávať.
      • MistakeMsgException – výnimka obsahujúca správu o chybe či neúspešnosti požadovanej akcie z dôvodu odstrániteľných príčin.
        • ActionFailedException - výnimka obsahujúca správu o chybe či neúspešnosti požadovanej akcie z dôvodu odstrániteľných príčin. Takou príčinou môže byť napríklad špatne vyplnený formulár alebo nesprávne zadané heslo.

Debug

Trieda Debug nám počas vývoja projektu významne pomáhala pri hľadaní chýb. Vzhľadom k tomu, že pre jazyk PHP neexistuje veľa nástrojov určených na podobné účely (breakpointy a pod.), vytvorili sme si vlastný spôsob, ktorého hlavným bodom bola tvorba trace súboru. Takýto trace súbor obsahoval informácie o tom, čo a v akom čase kód približne vykonával, aké triedy k tomu potreboval načítať, aké SQL dotazy boli posielané do databáze a pod.

Zjednodušený príklad trace súboru:

main(): Initializing...
main(): Loading common classes...
Loading class: Entity
Loading class: User
Loading class: Settings
Loading class: Database
Loading class: Process
Loading class: Body
Loading class: Page
Loading class: Lang
Loading class: Output
Loading class: Request
main(): Loading settings...
main(): Running...
Loading class: IndexPage
Process::Run(): Preparing the process..
> "Request contains 0 GET, 0 POST, 1 PATH, 1 COOKIE, 0 FILE."
>   cookie[__foto_foto_session] = "b3d87e084d3179e41d265d510e377a7d"
>   path[0] = "intro"
Database::RunSlaveQuery(): Querying the database..
> "SELECT * FROM `jsimlo_user` WHERE `session`='b3d87e084d3179e41d265d510e377a7d'"
> "User admin verified as logged-in user."
Loading language: LanguageEN
> "Language changed to English."
Process::Run(): Running Body->Run()..
Loading class: IntroPage
> "Nothing fancy to be done on the IntroPage."
Process::Run(): Body->Run() finished. Return value: true.
Process::Run(): Running QuickBox(main)->Run()..
Process::Run(): QuickBox(main)->Run() finished. Return value: true.
Process::Display(): Running Output->Display()..
> "Nothing fancy to be displayed on the IntroPage."
Process::Display(): Output->Display() finished.
Process::Display(): Process done.
> "Time taken: 0 secs."
> "Memory: 6825 kb."