V předchozí kapitole jsme se již zmínili o stránkách a skriptech. Ukázali jsme si, jak můžeme s pomocí parametrů PAGE resp. SCRIPT v HTTP požadavku otevřít příslušnou stránku resp. provést příslušný skript. Stránky a skripty společně s dosud nezmíněnými knihovnami budeme nadále souhrnně označovat jako komponenty.
Komponenty jsou v aplikaci logicky rozděleny do jednotlivých modulů. Moduly, komponenty a akce (akce budou představeny později) celkově zahrneme pod výraz entity. Vztah mezi moduly a různými komponentami ilustruje následující obrázek:
Entity v aplikaci a vztahy mezi nimi
Všechny stránky a skripty aplikace jsou logicky rozděleny do modulů. Například všechny stránky a skripty týkající se uživatele (přihlášení a odhlášení se, změna osobních údajů apod.) mohou být v modulu /user/, zatímco hlavní stránka celé aplikace či kontakt na provozovatele aplikace by byly v modulu /main/.
Ve skutečnosti jsou moduly běžné adresáře, zatímco stránky a skripty jsou soubory v nich. Všechny moduly jsou uložené ve speciálním adresáři, obecně jej nazvěme MOD_ROOT. Ten může být kdekoliv, konkrétní umístění se určuje při počáteční instalaci v konfiguraci aplikace. Důležitou podmínkou je pouze, aby byl mimo webový prostor, mimo DocumentRoot webového serveru [19]. V opačném případě by bylo možné kterýkoliv soubor v něm adresovat přímo přes webový server zadáním příslušného URL do HTTP požadavku [20], [12], bez využití Controlleru a jeho bezpečnostních mechanizmů. Na druhou stranu musí mít do MOD_ROOTu přístup uživatel, pod kterým je webový server spuštěn.
Moduly nemusí být jen jednoúrovňové, mohou tvořit logickou hierarchickou strukturu. Mějme například internetové noviny umístěné celé do modulu MOD_ROOT/news/. Pak všechny stránky a skripty pro administraci článků budou v submodulu MOD_ROOT/news/admin/article/, zatímco správu registrovaných autorů nalezneme v submodulu MOD_ROOT/news/admin/author/.
Stránky, skripty i dosud nezmiňované knihovny označíme souhrnně jako komponenty. Jednu komponentu tvoří stránka, skript a knihovna se stejným symbolickým jménem. Mluví-li se tedy například o komponentě /user/edit, myslí se tím souhrnně stránka PAGE=/user/edit, skript SCRIPT=/user/edit a knihovna LIBRARY=/user/edit. Kterákoliv součást může chybět, takže lze mít třeba komponentu, ve které bude jenom stránka.
Skutečné soubory jedné komponenty se od sebe odlišují předponami a příponami, jejichž podoba je jednotná napříč celou aplikací. Ve standardním nastavení:
Výše uvedenou komponentu /user/edit tedy ve skutečnosti tvoří tyto tři soubory, z nichž může kterýkoliv chybět:
Výpis zaregistrovaných modulů a komponent
Stránka obstarává grafický výstup aplikace, zpravidla ve formátu (X)HTML. Stránka by neměla provádět žádné aktualizace aplikačních dat, ty zajišťují skripty; výjimkou jsou volání přímo související se zobrazením, například inkrementace počítadla navštívených stránek.
Skripty primárně zpracovávají formulářová data odeslaná ze stejně nazvané stránky. Obecně řečeno, provádějí různé modifikace aplikačních dat, a to buď přímo, nebo přes volání funkcí či metod z některé knihovny. Na základě výsledků volání pak mohou určit další stránku, která se má zobrazit. Pro tento účel definuje framework funkce direct() a redirect(). První z nich pokračuje dál na určenou stránku. Druhá otevře danou stránku před HTTP redirect, čímž zabrání případnému pozdějšímu znovuodeslání právě zpracovaného formuláře. Skripty už ze své podstaty nesmí generovat žádný výstup, to je úkol stránek.
Knihovny slouží obecně k uchování částí aplikace sdílených různými stránkami či skripty. Primárně jsou v nich uloženy definice různých objektových tříd, které se podle potřeby natahují do příslušných stránek či skriptů. Vložení knihovny se provádí přes funkci library(). Například knihovnu /user/edit vložíme do stránky či skriptu voláním:
<?php
require_once library('/user/edit');
?>
Funkce tedy vrací systémovou cestu k souboru s určenou knihovnou. Analogicky existují i funkce page() a script(), ty však v praxi naleznou uplatnění v mnohem menší míře. Místo příkazu require_once lze samozřejmě podle situace použít i require, include nebo include_once.
Není-li celá aplikace postavena výlučně na OOP přístupu, lze knihovny používat i přímo pro vkládání částí PHP nebo (X)HTML kódu. Například do knihoven /main/head a /main/foot lze umístit jednotné záhlaví a zápatí stránek a poté v každé stránce již volat jen:
<?php
require library('/main/head');
// zde bude obsah stránky
require library('/main/foot');
?>
Zvláštním druhem entit jsou akce. Akce jsou jen virtuální entity – na rozdíl od modulů a komponent na ně nelze jednoznačně ukázat, neskrývají se za nimi žádné konkrétní soubory ani adresáře. Ale právě to jim dává daleko volnější možnosti použití, zejména při vytváření systému přístupových práv.
Na začátku názvu akce je vždy hvězdička. Akce může být definovaná:
pro celou aplikaci, pro všechny existující moduly a komponenty, například /*action nebo prostě *action;
pro konkrétní modul a všechny jeho komponenty, například /main/*action;
pro jednu konkrétní komponentu, například /main/contact*action.
Dojde-li ke kolizi dvou akcí se stejným jménem ale s jiným rozsahem působnosti, má přednost vždy akce specifičtější.
Co to vůbec akce jsou a proč jsou potřeba, si ukážeme na dvou situacích. V první z nich použijeme akce k řízení přístupových práv na jemnější úrovni než jen po celých komponentách. Například při výpisu seznamu uživatelů na stránce main/userlist budeme chtít administrátorům zobrazit i jejich telefon, zatímco ostatním jen jméno a příjmení. Zaregistrujeme tedy akci main/userlist*showPhone, administrátorům k ní povolíme přístup, ostatním zakážeme, a příslušnou část kódu stránky upravíme třeba takto:
<table><?php
foreach ($list as $user) { ?>
<tr>
<td><?php echo $user['firstname']; ?></td>
<td><?php echo $user['surname']; ?></td><?php
// jen pro uživatele s povolenou akcí *showPhone
if (perm('*showPhone')) { ?>
<td><?php echo $user['phone']; ?></td><?php
} ?>
</tr><?php
} ?>
</table>
V druhém případě budeme mít problém právě opačný. Jednu jedinou akci použijeme napříč více komponentami či dokonce moduly. Typicky když budeme využívat nějaké standardní knihovny, například z PEARu, které nemají přímo v sobě zabudovány kontroly přístupového mechanizmu phpBASE. Představme si třídu CUser pro nějakého uživatele. Na několika různých místech naší aplikace budeme volat její metodu pro smazání tohoto uživatele:
<?php
$user = new CUser($user_id);
$user->delete();
?>
Pokud bychom chtěli někomu dát či odejmout právo pro mazání uživatelů, museli bychom mu povolit či zakázat přístup ke všem skriptům, ve kterých se tato konstrukce vyskytuje. Řešením je zaregistrovat si akci, například *deleteUser, a všechny výše zmíněné skripty modifikovat:
<?php
if (perm('*deleteUser')) {
$user = new CUser($user_id);
$user->delete();
}
?>
Nyní už stačí jen povolit či zakázat přístup k této jediné akci a nastavené právo bude platné pro všechna mazání uživatelů v celé aplikaci.
Zaregistrování nové akce
Jak z předchozích kapitol vyplývá, můžeme každou entitu v aplikaci adresovat jedinečným názvem. Takové identifikátory entit využijeme mimo jiné na vstupu mnoha funkcí definovaných frameworkem. Pravidla pro rozlišení jednotlivých typů entit jsou tato:
identifikátor modulu neobsahuje hvězdičku a končí vždy lomítkem, například /main/;
identifikátor komponenty neobsahuje hvězdičku a lomítkem nekončí, například /main/contact;
akce může být definována pro celou aplikaci, pro jeden modul či pro jednu jedinou komponentu a je oddělena hvězdičkou, například /*action nebo /main/*action nebo /main/contact*action.
Lomítko na začátku kteréhokoliv identifikátoru signalizuje jeho absolutní umístění vzhledem k MOD_ROOTu. Pokud ovšem lomítko chybí, jedná se o relativní identifikátor entity. V takovém případě se vychází z místa, na kterém byl použit. Zavoláme-li například ve stránce /news/admin/index funkci perm('article/edit'), zjišťujeme přístupová práva ke komponentě /news/admin/article/edit.
V relativním identifikátoru lze používat i obvyklé tečkové konstrukce, to znamená dvě tečky pro přechod do vyššího modulu, jednu tečku pro modul aktuální. Systém nepřipustí přechod výš než do MOD_ROOTu. Nelze tedy žádnou konstrukcí typu /../../outthere otevřít žádný soubor mimo MOD_ROOT a jeho podadresáře [20], [13].
Neexistuje-li kontext pro dovození výchozího modulu, od kterého se relativní identifikátory odvozují, použije se kořenový MOD_ROOT. To se aplikuje například u již známých URL parametrů PAGE a SCRIPT. V důsledku je tak jedno, jestli uživatel zadá požadavek http://www.example.com/index.php?PAGE=/main/contact nebo http://www.example.com/index.php?PAGE=main/contact.
Druhou možností, jak vyjádřit identifikátor entity, je použití instance třídy CEntity. Framework phpBASE dokonce pro interní práci s entitami používá výhradně tuto třídu. A jestliže jeho funkce umožňují zadávat jako parametry identifikátory entit i ve výše popsaném řetězcovém tvaru, pak jej vždy hned na svém počátku konvertují do instance třídy CEntity.
Třída umožňuje uchování identifikátoru libovolné entity. Při každém novém nastavení své hodnoty validuje zadaný řetězcový identifikátor, u relativně zadaného identifikátoru dopočítává z kontextu celé absolutní umístění entity, vyhodnocuje tečkovou adresářovou notaci apod. Na druhé straně její členské funkce poskytují následně vrácení celého absolutního identifikátoru či jen jednotlivých částí (názvu modulu, komponenty či akce).
Uvnitř každé stránky i skriptu je definována globální proměnná $THIS. Ta je instancí třídy CEntity a jako identifikátor entity ukazuje na právě prováděný skript či stránku. Využití nalezne kdykoliv, když budeme potřebovat použít identifikátor aktuálního skriptu či stránky.
Obsah stránek vyjadřuje osobní názory, postoje a zkušenosti autora. Copyright © 2004–2010 Jan Tichý.