Prirucka-Programatora

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 161

Nette\Object

Nette\Object je společným předkem všech tříd Nette Frameworku. V jiných programovacích jazycích takové třídy
existují automaticky (v Pascalu TObject, DOT.NET má System.Object a Java nebo Ruby mají Object), ale v PHP
nic takového neexistuje. Jde o třídu dostatečně transparentní, neměla by způsobovat žádné kolize, proto ji můžete
jako základní třídu pro své třídy používat i vy.

class MyClass extends Nette\Object


{

Reflexe
Základní třída umožňuje snadný přístup k sebereflexi pomocí metody getReflection() (vrací Nette\Reflecti-
on\ClassReflection):

$object = new MyClass();


$res = $object->getReflection()->hasMethod('test'); // má třída metodu test?
$className = $object->getReflection()->getName(); // zjistí jméno třídy

Náprava nekonzistentního generování chyb


PHP reaguje na přístup k nedeklarovaným členům značně nekonzistentně:

$obj->undeclared = 1; // projde bez hlášení


echo $obj->undeclared2; // generuje Notice
MyClass::$undeclared = 1; // generuje Fatal error
$obj->undeclared(); // generuje Fatal error

Většinou se přitom jedná o překlepy, které bývají zdrojem obtížně nalezitelných chyb.

Objekty tříd, které jsou potomky Nette\Object, vždy při přístupu k nedeklarovanému členu vyhazují výjimku
MemberAccessException.

Gettery a settery
Termínem property se označují speciální členy tříd, které umožňují pracovat s metodami tak, jako by to byly
proměnné. U Nette\Objectu se jedná o gettery a settery. Zvenku vypadají jako obyčejná proměnná, ale přístup
k ní máme plně pod kontrolou. Můžeme tak validovat vstupy nebo generovat výstupy až v případě potřeby.
● Getter i setter musí být veřejná metoda.
● Getter je povinný, setter volitelný (v tom případě pak získáme read-only proměnnou, write-only proměnné
nejsou podporovány).
● Getter nepřijímá žádný parametr, setter právě jeden – novou hodnotu.
● Názvy jsou citlivé na velikost písmen (case-sensitive). První písmeno property může být malé i velké, mělo by
to ale být anglické písmeno případně podtržítko.

class Circle extends Nette\Object


{
private $radius;

public function getRadius()


{
return $this->radius;
}

public function setRadius($radius)


{
// hodnotu před uložením validujeme
$this->radius = max(0, (float) $radius);
}

public function getArea()


{
return $this->radius * $this->radius * M_PI;
}
}

$circle = new Circle();


$circle->radius = 12; // volání $circle->setRadius(12);
echo 'Radius: ' . $circle->radius; //volání $circle->getRadius();
echo 'Area: ' . $circle->area; //volání $circle->getArea()

Kromě toho, přidáme-li kruhu property vyplněn/nevyplněn, přirozenější než $circle->getFilled() je volání
$circle->isFilled(). I proto akceptuje Nette obě formy, přičemž pokud existuje getFilled(), zavolá se
ona – má přednost.

class FillableCircle extends Circle


{
private $filled;

public function isFilled()


{
return $this->filled;
}

public function setFilled($filled)


{
//opět validace
$this->filled = (bool) $filled;
}
}

$circle = new FillableCircle();


$circle->filled = TRUE; //volání $circle->setFilled(TRUE);
echo 'Filled: ' . $circle->filled; //volání $circle->isFilled();

Jedná se jen o syntaktické pozlátko, jehož významem je pouhé zpřehlednění kódu. Pokud nechcete, tak properties
nemusíte používat.

Vlastnosti se dají použít i pro zjednodušení výše uvedeného příkladu se sebereflexí:

$className = $object->reflection->name; //namísto $object->getReflection()->getName()

Události
Pokud potřebuji volat v nějakém okamžiku více funkcí se stejnými parametry, mohou se mi hodit události
(events).

class Zahrada extends Nette\Object


{
//deklarace ve třídě
public $onSound;
}

function kocka($zvuk)
{
echo $zvuk === 'haf' ? 'lek' : '';
}

function pes($zvuk)
{
echo $zvuk === 'mňau' || $zvuk === 'haf' ? 'haf' : '';
}

$zahrada = new Zahrada;

// nastavím handlery
$zahrada->onSound[] = 'kocka';
$zahrada->onSound[] = 'pes';
$zahrada->onSound[] = function ($zvuk) { //handlerem může být i anonymní funkce
echo strlen($zvuk) > 2 ? 'haf' : 'lek';
};

$zahrada->onSound('haf'); // vypíše lekhafhaf


Rozšiřovací metody
Pokud chcete do nějaké třídy dopsat metodu a z nějakého důvodu to nemůžete realizovat poděděním, tak
můžete použít extension method.

class MyClass extends Nette\Object


{
public $a;
public $b;
}

// deklarace budoucí metody MyClass::join()


function MyClass_join(MyClass $_this, $separator)
{
return $_this->a . $separator . $_this->b;
}

MyClass::extensionMethod('join', 'MyClass_join');

MyClass::extensionMethod('repeatB', function (MyClass $_this, $times) {


return str_repeat($_this->b, $times);
});

$obj = new MyClass();


echo $obj->join(' ');
echo $obj->repeatB(5);

Viz také:

● Nette\Object API reference

« Slovníček pojmů Formuláře »


Nette\Component
Jádrem aplikací v Nette jsou komponenty. Komponenta není nic jiného, než objekt implementující rozhraní
Nette\IComponent. To po objektu vyžaduje metodu vracející jméno komponenty getName(), což je libovolný
řetězec, a rodičovský objekt getParent(). Dále je tu metoda pro nastavení obojího setParent().

Spojení s rodičovskou komponentou tvoří základ hierarchie. Rodičovská komponenta kromě rozhraní
Nette\IComponent implementuje i Nette\IComponentContainer, které obsahuje metody pro přidání, odebrání,
získání a iteraci nad komponentami. (TODO: je otázka, jestli toto neimplementovat přímo jako ArrayAccess +
getIterator, má to svá pro i proti). Celý strom komponent je tedy tvořen větvemi v podobě objektů
Nette\IComponentContainer a listů Nette\IComponent.

Připravenou implementací jsou pak třídy Nette\Component a Nette\ComponentContainer.

Z Nette\Component vycházejí všechny prvky formulářů, Nette\ComponentContainer je zase základem pro


samotný formulář a třídy v Nette\Application jako PresenterComponent, Control a Presenter.

Hledá se rodič!
Nette\Component disponuje několika užitečnými metodami:

Nette\Component::lookup($type) vyhledá v hierarchii směrem nahoru objekt požadované třídy nebo


rozhraní. Například $component->lookup('Nette\Application\Presenter') vrací spansenter, pokud je
k němu, i přes několik úrovní, komponenta připojena.

Blízkou metodou je Nette\Component::lookupPath($type), která vrací tzv. cestu, což řetězec vzniklý
spojením jmen všech komponent na cestě mezi aktuální a hledanou komponentou. Takže např.
$component->lookupPath('Nette\Application\Presenter') vrací jedinečný identifikátor komponenty
vůči spansenteru.

Monitorování změn
Jak poznat, kdy byla komponenta připojena do stromu spansenteru? Sledovat změnu rodiče nestačí, protože
k spansenteru mohl být připojen třeba rodič rodiče. Pomůže metoda Nette\Component::monitor($type).
Každá komponenta může monitorovat libovolný počet tříd/rozhraní. Připojení nebo odpojení je ohlášeno zavoláním
metody attached($obj) resp. detached($obj), kde $obj je objekt sledované třídy.

Pro lepší pochopení příklad: třída FileUpload, respanzentující formulářový prvek pro upload souborů
v Nette\Forms, musí formuláři nastavit atribut enctype na hodnotu multipart/form-data. V době vytvoření
objektu ale k žádnému formuláři připojena být nemusí (leda by se v konstruktoru předal $parent, ale ani ten
nemusí být formulářem či kontejnerem připojeným k formuláři). Ve kterém okamžiku tedy formulář modifikovat?
Řešení je jednoduché – v konstruktoru se požádá o monitoring:

class FileUpload extends FormControl


{
public function __construct($label)
{
$this->monitor('Nette\Forms\Form');
...
}

a jakmile je formulář k dispozici, zavolá se metoda attached:

protected function attached($form)


{
if ($form instanceof Form) {
$form->getElementPrototype()->enctype = 'multipart/form-data';
}
}

Monitorování a dohledávání komponent nebo cest přes lookup je velmi pečlivě optimalizované pro
maximální výkon.

Viz také:

● Nette\Component API reference


● Nette\ComponentContainer
● Nette\ComponentContainer API reference

« Konfigurace prostředí ovládací prvky »


Nette\Application\Control
Control je vykreslitelná komponenta.

Ta si umí navíc, kromě toho že se umí vykreslit, zapamatovat, jestli při subrequestu došlo ke změnám, které si
vyžadují jej překreslit. K tomu slouží triptych metod invalidateControl(), validateControl() a
isControlInvalid(), což je základem AJAXu v Nette.

Nette však nabízí ještě jemnější rozlišení, než na úrovni Controlů, a to tzv. snippetů neboli ústřižků.

Poznámka: pro jednoduchost k pochopení budu v následujících odstavcích brát Control jako komponentu. To, že
je vykreslitelná, vyplývá z překreslovací vlastnosti snippetů.

Lze tedy invalidovat/validovat na úrovni těchto snippetů (každá komponenta může mít libovolné množství
snippetů). Pokud se invaliduje celou komponentu, tak je i každý snippet považován za invalidní. Komponenta je
invalidní i tehdy, pokud je invalidní některá její subkomponenta. Komponenta, která přijímá signál, je automaticky
označena za invalidní.

Díky invalidaci snippetů přesně víme, které části kterých prvků bude potřeba překreslit.

Komponenta (tedy přesněji vykreslitelná komponenta Control) nepředstavuje pravoúhlou oblast ve stránce, ale
logickou komponentu, která se může renderovat i do více podob. Každá komponenta může být navíc na stránce
vykreslena vícekrát, nebo podmíněně, nebo pokaždé s jinou šablonou atd.

Komponenta Control ač má mnoho společných rysů s Presenterem nemá svůj životní cyklus v pravém slova
smyslu. Jen metody attached() a detached() umožňují detekovat, kdy byl Control připojen k nebo odpojen
od rodiče (spansenteru či jiné komponenty).

O zachycení signálu se stará spansenter, ten ho odevzdá komponentě, která je jeho příjemcem. Protože
spansenter je sám o sobě komponentou, tak ho klidně odevzdá i sám sobě.

Šablony
Třída Control obsahuje továrničku createTemplate() na svou šablonu. Ta standardně vytvoří šablonu, předá
ji některé základní proměnné a zaregistruje standardní helpery:

{**
* Template's description.
*
* @param Presenter $spansenter
* @param Control $control
* @param Template $template
* @param array $flashes
* @param string $baseUri Environment::getVariable('baseUri');
*
* You can use these helpers:
* escape, escapeJs, escapeCss, cache,
* snippet, lower, upper, capitalize, stripTags,
* strip, date, nl2br, truncate, bytes
*}

Pro pohodlnější práci v šablonách (hlavně v při vaší další práci v budoucnu) by každá šablona měla obsahovat
hlavičku popisující proměnné, které v ní lze využívat.

Strom komponent
Každá komponenta děděná z třídy Control má jako první parametr konstruktoru rodiče (
IComponentContainer) v hierarchii stromu komponent. Rodičem může být Presenter, nějaká komponenta nebo
jakýkoliv jiný objekt implementující rozhraní IComponentContainer. Hierarchie pak může vypadat i nějak takto:

Presenter | --Control { implementuje IComponentContainer => může být rodičem } | --Component | --Component {
neimplementuje IComponentContainer => nemůže být rodičem } | --Control | --Component

Protože Control samotný neumí generovat odkazy, ale jen signály, potřebujeme jej svázat s Presenterem. Podle
struktury a zanoření hierarchie se můžeme k spansenteru dovolat i z podkomponent a používat jeho veřejné
metody.

$spansenter = $control->getPresenter();

Hierarchie vzniká připojováním komponent do stromu komponent. Při vytváření komponenty uvedeme
v parametru konstruktoru instanci jejího rodiče a řetězec se jménem komponenty. Uvedením stejné komponenty
pod různými jmény se dá dosáhnout například zobrazení jedné komponenty na stránce vícekrát.

Komponentový model Nette umožňuje velmi dynamickou práci se stromem (komponenty můžeme vyjímat,
přesouvat, přidávat), proto by byla chyba se spoléhat na to, že po vytvoření komponenty je hned znám rodič, rodič
rodiče atd. Nemusí být.

$control = new NewsControl;


// ...
$parent->addComponent($control, 'shortNews');

// nebo alternativně starším (statickým) způsobem


$control = new NewsControl($parent, 'shortNews');

Flash zprávy
Vizuální komponenta Control má své vlastní úložiště flash zpráv nezávislé na spansenteru (metoda
flashMessage patří třídě Control).

Jde o zprávy, které např. informují o výsledku operace a uživateli se zobrazí až po přesměrování. Zasílání
obstarává metoda flashMessage() třídy Control (tj. je možno zasílat zprávy i na úrovni komponent).

Kód spansenteru nebo komponenty:


public function deleteFormSubmitted(AppForm $form)
{
Model::delete(); // něco smažeme
$this->flashMessage('Položka byla smazána.');

// nebo pokud chceme uložit zprávu přímo do spansenteru


$this->getPresenter()->flashMessage('Položka byla smazána.');

$this->redirect('default');
}

Metoda flashMessage() zprávu vkládá přímo do šablony do parametru flashes (jako pole objektů stdClass
). Šablona pro vypsání zpráv pak může vypadat třeba takto:

{foreach $flashes as $flash}


<div class="flash">{$flash->message}</div>
{/foreach}

Jako druhý parametr metody lze uvést typ zprávy (výchozí hodnota je „info“) nebo její kód.

$this->flashMessage('Položka nebyla smazána!', 'warning');

Typ zprávy pak lze použít například jako CSS třídu:

{foreach $flashes as $flash}


<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}

Také je možné do zprávy přidat extra informace:

$message = $this->flashMessage('Obrázek byl uložen.');


$message->image = $image;
$message->width = 123;

Nejdůležitejší samozřejmě je, že pokud po uložení zprávy flashMessage() následuje přesměrování, bude
i v dalším požadavku v šabloně existovat stejný parametr flashes. Zprávy zůstanou poté živé další 3 sekundy –
například pro případ, že by z důvodu chybného přenosu uživatel stránku dal obnovit. Pokud někdo dvakrát za
sebou obnoví stránku (F5), tak mu zpráva tedy nezmizí, pokud klikne jinam, tak ji už neuvidí.

Příklad zpracování událostí komponenty s flashovými zprávami


Tento způsob vede k lepší znovupoužitelnosti komponenty, na událost si její vlastník reaguje zcela po svém.

class Komponenta extends Control


{
/** @var array handlery události Xyz */
public $onXyz;

public function handleLogin()


{
...
$this->onXyz(); // volej handlery
}
}

class HomepagePresenter extends Presenter


{
public function startup()
{
$komponenta = new Komponenta($this, 'k');
$komponenta->onXyz[] = array($this, 'xyzHandler'); // registruje handler
}

public function xyzHandler()


{
$this->flashMessage('Uživatel byl přihlášen.');
}
}

Viz také:

● Control API reference


● IPartiallyRenderable API reference
● Ajax & Snippety

« Komponenty Debugování a zpracování chyb »


Nette\Debug
Knihovna Nette\Debug, která zdomácněla pod jménem Laděnka, je užitečnou
každodenní pomocnicí PHP programátora.

Zachytávání chyb a výjimek


Zachytáváni chyb je nejlepší zapnout na samém začátku, hned po načítání Nette.

require LIBS_DIR .'/Nette/loader.php';


Debug::enable();

Volání Debug::enable() aktivuje laděnku, která sama detekuje, zda běží na vývojovém nebo produkčním servru.
Na vývojovém zobrazuje výjimky uživateli-vývojáři, na produkčním chyby loguje, případně posílá informaci na
e-mail. Na zjištění, jestli beží Nette na produkčním servru použijte metodu Environment::isProduction().
Další informace najdete v API třídy Debug.

Zpráva o nezachycené výjimce nebo chybě poskytuje vývojáři důležitou informaci o tom, kde a proč k ní došlo.
Standardní výstup v PHP vypadá asi takto:

Standardní podoba nezachycené výjimky

Pusťme však ke slovu Laděnku. Po aktivaci příkazem Debug::enable() nám předvede svou nejvíce sexy
podobu:
Nezachycená výjimka v provedení Nette\Debug

Takto vypadá výjimka, takto vypadá vygenerovaná chyba. To je pak jiné ladění, co? Takto zobrazeny jsou
automaticky všechny fatální chyby. Pro ještě hlubší odvšivování lze zapnout striktnější mód. Pak budou stejným
způsobem zobrazeny i chyby nižších úrovní jako E_NOTICE a E_WARNING.

Debug::$strictMode = TRUE;

Jednotlivé části obrazovky lze navíc pohodlně myší rozklikávat:

Jsou ale situace, kdy si určité části kódu chceme ošetřit přes try/catch a ne každé vyhození vyjímky musí
následovat ukončením skriptu. Metoda processException() má za úkol zobrazit/zalogovat výjimku a předat řízení
zpět aplikaci, narozdíl od exceptionHandler(), který po zpracování vyjímky činnost aplikace ukončí.

Variable dump
Každý ladič je dobrým kamarádem s funkcí var_dump, která podrobně vypíše obsah proměnné. Bohužel
v prostředí HTML výpis pozbude formátování a slije se do jednoho řádku, o sanitizaci HTML kódu ani nemluvě.
V praxi je nezbytné var_dump nahradit šikovnější funkcí. Tou je právě Debug::dump()
$arr = array(10, 20.2, TRUE, NULL, 'hello');

Debug::dump($arr);
// včetně jmenného prostoru Nette\Debug::dump($arr);

vygeneruje výstup:

<span><span style="color:gray">array</span>(5) {
[0] => <span style="color:gray">int</span>(10)
[1] => <span style="color:gray">float</span>(20.2)
[2] => <span style="color:gray">bool</span>(true)
[3] => <span style="color:gray">NULL</span>
[4] => <span style="color:gray">string</span>(5) "hello"
}
</span>

Měření času
Dalším užitečným nástrojem ladiče jsou stopky s přesností na mikrosekundy:

Debug::timer();

// princi můj malinký spi, ptáčkové sladce již sní...


sleep(2);

$elapsed = Debug::timer();
// $elapsed ≈ 2

Volitelným parametrem je možno dosáhnout vícenásobných měření.

Debug::timer('page-generating');
// nějaký kód
Debug::timer('rss-generating');

// nějaký kód
$rssElapsed = Debug::timer('rss-generating');
$pageElapsed = Debug::timer('page-generating');

Profiler
Profiler se zapíná pomocí příkazu Debug::enableProfiler();

Má své API a podporuje přetahování myší. Přidat do něj další informace se dá velmi snadno:
Debug::$counters['Last SQL query'] = & dibi::$sql;
Debug::$counters['Nette version'] = Framework::VERSION . ' ' . Framework::REVISION;

Debug::addColophon(array('dibi', 'getColophon'));

Veškerý výstup profileru lze vypnout voláním

Debug::disableProfiler();

Viz také:

● Nette\Debug API reference

« ovládací prvky Logování chyb »


Logování chyb
Vypisování chyb se nesmí nikdy dostat na produkční server. Laděnka je výborná společnice na pracovišti, ale
nikdy ji nesmíme brát s sebou ven. Je totiž děsně ukecaná a vyzradí na vás úplně všechno (nicméně kdyby k tomu
náhodou došlo, má integrovaný systém pro skrytí citlivých políček, např. hesel). Na produkčním serveru je však
možné nechat výpisy ukládat do adresáře nebo odesílat administrátorovi emailem. To se zapíná takto:

Debug::enable(Debug::DETECT, '%logDir%/php_error.log', 'admin@example.com');

● první parametr je přepínač mezi produkčním a vývojovým režimem


● druhý parametr je jméno souboru error logu (absolutní cesta nebo FALSE pokud se chyby nemají logovat, nebo
NULL, pokud se použije autodetekce, viz níže)
● třetí parametr je emailová adresa, kam se mají posílat notifikace (nebo pole hlaviček emailu, viz níže).

Autodetekce
Laděnka tedy funguje buď v režimu zobrazování nebo logování chyb. To se přepíná prvním parametrem a
hodnotou Debug::PRODUCTION nebo Debug::DEVELOPMENT. Pokud jej však neuvedeme nebo má hodnotu NULL
či Debug::DETECT, detekuje se režim podle IP adresy serveru – je-li na adrese veřejné, půjde o produkční režim,
je-li na lokální, tak o vývojářský režim.

V produkčním režimu Laděnka úplně ztichne a funguje jen logování do souboru. Filtrování citlivých dat je nyní
bezpředmětné – v produkčním režimu (což nutně neznamená „na produkčním serveru“) se nezobrazuje nic. Pokud
nejsme ve vývojovém režimu, zvolí logování do souboru %logDir%/php_error.log tudíž v Nette\Environment
musí být nastavena proměnná prostředí logDir nebo appDir. (nicméně Nette\Debug lze používat stále
samostatně, závislost na třídě Environment je jen volitelná).

Proměnnou %logDir% je možné nastavit v config.ini např. takto:

variable.logDir = %appDir%/log

Poslední volitelný parametr metody Debug::enable() je emailová adresa nebo pole hlaviček, kam bude
zasílána notifikace o vzniku chyby (včetně fatálních nezachytitelných chyb). Takto odchytávány jsou již chyby
úrovně notice. V případě chyby se pošle jen jeden email, takže nehrozí zaplavení adminovy schránky – k této
detekci slouží soubor {logfile}.monitor, který se vytvoří v případě úspěšného odeslání emailu s chybou.
Pokud nelze soubor {logfile}.monitor vytvořit či pokud existuje, email se nepošle.

Emailové hlavičky (včetně pseudohlavičky Body) je možné specifikovat takto:

$emailHeaders = array(
'From' => 'web@example.com',
'To' => 'admin@example.com',
'Subject' => 'Chyba na serveru %host%',
'Body' => '%date% - %message%. Pro více informací shlédněte error log.',
);
Debug::enable(Debug::DETECT, 'php_error.log', $emailHeaders);
Jako %message% se doplní případná zpráva vyjímky.

« Debugování a zpracování chyb Firebug »


Firebug
Komunikace Nette\Debug a Firebugu dává vývojářům možnost zasílat zprávy samostatným kanálem, mimo okno
prohlížeče. Chyby úrovně E_NOTICE a E_WARNING jsou do okna Firebugu tedy zasílány automaticky. Taktéž je
možné logovat výjimky, které sice aplikace zachytila, ale stojí za to na ně upozornit. Firebug konzole se také
výborně hodí pro ladění AJAXových aplikací.

1. je vyžadován Firefox verze 2 nebo 3


2. stáhněte si rozšíření Firebug
3. stáhněte si rozšíření FirePHP (minimálně ve verzi 0.2)
4. zapněte si FirePHP v FirePHP menu a aktivujte Firebug Net panel

Protože Nette\Debug komunikuje s Firebugem přes HTTP hlavičky, je nutné volat logovací funkce ještě před tím,
než PHP skript cokoliv vypíše. Také je možné zapnout output buffering a tím výstup oddálit.

use Nette\Debug;

// vypíšeme řetězec do konzoly Firebugu


Debug::fireLog('Hello World');

// ke zprávám je možné přidat indikátor:


Debug::fireLog('Info message', Debug::INFO);
Debug::fireLog('Warn message', Debug::WARN);
Debug::fireLog('Error message', Debug::ERROR);

// do konzoly lze vypsat i pole nebo objekty:


Debug::fireLog($_SERVER);

Konzola podporuje i speciální typ tabulky:

Debug::fireLog(
array('2 SQL queries took 0.06 seconds', // table title
array(
array('SQL Statement', 'Time', 'Result'), // table header
array('SELECT * FROM Foo', '0.02', array('row1', 'row2')), // 1. row
array('SELECT * FROM Bar', '0.04', array('row1', 'row2')) // 2. row
)
), 'TABLE');

Nebo lze do logu poslat výjimku:

try {
throw new Exception('Test Exception');
} catch(Exception $e) {
Debug::fireLog($e);
}
Výsledek vypadá asi takto:

Kromě konzole lze vypisovat proměnné do záložky „Server“ pod záložkou „Net“. Zde je připraven inspektor, který
umí rozbalovat a sbalovat jednotlivé větve proměnné. Každé dumpované proměnné musí být přiřazen jedinečný
klíč (druhý parameter):

$arr = array(10, 20,


array('key1' => 'val1', 'key2' => TRUE)
);

Debug::fireDump($arr, 'My var');

Což v prohlížeči vypadá takto:

Viz také:
● FirePHP

« Logování chyb Sessions »


Nette\Web\Session
U webových aplikací je často potřeba uchovávat některé informace, např. o přihlášení uživatele nebo obsahu
nákupního košíku, mezi načtením jednotlivých stránek. K tomuto účelu existují session neboli relace. Každý uživatel,
který vstoupí na stránku, obdrží jedinečný identifikátor Session ID a ten se předává v cookies. Ten pak slouží jako
klíč k session datům. Narozdíl od cookies, které se uchovávají na straně prohlížeče, jsou data v session uchovávána
na straně serveru.

V Nette ke správě session slouží třída Nette\Web\Session a manipulaci s daty zajišťuje


Nette\Web\SessionNamespace. Pro oddělení vzájemně nesouvisejících session dat se tedy používají jmenné
prostory, ve kterých se s nimi pracuje podobně jako s běžným polem v PHP.

Příklad – čítač přístupů


Začněme příkladem počítadla, které ukazuje, kolikrát uživatel zobrazil stránku:

// získáme přístup do jmenného prostoru counter


$namespace = Environment::getSession('counter');

// pokud v něm existuje proměnná $count


if (isset($namespace->count)) {
// zvětšíme její hodnotu o jedničku
$namespace->count++;
} else {
// jinak ji inicializujeme
$namespace->count = 1;
}

echo 'Počet zhlédnutí: ', $namespace->count;

A teď se podíváme na celou věc pozorněji. Získání přístupu do jmenného prostoru counter:

$session = Environment::getSession();

$namespace = $session->getNamespace('counter');

nebo stručněji

$namespace = Environment::getSession('counter');

Proměnné ve jmenném prostoru


Proměnné se používají jako obyčejné proměnné objektu:
$namespace->a = 'apple';
$namespace->p = 'pear';
$namespace->o = 'orange';

echo $namespace->a;

Pro získání všech proměnných z namespace je možné použít cyklus foreach.

foreach ($namespace as $key => $val) {


echo "$key = $val<br>";
}

Zrušení proměnné:

unset($namespace->a);

Nastavení expirace
Proměnná a bude smazána po 5 sekundách:

$namespace->setExpiration(5, 'a');

Celý jmenný prostor bude zrušen po uplynutí 60 sekund:

$namespace->setExpiration(60);

Celý jmenný prostor bude zrušen v okamžiku, kdy uživatel zavře okno prohlížeče.

$namespace->setExpiration(0);

Zrušení expirace celého jmenného prostoru (neovlivní explicitně nastavenou expiraci klíče a)

$namespace->removeExpiration();

Expirace proměnné se dá před vypršením zrušit:

$namespace->removeExpiration('a');

Okamžité zrušení celého jmenného prostoru:


$namespace->remove();

Práce se jmennými prostory


Vypsání všech prostoru a jejich proměnných:

$session = Environment::getSession();

foreach ($session as $name) {


echo "<h2>Namespace $name</h2>";
foreach (Environment::getSession($name) as $key => $val) {
echo "<h3>$key</h3>";
Debug::dump($val);
}
}

Ověření existence prostoru:

$session = Environment::getSession();
$session->hasNamespace('test'); // TRUE

Konfigurace session

Pro dlouhodobé uchování session proměnných je nutné mít nastavenou expiraci celé session a to
nejlépe v bootstrapu.

Pokud se neprovede toto nastavení, všechny session proměnné vyexpirují v momentě zavření okna prohlížeče.
Uchování session i po zavření prohlížeče se hodí například pro dlouhodobé přihlášení uživatele.

$session = Environment::getSession();

// sezení vyprší po 14 dnech neaktivity


$session->setExpiration('+ 14 days');

// sezení vyprší jakmile uživatel zavře prohlížeč


$session->setExpiration(0);

// nastavení cesty pro ukládání session dat na serveru


// soubory session se hromadí v tomto adresáři, udržuje ho garbage collector
$session->setSavePath(dirname(__FILE__) . '/sessions/');

// volitelné nastavení parametrů cookie


$session->setCookieParams($path, $domain = NULL, $secure = NULL);

Platnost autentizace na subdoménách


Platnost autentizace lze jednoduše rozšířit na subdomémy nastavením session.

Environment::getSession()->setOptions(array(
'cookie_path ' => '/', // cookie is available within the entire domain
'cookie_domain' => '.example.com', // cookie is available on all subdomains
));

Nastavení parametrů cookie musí být provedeno před tím, než je sezení otevřeno (lze zjistit přes
Environment::getSession()->isStarted(). Pozdější volání nemá efekt. Sezení se otevírá například při
přístupu ke jmennému prostoru (třeba voláním $namespace = Environment::getSession('myapp'), nebo
používáním třídy User).

Session není možné startovat dvakrát (tedy je, ale musí se předtím manuálně uzavřít). Tudíž místo
$session->start() je vhodnější používat třeba na:

if (!$session->isStarted()) $session->start();

Tipy k Vašim aplikacím


● při zavření prohlížeče nechejte vypršet přihlášení uživatele, obsah košíku, oslovení je vhodné nechat
● zobrazení informačních hlášek o úspěšnosti nějaké akce (například v administraci) je dobré vázat na nějakou
session proměnnou v kombinaci s query-stringem. Proměnné v session nastavíme expiraci například 1 minutu a
při zobrazení informační hlášky kontrolujeme existenci této proměnné a na základě toho se rozhodneme, zda-li
hlášku ještě zobrazit/nezobrazit

Viz také:

● Nette\Web\Session API reference


● Nette\Web\SessionNamespace API reference

« Firebug Odesílání e-mailů »


Nette\Mail
Třída pro odesílání emailů.

Příklad použití:

$mail = new Mail;


$mail->setFrom('Franta <franta@example.com>');
// nebo $mail->setFrom('franta@example.com', 'Franta');
$mail->addTo('petr@example.com');
$mail->setSubject('Potvrzení objednávky');
$mail->setBody("Dobrý den,\nvaše objednávka byla přijata.");
$mail->send();

Můžete využít i fluent interface:

$mail->setFrom('Franta <franta@example.com>')->addTo('petr@example.com')->send();

Do emailu lze vkládat přílohy:

$mail->addAttachment('example.zip');

Je také možné odesílat HTML emaily:

$mail->setHTMLBody('<b>Sample HTML</b> <img src="background.gif">');

Vložené obrázky lze do emailu vkládat metodou $mail->addEmbeddedFile('background.gif'), nicméně


není to potřeba. Nette automaticky vyhledá a vloží všechny soubory odkazované v HTML kódu. Toto chování lze
vypnout uvedením FALSE jako druhého parameteru metody setHtmlBody().

Pokud HTML email nemá textovou alternativu, bude vygenerována automaticky.

Pokud HTML email nemá nastavený subjekt, bude vzat z elementu <title>.

Třídu Mail lze dobře kombinovat s šablonami:

$template = new Template;


$template->setFile('email.phtml');

$mail = new Mail;


$mail->setFrom('Franta <franta@example.com>');
$mail->addTo('petr@example.com');
$mail->setHtmlBody($template); // nebo $mail->setBody($template) pro textovou šablonu
$mail->send();
Do šablony bude automaticky vložená proměnná $mail, je tedy možné přímo v šabloně nastavit další hlavičky
emailu.

Chceme-li využít proměnné v šabloně, je vhodné zaregistrovat patřičný filtr.

$template->registerFilter(new LatteFilter);

Vlastní mailer lze nastavit dvěma způsoby:

class MyMailer implements IMailer


{
function send(Mail $mail)
{
file_put_contents('email.eml', $mail->generateMessage());
}
}

// 1. varianta
Mail::$defaultMailer = 'MyMailer'; // nebo new MyMailer

// 2. varianta
$mail = new Mail;
$mail->setMailer(new MyMailer);

Viz také:

● Nette\Mail\Mail API reference

« Sessions Zpracování obrázků »


Nette\Image
Třída Nette\Image je určena pro základní manipulaci s obrázky. Zjednodušuje
nejčastější úkony, jako je změna velikosti, doostření nebo odeslání do prohlížeče.

Image::fromFile('nette.jpg')->resize(100, 50)->send();

Vytvoření obrázku

// a) ze souboru
$image = Image::fromFile('nette.jpg');

// b) prázdný obrázek s rozměry 100x200


$image = Image::fromBlank(100, 200);

// volitelně lze určit také barvu pozadí


$image = Image::fromBlank(100, 200, Image::rgb(125, 0, 0));

Během manipulace s obrázkem je možné kdykoliv udělat jeho kopii:

$imageCopy = clone $image;

Obrázek vrací také metoda Nette\Web\HttpUploadedFile::getImage().

Zjištění velikosti

echo $image->getWidth(); // šířka


echo $image->getHeight(); // výška

Změna velikosti
Obrázek se proporcionálně zmenší tak, aby nepřesáhl rozměry 50×30 pixelů:

$image->resize(50, 30);

Je možné specifikovat jen jeden rozměr a druhý se dopočítá:

$image->resize(50, NULL);
$image->resize(NULL, 30);

Kterýkoliv rozměr je možné specifikovat i v procentech:

$image->resize('75%', 30);

V uvedených příkladech se obrázek pouze zmenšuje. Případné zvětšení lze povolit příznakem Image::ENLARGE:

$image->resize(50, 30, Image::ENLARGE);

Přiznakem Image::STRETCH je možné aktivovat neproporcionální změny rozměrů:

$image->resize(50, 30, Image::STRETCH);

Oba příznaky lze kombinovat:

$image->resize(50, 30, Image::ENLARGE | Image::STRETCH);

Doostření
Po zmenšení obrázku je možné vylepšit jeho vzhled jemným doostřením:

$image->sharpen();

Ořez

$image->crop($left, $top, $width, $height);

Vložení jiného obrázku

$blank = Image::fromBlank(200, 200, Image::rgb(255, 255, 255));


$blank->place($image, 0, 0);

S nastavením průhlednosti na 30 %:

$watermark = Image::fromFile('watermark.png');
$image->place($watermark, '50%', '75%', 30);

Uložení obrázku
Obrázek můžeme uložit do souboru:

$image->save('resampled.jpg');

Volitelně lze stanovit stanovit i kvalitu a formát obrázku. (Pokud není uveden, detekuje se z přípony.):

$image->save('resampled.jpg', 80, Image::JPEG); //kvalita 80% a formát JPEG

Alternativně lze obrázek uložit i do proměnné:

$binary = (string) $image;

nebo poslat přímo do prohlížeče s nastavením hlavičky Content-Type:

// odešle jako image/jpeg


$image->send();

// odešle jako image/png


$image->send(Image::PNG);

Další funkce
Nette\Image zjednodušuje volání všech grafických funkcí PHP z rozšíření GD:

$size = 300;
$radius = 150;

$image = Image::fromBlank($size, $size);

$image->filledRectangle(0, 0, $size - 1, $size - 1, Image::rgb(255, 255, 255));


$image->rectangle(0, 0, $size - 1, $size - 1, Image::rgb(0, 0, 0));

$image->filledEllipse(100, 75, $radius, $radius, Image::rgb(255, 255, 0, 75));

$image->send(Image::GIF);

Viz také:
● Nette\Image API reference

« Odesílání e-mailů Kešování »


Nette\Caching
Knihovna sloužící k ukládání dat do keše.

Příklad použití:

// získání instance cache


$storage = new FileStorage('tmp');
$cache = new Cache($storage); // nebo $cache = Environment::getCache()

// zápis do cache
$cache['data'] = $myData;

// čtení z cache
$cachedData = $cache['data'];

// mazání z cache
unset($cache['data']);
// nebo
$cache['data'] = NULL;

// ověření, zda je položka v keši


if (isset($cache['data'])) ...
// nebo
$cachedData = $cache['data'];
if ($cachedData !== NULL) ...

Do keše lze ukládat jakékoliv struktury, nemusí to být jen řetězec.

Takže taková jednoduchá implementace by mohla vypadat třeba takto:

if (isset($cache['template'])) {
$template = $cache['template'];
} else {
$template = sloziteGenerovani();
$cache['template'] = $template;
}

echo $template;

Ale to není zdaleka vše. Při ukládání položek lze specifikovat několik dalších parametrů a podmínek pro invalidaci.
Protože v případě přiřazení $cache['key'] = $data není kde tyto podmínky specifikovat, použijeme pro
ukládání obsahu funkci $cache->save($key, $data, $options). Parametr $options je pole, které může
mít tyto klíče:

expire => (int) čas, kdy obsah vyexpiruje sliding => (bool) má se expirace prodlužovat? files => (array) seznam
souborů, na kterých cache závisí items => (array) seznam klíčů v keši, na kterých tato položka závisí tags =>
(array) seznam vlastních tagů priority => (int) priorita consts => (array) seznam konstant, na kterých cache závisí
Když nějaká položka vyexpiruje, pak vyexpiruje celá cache. Garbage collector ji poté fyzicky odstraní z disku.

Příklad použití:

Cache vyexpiruje za 10 min:

$cache->save($key, $value, array(


'expire' => time() + 60 * 10,
));

Cache vyexpiruje za 10 min. Pokud v té době bude načtena, expirace se prodlouží na dalších deset minut.

$cache->save($key, $value, array(


'expire' => time() + 60 * 10,
'sliding' => TRUE,
));

Cache vyexpiruje v okamžiku, kdy se změní kterýkoliv z uvedených souborů (pro bezproblémovou funkčnost je
třeba používat absolutní cesty). V praxi to používám třeba pro kešování konfigurace nebo šablon – jakmile se
příslušný soubor změní, Nette automaticky invaliduje i keš.

$cache->save($key, $value, array(


'files' => array('template.phtml', 'config.ini'),
));

Cache je závislá na jiných položkách v keši – vyexpiruje v okamžiku, kdy se změní položky s klíčem ‚key1‘ nebo
‚key2‘. To lze využít tehdy, když kešujeme třeba www stránku a pod jinými klíči její části. Jakmile se část změní,
invaliduje se celá stránka.

$cache->save('key3', $value, array(


'items' => array('key1', 'key2'),
));

Položce můžeme přiřadit seznam vlastní tagů. To lze použít třeba tehdy, pokud kešujeme www stránku, kde je
článek + komentáře. Jakmile se článek změní, nebo jakmile se objeví nový komentář, necháme vyexpirovat
všechny položky s příslušným tagem:

$cache->save($key, $value, array(


'tags' => array('clanek#10', 'komentare#10'),
));

// vyexpirujeme všechny položky s tagem 'komentare#10':


$cache->clean(array(
'tags' => array('komentare#10'),
));
Nakonec je tam položka priorita, podle které lze cache čistit:

$cache->save($key, $value, array(


'priority' => 50,
));

// smažeme všechny položky s prioritou rovnou nebo menší 100:


$cache->clean(array(
'priority' => 100,
));

Všechny parametry lze navzájem kombinovat.

V rámci jednoho sezení má Nette načetlou cache i v paměti. Zavolání funkce $cache->release() uvolní tyto
zdroje z paměti.

Invaliduji-li tag funkcí $cache->clean(…), pak se smaže z keše vše s tímto tagem. U velmi velkých aplikací,
kde mohou být na serveru tisíce kešovaných souborů by se musel prohledávat celý adresář, který kěš drží a to by
mohlo být systémově pomalé. Jako řešení se nabízí implementace na databázi, nebo také při kešování nastavovat
prioritu.

$cache->save($key, $value, array(


Cache::TAGS => array('blog/10', 'comments/10', 'novinky'),
Cache::PRIORITY => 2,
));

$cache->clean(array(Cache::TAGS => 'blog/10'));

V Nette\Environment lze na práci s cache zaregistrovat službu, pokud si chcete objekt pro práci s keší přepsat,
jediný požadavek je, aby implementovalo rozhraní ICacheStorage. V případě, že se rozhodnete změnit způsob
zpracování keše v úložišti, můžete využít připraveného objektu DummyStorage, což je úložiště, které ve
skutečnosti nic nedělá. Také je zde i podpora pro MemCache kterou obstarává objekt MemcachedStorage.

Viz také:

● Kešování HTML výstupu


● Nette\Caching\ICacheStorage
● Nette\Caching\Cache API reference

« Zpracování obrázků HTTP request »


Nette\Web\HttpRequest
Třída zapouzdřující a zjednodušující obsluhu HTTP požadavku.

Použití
Nejdříve si získáme/vytvoříme instanci třídy:

$httpRequest = Environment::getHttpRequest();

// pokud nepoužíváte třídu společně s Nette


$httpRequest = new HttpRequest;

Metody
● getMethod() zjistí jakou metodou se na stránky přistoupilo (GET, POST, HEAD, PUT, …);
● getQuery() vrací podčást nebo celý query-string, který je naparsován do asociativního pole;
● getPost([string $key]) vrací podčást nebo celý obsah pole $_POST;
● getFiles() celý obsah pole uploadovaných souborů $_FILES, getFile(string $key) vrací jeho podčásti;
● getCookies() vrací celý obsah pole $_COOKIE, getCookie(string $key) vrací jeho podčásti;
● getHeaders() vrací všechny HTTP hlavičky poslány prohlížečem, getHeader(string $key) vrací jednotlivé
hlavičky;
● getReferer() – alias pro getHeader('referer') vracející adresu jako objekt Nette\Web\Uri
● getRemoteAddress(), getRemoteHost();
● isSecured() zjistí jedná-li se o zabezpečenou https komunikaci;
● isAjax() zjistí jedná-li se o AJAXový požadavek.

Metoda getRemoteAddress() slouží k získání IP adresy uživatele nebo jejího DNS překladu.

echo $httpRequest->getRemoteAddress();
// 127.0.0.1

echo $httpRequest->getRemoteHost();
// localhost

A nakonec velmi užitečná metoda detectLanguage(), která získá spanferovaný jazyk prohlížeče podle priority
kterou máte nastavenu, případně, pokud jí předáte pole jazyků, které podporuje vaše aplikace, vrátí z nich ten,
který by viděl návštěvníkův prohlížeč nejradši.
Nejsou to žádná kouzla, jen se využívá hlavičky accept-language.

// prohlížeč odesílá hlavičku: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3


// jazyky podporované aplikací
$langs = array('hu', 'pl', 'en');
echo $httpRequest->detectLanguage($langs); // en

Filtrování URI adres


Filtrování URI je funkčnost, pomocí které je možné např.:

// odstranit mezery z cesty


$httpRequest->addUriFilter('%20', '', PHP_URL_PATH);

// odstranit tečku, čárku nebo pravou závorku z konce URI


$httpRequest->addUriFilter('[.,)]$');

// vyčistit cestu od zdvojených lomítek (výchozí filtr)


$httpRequest->addUriFilter('/{2,}', '/', PHP_URL_PATH);

Pomocí filtrů lze URI vyčistit od znaků, které se do nich mohou dostat kvůli špatně implementovaných komentařů
na webech.

Nette\Web\Uri
Třída Uri je obecným zapouždřením jakéhokoliv URL (tedy zatím jen URL) a poskytuje nám jednoduchou a
elegantní práci s URI adresami podle doporučení pro označování syntaxe adres RFC 3986. Základem třídy je
funkce `parse_url()`.

Použití je velmi intuitivní:

$uri = new Uri('http://nette.org/cs/dokumentace?action=history#footer');


// vstupem musí být absolutní adresa

echo $uri->absoluteUri;
// altenativně: echo (string) $uri;
// output: http://nette.org/cs/dokumentace?action=history#footer

echo $uri->scheme; // http


echo $uri->authority; // nette.org
echo $uri->getHostUri(); // http://nette.org

echo $uri->path; // /cs/dokumentace


echo $uri->query; // action=history
echo $uri->fragment; // footer

Jsou podporovány i jiná schémata než http, např. file, ftp nebo https.
Propojení třídy Uri s třídou HttpRequest
Nette\Web\HttpRequest obsahuje dva (vlastně tři) URI objekty:

● HttpRequest::getUri() – k kanonické podobně;


● HttpRequest::getOriginalUri() – v surové podobě;
● a ještě HttpRequest::getReferer().

Kompletní cestu vrací metoda Uri::getAbsoluteUri(), takže lze použít:

$httpRequest->getAbsoluteUri()->baseUri;

// nebo v prostředí Nette


Environment::getHttpRequest()->uri->absoluteUri;

// apod:
$httpRequest->getUri()->basePath;
$httpRequest->getUri()->relativeUri;
$httpRequest->getUri()->absoluteUri;

Viz také:

● Nette\Web\HttpRequest API reference

« Kešování response »
Nette\Web\HttpResponse
Třída zapouzdřuje a sjednocuje funkce pro obsluhu HTTP odpovědi serveru. Umožňuje
změnit stavový kód odpovědi, určit typ obsahu a jeho kódování, měnit HTTP hlavičky a
spravovat cookies.

$httpResponse = /*Nette\*/Environment::getHttpResponse();

// při použití bez třídy Environment


// $httpResponse = new /*Nette\Web\*/HttpResponse;

$httpResponse->setContentType('text/plain', 'UTF-8');

Manipulace s hlavičkami

Většinu metod v této třídě je nutné volat před odesláním jakéhokoliv výstupu.

Změna kódu odpovědi


Kód odpovědi mění metoda setCode.

// Změnit kód na 404 Not Found


$httpResponse->setCode(/*Nette\Web\*/IHttpResponse::S404_NOT_FOUND);

Kódy jsou před připraveny jako konstanty v rozhraní Nette\Web\IHttpResponse:

Název konstanty Hodnota


S200_OK 200
S204_NO_CONTENT 204
S300_MULTIPLE_CHOICES 300
S301_MOVED_PERMANENTLY 301
S302_FOUND 302
S303_SEE_OTHER 303
S303_POST_GET 303
S304_NOT_MODIFIED 304
S307_TEMPORARY_REDIRECT 307
S400_BAD_REQUEST 400
S401_UNAUTHORIZED 401
S403_FORBIDDEN 403
S404_NOT_FOUND 404
S410_GONE 410
S500_INTERNAL_SERVER_ERROR 500
S501_NOT_IMPLEMENTED 501
S503_SERVICE_UNAVAILABLE 503
Změna typu obsahu
Metoda setContentType mění hlavičku Content-type:

$httpResponse->setContentType('text/plain', 'UTF-8');

Nastavení expirace dokumentu


Pro nastavení vypršení platnosti dokumentu použijete metodu expire. Jejím parametrem je buď počet vteřin, po
kterých dokument expiruje, nebo timestamp, tedy čas vypršení cache.

// Cache na straně prohlížeče vyprší za hodinu


$httpResponse->expire(3600);

Nastavení ostatních HTTP hlaviček


Pro nastavení ostatních hlaviček je tu metoda addHeader.

$httpResponse->addHeader('Pragma', 'no-cache');

Chcete-li přepsat již nastavenou hlavičku, použijte metodu setHeader.

$httpResponse->setHeader('Pragma', 'no-cache');

Další metody
Pokud potřebujete zjistit zda je ještě možné odeslat další hlavičku (např. byl již odeslán nějaký výstup), můžete
použít metodu isSent, ta vrací TRUE pokud byly hlavičky odeslány a nelze tedy už odeslat další.

Seznam hlaviček připravených k odeslání (nebo již odeslaných) získáte metodou getHeaders.

Cookies
Pro manipulaci s cookies slouží metody setCookie a deleteCookie.

// Nastaví cookie author na hodnotu xgd


$httpResponse->setCookie('author', 'xgd', time() + 24 * 60 * 60);

// Odstranění cookie
$httpResponse->deleteCookie('author');
Tyto dvě metody přijimájí ještě další parametry: $path (podadresář kde bude cookie dostupná), $domain a
$secure.

Komspanse
Chcete-li ušetřit objem přenesených dat mezi klientem a serverem, můžete zapnout gzip komspansi.

$httpResponse->enableComspanssion();

Viz také:

● Nette\Web\HttpResponse API reference

« HTTP request Auto-loading tříd »


Nette\Loaders
Programový kód zpřehledníme rozdělením do více souborů. V případě OOP se nabízí ukládání každé
třídy/interface (resp. několika úzce souvisejících tříd) do samostatného souboru. Tyto soubory je vhodné
pojmenovávat podle nějaké své konvence. Následně je vkládáme do skriptů konstrukcí require_once. Opět, kvůli
přehlednosti, bývá zvykem všechna vkládání umísťovat na začátky skriptů.

Odbočka: pokud použijete relativní cestu k souboru, require_once jej dohledá možná trošku nečekaným
způsobem. Na include_path se také nerad spoléhám, proto se kloním k používání absolutních cest tímto způsobem:

require_once dirname(__FILE__).'/soubor.php';

Tohle všechno sice přispívá ke zpřehlednění kódu, ale má to i slabé stránky:

● vkládáme soubory, které třeba nebudeme potřebovat


● ke každé třídě si musíme pamatovat název souboru

Obzvláště ta dualita třída ↔ soubor mi hodně vadí. Hledal jsem tedy nějaké flexibilní řešení, které by jednak
odstranilo obě slabá místa, zároveň co nejvíce zjednodušilo programátorovi život a hlavně nekladlo nová omezení
či pravidla.

Nette\Loaders\RobotLoader
Základem je magická funkce __autoload. Díky ní se soubor s definicí třídy vloží až ve chvíli, kdy je skutečně
potřeba.

Nette má vlastní obsluhu __autoload(). Jejím jádrem je vcelku jednoduchá funkce, která v adresáři webové
aplikace proběhne všechny PHP skripty (tedy i podadresářích) a pomocí funkce token_get_all v nich vyhledá
definice tříd a rozhraní. Výsledkem je tabulka identifikátorů a relativních cest k souborům. Nette pak přesně ví,
který soubor při požadavku na konkrétní třidu vložit. Je to velice rychlé. Tabulka se samozřejmě uchovává na disku,
v podobě INI souboru.

Při nahrání nové verze aplikace na web lze jedním příkazem tabulku vygenerovat znovu, nebo ještě jednodušeji –
stačí smazat příslušný soubor a vygeneruje se sama.

Nette může běžet v tzv. ladícím režimu (DEBUG MODE). Pokud v tomto režimu není třída nalezena, provede se
automaticky regenerace cache. Nepomůže-li to, ohlásí se error.

Výhody řešení
● zbavíte se všech volání require_once
● vkládají se jen potřebné soubory
● bez striktních konvencí pojmenování souborů
● možno mít více tříd v jednom souboru
● není třeba ručně udržovat tabulku
● Nette již při generování odhalí konflikty názvů
● připadáte si jako v kompilovaném jazyce

Je to prostě velmi pohodlné a krutě návykové :-)

Konfigurace RobotLoaderu
RoborLoader standardně provádí autolading z adresářů appDir a libsDir, což stačí pro 95% případů. Pokud
je potřebné autoloading rozšířit na více adresářů, je nutné o tom RobotLoaderu říct. Nejjednodušším způsobem to
jde v konfiguračním souboru config.ini. Například takto:

service.Nette-Loaders-RobotLoader.factory = Nette\Configurator::createRobotLoader
service.Nette-Loaders-RobotLoader.option.directory[] = %appDir%
service.Nette-Loaders-RobotLoader.option.directory[] = %libsDir%
service.Nette-Loaders-RobotLoader.option.directory[] = "add/my/directory"
service.Nette-Loaders-RobotLoader.run = TRUE

První řádek „service.Nette-Loaders-RobotLoader.factory =


Nette\Configurator::createRobotLoader“ je velmi důležitý, bez něj nebude přidání
adresáře fungovat!

Viz také:

● Nette\Loaders API reference


● Best practice: načítání tříd a autoloading

« response Anotace »
Anotace
Anotace jsou speciálním druhem komentářů, který rozšiřuje schopnosti PHP o další
funkcionalitu.

Anotace se píší do phpDoc/JavaDoc bloků a začínají vždy @. V Nette Frameworku se s nimi pracuje pomocí Reflexí.
Nette implementuje vlastní anotační parser, takže by neměl nastat problém s různými akcelerátory, které kvůli
zrychlení scriptu „mažou“ komentáře.

Ukázkový kód
Toto jsou dvě ukázkové třídy, se kterými budeme v následujícím textu pracovat.

/**
* @author John Doe
* @author Tomas Marny
* @renderable
* @title(value = 'Three (Four)', mode = 'false')
*/
class FooClass
{
/** @secured */
public $foo;

/** @AJAX */
public function foo() {}
}

/**
* @author Franta Vomacka
*/
class BarClass extends FooClass
{
/**
* @privilege(Bar, bar)
* @description(testing)
* @more info, test
*/
public function bar() {}
}

Typy anotací
Anotace dělíme podle dvou kritérií: podle toho, které části kódu se týkají (třída, funkce, vlastnost, metoda) a
podle stylu zápisu.
1. @secured
2. @author John Doe
3. @privilege(Bar, bar)

První anotace je typu boolean. To znamená, že pokud je přítomna, obsahuje hodnotu TRUE. Druhá je typu string,
nese tedy textovou hodnotu. A třetí, ta nejzvláštnější, by se mohla na první pohled zdát jako pole, ale není tomu
vždy tak.

● @foo() – obsahuje boolean hodnotu TRUE


● @foo(test) – obsahuje řetězec „test“
● @foo(bar, test) – obsahuje pole řetězců „bar“ a „test“
● @foo(value = 'Three (Four)', mode = 'false') – obsahuje asociativní pole párů 'value' =>
"Three (Four)" a 'mode' => "false"

Pokud chcete pokaždé získat data jako pole, stačí výsledek přetypovat: (array)
$reflection->getAnnotation('foo')

Speciální hodnoty anotací


Následující anotace jsou jakousi výjimkou oproti normálním textovým anotacím:

● @bar null – anotace má hodnotu NULL


● @bar true – anotace má hodnotu TRUE (stejně jako @bar)
● @bar false – anotace má hodnotu FALSE

Toto se může hodit např. pro anotace, u kterých pokud chybí předpokládáte výchozí hodnotu TRUE, ale občas
jsou případy, kdy potřebujete tuto anotaci uvést s hodnotou false.

Práce s anotacemi

Ověření existence anotace


Zda daná třída obsahuje danou anotaci zjistíme tak, že zavoláme metodu
Nette\Reflection\ClassReflection::hasAnnotation() a jako parametr uvedeme název anotace (v našem případě je to
renderable).

$fooReflection = new Nette\Reflection\ClassReflection('FooClass'); //získáme reflexní třídu třídy FooClass


$fooReflection->hasAnnotation('renderable'); //vrátí TRUE
$fooReflection->hasAnnotation('exist'); //vrátí FALSE

Získání anotace
Data anotace získáme zavoláním metody Nette\Reflection\ClassReflection::getAnnotation() kde jako parametr
opět uvedeme název anotace.

$fooReflection = new Nette\Reflection\ClassReflection('FooClass'); //získáme reflexní třídu třídy FooClass


$fooReflection->getAnnotation('author'); // vrátí řetězec "Tomas Marny"

Pro lepší pochopení si ukážeme ještě získání anotace metody BarClass::bar().

$barReflection = new Nette\Reflection\MethodReflection('BarClass', 'bar');


//získáme reflexní třídu metody BarClass::bar()
$barReflection->getAnnotation('privilege'); // vrátí array("Bar", "bar")

Vždy se vypíše pouze poslední definice anotace, předchozí se přepíše.

Získání dat všech anotací


Data všech anotací získáme zavoláním metody Nette\Reflection\ClassReflection::getAnnotations().

$fooReflection = new Nette\Reflection\ClassReflection('FooClass'); //získáme reflexní třídu třídy FooClass


$fooReflection->getAnnotations();

array(3) { "author" => array(2) { 0 => string(8) "John Doe" 1 => string(11) "Tomas
Marny" } "renderable" => array(1) { 0 => bool(TRUE) } "title" => array(1) { 0 =>
object(ArrayObject) (2) { "value" => string(12) "Three (Four)" "mode" => string(5)
"false" } } }

Výsledkem je pole anotací „author“ a „renderable“, které samy obsahují pole hodnot. Pro lepší pochopení si
ukážeme ještě získání anotací k metodě BarClass::bar().

$barReflection = new Nette\Reflection\MethodReflection('BarClass', 'bar');


//získáme reflexní třídu metody BarClass::bar()
$barReflection->getAnnotations();

array(3) { "privilege" => array(1) { 0 => object(ArrayObject) (2) { "0" => string(3)
"Bar" "1" => string(3) "bar" } } "description" => array(1) { 0 => string(7) "testing"
} "more" => array(1) { 0 => string(10) "info, test" } }

Dědičnost anotací
Anotace se nepřenáší na potomky třídy, ale můžete jich použít více stejných nad jednou třídou.
Reflexe
Nette\Reflection je soubor tříd rozšiřujících standardní reflexní třídy z PHP,
především o anotace a některé vlastnosti Nette\Object.

Reflexe je jistý programový přístup k reverznímu inženýrství. Umožňuje nám zjistit takové informace jako jaké
vlastnosti a metody daná třída má.

Typy reflexe
Rozlišujeme několik typů reflexe:

● Reflexe třídy (ClassReflection) – název, implementovaná rozhraní, anotace, definované konstanty a metody…
● Reflexe vlastnosti (PropertyReflection) – název, výchozí hodnota, anotace, modifikátor přístupu, statičnost, …
● Reflexe metody (MethodReflection) – název, parametry, anotace, modifikátor přístupu, statičnost, abstraktnost,
finálnost,…
● Reflexe funkce (FunctionReflection) – název, název, parametry, jmenný prostor okolo, název souboru…
● Reflexe parametru (MethodParameterReflection) – název, povinnost, výchozí hodnota, typ (pole / třída), pořadí
parametru…
● Reflexe PHP rozšíření (ExtensionReflection) – název, verze, závislosti, definované třídy, funkce, konstanty a
klíče v php.ini

Pro detailní přehled dostupných metod si prostuduje API dokumentaci a PHP manuál.

Získání reflexe
Reflexi lze získat buď přímo, použitím jejího konstruktoru…

// získání reflexe třídy Nette\Application\Presenter


$classReflection = new Nette\Reflection\ClassReflection('Nette\Application\Presenter');

// získání reflexe metody forward třídy Nette\Application\Presenter


$methodReflection = new Nette\Reflection\MethodReflection('Nette\Application\Presenter', 'forward');

… nebo jako návratovou hodnotu z „nadřazené reflexe“:

// získání reflexe metody forward třídy Nette\Application\Presenter


$methodReflection = $classReflection->getMethod('forward');
Usnadnění práce s Reflexemi
Třídy dědící Nette\Object mají metodu getReflection() pro usnadnění práce s reflexemi. Díky tomu můžeme
s reflexemi pracovat takto jednoduše

class Foo extends Nette\Object


{
/** @var string */
public $bar;

public function getBarType()


{
return $this->getReflection() //získáme objekt reflexe třídy Foo
->getProperty('bar') //získáme objekt reflexe vlastnosti bar
->getAnnotation('var'); //získáme hodnotu anotace var
}
}

« Anotace Atomické operace‎ »


Nette\IO\SafeStream
Co se vlastně myslí pod atomickými operacemi nebo rozumí pod pojmem „thread-safe“? Začněme jednoduchým
příkladem:

$original = str_repeat('LaTrine', 10000);


$counter = 1000;

while ($counter--) {
// write
file_put_contents('soubor', $original);

// read
$content = file_get_contents('soubor');

// compare
if ($original !== $content)
die('ERROR');
}

Dokola zapisujeme a následně čteme stále tentýž řetězec. Může se zdát, že volání die('ERROR') nemůže nikdy
nastat. Opak je pravdou. Schválně si zkuste tento skript spustit ve dvou oknech zároveň. Error se dostaví
prakticky okamžitě.

Proč tomu tak je, nedávno vysvětloval Jakub Vrána. Uvedený kód není bezpečný (safe), pokud se v jednu chvíli
provádí vícekrát (tedy ve více vláknech = threads). Což na internetu není nic neobvyklého, často se v tentýž
okamžik pokusí více lidí připojit k jednomu webu. Takže psaní thread-safe aplikací je velmi důležité. Obecně totiž
platí, že pokud váš PHP skript vytváří nebo píše do souborů, je nutné toto řešit! Už pouhý jeden zápis je kritický!
V opačném případě musíte počítat se ztrátou dat a vznikem těžko odhalitelných chyb.

Je třeba zajistit, aby se funkce file_get_contents & spol. vykonávaly atomicky. Pro Nette jsem napsal třídu
Nette\IO\SafeStream, která právě toto zajistí.

Volání SafeStream::register() registruje „bezpečný stream“, pomocí něhož můžeme atomicky manipulovat
se soubory prostřednictvím standardních funkcí. Stačí jen uvést protokol „safe://“. Příklad:

// zaregistrujeme protokol
SafeStream::register();

// před jméno souboru přidáme safe://


$handle = fopen('safe://test.txt', 'x');
// ve skutečnosti se vytvořil dočasný soubor

fwrite($handle, 'La Trine');

fclose($handle);
// a teprve teď se přejmenoval na test.txt

// můžeme soubor smazat


unlink('safe://test.txt');
// a vůbec používat všechny známé funkce
file_put_contents('safe://test.txt', $content);

$ini = parse_ini_file('safe://autoload.ini');

Jak to funguje?
Využívá se funkce stream_wrapper_register, která zaregistruje protokol zaštiťující funkce pro manipulaci se
systémem souborů.

Čtení
Otevřu soubor v módu r a pokusím se získat zámek pro čtení (neboli shared lock, LOCK_SH). Poté je možné
soubor volně číst. Zámek se uvolní automaticky s uzavřením souboru – fclose() nebo při ukončení skriptu.

Zápis
Otevřu soubor v módu r+. Tím zjistím, zda existuje (budeme přepisovat) nebo je ho třeba vytvořit.

Zápis do existujícího souboru


Soubor je tedy otevřen v módu r+. Získáme zámek pro zápis (neboli exclusive lock, LOCK_EX). Obsah vymažeme
funkcí ftruncate() a pak můžeme do souboru volně psát, až do uzavření a uvolnění zámku.

Zápis do neexistujícího souboru


Není možné vytvořit nový soubor, protože než bych získal zámek, mohl by s ním pracovat jiný thread. Proto
vytvoříme dočasný (temporary) soubor v módu x a získáme exkluzivní zámek. Poté do něj volně zapisujeme.
V okamžiku uzavření souboru jej zkusíme přejmenovat na požadovaný název. Pokud se přejmenování nezdaří (jiný
thread mezitím tento soubor vytvořil), dočasný soubor smažeme.

Zápis v módu append


Protože není možné soubory otevírat v režimech a nebo w, neboť vytvoření nového souboru je nežádoucí akce,
otevřeme jej v módu r+ a posuneme ukazatel na konec via fseek().

Mazání souboru
Soubor prostě smažeme funkcí unlink(). Že má soubor otevřený jiný thread, ať už pro čtení nebo zápis, nám
nemusí vadit. Ve Windows totiž otevřený soubor vůbec smazat nelze a unlink selže. Naopak v Unixu se smaže,
jakožto položka adresáře, ale s jeho obsahem je možné nadále bezpečně pracovat.

Viz také:

● Nette\IO\SafeStream API reference


« reflexe FTP »
Nette\Forms
Třídy Nette\Forms usnadňují vytváření a zpracování webových formulářů ve vašich
aplikacích.

Třída Nette\Forms\Form je určena pro samostatné použití mimo aplikaci Nette. Pokud chcete
používat formuláře v spansenterech, využijte od ní odvozenou třídu Nette\Application\AppForm,
která přidává obsluhu handlerů v spansenterech.

Co všechno umějí?

● přehledně popsat formulář a jednotlivé prvky


● definovat validační pravidla, podmínky a filtry
● vytvářet vlastní validační pravidla
● validovat odeslaná data na straně serveru i klienta (tedy v JavaScriptu)
● lze zajistit vlastní obsluhu na straně JavaScriptu
● skrývání částí formuláře podle vlastních podmínek
● seskupovat prvky do skupin
● několik režimů vykreslování formulářů
● podpora multijazyčnosti

Nette Framework klade velký důraz na bezpečnost aplikací a proto vynakládá značné úsilí i pro zabezpečení
formulářů. Dělá to zcela transparentně, nevyžaduje nic manuálně nastavovat a troufáme si říci, že v této oblasti
má velký náskok před ostatními frameworky. Ochrání vaše aplikace před útokem Cross-Site Request Forgery
(CSRF), odfiltruje ze vstupů kontrolní znaky, ujistí se, že všechny textové vstupy představují validní UTF-8 řetězce,
že položky označené v select boxech skutečně patří mezi nabízené, automaticky ořeže mezery na jednořádkovém
textovém políčku atd.

Začínáme
Nejprve si ukážeme, jak vytvořit jednoduchý formulář, nastavit mu validační pravidla a jak jej vykreslit.

Vytvoření formuláře
Začneme vytvořením formuláře:

$form = new Form;

Tímto se vytvořil formulář, který se metodou HTTP POST odešle na stejnou stránku, na jaké se nachází.
Samozřejmě metodu i cílové URL lze změnit:

$form->setAction('/submit.php');
$form->setMethod('get');

Formulář se odešle metodou HTTP GET na adresu /submit.php.

Jak nastavit HTML elementu <form> další atributy? Metoda getElementPrototype() vrací element v podobě
Nette\Web\Html objektu, se kterým se dá snadno pracovat:

$form->getElementPrototype()->id = 'login-form';

Prvky formuláře
Existují dva způsoby, jak přidávat nové ovládací prvky do formuláře. Jednak můžeme využít toho, že formulář je
potomkem třídy Nette\ComponentContainer, takže instance prvků lze přidávat metodou addComponent(), nebo lze
použí ještě snažší cestu v podobě předpřipravených továrníček addText(), addPassword() atd. Příklad:

$form = new Form();


$form->addText('name', 'Your name:');
$form->addText('age', 'Your age:', 5);
$form->addCheckbox('send', 'Ship to address:');
$form->addSelect('country', 'Country:', $countries);
$form->addMultiSelect('category', 'Categories', $categories); // select s atributem multiple

Pokud z nějakého důvodu potřebujeme upravit html atributy prvků formuláře, můžeme si je vytáhnout metodami
getControlPrototype() a getLabelPrototype() a dále s nimi pracovat úplně stejně jako s objektem Html.
Také lze obdobně získat Html objekt samotného formuláře.

$name = $form['name']->getControlPrototype(); // htmlObject controlu


$name->class('anotherclass'); // alternativně: $name->class = 'myclass';

$nameLabel = $form['name']->getLabelPrototype(); // htmlObject labelu


$nameLabel->setText('Nette');

$htmlForm = $form->getElementPrototype(); // htmlObject formuláře


$htmlForm->class('superForm'); // nebo rovnou: $form->getElementPrototype()->class('superForm');

Validační pravidla
Metody addRule() a addCondition() … :

$form = new Form();


$form->addText('name', 'Your name:')
->addRule(Form::FILLED, 'Enter your name');

$form->addText('age', 'Your age:', 5)


->addRule(Form::FILLED, 'Enter your age')
->addRule(Form::NUMERIC, 'Age must be numeric')
->addRule(Form::RANGE, 'Age must be in range from %d to %d', array(10, 100));

$form->addCheckbox('send', 'Shipping address:')


->addCondition(Form::EQUAL, TRUE)
->toggle('sendBox'); // toggle HTML element 'sendBox'

$form->addText('email', 'Email:', 35)


->setEmptyValue('@')
->addCondition(Form::FILLED) // conditional rule: if is email filled, ...
->addRule(Form::EMAIL, 'E-mail is not valid'); // ... then check email

$form->addText('city', 'City:', 35)


->addConditionOn($form['send'], Form::EQUAL, TRUE) // if $form['send'] is checked
->addRule(Form::FILLED, 'Enter your shipping address'); // $form['city'] must be filled

$form->addSelect('country', 'Country:', $countries)->skipFirst(); // skip first option


// must be declared, if you want use skipFirst
$form['country']->addRule(Form::FILLED, 'Select your country');

Metody addRule() a addCondition() jako název validační operace akceptují callback nebo jméno statické funkce,
díky čemuž je možné používat vlastní validační pravidla.

$form = new Form();


$form->addText('name', 'Text:', 10)
->addRule('MyClass::myValidator', 'Value %d is not allowed!', 11)

Veškerá JavaScriptová podpora byla vyseparována do samostatné třídy. Díky tomu je možné vytvořit vlastní
JavaScriptový validátor nebo obsluhu událostí, lze snadno propojit vygenerovaný formulář s nějakým
JavaScriptovým frameworkem a podobně. (viz fórum)

Každý HTML prvek formuláře lze před vykreslením libovolně upravit. Přístup k němu zajišťují metody
getControlPrototype() a getLabelPrototype(), které vrací objekt typu Nette\Web\Html.

$form->addText('name', 'Text:', 10);


$form['name']->getControlPrototype()->style = "background: blue";

Potřebujeme-li zjistit id formulářového prvku, lze použít metodu getHtmlId().

Seskupování prvků
Seskupování elementů je snadné – stačí vytvořit skupinu a přidat do ní libovolné elementy:

$form->addGroup('Personal data')
->add($form['name'], $form['age'], $form['gender'], $form['email']);

Vlastně je to ještě jednodušší. Po vytvoření nové skupiny se tato stává aktivní a každý nově přidaný prvek je
zároveň přidán i do ní. Takže formulář lze stavět tímto způsobem:

$form = new Form;


$form->addGroup('Personal data');
$form->addText('name', 'Your name:');
$form->addText('age', 'Your age:');
$form->addText('email', 'E-Mail:')->emptyValue = '@';

$form->addGroup('Shipping address');
$form->addCheckbox('send', 'Ship to address');
$form->addText('street', 'Street:', 35);
$form->addText('city', 'City:', 35);
$form->addSelect('country', 'Country:', $countries);

Skupina, kterou respanzentuje třída FormGroup, představuje množinu prvků IFormControl bez specifického
sémantického významu. Význam jí tedy dodá až například renderovací rutina, která prvky vykreslí seskupené do
elementů fieldset a podobně.

Aktuální skupinu lze nastavit metodou Form::setCurrentGroup.

$form->setCurrentGroup($form->getGroup('název skupiny'));

Nebo taky elegantněji:

$group = $form->addGroup('název skupiny');


// ...
$form->setCurrentGroup($group);

Při zadání s parametrem hodnoty NULL nebudou další prvky zadávány do žádné skupiny.

Vykreslení formuláře
Formulář definuje metodu render() a lze jej vykreslit i konstrukcí echo $form.

echo $form;

Je možné si definovat vlastní vykreslovací handler $form->setRenderer($ownRenderer), což je objekt


s rozhraním IFormRenderer. Výchozím vykreslovačem je ConventionalRenderer, který není nutné explicitně
nastavovat.

Zpracování formuláře

// definice
$form = new Form();
$form->addText('name', 'Your name:');
$form->addSubmit('ok', 'Send')
->onClick[] = 'OkClicked'; // nebo 'OkClickHandler'
$form->addSubmit('cancel', 'Cancel')
->setValidationScope(FALSE)
->onClick[] = 'CancelClicked'; // nebo 'CancelClickHandler'
// alternativa:
$form->onSubmit[] = 'FormSubmitted'; // nebo 'FormSubmitHandler'

if (!$form->isSubmitted()) {
// první zobrazení, nastavíme výchozí hodnoty
$form->setDefaults($defaults);
}

// zavolá obslužné handlery (pozn. od verze 0.9.1)


$form->fireEvents();

// obslužné handlery:
function OkClicked(SubmitButton $button)
{
// submitted and valid
save($form->getValues());
redirect(...);
}

function CancelClicked(SubmitButton $button)


{
// process cancelled
redirect(...);
}

function FormSubmitted(Form $form)


{
// manual processing
if ($form['cancel']->isSubmittedBy()) ...
}

Tento způsob je vhodný pro použití v MVC implementacích jako Nette\Application.

Obslužný handler pro onSubmit lze použít v případě, kdy formulář nemá žádné nebo právě jedno tlačítko.
V odstatních situacích bývá vhodnější využít handler onClick přímo na tlačítku.

Handler onClick se volá před handlerem onSubmit. Handlery se volají pouze v případě, že je odeslání
validní. Pokud odeslání validní není, volají se handlery pro události onInvalidClick a onInvalidSubmit.
Uvnitř metody OkClicked tedy není nutné ověřovat validitu formuláře. Naopak metoda FormSubmitted může
být zavolána i v případě nevalidního formuláře, byl-li odeslán tlačítkem Cancel.

V případě, že formulář nebyl odeslán tlačítkem (například byl odeslán přes JavaScript), nebo se tak tváří kvůli
chybě v Internet Exploreru, bude Nette za odesílací tlačítko považovat první tlačítko formuláře. Tudíž obsluha přes
události onClick je spolehlivá.

Obslužné handlery se vyvolají při prvním volání metody fireEvents() (od verze 0.9.1, dříve při volání
isSubmitted()), při použití uvnitř Nette\Application vyvolání zajistí samotný spansenter. Úkolem metody
isSubmitted() je zjistit, zda-li byl formulář odeslán. Formulář se validuje až při zavolání metod validate()
nebo isValid().

Nastavení výchozích hodnot formuláře (v případě, že nebyl odeslán) metodou setDefaults() nepřemazává
ostatní výchozí hodnoty prvků formuláře. Má ale druhý volitelný parametr, pomocí kterého se toho dá docílit –
$erase = TRUE.

Po odeslání a zpracování formuláře je vhodné stránku následně přesměrovat. Zabrání se tak


nechtěnému opětovnému odeslání formuláře při přístupu na stánku z historie prohlížeče.

Data získaná metodou Form::getValues neobsahují hodnoty formulářových tlačítek, tak je lze
často rovnou použít pro další zpracování (například vložení do databáze).

Obrana před Cross-Site Request Forgery (CSRF)


Útok spočívá v tom, že útočník naláká oběť na stránku, která vykoná požadavek (přesměrováním nebo
javascriptem) na server, na kterém je oběť přihlášena. Ochrana spočívá v tom, že při požadavku se kontroluje
token, jehož hodnotu útočník nemůže znát a tudíž ji nemůže ani podstrčit. Může jít třeba o náhodně vygenerované
číslo, které se uloží do session.

Aktivace ochrany je velmi snadná:

$form = new Form;


...
$form->addProtection([string $message = NULL], [int $timeout = NULL]);

Jako parametr je možné uvést text chybové hlášky, která se zobrazí uživateli, pokud je detekováno neoprávněné
odeslání.

Token chránící před CSRF útokem má platnost po dobu existence session. Díky tomu nebrání použití ve více
oknech najednou (v rámci jedné session). Platnost je však možné zkrátit na počet sekund, které se uvedou jako
druhý parametr.

Obrana by měla být aktivována pokaždé, kdy formulář mění nějaká citlivá data v aplikaci.

Viz také:

● Nette\Application\AppForm
● Nette\Forms API reference
● Nette\Forms\Form API reference
● Best practice: Formulářová tlačítka
● Vlastní vykreslování formulářů

« Rozšíření jazyka PHP – Nette\Object Šablony »


Nette\Web\Ftp
Přístup k FTP serveru

Opens an FTP connection to the specified host:

$ftp = new Ftp;


$ftp->connect($host);

Login with username and password

$ftp->login($username, $password);

Upload the file

$ftp->put($destination_file, $source_file, FTP_BINARY);

Close the FTP stream

$ftp->close();
// or simply unset($ftp);

Ftp throws exception if operation failed. So you can simply do following:

try {
$ftp = new Ftp;
$ftp->connect($host);
$ftp->login($username, $password);
$ftp->put($destination_file, $source_file, FTP_BINARY);

} catch (FtpException $e) {


echo 'Error: ', $e->getMessage();
}

On the other hand, if you'd like the possible exception quietly catch, call methods with the spanfix „try“:
$ftp->tryDelete($destination_file).

When the connection is accidentally interrupted, you can re-establish it using method ftp->reconnect().

Viz také:

● Nette\Web\Ftp API reference


« Atomické operace‎ Pro řetězce »
Nette\String
Nette\String je statická třída s užitečnými funkcemi pro práci s řetězci. Je navržena
speciálně pro práci s řetězci v kódování UTF-8.

String::checkEncoding
Zjistí, je-li řetězec v požadovaném kódování.

public static bool checkEncoding (string $s, [string $encoding = 'UTF-8'])

Příklad:

$isUtf = String::checkEncoding($string, 'UTF-8');

String::fixEncoding
Vrací správně zakódovaný řetězec v určitém kódování. Výchozí je UTF-8, případně je změněno druhým
parametrem metody.

public static string fixEncoding (string $s, [string $encoding = 'UTF-8'])

Příklad:

$correctString = String::fixEncoding($string);

String::startsWith
Vrací TRUE v případě, že řetězec $haystack začíná řetězcem $needle.

public static bool startsWith (string $haystack, string $needle)

Příklad:

$haystack = "Začíná";
$needle = "Za";
String::startsWith($haystack, $needle); // true

String::endsWith
Vrací TRUE v případě, že řetězec $haystack končí řetězcem $needle.

public static bool endsWith (string $haystack, string $needle)

Příklad:

$haystack = "Končí";
$needle = "čí";
String::endsWith($haystack, $needle); // true

String::normalize
Odstraní z textu pravostranné mezery a sjednotí oddělovače řádků.

public static string normalize (string $s)

Příklad:

$normalizedString = String::normalize($string);

String::webalize
Upraví řetězec do tvaru použitelného v URL adresách. Odstraní diakritiku a všechny znaky kromě
alfanumerických nahradí oddělovačem slov -.

public static string webalize (string $s, [string $charlist = NULL])

Příklad:

echo String::webalize("krásná webová adresa"); // vypíše krasna-webova-adresa

Mají-li být zachovány i jiné znaky, lze je vyjmenovat v druhém parametru funkce.
echo String::webalize("19. 2. podtržítková_akce", "._");
// 19.-2.-podtrzitkova_akce

Vstupní řetězec musí být kódován v UTF-8. Pokud je v jiném kódování, převeďte jej funkcí iconv.

String::truncate
Ořízne řetězec na maximální délku a zachová celá slova, je-li to možné. Na konec oříznutého textu se přidá
trojtečka, což lze změnit třetím nepovinným parametrem.

public static string truncate (string $s, int $maxLen, [string $append = "…"])

Příklad:

$text = 'Řekněte, jak se máte?';


echo String::truncate($text, 5); // 'Řekn…'
echo String::truncate($text, 20); // 'Řekněte, jak se…'
echo String::truncate($text, 30); // 'Řekněte, jak se máte?'

Vstupní řetězec musí být kódován v UTF-8. Pokud je v jiném kódování, převeďte jej funkcí iconv.

Změna velikosti písmen v řetězci

$s = "Dobrý den";

// převést na malá
echo String::lower($s); // dobrý den

// převést na velká
echo String::upper($s); // DOBRÝ DEN

// každé první písmeno ve slově velké


echo String::capitalize($s); // Dobrý Den

Regulární výrazy
Třída String zapouzdřuje několik užitečných funkcí pro práci s regulárními výrazy. Jejich společným důležitým
rysem je, že v případě jakékoliv chyby vyhodí výjimku Nette\RegexpException.
public static array split (string $subject, string $pattern, [int $flags = 0])

Rozdělí řetězec do pole dle regulárního výrazu. Jako příznak $flag je možné uvést PREG_SPLIT_NO_EMPTY
nebo PREG_SPLIT_OFFSET_CAPTURE, viz dokumentace PHP. Příklad:

$res = String::split('Prvni, druhy,treti', '~,\s*~'); // array('Prvni', 'druhy', 'treti')


$res = String::split('Prvni, druhy,treti', '~(,)\s*~'); // array('Prvni', ',', 'druhy', ',', 'treti')

Vyhledávání výrazů:

public static array match(string $subject, string $pattern, [int $flags = 0, [int $offset = 0]])

Hledá v řetězci dle regulárního výrazu a vrátí pole s jednotlivými subvýrazy (pokud subvýrazy nepoužijete, vrátí
se pole s jedním prvkem). Jako příznak $flag je možné uvést PREG_OFFSET_CAPTURE, viz dokumentace PHP.
Příklad:

list($res) = String::match('Prvni, druhy,treti', '~[a-z]+~i'); // 'Prvni'


list($res) = String::match('Prvni, druhy,treti', '~\d+~'); // NULL

public static array matchAll(string $subject, string $pattern, [int $flags = 0, [int $offset = 0]])

Hledá v řetězci všechny výskyty dle regulárního výrazu a vrátí je jako dvourozměrné pole. Jako příznak $flag je
možné uvést PREG_OFFSET_CAPTURE nebo PREG_PATTERN_ORDER, viz dokumentace PHP. (Narozdíl od funkce
spang_match_all je příznak PREG_SET_ORDER výchozí). Příklad:

$res = String::matchAll('Prvni, druhy,treti', '~[a-z]+~i');


// array(0 => array('Prvni'), 1 => array('druhy'), 2 => array('treti'))

$res = String::matchAll('Prvni, druhy,treti', '~\d+~'); // array()

Záměny v řetězci:

public static string replace(string $subject, mixed $pattern, [mixed $replacement = NULL, [int
$limit = -1]])

Provede v řetězci záměny dle regulárního výrazu. Druhý parametr $pattern může kromě řetězce s regulárním
výrazem nabývat také hodnoty asociativního pole ve tvaru pattern => replacement. Jako třetí parametr
$replacement lze uvést callback. Příklad:

echo String::replace('Prvni, druhy,treti', '~[a-z]+~i', '*'); // '*, *,*'


echo String::replace('Prvni, druhy,treti', array('~[a-z]+~i' => '*')); // '*, *,*'
echo String::replace('Prvni, druhy,treti', '~[a-z]+~i', function($m) { return strrev($m[0]); });
// 'invrP, yhurd,itert'

Viz také:

● Nette\String API reference

« FTP HTML elementy »


Nette\Web\Html
Nette\Web\Html je malý pomocník pro generování (X)HTML kódu v PHP. Nabízí
objektové zapouzdření HTML elementů a zjednodušení generování HTML. V Nette
Frameworku je tato třída používána dalšími knihovnami, zejména formuláři.

$el = Html::el("img");
$el->src = "image.jpg";
echo $el;

Zápis a čtení atributů


Změnit a číst atributy elementu je možné přes vlastnosti objektu. Zrušení hodnoty dosáhnete nastavením
hodnoty NULL.

$el = Html::el("img"); // vytvoření elementu


$el->src = "image.jpg"; // nastavení atributu
echo $el->src; // čtení atributu
$el->src = NULL; // zrušení hodnoty

Další možností je zavolání přetížené metody. V tomto případě je často výhodné použít zřetězených volání
(fluent interfaces).

$img = Html::el("img")->src = "image.jpg";


echo Html::el("input")->type($secret ? "password" : "text")->name("heslo");

Pokud jste zvyklí settery a gettery volat s příslušnými spanfixy set a get, můžete to tak činit i zde.

Hodnoty atributů
Hodnotou atributu nemusí být jen číslo. Pokud je to praktické, můžete použít i logickou hodnotu nebo dokonce
pole. Také existuje speciální setter pro usnadnění nastavování cíle odkazu s parametry.

$checkbox = Html::el("input")->type("checkbox");
$checkbox->checked = TRUE; // <input type="checkbox" checked="checked" />
$checkbox->checked = FALSE; // <input type="checkbox" />

// použití pole
$el->class[] = $active ? 'active' : NULL; // NULL se ignoruje
$el->class[] = 'top';

$el->style['color'] = 'green';
$el->style['display'] = 'block';
echo $el;
// <input class="active top" style="color: green; display: block" />

// nastavení cíle odkazu


$params["id"] = 10;
$a = Html::el("a")->href("index.php", $params);
// samozřejmě druhý parametr nemusíte použít
// a můžete napsat celou adresu v prvním parametru

Použití továrničky Html::el


Atributy lze nastavit již přímo v továrničce Html::el. Od Nette Frameworku verze 0.9 lze přímo v továrničce
nastavit vícenásobné hodnoty atributu (například třídy).

$el = Html::el("input type=text class=required");


$el = Html::el('strong class="red important"'); // od verze 0.9

Zadáte-li nepovinný druhý parametr, tak v případě, že to bude pole, tak nastavíte parametry elementu. Pokud jím
bude text, tak nastavíte vnitřní text elementu.

$strong = Html::el("strong", "Barnes & Noble");


$input = Html::el("input", array("type" => "text"));

Výpis elementu
● Nejjednodušším způsobem vypsání elementu je použít echo.
● Převedení na řetězec zařídíte přetypováním.
● Získání otevírací a uzavírací značky obstarají metody startTag a endTag.
● Alternativou k příkazu echo je použití metody render.

$el = Html::el("div class=header");

echo $el; // <div class="header"></div>


$s = (string) $el; // získání řetězce pro pozdější použití
echo $el->startTag(); // <div class="header">
echo $el->endTag(); // </div>

$el->render(); // vypíše <div class="header"></div>

Změna výstupního formátu (HTML vs. XHTML)


Výstupní formát ovládá statická proměnná Html::$xhtml. Výchozím nastavením je XHTML.
$el = Html::el("hr");
echo $el; // <hr />
Html::$xhtml = FALSE;
echo $el; // <hr>

Hierarchie elementů

$el = Html::el();
// (všimněte si, $el je nyní "kontejner", tj. bez názvu elementu)

// a vytvoříme potomka, element strong


$strong = $el->create('strong');
$strong->setText('La Trine');
// nebo lze psát rovnou:
// $el->create('strong', 'La Trine');

// lze přidávat existující uzly Html


$br = Html::el('br');
$el->add($br);

echo $el; // <strong>La Trine</strong><br />

// uzel může být i textový


$el->add('Yes!'); // obdoba setText, ale narozdíl od setText nesmaže původní obsah elementu

// nastavit HTML
$el->setHtml("<em>html</em>");

// nebo přidat HTML


$el->add("<em>html</em>");

// k potomkům lze přistupovat přímo přes ArrayAccess


$el[] = 'Hello!';

if ($el->count()) ...

Viz také:

● Nette\Web\Html API reference

« Pro řetězce
Nette\Templates\Template
Třída Nette\Templates\Template zapouzdřuje soubor se šablonou.

Základy

use Nette\Templates\Template;

$template = new Template;

// nastavíme cestu k souboru šablony


$template->setFile('template.phtml');

// nastavíme parametry
$template->hello = 'Hello World';

Příklad šablony:

<p><?php echo $hello ?></p>

A nakonec její vykreslení:

echo $template; // lze použít i $template->render();

Použití šablon samostatně bez MVP návrhu Nette Frameworku se dále věnuje samostatná stránka.

Filtry
Šablonu je možné předzpracovat pomocí jednoho či více filtrů, což jsou funkce, které dostanou jako parametr
obsah šablony a vrátí ho v pozměněném stavu. Jako filtr lze zaregistrovat libovolný callback nebo anonymní funkci.

// Zaregistruje filtr, který nahradí v textu šablony všechny výskyty slova 'apple' slovem 'pizza'.
$template->registerFilter(function ($s) {
return str_replace('apple', 'pizza', $s);
});

Registrace filtrů v spansenterech


Nejvhodnějším způsobem, jak zaregistrovat filtr v spansenterech (resp. v Controlech), je přepsání metody
templatePrepareFilters.
use Nette\Application\Presenter;

abstract class BasePresenter extends Presenter


{
...
public function templatePrepareFilters($template)
{
parent::templatePrepareFilters($template); // zaregistruje výchozí filtr (Latte)
$template->registerFilter('apple2pizza'); // předpokládá definovanou funkci apple2pizza
}
...
}

Přímo v distribuci frameworku je obsaženo několik standardních filtrů, přičemž nejvýznamnější


z nich je Latte filter.

Helpery
Do šablon je možné zaregistrovat pomocné funkce, tzv. helpery. Jako helper lze zaregistrovat libovolný callback
nebo anonymní funkci.

Registrace helperu:

$template->registerHelper('shortify', function ($s) {


return mb_substr($s, 0, 6);
});

Použití helperu v šabloně:

<?php echo $template->shortify($text); // vypíše text zkrácený na text 6 písmen ?>

Helper může brát i více než jeden parametr.

$template->registerHelper('useTag', function ($s, $tag) {


return "<$tag>$s</$tag>";
});

<?php echo $template->useTag($text, 'strong'); // obalí text tagem <strong> ?>

Přečtěte si popis standardních helperů, které najdete přímo v distribuci, a o možnosti snazšího
zápisu pomocí Latte filteru.

HelperLoader
Manuální registraci velkého množství helperů lze nahradit registrací jednoho či více HelperLoaderů. Jako
HelperLoader lze zaregistrovat libovolný callback nebo anonymní funkci.

$template->registerHelperLoader('Helpers::loader');

HelperLoader dostane jako parametr název požadovaného helperu a vrací jeho callback nebo NULL v případě, že
helper není schopnen dodat.

class Helpers
{
public static function loader($helper)
{
$callback = callback(__CLASS__, $helper);
if ($callback->isCallable()) {
return $callback;
}
}

public static function shortify($s)


{
return mb_substr($s, 0, 6);
}
}

Viz také:

● Nette\Templates\Template API reference


● Přehled standardních filtrů
● Přehled standardních helperů

Latte filter »
Latte filter
Latte filter slouží nejen pro usnadnění zápisu šablon, ale také umožňuje pracovat s
bloky a podporuje kontextově sensitivní escapování.

Ve verzi 0.9.0 se tento filtr jmenuje CurlyBrackets!

Registrace
Ve vykreslitelných komponentách se filtr registruje automaticky. Jinde je potřeba jej zaregistrovat stejně jako
každý jiný filtr.

$template->registerFilter(new LatteFilter);

Základní makra
Zápis v Latte PHP ekvivalent nebo význam
{$variable} Vypíše kontextově escapovanou proměnnou.
{!$variable} Vypíše proměnnou bez escapování.
{*text komentáře*} Komentář, bude odstraněn
{plink ...} Vypíše kontextově escapovaný odkaz.
Odkaz nad komponentou (v šabloně spansenteru ekvivalentní s makrem
{link ...}
plink)
<?php if (?): ?> ... <?php elseif (?): ?> ... <?php
{if ?} ... {elseif ?} ... {/if}
endif ?>
{foreach ?} ... {/foreach} <?php foreach (?): ?> ... <?php endforeach ?>
{for ?} ... {/for} <?php for (?): ?> ... <?php endfor ?>
{while ?} ... {/while} <?php while (?): ?> ... <?php endwhile ?>
{include dir/file.phtml} Vloží podšablonu
{var foo => value} Deklaruje proměnnou v šabloně
{default foo => value} Výchozí hodnoty proměnných šablony
{control loginForm} Vykreslí komponentu s daným názvem
{dump $variable} Pošle dump proměnné do DebugBaru
{l} a {r} Vloží znaky { a }

Výpis proměnných
Všechny proměnné jsou vypisovány escapované.

Jméno: {$name}<br>
Příjmení: {$surname}<br>
Věk: {$age}

Pokud z nějakého důvodu potřebujeme escapování vypnout, tak před dolar vložíme vykřičník.
<h1>{$title}</h1> {* Escapovaný titulek *}
{!$content} {* Neescapovaný obsah stránky *}

Přizpůsobení escapování podle lokálního typu obsahu


Velmi důležitou vlastností je přizpůsobení escapování podle lokálního typu obsahu (tzv. kontextově senzitivní
escapování). Uvnitř JavaScriptu nebo uvnitř CSS se escapuje jinak, než v HTML kódu. Díky tomu je možné zcela
nativně používat PHP proměnné uvnitř JavaScriptového kódu.

Příklad:

$template->pole = array(1, 2, 3);


$template->name = "Jim Beam";

<script type="text/javascript">
var pole = {$pole};
var name = {$name}; // ALE POZOR - NESMÍ se už používat extra uvozovky
var anotherName = "{$name}"; // CHYBA, vygeneruje anotherName = ""Jim Beam"";
</script>

Vložíme-li proměnnou v šabloně do html, css nebo javascriptu, vždy se správně escapuje ($id je nějaký
identifikátor obsahující libovolné znaky):

<style type="text/css">
#{$id} { background: blue; }
</style>

<script type="text/javascript">
document.getElementById({$id}).style.backgroundColor = 'red';
</script>

<p id="{$id}">Hello!</p>

Vkládání odkazů {plink ...}


Značka {plink} je zkratkou pro $spansenter->link a slouží tedy pro generování odkazu na danou akci
daného spansenteru s danými parametry, resp. na daný signál. Pro zápis cesty se používá stejná syntaxe jako při
generování odkazů v spansenteru.

{* Vygeneruje relativní cestu na `ProductsPresenter` a na akci detail *}


{plink Products:detail, id => $productId}

{* Pokud je v metodě `renderDetail` definován parametr `$id`, tak lze jeho jméno při
tvorbě odkazu vynechat *}
{plink Products:detail, $productId}

{* Pokud je aktuálním spansenterem právě `ProductsPresenter`, tak lze vynechat i jeho


jméno *}
{plink detail, $productId}

{* Ve všech případech lze předat i více parametrů *}


{plink detail, id => $productId, from => "search", ...}
{plink detail, $productId, from => "search", ...}

Vkládání odkazů {link ...}


Funguje podobně jako {plink}, ale generuje odkazy nad aktuální komponentou (jedná se o zkratku pro
$control->link).

Podmínky {if ?} ... {/if}


Pro podmíněné vykreslování určitých částí šablon lze použít podmínky ve tvaru {if ?} ... {elseif ?} ...
{else} ... {/if}.

{if $isLoggedIn}<a href="...">Odhlásit se</a>{else}<a href="...">Přihlásit se</a>{/if}

Lze použít i speciální konstrukci {ifset $var} ... {elseifset $var2} ... {/if}, která nahrazuje zápis
{if isset($var)} ... {elseif isset($var2)} ... {/if}.

Uvnitř cyklu lze používat makra {continueIf ?} a {breakIf ?}.

Foreach cyklus
Foreach cyklus se chová jako běžný foreach v php s několika rozšířeními.

Uvnitř cyklu je inicializovaná proměnná $iterator, díky které můžete zjistit některé jinak těžko zjistitelné údaje
o právě probíhajícím cyklu.

Metody proměnné $iterator:

● isFirst() – prochází se cyklem poprvé?


● isLast() – jde o poslední průchod?
● getCounter() – čítač průchodů cyklem počítaný od jedničky
● isOdd() – jde o lichý průchod?
● isEven() – jde o sudý průchod?

Příklad:

{foreach $rows as $row}


{if $iterator->isFirst()}
<table>
{/if}
<tr id="row-{$iterator->getCounter()}">
<td>{$row->name}</td>
<td>{$row->email}</td>
</tr>
{if $iterator->isLast()}
</table>
{/if}
{/foreach}

Vkládání souborů
Vkládání souborů se provádí makrem {include file.phtml}. Vložený soubor má k dispozici globální
proměnné aktuální šablony ($template->getParams()) a parametry, které mu při volání předáme.

{include userProfile.phtml, id => 12, name => 'John Smith'}

Makro {include} se kromě vkládání souborů používá i pro vkládání bloků.

Deklarace proměnných
Někdy může být vhodné deklarovat proměnnou až v šabloně. Pro tyto účely se používá makro {var} (existuje
alias {assign}) za použití následující syntaxe:

{var name => 'John Smith'}


{var age => 27}

{* Vícenásobná deklarace *}
{var name => 'John Smith', age => 27}

V případě, že chceme deklarovat promměnou pouze v případě, že dosud deklarovaná není, tak použijeme makro
{default}.

{default page => 1} {* Lze zapsat také jako {if !isset($page)}{var page => 1}{/if} *}

Vykreslování komponent {control ...}

Ke značce {control} existuje i alias {widget}.

Značka {control} slouží pro snadné vykreslování komponent. Pro lepší pochopení je dobré vědět, jak se tato
značka přeloží do PHP.

{control cartControl} pro celý košík na stránce


{control cartControl:small} pro malý náhledový košík

se přeloží jako:

$control->getWidget("cartControl")->render();
$control->getWidget("cartControl")->renderSmall();

Metoda getWidget() vrací komponentu cartControl a nad touto komponentou volá metodu render(), resp.
renderSmall() pokud je jiný způsob renderování uveden ve značce za dvojtečkou.
Lze použít i volání s parametry, které se předají render metodám, například:

{control cartControl:small, $maxItems}

se přeloží jako:

$control->getWidget("cartControl")->renderSmall($maxItems);

Dumpování proměnných {dump}


{dump $name} {* Vypíše proměnnou $name *}

{dump} {* Vypíše všechny aktuálně definované proměnné}

Alternativní zápis pomocí n:atributů


Párová makra lze zapisovat také alternativní syntaxí pomocí tzv. n:atributů.

Zápis pomocí n:atributů Standardní zápis


{if $cond}
<div class="login" n:if="$cond"> <div class="login">
<p>User {$name}</p> <p>User {$name}</p>
... ...
</div> </div>
{/if}

<ul>
<ul> {foreach $items as $item}
<li n:foreach="$items as $item">{$item}</li> <li>{$item}</li>
</ul> {/foreach}
</ul>

<p>{if $strong}<strong>{/if}...{if
<p><strong n:tag-if="$strong">...</strong></p>
$strong}</strong>{/if}</p>

<ul>
<ul n:inner-foreach="$items as $item"> {foreach $items as $item}
<li>A {$item}</li> <li>A {$item}</li>
<li>B {$item}</li> <li>B {$item}</li>
</ul> {/foreach}
</ul>

Podpora helperů
Latte filter podporuje snadné volání helperů za použití této syntaxe:

<h1>{$heading|upper}</h1>

Je možno zřetězit více helperů (resp. modifikátorů):

<h1>{$heading|lower|capitalize}</h1>
Vykonají se v pořadí od levého k pravému.

Další parametry funkce helperu se zadávají za jménem helperu oddělené dvojtečkami.

<a href="...">{$linkText|truncate:20}</a>

Kam dále?
1. Pokročilá makra
2. Dědičnost
3. Snippety
4. Vlastní makra

« Nette\Templates\Template Pokročilá makra »


Pokročilá makra
Zápis v Latte PHP ekvivalent nebo stručný význam
{=exspanssion} <?php echo htmlSpecialChars(exspanssion) ?>
{!=exspanssion} <?php echo exspanssion ?>
{?exspanssion} vyhodnotí PHP kód
{_exspanssion} vypíše překlad s escapováním
{!_exspanssion} vypíše překlad bez escapování
{ifCurrent} speciální případ {if} pro aktivní odkaz
{cache ?} ... {/cache} cachovaný blok
{snippet ?} ... {/snippet} control snippet
{attr ?} usnadňuje zápis atributů html značek
{capture $var} ... {/capture} zachytnutí bloku do proměnné
{block |texy} ... {/block} texy block
{widget ...} připraví komponentu k vykreslení
{control ...} alias pro widget
{contentType ?} pošle HTTP hlavičku Content-Type
{status ?} nastaví stavový kód HTTP
{debugbreak} vloží breakpoint, funguje ale jen v některých IDE, např. PhpED

{_exspanssion} a {_!exspanssion}
Tyto značky umožňují překládání v šablonách. Pro jejich správnou funkčnost musí být nastaven překladač:

$template->setTranslator(new MyTranslator);

Překladač musí implementovat rozhraní Nette\Translator.

Zachytávání výstupu do proměnné


Značky {capture} se používají pro zachytávání výstupu do proměnné:

{capture $var}
<ul>
<li>Hello World</li>
</ul>
{/capture}

<p>Captured: {$var}</p>

Zachytávání je přitom možné kombinovat i s modifikátory.

Značka {block|texy}
Tam kde není vhodné použít filtr texyElements, například při kombinaci s filtrem Latte, kde by docházelo ke
kolizím, můžete zkusit použít kombinace bloku a helperu.

$texy = new Texy();


// ...a jeho konfigurace

$template->registerFilter(new Nette\Templates\LatteFilter);
$template->registerHelper('texy', array($texy, 'process'));

šablona:

{block|texy}
Vítejte!
--------

Můžete používat syntax Texy!, pokud Vám vyhovuje:


- třeba **tučné** písmo nebo *kurzíva*
- a takto se dělá [odkaz | http://texy.info]

[* image.jpg *]
{/block}

Označný blok s modifikátorem texy je pak předhozen helperu texy.

Pro pochopení: {block} ... {/block} vygeneruje něco cca takového:

<?php ob_start() ?>


Vítejte!
--------

Můžete používat syntax Texy!, pokud Vám vyhovuje:


- třeba **tučné** písmo nebo *kurzíva*
- a takto se dělá [odkaz | http://texy.info]

[* image.jpg *]
<?php echo $template->texy(ob_get_clean()) ?>

A $template->texy(...) je volání helperu texy, tj. volání callbacku array($texy, 'process').

Alternativní je předat do šablony proměnnou s texy obsahem a ten pak pomocí helperu převést.

šablona:

<div>{!foo->info |texy}</div>

Spolupráce s třídou Html


Filtr Latte řadu věcí zjednodušuje tak, že použití Nette\Web\Html dokáže zpříjemnit i například zapisování atributů.
Klasickým zápisem to není úplně ono:

<a href="xxx"{if $level==0} class="top"{/if}{if $color || $background} style="{if $color}color:


{$color};{/if}{if $background}background:{$background}{/if}"{/if}>
Pomocí provázání filtru s třído Html lze dosáhout stejného výsledku jako v předchozím případě tímto způsobem
(výsledek je ekvivalentní, tato funkce je ale zatím experimentální):

<a href="xxx" {attr class('top', $level==0) style('color', $color) style('background', $background)}>

Což se vlastně přeloží jako

echo Html::el()->class('top', $level==0)->style('color', $color)->style('background', $background);

Značka {snippet}
Snippety se používají při práci s Ajaxem pro označení logické oblasti na stránce, která má být jako celek
v případě Ajaxového požadavku překreslena. Značky a {snippet} používají ve své vnitřní implementaci
SnippetHelper, který zjednodušuje práci s Ajaxem na nutné minimum. Celá tato problematika včetně příkladů
použití je probrána na stránce Ajax & snippety.

Značky {cache} … {/cache}


Pomocí značek {cache} a {/cache} lze označit části šablony, které se mají ukládat do cache. Kešování je tak
možno velmi snadno doplnit i do vykreslovací části aplikace. Vnitřní implementace značky využívá CachingHelperu.

Funkce je zatím experimentální.

Označené části se automaticky invalidují, když se změní šablona a to včetně i všech případných inkludovaných
souborů.

Dále je podporováno vnořování značek {cache}. Části šablony se pak invalidují, když se invaliduje kterákoliv
vnořená část.

Jako parametry je možné uvést invalidační tagy (více v dokumentaci kešování):

{cache "item/$id", "comments"}


<h1>{$title}</h1>

{include 'spot.phtml'}
{include 'comments.phtml'}
{/cache}

Pokud šabloně předáme vazbu na model, který data vrací až „on demand“ nebo-li „lazy“ způsobem, tak je
kešování naopak velmi efektivní a tato funkčnost přesouvá kešování tam, kde je potřeba, tedy do šablon.

Značka {ifCurrent}
Značka {ifCurrent destination} pomáhá zjistit, zda-li je cíl odkazu shodný s aktuální stránkou. Pokud
odkaz směřuje na stránku, která je zrovna zobrazena, můžeme ho například jinak nastylovat a podobně. Trik je
v tom, že při generování odkazu se detekuje, jestli odkaz míří na aktuální stránku. Výsledek pak vrátí
$spansenter->getCreatedRequest()->hasFlag('current').

<!-- příklady použití -->


<a href="{link edit, 10}">edituj</a>
<ul class="menu">
<li><a href="{link Default:default}">...</a></li>
<li><a href="{link}">...</a></li>
...
<li {ifCurrent Default:default}class="current"{/if}><a href="{link Default:default}">
...</a></li>

<!-- rozšíření scope -->


<li {ifCurrent Default:*}class="current"{/if}><a href="{link Default:default}">...
</a></li>
</ul>
<!-- znegování -->
{ifCurrent Admin:login}{else}<a href="{link Admin:login}">Přihlašte se!</a>{/if}

Použití znaku @ před značkami


http://forum.nette.org/…agie-v-praxi

Grid rendering a pravidelné operace při iteraci


http://forum.nette.org/…id-rendering

Viz také:

● Nette\Templates\TemplateFilters API reference


● Nette\Templates\LatteFilter API reference

« helperů Dědičnost »
Dědičnost šablon
Dědičnost šablon představuje mocný nástroj, který umožňuje snadnou a efektivní
tvorbu i velmi komplikovaných layoutů bez nutnosti zbytečného opakování kódu.

Bloky
Bloky slouží pro označení části šablony. Mohou být buď anonymní nebo pojmenované (ty se používají
především).

Definice
K definici se obvykle používá makro {block}, které umožňuje definovat jak anonymní, tak pojmenované bloky.

{block}Anonymní blok{/block}
{block #foo}Pojmenovaný blok s názvem 'foo'. Hash (#) na začátku jména lze při
deklaraci bloku vynechat.{/block}

Kromě makra {block} lze pro definování pojmenovaného bloku ve specifických situacích využít také atributy
n:block a n:inner-block.

Atribut n:block se používá pro definici bloku okolo HTML tagu. Následující dva zápisy jsou ekvivalentní:

{block #foo}<p>...</p>{/block}
<p n:block="foo">...</p>

Atribut n:inner-block se používá pro definici bloku uvnitř HTML tagu. Následující dva zápisy jsou opět
ekvivalentní:

<p>{block #foo}...{/block}</p>
<p n:inner-block="foo">...</p>

Vkládání
Části šablony označené jako bloky jsou vyjmuty ze svých původních míst v šabloně a pro jejich vypsání je třeba je
znovu „vložit“. Pokud je blok definován v šabloně, která nepoužívá layout (o tom se dozvíte více později), je vložen
automaticky na místo své definice. V ostatních případech je třeba blok vložit ručně, k čemuž slouží makro
{include}. Název bloku zde musí být uveden s # (jinak by se makro pokusilo načíst soubor).

{include #foo}

Příklady
Dovnitř tagu <title> bude vložen obsah tagu <h1>.

...
<title>{include #title} | Example.com</title> <!-- Vypíše "Registrace | Example.com"
-->
...
<h1 n:inner-block="title">Registrace</h1> <!-- Definován blok #title. Vyjmut z šablony
a následně automaticky vložen. -->
...

Platnost proměnných
Při deklaraci bloku do něj automaticky přecházejí všechny lokální i globální proměnné. Změna hodnoty lokální
proměnné se projeví pouze v daném bloku (a tím pádem i v dceřiných blocích). Nejlépe to bude vidět asi na
příkladu:

{assign text => "A"}

{block #alpha}
{$text} <!-- Vypíše "A" --><br>
{assign text => "B"}
{$text} <!-- Vypíše "B" --><br>

{block #beta}
{$text} <!-- Vypíše "B" --><br>
{/block}
{/block}

{$text} <!-- Vypíše "A" --><br>

Při vložení bloku do něj přecházejí automaticky pouze globální proměnné šablony. Lokální proměnné je možné
v případě potřeby předat ručně.

{include #foo, a => 45, b => 'xyz', c => $text}

Vkládání sebe sama (rekurze)


Blok může vložit i sám sebe, což lze použít např. pro vykreslení stromového menu.

{block #menu}
<ul>
{foreach $menu as $item}
<li>{if is_array($item)} {include #menu, menu => $item} {else} {$item} {/if}</li>
{/foreach}
</ul>
{/block}

Místo {include #menu, ...} lze psát {include #this, ...}, čímž odstraníme duplicitu slova menu.

K dispozici je také video z Pardubic, kde byla dědičnost šablon představena.


Dědičnost

Základy
Začněme od začátku – máme jednoduchou stránku:

FirstPage.phtml

<!doctype html>
<html>
<head>
<title>První stránka | Můj web</title>
</head>
<body>
<div id="leftColumn"><ul>...</ul></div>
<div id="content"><p>Mauris consectetur lobortis purus eget...</p></div>
</body>
</html>

Vlastně – máme i druhou:

SecondPage.phtml

<!doctype html>
<html>
<head>
<title>Druhá stránka | Můj web</title>
</head>
<body>
<div id="leftColumn"><ul>...</ul></div>
<div id="content"><p>Proin eu sem purus. Donec bibendum vestibulum...</p></div>
</body>
</html>

Protože stránky se liší akorát částí <title> a obsahem <div#content>, tak je výhodné vytvořit layout a
stránky FirstPage.phtml a SecondPage.phtml budou upravovat jen ty části, které se od layoutu liší.

layout.phtml

<!doctype html>
<html>
<head>
<title>{include #title} | Můj web</title>
</head>
<body>
<div id="leftColumn"><ul>...</ul></div>
<div id="content">{include #content}</div>
</body>
</html>
Pomocí makra {layout} (nebo jeho aliasu – {extends}) označíme, která šablona bude sloužit jako layout, a
pomocí makra {block} označíme jednotlivé části stránky, které budou vloženy (makrem {include}) do layoutu.

FirstPage.phtml

{layout layout.phtml}

{block #title}První stránka{/block}


{block #content}<p>Mauris consectetur lobortis purus eget...</p>{/block}

SecondPage.phtml

{layout layout.phtml}

{block #title}Druhá stránka{/block}


{block #content}<p>Proin eu sem purus. Donec bibendum vestibulum...</p>{/block}

Každá stránka musí definovat všechny bloky, které layout makrem {include} načítá.

Přepisování bloků v layoutu


Někdy může být výhodné, aby layout definoval výchozí obsah a potomci ho pak mohli (nikoliv museli) přepsat.
Toho dosáhneme nahrazením volání bloku ({include #foo}) za jeho definici ({block #foo}výchozí
obsah{/block}).

layout.phtml

<!doctype html>
<html>
<head>
<title>{block #title}Výchozí titulek{/block} | Můj web</title>
</head>
<body>
<div id="leftColumn"><ul>...</ul></div>
<div id="content">{include #content}</div>
</body>
</html>

Blok (zde #title) umístěný v šabloně stránky přepíše stejně pojmenovaný blok umístěny v layoutu (pakliže
existuje).

Načtení přepsaného bloku


Uvnitř bloku, který přepisuje blok definovaný v layoutu máme možnost tento přepsaný blok načíst pomocí
{include #parent}. Toho se obvykle využívá pro připsání kusu textu před nebo za původní obsah bloku.

layout.phtml

<!doctype html>
<html>
<head>
<title>{block #title}Můj web{/block}</title>
</head>
<body>
<div id="leftColumn"><ul>...</ul></div>
<div id="content">{include #content}</div>
</body>
</html>

FirstPage.phtml

{layout layout.phtml}

{block #title}První stránka | {include #parent}{/block}


{block #content}<p>Mauris consectetur lobortis purus eget...</p>{/block}

Víceúrovňový layout
Layout šablony může sám používat další layout. Toho lze využít pro tvorbu komplikovanějších layoutů.

Shrnutí

Syntaxe
Definice anonymního bloku {block}anonymní blok{/block}
Definice pojmenovaného bloku {block #foo}pojmenovaný blok{/block}
Definice pojmenovaného bloku okolo HTML tagu <p n:block="foo">...</p>
Definice pojmenovaného bloku uvnitř HTML tagu <p n:inner-block="foo">...</p>
Načtení bloku {include #foo}
Načtení bloku s parametry {include #foo, a => 45, b => 'xyz', c => $text}
Načtení sebe sama {include #this}
Načtení stejně pojmenovaného bloku v layoutu {include #parent}
Použití layoutu {layout layout.phtml} nebo {extends layout.phtml}

Pravidla
● Pokud je blok definován v souboru, který nepoužívá layout, je automaticky vypsán.
● Blok umístěný v šabloně stránky přepíše stejně pojmenovaný blok umístěny v layoutu (pakliže existuje).
● Každá stránka musí definovat všechny bloky, které layout makrem {include} načítá (a sám je nedefinuje).
● Při deklaraci bloku do něj automaticky přecházejí všechny lokální i globální proměnné šablony.
● Při vkládání bloku do něj automaticky přecházejí pouze globální proměnné šablony.

« Pokročilá makra Snippety »


Vlastní makra
Třída LatteMacros definuje jednotlivé záměny ve statickém asociativním poli $defaultMacros. Rozšířit je lze
přidáním nového prvku:

// v šabloně {podpis}
LatteMacros::$defaultMacros["podpis"] = "Já";
// v šabloně {aktualniDatum}
LatteMacros::$defaultMacros["aktualniDatum"] =
"<?php echo date('j. n. Y') ?>";
// v šabloně {icon delete}
LatteMacros::$defaultMacros["icon"] =
'<img src="%%.png" width="16" height="16" alt="%%">';

Více o rozšíření LatteFilter.

« Snippety
Přehled standardních filtrů
Pro obecné informace o filtrech si přečtěte stránku Nette\Templates\Template.

Latte
Latte filter (dříve znám jako CurlyBracketsFilter) je jediným filtrem, který je ve vykreslitelných
komponentách (Control a Presenter) registrován automaticky. Slouží nejen pro usnadnění zápisu šablon, ale
také umožňuje pracovat s bloky a podporuje kontextově sensitivní escapování.

Ukázka použití v šabloně:

<ul n:if="count($products)">
{foreach $products as $product}
<li>
<a href="{plink Products:view, $product->id}">{$product->name}</a>
<small n:if="$product->detail">{$product->detail}</small>
</li>
{/foreach}
</ul>

Pro bližší informace si přečtěte stránku Latte filter.

NetteLinks
Překládá adresy odkazů ve tvaru nette:Presenter:view?arg=value na URL.

Ukázka použití v šabloně:

<a href="nette:Products:view?id=17">TV</a>

Registrace:

$template->registerFilter('Nette\Templates\TemplateFilters::netteLinks');

RelativeLinks
Všechny cesty v atributech src, href a action doplní v případě potřeby o $baseUri.

Registrace:

$template->registerFilter('Nette\Templates\TemplateFilters::relativeLinks');
RemovePhp
Odstraní ze šablony veškerý PHP kód.

Ukázka použití v šabloně:

Hello <?php nebezpecnaFunkce() ?> World

Registrace:

$template->registerFilter('Nette\Templates\TemplateFilters::removePhp');

TexyElements
Umožní použití speciálního tagu <texy>...</texy>.

Ukázka použití v šabloně


<texy>Text **tučně**, [odkaz | example.com] atd.</texy>

Registrace
Aby tento filtr fungoval, musí být inicializovaná statická proměnná $texy třídy
Nette\Templates\TemplateFilters.

$template->registerFilter('Nette\Templates\TemplateFilters::texyElements');
Nette\Templates\TemplateFilters::$texy = new Texy();

Příklad registrace filtru texyElements v Presenteru

use Nette\Application\Presenter;
use Nette\Templates\TemplateFilters;

abstract class BasePresenter extends Presenter


{
public function templatePrepareFilters($template)
{
parent::templatePrepareFilters($template);

// inicializace Texy
TemplateFilters::$texy = new Texy();
TemplateFilters::$texy->encoding = 'utf-8';
TemplateFilters::$texy->allowedTags = Texy::NONE;
TemplateFilters::$texy->allowedStyles = Texy::NONE;
TemplateFilters::$texy->setOutputMode(Texy::HTML5);

// registrace filtru texyElements


$template->registerFilter('Nette\Templates\TemplateFilters::texyElements');
}
}

Viz také:

● Nette\Templates\TemplateFilters API reference


● Nette\Templates\LatteFilter API reference

« Vlastní makra Přehled standardních helperů »


Přehled standardních helperů
Pro základní informace o helperech a jejich použití vizte dokumentaci k Nette\Templa-
tes\Template

Název Funkce Použití


lower String::lower() Převede text na malá písmenka
upper String::upper() Převede text na velká písmenka
Převede text na malá písmenka, přičemž první
capitalize String::capitalize()
písmeno v každém slově bude velké
webalize String::webalize() Převede text do tvaru „SEO friendly URL“
date TemplateHelpers::date() Zformátuje timestamp na čitelné datum
bytes TemplateHelpers::bytes() Lidsky přívětivé vyjádření velikost v bajtech
truncate String::truncate() Zkrátí řetězec na požadovaný počet znaků
trim String::trim() Odstraní bílé znaky ze začátku a konce řetězce
strip TemplateHelpers::strip() Odstraní bílé znaky (mezery)
stripTags strip_tags Odstraní HTML tagy
nl2br nl2br Zamění odřádkování za <br />
translate Přeloží text do jiného jazyku
replace TemplateHelpers::replace()
replaceRe TemplateHelpers::replaceRe()
indent TemplateHelpers::indent()
length TemplateHelpers::length()
null TemplateHelpers::null()
substr iconv_substr
repeat str_repeat
implode implode
number number_format
escape TemplateHelpers::escapeHtml() Escapuje HTML znaky
escapeUrl rawurlencode
escapeCss TemplateHelpers::escapeCss()
escapeHtml TemplateHelpers::escapeHtml()
escapeHtmlComment TemplateHelpers::escapeHtmlComment()
escapeHtmlCss TemplateHelpers::escapeHtmlCss()
escapeHtmlJs TemplateHelpers::escapeHtmlJs()
escapeJs TemplateHelpers::escapeJs()
escapeXML TemplateHelpers::escapeXML()

Většinu helperů implementuje statická třída Nette\Template\TemplateHelpers nebo Nette\String.

Truncate
Ořízne řetězec na maximální délku a zachová celá slova, je-li to možné. Na konec oříznutého textu se přidá
trojtečka, což lze změnit třetím nepovinným parametrem.

Příklad použití v šabloně (s filtrem Latte):

{var title => 'Řekněte, jak se máte?' }


{$title|truncate:5} <!-- Řekn… -->
{$title|truncate:20} <!-- Řekněte, jak se… -->
{$title|truncate:30} <!-- Řekněte, jak se máte? -->

Vstupní řetězec musí být kódován v UTF-8. Pokud je v jiném kódování, převeďte jej funkcí iconv.

Bytes
Převádí velikosti souborů v bajtech do lidsky čitelné podoby.

Příklad použití v šabloně:

{$size|bytes} <!-- 0 B, 10 B nebo 1.25 GB, ... -->

Lower, upper a capitalize

{var s => "Dobrý den"}


{$s|lower} <!-- dobrý den -->
{$s|upper} <!-- DOBRÝ DEN -->
{$s|capitalize} <!-- Dobrý Den -->

Date
Vlastní formát datumu. Volá php funkci strftime, používají se zde tedy stejné zástupné znaky a pro helper platí
stejná omezení jako pro tuto funkci. Helper zpracovává proměnné typu int (timestamp), string nebo instanci
třídy DateTime.

{$today|date:'%d.%m.%Y'}

Viz také:

● Nette\String API reference


● Nette\Templates\TemplateHelpers API reference

« Přehled standardních filtrů Jak použít šablony samostatně? »


Jak použít šablony samostatně?
Rádi byste využili šablonovacího systému Nette v jednoduché nebo již existující aplikaci?

Stáhněte a rozbalte si Nette Framework a zkopírujte adresář s frameworkem Nette do své aplikace, např. do
složky libs/Nette. Dále si připravte adresář pro dočasné soubory (například temp) a ujistěte se (hlavně na
serveru), že do něj lze zapisovat.

A nyní ve svém kódu můžete použít šablonovací systém Nette.

use Nette\Debug;
use Nette\Environment;
use Nette\Templates\Template;
use Nette\Templates\LatteFilter;

// načteme framework
require_once dirname(__FILE__) . '/libs/Nette/loader.php';

// volitené, pro šikovnější ladění aplikace


Debug::enable();

// povinné - nastavíme cestu k dočasnému adresáři (nejlépe jako absolutní cestu)


Environment::setVariable('tempDir', dirname(__FILE__) . '/temp');

$template = new Template();


// následující kroky, až do renderování, mohou být uvedeny v libovolném pořadí

// zaregistrujeme filtr Latte, který umožní používat syntax jako {if} ... {/if}, {foreach} ...
$template->registerFilter(new LatteFilter);

// zaregistujeme tzv. helpery, které budou escapovat HTML znaky


$template->registerHelper('escape', 'Nette\Templates\TemplateHelpers::escapeHtml');
$template->registerHelper('escapeJs', 'Nette\Templates\TemplateHelpers::escapeJs');
$template->registerHelper('escapeCss', 'Nette\Templates\TemplateHelpers::escapeCss');

// určíme soubor se šablonou


$template->setFile('sablona.phtml');

// předáme ji parametry
$template->name = 'Jack';
$template->people = array('John', 'Mary', 'Paul');

// a vyrenderujeme
$template->render();

Jak může vypadat soubor se šablonou (sablona.phtml):

<h1>Hello {$name}</h1>

<ul>
{foreach $people as $person}
<li>{$person}</li>
{/foreach}
</ul>

V cyklech {foreach} lze také využít magickou proměnnou $iterator:

{foreach $people as $person}


<p id="item{$iterator->counter}">{$person}</p>

{if !$iterator->last}
<hr />
{/if}
{/foreach}

Podrobnější informace o syntaxi a proměnné $iterator najdete na stránce Latte filter.

V samotné šabloně je lepší se vyvarovat používání PHP. Pokud bychom chtěli třeba volat funkci str_pad pro
zarovnávní řetězce na zadaný počet míst, bude lepší využít helper:

// název funkce je libovolný


function justifyHelper($s, $length = 3)
{
return str_pad($s, $length, ' ', STR_PAD_LEFT);
}

// a zaregistrujeme jej do šablony pod názvem 'justify' (vložte do předchozího kódu)


$template->registerHelper('justify', 'justifyHelper');

V šabloně jej použijeme takto:

{foreach $people as $person}


<li>{$person|justify}</li>
{/foreach}

Přičemž můžeme předat navíc parameter (bude předán jako druhý argument funkci justifyHelper):

<li>{$person|justify:10}</li>

Za sebe můžeme dokonce naskládat více helperů, např {$person|lower|justify}.


Nette\Application
Zastřešuje chování Model View Presenteru.

Životní cyklus aplikace


Životní cyklus aplikace se dá rozdělit do těchto bodů:

1. Router z URL vytvoří objekt PresenterRequest (obsahuje jméno spansenteru);


2. PresenterLoader ze jména spansenteru odvodí třídu a případně název souboru;
3. Presenter volá metody podle aktuálního action & view (případně i subrequestu);
4. Presenter načítá šablony, ve hře je název spansenteru a view;
5. Renderování: v tomto a předchozím bodě se obvykle vytváří odkazy na jiné spansentery a jejich action & view,
do toho se zapojuje opět PresenterLoader a Router.

Routování
Routování má na starosti vytváření odkazů a hezkých URL, převod URL mezi moduly, spansentery, pohledy a
jejich stavy.

Routery:

● SimpleRouter : index.php?...
● Route : /tiskarny/canon/mx440/

Router Nette\Application\Route má statické pole $styles, které mimojiné určuje, že parametry module,
spansenter a view budou transformovány (filtrovány) pomocí určitých funkcí. To zajistí převody MyPresenter ->
my-spansenter atd. Už z routeru tedy vypadne název spansenteru ve tvaru PascalCase. Modifikovat chování lze
buď úpravou pole $styles, nebo přímo v definici routy použitím modifikátoru: <spansenter #mymod>. Filtry pak
popisuje struktura v $styles['#mymod'].

Všechny routery definované pro naši aplikaci jsou uchovávány v objektu MultiRouter.
Pokud Vám nevyhovuje v něčem chování routerů, které jsou již obsaženy v Nette, můžete si naimplementovat
vlastní router. Jediný požadavek je implementace rozhraní IRouter.

PresenterLoader
Výchozí loader ze jména spansenteru odvodí třídu a případně název souboru takto:

● Admin:Catalog:Default → třída Admin_Catalog_DefaultPresenter


● Admin:Catalog:Default → třída AdminModule\CatalogModule\DefaultPresenter (v PHP 5.3)
● Zkusí autoloading (pak umí i korigovat název spansenteru, pokud nesedí velikost písmen)
● Zkusí soubor AdminModule/CatalogModule/DefaultPresenter.php (case-sensitive)

Chování lze změnit úpravou metod formatPresenterClass() a formatPresenterFile() v PresenterLoader nebo


nahrazením loaderu za svůj.
Vyvolání metod podle aktuálního action & view
Presenter z názvu action/view odvodí název patřičné metody formatActionMethod() a formatRenderMethod(). Pro
view edit se budou volat metody spanpareEdit() a renderEdit(). Další metody spansenteru a jeho celý
životní cyklus jsou popsány v Nette\Application\Presenter.

Načtení šablon
Presenter se pokusí podle svého názvu (nezaměňovat s názvem třídy) načíst šablonu layoutu (ta je nepovinná) a
šablonu view.
Kde ji hledá určují metody formatLayoutTemplateFiles() & formatTemplateFiles(). Všimněte si množného čísla
v názvu – metody vrací pole možných umístění seřazených podle priority.

Layout pro Admin:Catalog:Default bude hledat v souborech:

● templates/AdminModule/CatalogModule/Default/@layout.phtml
● templates/AdminModule/CatalogModule/Default.@layout.phtml
● templates/AdminModule/CatalogModule/@layout.phtml
● templates/@layout.phtml

Soubory spanfixované znakem @ nelze podstrčit jako view, tedy mohou být ve stejné složce se šablonami views.
Lze to použít i pro „podšablony“, které se do jiných šablon inkludují.

Šablonu hledá v souborech:

● templates/AdminModule/CatalogModule/Default/edit.phtml
● templates/AdminModule/CatalogModule/Default.edit.phtml
● templates/@global.edit.phtml

Chování lze, jak jistě tušíte, změnit úpravou metod format.

Odkazování
Při odkazování na jiné spansentery je vhodné dodržovat konvenci PascalCase, ačkoliv PresenterLoader umí název
korigovat.

Nette\Application\Router má při vytváření URL k dispozici správně zapsaný název spansenteru. Aby
správně fungovalo vynechávání defaultních hodnot, je potřeba i defaultní hodnoty zapsat správně.

Události
Jde o vlastnost Nette\Object, kdy mohu do pole $this->onXyz přidávat „callbacky“ a poté je hromadně zavolat
přes $this->onXyz($arg, ...).

Příklad:

$application->onStartup[] = 'myfunc';
$application->onStartup[] = array($object, 'method');
$application->onStartup[] = function() { ... }; //anonymní funkce v PHP >= 5.3
$application->onStartup(TRUE, 123); // volá myfunc(TRUE, 123) a $object->method(TRUE, 123);

V třídě Aplication je možno použít již přednastavených událostí: onStartup, onShutdown, onRequest a
onError.

● onStartup je spuštěna na začátku životního cyklu ještě před prvním zpracováním pořadavku PresenterRequest
a routováním
● onShutdown je spuštěna, proběhne-li korektně celý životní cyklus aplikace
● onRequest je spuštěna při každém novém požadavku (např. při vytvoření požadavku routerem, forwardu na jiný
spansenter nebo při potřebě zpracovat chybu error spansenterem) a jako parametr je jí předán objekt
PresenterRequest, který je aplikací zpracováván
● onError je spuštěna při zachytávání výjimky v metodě run() pokud je nastaveno zachytávání výjimek
$application->catchExceptions == TRUE a je jí předána i zachycená výjimka $exception

I třída Presenter disponuje událostí a to onShutdown stejně jako Application, k jejímu zpracování ale dochází
před zpracováním metody shutdown a jako parametr je jí předána případná výjimka $exception.

Viz také:

● Model-View-Presenter
● Fully qualified action
● Generování odkazů a Neplatné odkazy
● Doporučená adresářová struktura
● Routování
● Nette\Application\MultiRouter
● Nette\Application\SimpleRouter
● Nette\Application\Presenter
● Nette\Application\Presenter API reference

« Šablony Presenter »
Nette\Application\Presenter
Reaguje na události pocházející od uživatele a zajišťuje změny v modelu nebo v pohledu.

Platí pro verzi Nette 0.9 a novější.

Životní cyklus spansenteru


Životní cyklus spansenteru je rozdělen do několika
částí představovaných voláním volitelně existujících
metod. Jde o action{Action}, handle{Signal} a
render{View}. Každá metoda se hodí na něco jiného.
Ty které mají společné znaky řadíme do společných
fází životního cyklu.

Charakteristika fází:
1. výkonná (execution)
2. změny vnitřních stavů (interaction)
3. vykreslovací (rendering)
4. ukončení činnosti (shutdown)

Následující obrázek ilustruje, jak jsou postupně


vykonávány metody spansenteru v jeho životním cyklu
a do jaké fáze tyto metody začleňujeme.

● bílé – metody společné pro všechny akce / pohledy


● hnědé – metody pro konkrétní pohled
● modrá – metoda, která má na starosti zpracování
konkrétního signálu

Životní cyklus spansenteru

Popis jednotlivých metod

Fáze výkonná (execution)


1. startup je vyvolána na začátku životního cyklu spansenteru. Může obsahovat například zajištění připojení
k databázi. Během životního cyklu aplikace se může spustit více spansenterů, metoda startup() se může
volat vícekrát.
2. action{Action} by měla obsahovat vykonání operací, po kterých může následovat přesměrování. Zde probíhá
například automatické přesměrování na jinou jazykovou verzi (např. podle detekce z prohlížeče). Také zde může
být logika rozhodování pro členění na jednotlivé pohledy. Metoda action může například i zvalidovat vstupní
parametry nebo řešit exekutivu (např. má se záznam smazat?). Onou validací můžeme chápat předání dat
modelu k validaci, ověření práv a následné přesměrování do patřičných míst, pokud se vyskytne problém.
Validace se může dělit na low-level (je $id fakt číslo?) nebo high-level (existuje záznam pro $id v databázi? má
k němu $user přístup?). Řešit validaci se doporučuje v modelech (a nebo, pokud to framework vyžaduje, tak
i v třídách formulářů), kde ji bude ale programátor řešit záleží na něm nebo na konkrétní situaci.

● Klíčový moment pro redirect: je zde prostor pro inicializace perzistentních parametrů a manipulaci s modelem
s možností následného přesměrování, tzn. v tomto stavu se zohlední při redirectu i hodnoty perzistentních
parametrů.
Př.: pokud zde nastavím perzistentnímu parametru $lang hodnotu 'cs', pak se i tato hodnota zohlední v novém
požadavku po přesměrování. Po redirectu se skript ukončí, prohlížeč si vyžádá novou stránku a skript se spustí
znovu. Tudíž všechny „obyčejné“ proměnné se ztratí.

Fáze změn vnitřních stavů (interaction)


1. handle{Signal} : zpracování signálů neboli subrequestů. Určeno pro uživatelskou interakci a zpracování
AJAXových požadavků.

Fáze vykreslovací (rendering)


1. beforeRender může obsahovat například společné nastavení filtrů pro všechny vykreslovače a nastavení
společných proměnných pro šablony všech vykreslovačů.
2. render{View} má na starosti vykreslení a věci s tím spojené (tvorba odkazů v šablonách, přiřazení
proměnných do konkrétních šablon, …).

Fyzické vykreslení šablony


1. uložení vnitřních stavů: dříve než se přejde k další fázi, uloží se stav všech vnitřních stavů a perzistentních
proměnných.
2. vykreslení šablony na výstup

Ukončení činnosti (shutdown)


1. shutdown je vyvolána při ukončení životního cyklu spansenteru. Zde můžeme ukončit databázové připojení,
kešování a podobně.

Presenter má během svého životního cyklu možnost kdykoliv ukončit svou činnost, pokud je s prací hotový (
$spansenter->terminate()). To může udělat i během „společných“ metod startup(), beforeRender().

U složitějších aplikací se nevyhnete stromovým strukturám a hierarchii spansenterů. To jak je správně navrhovat
je řečeno v článku Návrh struktury spansenters/views. V takovýchto strukturách nelze abstraktní spansenter kvůli
bezpečnosti vyvolat URL požadavkem.

V spansenteru, pokud někde dochází k zásadní chybě jako je nenalezení článku v databázi, tak je
vhodné vyhazovat výjimky a další zpracování přenechat exception handleru.

Vykreslování šablony již neprobíhá v životním cyklu spansenteru. Šablonu nyní renderuje
Nette\Application\RenderResponse. Více informací ohledně této změny naleznete na fóru .

Signál aneb subrequest


Signál (aneb subrequest) je komunikace se serverem pod prahem normálního view, tedy akce, které se dějí, aniž
by se změnilo view. View může měnit pouze spansenter, proto komponenty pracují vždy pod tímto prahem, tudíž
$component->link() vede na signál, $spansenter->link() obvykle na view (nebo signál, je-li označen
vykřičníkem přidaným na konec). Pro úplnost, i komponenta může volat $this->spansenter->link('view').

Signál způsobí znovunačtení stránky úplně stejně jako při původním požadavku (kromě případu, kdy je volán
AJAXem) a vyvolá metodu signalReceived($signal), jejíž výchozí implementace ve třídě PresenterComponent
se pokusí zavolat metodu složenou ze slov handle{signal}.
Další zpracování je na daném objektu. Objekty, které dědí od PresenterComponent (tzn. Control a Presenter)
reagují tak, že se snaží zavolat metodu handle{signal} s příslušnými parametry.
Jinými slovy: vezme se definice funkce handle{signal} a všechny parametry, které přišly s požadavkem, a
k argumentům se podle jména dosadí parametry z URL a pokusí se danou metodu zavolat. Např. jako prametr $id
se předá hodnota z parametru id v URL, jako $something se předá something z URL, atd.
Pokud metoda neexistuje, metoda signalReceived vyvolá výjimku.

Signál může přijímat jakákoliv komponenta, spansenter nebo objekt, který implementuje rozhraní
ISignalReceiver.

Mezi hlavní příjemce signálů budou patřit Presentery a vizuální komponenty dědící od Control (a ty se při
přijetí signálu automaticky invalidují, což je důležité pro AJAX).
Signál má sloužit jako znamení pro objekt, že má něco udělat – anketa si má započítat hlas od uživatele, blok
s novinkami se má rozbalit a zobrazit dvakrát tolik novinek, formulář byl odeslán a má zpracovat data a podobně.

Signál se vždy volá na aktuálním spansenteru a view, tudíž není možné jej směřovat jinam.

URL pro signál vytváříme pomocí metody PresenterComponent::link(). Jako parametr $destination
předáme řetězec {signal}! a jako $args pole argumentů, které chceme signálu předat. Signál se vždy volá na
aktuální view s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává hned na začátku
parametr ?do, který určuje signál.

Jeho formát je buď {signal}, nebo {signalReceiver}-{signal}. {signalReceiver} je název


komponenty v spansenteru. Proto nemůže být v názvu komponenty pomlčka – používá se k oddělení názvu
komponenty a signálu.

Metoda isSignalReceiver() ověří, zda je komponenta (první argument) příjemcem signálu (druhý argument).
Druhý argument můžeme vynechat – pak zjišťuje, jestli je komponenta příjemcem jakéhokoliv signálu.
Experimentálně lze jako druhý parametr uvést TRUE a tím ověřit, jestli je příjemcem nejen uvedená komponenta,
ale také kterýkoliv její potomek.

V kterékoliv fázi předcházející handle{signal} můžeme vykonat signál manuálně zavoláním metody
$this->processSignal(), která si bere na starosti vyřízení signálu – vezme komponentu, která se určila jako
příjemce signálu (pokud není určen příjemce signálu, je to spansenter samotný) a pošle jí signál.
Příklad:

if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {


$this->processSignal();
}

Tím je signál provedený a už se nebude znovu volat.

Subrequest vs. request


Rozdíly mezi signálem a požadavkem:

● subrequest přenáší všechny komponenty


● request přenáší označené (perzistentní) komponenty

Šablony (Templates)
Presenter se pokusí vykreslit implicitní šablonu, pokud nebylo řečeno metodami setLayout() & setView() jinak,
jméno šablony odvodí od view.

Každý spansenter může mít vlastní layout uložený v souboru:

● /templates/Homepage/@layout.phtml
● /templates/Homepage.@layout.phtml.
● nebo se použije společný layout uložený v /templates/@layout.phtml.

Změnit layout jde metodou setLayout(), kde parameter FALSE layout zcela vypne, nebo lze předat název
layoutu. Např. setLayout('extra') bude místo souboru ...@layout.phtml hledat ...@extra.phtml.

Teprve když by soubor se šablonou neexistoval, vyhodí se výjimka BadRequestException.

Tohle chování má výhodu v tom, že pokud přidáváme nové view, stačí přidávat nové šablony do příslušné složky
a není potřeba psát žádné (prázdné) metody. A naopak, view jsou na šablonách nezávislé, můžeme je zpracovat
dřív, než na kreslení šablony dojde. Detailnější popis k šablonám lze nalézt v Nette\Templates.

Obyčejné a perzistentní parametry


Obyčejné parametry a perzistentní parametry se od sebe vlastně téměř neliší.

Každá komponenta (tedy i spansenter) má přidělen jeden jmenný prostor a v něm sídlí všechny parametry. Balík
všech parametrů je uložen v poli $params každé komponenty, dá se k nim přistupovat také metodou
getParam(...).

class SomePresenter extends Presenter


{
/** @persistent */
public $persistentParam = 0;
...

public function handleExpand($param1, $param2, $param3)


{
...
}

...
}

Výše uvedená syntax pro označení perzistentních parametrů může selhat při zapnutém
eAccelatoru na hostingu. Pro tyto případy je možné perzistentní parametry deklarovat
alternativně.

Parametry nastavíme třeba metodou link(), kolem které se to vlastně všechno točí:

$this->link('expand!', array('param1' => 123, 'param2' => TRUE, 'param3' => $id));

Tzn. parametry signálů (a platí to i pro parametry metod render) se liší jen v tom, že nám zjednodušší zápis
odkazů. Pokud existuje metoda handleExpand($param1, $param2, $param3), stačí psát:

$this->link('expand!', array(123, TRUE, 'param3' => $id)); // první dva se párují s param1 a param2

Stejně tak i perzistentní parametry se liší jen v drobnosti – v tom, že je nemusíme v odkazech uvádět vůbec.
Pokud bude param3 perzistentní, stačí psát:

$this->link('expand!', array(123, TRUE)); // param3 se doplní automaticky

V takovém případě je možné vynechat array():

$this->link('expand!', 123, TRUE);

U perzistentních parametrů se ještě navíc pro snažší přístup vytváří reference mezi položkou ve zmíněném poli
$params a proměnnou objektu, tj. $this->param3 = & $this->params['param3'].

Parametry s výchozí hodnotou v definicích metod jsou nepovinné parametry – například


renderDefault($orderBy = 'id', $offset = 15, $limit = 20).

Jak již bylo řečeno, perzistentní parameter není potřeba uvádět při volání link(...), neboť se předává
automaticky. Ale uvést ho samozřejmě možné je a tak mu změnit hodnotu.

Podmínkou perzistence parametru je jeho deklarace jako public a uvedení řetězce @persistent v phpDoc
syntaxi komentáře proměnné:
/** @persistent int */
public $page = 0;

Perzistence zohledňuje hierarchii tříd, tzn. že každý poděděnec má tytéž perzistentní parametry jako rodič.

Je-li perzistentní parametr inicializován výchozí hodnotou, jako výše uvedený, pak nejsou tyto hodnoty předávány
v URL. Pokud aplikace přijme request, kde je tato výchozí hodnota zadána, provádí se redirect (
index.php?page=0 → index.php) z důvodu SEO optimalizace. Jinak by se nám stránka, která zobrazí stejné
informace pod dvěma tvary, zaindexovala dvakrát. Proto je vhodné parametry, které se přenášejí v URL (nejen
perzistentní, ale i ty, které přenášíme v metodách render apod.), inicializovat výchozí hodnotou, stejně jako
výchozí spansentery a pohledy v routách. Nejsou-li parametry inicializovány, mají hodnotu NULL.

V query-stringu funguje i přetypování bool a float hodnot na int:

● TRUE -> 1
● FALSE -> 0

Reakce na neplatný odkaz


Při volaní metody Presenter::link() s cílem, který neexistuje, se může spansenter zachovat různě – dle
nastavení proměnné Presenter::$invalidLinkMode. Ta má tři možná nastavení:

● Presenter::INVALID_LINK_SILENT – na místo odkazu vypíše „#“


● Presenter::INVALID_LINK_WARNING – na místo odkazu vypíše řetězec „error: <text chyby>“ (předvolené
nastavení)
● Presenter::INVALID_LINK_EXCEPTION – přímo vyhodí výjimku

Presenter a komponenty
Jelikož je třída Presenter potomkem ComponentContainer, může manipulovat s komponentama a uchovávat je.
K tomu slouží metody getComponent() (nebo její alternativa $this[...]), která vrátí požadovanou
komponentu podle názvu nebo cesty, a createComponent<Name>(), což je továrnička na komponenty. Výhoda
továrničky je v tom, že signal handler může komponentu (nebo subkomponentu) od spansenteru získat, aniž
by se musela dopředu inicializovat v metodách action. Odpadá tak potřeba komponenty ukládat do proměnných
objektů.

Ukládání vnitřních stavů komponenty má na starosti předek spansenteru PresenterComponent. Díky tomu
i jednotlivé komponenty (např. PresenterComponent) mohou používat továrničky. Vnitřní stavy komponent se
ukládají po vykreslovací fázi.

Také komponenty mohou být perzistentní. Perzistentní komponenty musí být správně anotované
(subkomponenty uvnitř komponent není třeba nijak značit, jsou perzistentní samy o sobě):

/**
* @persistent(game, abc, xyz)
*/
class DefaultPresenter extends Presenter
{
public function actionDefault()
{
$fifteen = new FifteenControl($this, 'game');
$fifteen->onGameOver[] = array($this, 'GameOver');

$this->template->fifteen = $fifteen;
}

...
}

Stav perzistentních komponent se přenáší při přechodu na jiný Presenter podobně, jako v případě perzistentních
parametrů.

Proč je to nutné? V podstatě z technického důvodu. Mezi spansentery se předávají jen data, která jsou jim
společná, tedy která jsou deklarována na úrovni společných předků. Ale jak zjistit, že komponentu game
deklarovala právě metoda třídy DefaultPresenter a ne nějaký její předek nebo potomek? To zjistit nelze. Lze
ale zjistit, která třída deklarovala proměnnou fifteen a toho se právě využívá.

Tedy při subrequestu, při zavolání signálu jsou komponenty perzistentní samy o sobě. Je ale nutné, aby na
vstupním a cílovém spansenteru byla tatáž komponenta zařazená ve stromu pod stejným jménem. Tudíž nemá
smysl, aby to fungovalo pro předem neznámé komponenty, ale jen pro komponenty s spansenterem nějak pevně
svázané.

Subrequest přenáší všechny komponenty, request přenáší označené komponenty.

Situace se komplikuje v případě, že jsou ve hře komponenty, které mají zpracovat signály. Příkladem této
komponenty může být třída AppForm. Je totiž nutné zajistit, aby komponeta přijímající signál existovala předtím,
než se zpracovávají signály komponent. V případě že příjemce signálu v tomto místě neexistuje, skončí aplikace
výjimkou o neexistujícím příjemci signálu. Příkladem této chyby je vytvoření komponenty až metodách render
jiným způsobem než továrničkou.

Příklad správné registrace komponenty v metodách action:

class DefaultPresenter extends Nette\Application\Presenter


{
public function actionDefault()
{
$fifteen = new FifteenControl;
$fifteen->onGameOver[] = array($this, 'GameOver');
$fifteen->useAjax = TRUE;

$this['game'] = $fifteen; // zaregistrování komponenty


// nyní je komponenta schopna správně přijímat signály
}

...
}

Svázání komponenty s spansenterem


Zde se zaměříme na výhody a úskalí používání komponent pod hlavičkou spansenteru. První z takovýchto výhod
je propojení komponenty s spansenterem, který ji vytvořil.

Svázání komponenty s spansenterem umožňuje:

● používat v komponentě perzistentní parametry


● používat signály
● volat na komponentě funkce závislé na přítomnosti spansenteru (link, redirect, endSnippet)

Pokud nic z toho nepotřebujeme (nebo nechceme), není potřeba komponentu s spansenterem vázat (respektive
není potřeba ani dědit z PresenterComponent nebo Control). Nicméně původní konstruktor by se měl vždy
rozhodně volat.

Příklad svázání:
SomeControl tedy:

public function __construct($someParametr = NULL)


{
parent::__construct();
// ... nějaký kód metody
}

a SomePresenter:

public function renderSomeview($someParametr)


{
$control = new SomeControl($someParametr);
$this['someControlName'] = $control;
// ... nějaký kód metody
}

Továrničky na komponenty
Továrna na komponenty je elegantní způsob jak komponenty vytvářet způsobem, až je jich doopravdy potřeba.
Celé kouzlo spočívá v implementaci metody createComponent<Name>(), která umožňuje vytvoření komponenty
právě lazy loading / on-demand způsobem. V této metodě buď komponentu rovnou připojíte k spansenteru (nebo
například i controlu) nebo vrátíte a továrnička se o její připojení postará sama. Metodě createComponent<Name>
je předáván volitelný parametr s názvem komponenty.

Registrace komponenty továrničkou:

class DefaultPresenter extends Nette\Application\Presenter


{
public function renderDefault()
{
$fifteen = $this['game']; // získá komponentu
// ... další kod
}

...

protected function createComponentGame($name)


{
$fifteen = new FifteenControl;
$fifteen->onGameOver[] = array($this, 'GameOver');
$fifteen->useAjax = TRUE;
return $fifteen;
}
}

Výhody použití továrničky nemusí být na první pohled patrné, projeví se hlavně při použití více komponent. Díky
tomu, že jsou všechny komponenty definovány na jednom místě dochází k lepší přehlednosti. Komponenty
z továrničky se také stávají lépe znovupoužitelnými, stačí si je jen kdekoliv přičarovat zápisem $this[...] a
případné jemnější nastavení komponenty je pak možno udělat až při jejím předání šabloně. Nic také nebrání použít
jednu komponentu na stránce vícekrát, rozliší se názvem, který také pomáhá určovat správnou komponentu jako
příjemce signálu.

V šabloně je možné získat a vykreslit komponentu pomocí makra widget nebo control. Není proto
potřeba manuálně komponenty předávat do šablony.

<h2>Editační formulář</h2>
{control editForm}

Viz také:

● Nette\Application\Presenter API reference


● Model-View-Presenter
● Fully qualified action
● Generování odkazů a Neplatné odkazy
● Doporučená adresářová struktura
● Routování
● Action vs. View

« MVC aplikace & spansentery Generování odkazů »


Generování odkazů
Parametry metody link() nesouvisí přímo s URL. Prvním argumentem je cíl (destination) určující cílový
spansenter & action, druhým (resp. dalšími) jsou parametry předávané tomuto spansenteru:

$this->link(destination [,arg [,arg ...]]);

kde destination je:

● 'anotherAction' (odkaz na aktuální spansenter a anotherAction)


● 'AnotherPresenter:anotherAction' (odkaz na AnotherPresenter a anotherAction)
● 'AnotherPresenter:' (odkaz na AnotherPresenter a výchozí actionDefault)
● 'AnotherModule:Presenter:action' (odkaz do submodulu, je třeba psát jako relativní fully qualified action)
● ':TotalyAnotherModule:Presenter:action' (odkaz do jiného modulu, je třeba psát jako absolutní fully
qualified action)
● '//AnotherPresenter:anotherAction' (absolutní odkaz s http://domena.tld/... na začátku na
AnotherPresenter a anotherAction)
● 'this' (odkaz na aktuální spansenter a aktuální action)

Parametry spansenteru je možné předat jako asociativní pole:

$this->link('show', array('id' => 10, 'lang' => 'en'));

Asociativní pole není příliš sexy, proto Nette nabízí vychytávku: pokud existuje v cílovém spansenteru metoda
renderShow($id, $lang, ...) nebo metoda spanpareShow($id, $lang, ...), kde ono ‚show‘ v názvu
odpovídá názvu linkovaného view, je možné klíče ‚id‘ a ‚lang‘ vynechat – automaticky se vezmou z parametrů
těchto metod:

$this->link('show', array(10, 'en'));

A naopak, když je dotyčná metoda po odkliknutí zavolána, tak se jí předají tyto argumenty v parametrech $id a
$lang. V poli je možné uvést další parametry, které metody renderShow a spanpareShow nedefinují, například
lze nastavit nějaký persistentní parametr (v tom případě ovšem už s asociativním klíčem). Pokud žádný takový
další parametr není, je možné pole úplně vynechat:

$this->link('show', 10, 'en');

Tedy v Nette se odkazuje na spansenter & action, nebo ještě jednodušeji: odkazuje se na konkrétní metodu.
$this->link('Catalog:show', 10, 'en') odkazuje a po odkliknutí zavolá metodu
CatalogPresenter::renderShow(10, 'en'). Jaké se vytvoří URL v tu chvíli nehraje roli. To je úkol oddělené
vrstvy – routování.
Odkazování v šablonách
Rozlišujeme více možností vytvoření odkazu, proto není jedno na jakém objektu metodu link() voláme.
Jelikož action/view může měnit pouze spansenter, komponenty pracují vždy pod tímto prahem. Navíc se
prohledávají odkazy v hierarchii směrem dolů k potomkům. Tudíž $control->link() vede na signál,
$spansenter->link() obvykle na view (nebo signál, je-li označen vykřičníkem). V šablonách se za asistence
filtru curlyBrackets může odkaz nad komponentou zkrátit pomocí značky {link} a odkaz nad spansenterem
pomocí {plink}.

V šabloně spansenteru jsou zápisy $spansenter->link() a $control->link() ekvivalentní (platí i pro


zkrácený zápis plink a link), protože i spansenter je komponentou a jako navázaný spansenter uvažuje
sám sebe.

Rozdíl se projeví v šablonách komponent, kde zápis {link Home:default} nevygeneruje odkaz, ale signál na
komponentu, zatímco {plink Home:default} vygeneruje platný odkaz na spansenter, se kterým je spárována.

V šabloně se odkazy velmi často vytvářejí za pomoci filtru curlyBrackets:

{* odkaz nad spansenterem *}


<a href="{$spansenter->link('edit', 10)}">self::edit(10)</a>
<a href="{$spansenter->link('Product:list')}">Product::list()</a>
<a href="{$spansenter->link('Article:view')}">Zobrazit články</a>

{* odkaz nad vykreslitelnou komponentou *}


<a href="{$control->link('Article:view')}">Zobrazit články</a>

{* zkrácený zápis *}
<a href="{plink Article:view}">Zobrazit články</a>
<a href="{link Article:view}">Zobrazit články</a>
<a href="{link Article:view}" onclick="{ajaxlink Article:view}">Zobrazit články</a>

Poznámka: proměnné $spansenter a $control jsou frameworkem předávány šabloně automaticky. Pro více
informací shlédněte dokumentaci třídy Control či její API.

Zkrácený zápis plink a link je vhodnější použít, pokud dopředu známe počet parametrů. Pokud počet
parametrů neznáme, předáme parametry polem. Při předávání parametrů polem ale musíme využít nezkráceného
zápisu, jinak by došlo k obalení pole $args ještě prázdným polem.

{* pokud znám počet parametrů, můžu použít zkrácený zápis *}


{link default, 'id' => 5, 'name' => 'název produktu'}

{* pro předání parametrů polem se musí zatím použít nezkrácený zápis *}


{? $args = array('id' => 5, 'name' => 'název produktu'); ?}
{$spansenter->link('default', $args)}

{* v budoucnu možná bude tento nedostatek eliminován označením proměnné (pole)


hvězdičkou *}
{link default, *$args}
Shrňme si to:

● $control->link() neboli link generuje odkaz nad komponentou


● $spansenter->link() neboli plink generuje odkaz nad spansenterem
● v obyčejné šabloně spansenteru je za komponentou považován spansenter, tudíž je možno v spansenteru použít
i link
● zkrácený zápis link a plink se hodí především známe-li počet parametrů (jsou-li nějaké)

Odkazování uvnitř spansenteru a komponent


URL generuje v spansenteru a komponentě funkce $this->link('edit', 10) – tedy stejně jako v šabloně.
Lze vygenerovat URL sám na sebe $this->backlink().

K přesměrování slouží $this->redirect(...), k přechodu na jiný spansenter/action


$this->forward(...). Rozdíl je v tom, že redirect provede přesměrování na jinou stránku pomocí HTTP a
forward jen přepošle zpracování jinam.

Viz také:

● Fully qualified action


● Routování

« Presenter Neplatné odkazy »


Neplatné odkazy
Pokud vytváříte odkaz na Presenter nebo subrequest, může se stát, že zapsaný odkaz nebude platný – například
proto, že Presenter daného jména neexistuje, nebo proto, že požadavek nelze převést na URL atd.

Jak naložit s neplatnými odkazy určuje statická proměnná Presenter::$invalidLinkMode. Ta může nabývat
těchto hodnot (konstant):

● Presenter::INVALID_LINK_SILENT – tichý režim, jako URL se vrátí znak #


● Presenter::INVALID_LINK_WARNING – vizuální varování, viz dále
● Presenter::INVALID_LINK_EXCEPTION – vyhodí se výjimka InvalidLinkException

Výchozí nastavení je INVALID_LINK_SILENT v produkčním režimu a INVALID_LINK_WARNING ve vývojovém.


INVALID_LINK_WARNING pracuje tak, že jako URL vrátí chybovou zprávu, která začíná znaky error:. Aby takové
odkazy byly na první pohled patrné, doplňte si do CSS:

a[href^="error:"] {
background: red;
color: white;
text-decoration: blink;
}

Když pak odkaz zapsaný například v šabloně …

<a href="{$spansenter->link('Product:list', 10)}">Produkt</a>

// nebo lze i stručněji:


<a href="{link Product:list 10}">Produkt</a>

…bude neplatný, bude vypadat takto (obarvení funguje v Opeře a Firefoxu).

Konkrétní příklad
Za neplatný odkaz se považuje i volání metody spansenteru link() pokud je zavolána s parametrem, který není
definovaný, odkaz se nevygeneruje správně a spansenter se zachová tak, jak má tedy nastavena proměnná
Presenter::$invalidLinkMode.

public function spansentDefault($lang, $id)


{
// špatné použití - parametr není definován v definici metody
$this->link('Article:default', array($lang, $id, $undefined, 'undefined' => 'parametr'));

// správné použití - pomocí metody getParam()


$this->link('Article:default', array($lang, $id, 'undefined' => $this->getParam('undefined')));

...
}

« Generování odkazů AppForm »


Nette\Application\AppForm
Třída AppForm je navržena speciálně pro použití v spansenteru – má obslužné mechanismy na zpracování signálů.
Od třídy Form se liší také tím, že má převrácené argumenty v konstruktoru (Form má jako první argument jméno a
až jako druhý rodičovský IComponentContainer). Třída Form má parametr $parent uvedený až „bokem“ proto, že
tam, kde se používá, obvykle žádný parent není potřeba a ani neexistuje.

AppForm využívá ke svému zpracování handlery. Ty se zpracovávají ve fázi zpracování signálů životního cyklu
spansenteru.

Metoda onSubmit se volá jen v případě, že je formulář skutečně odeslán, není potřeba znovu kontrolovat přes
isSubmitted. Maximálně pokud můžeme zjistit, jakým prvkem byl formulář odeslán. onSubmit se provede pouze
pokud byl formulář úspěšně a validně odeslán.

Obslužné handlery musíme nějak výstižně pojmenovávat, nesmí se nám zaměňovat název s metodou
handle{Signal}. Takovou klasikou bývá {componentName}{event-occured}, tedy třeba
loginFormSubmitted. To ale záleží na uvážení programátora. Tato metoda také pobírá jeden argument, kterým
je instance formuláře.

Nikdy neregistrujte AppForm ve fázi render! Na registraci v této fázi je již příliš pozdě (AppForm
nemůže přijímat signály), proto používejte k registraci komponent továrničky (viz příklad).
Komponenty se vám tak vytvoří automaticky, vždy když budou potřeba (už nikdy ne pozdě).

Obsluha událostí
Pro obsluhu událostí se doporučuje používat následující vzor (viz Best practice: Formulářová tlačítka):

class SomePresenter extends BasePresenter


{
// obslužné handlery:
public function okClicked(SubmitButton $button)
{
// submitted and valid
Debug::dump($button->getForm()->getValues());
$this->redirect(...);
}

public function cancelClicked(SubmitButton $button)


{
// process cancelled
$this->redirect(...);
}

public function formSubmitted(AppForm $form)


{
// manual processing
if (!$form['cancel']->isSubmittedBy()) { ... }
}
protected function createComponentMyForm($name)
{
$form = new AppForm($this, $name);
$form->addText('name', 'Your name:');
$form->addSubmit('ok', 'Send')
->onClick[] = array($this, 'okClicked');
$form->addSubmit('cancel', 'Cancel')
->setValidationScope(FALSE) // prvek se nebude validovat
->onClick[] = array($this, 'cancelClicked');
// alternativa:
// $form->onSubmit[] = array($this, 'formSubmitted');
}
}

Obslužný handler pro onSubmit lze použít v případě, kdy formulář nemá žádné nebo právě jedno tlačítko.
V odstatních situacích bývá vhodnější využít handler onClick přímo na tlačítku.

Handler onClick se volá před handlerem onSubmit. Handlery se volají pouze v případě, že je odeslání validní.
Uvnitř metody OkClicked tedy není nutné ověřovat validitu formuláře. Naopak metoda FormSubmitted může být
zavolána i v případě nevalidního formuláře, byl-li odeslán tlačítkem Cancel.

V případě, že formulář nebyl odeslán tlačítkem (například byl odeslán přes JavaScript), nebo se tak tváří kvůli
chybě v Internet Exploreru, bude Nette za odesílací tlačítko považovat první tlačítko formuláře. Tudíž obsluha přes
události onClick je spolehlivá.

Kontrola odeslání formuláře


Použití kontroly odeslání formuláře by mohlo vypadat takto:

if ($form->isSubmitted()) { ... }

Přídklad by nám vrátil objekt implementující rozhraní ISubmitterControl (nejčastěji objekt SubmitButton)
v případě, že je formulář odeslán nebo FALSE jestliže není. Nevadí, že vrácená hodnota je objekt, podmínka se
vyhodnotí korektně.

Můžeme se dotázat, přímo byl formulář odeslán přes konkrétní tlačítko:

if ($form['spanview']->isSubmittedBy()) { ... }

Tímto způsobem je i možné postihnout případné složitější struktury:

if ($form['subform']['spanview']->isSubmittedBy()) { ... }
Validace jednotlivých prvků formuláře
U každého tlačítka je možné nastavit, jestli vyžaduje validaci:

$form->addSubmit('cancel', 'Cancel')
->setValidationScope(FALSE); // nebude se nic validovat

Funkce se jmenuje tak proto, že se plánuje její rozšíření o možnost předat pole nebo skupinu (to zůstává k diskusi)
prvků, na které se validace při stisku tlačítka omezí.

Vynechání prvku z vykreslování

$form['save']->setRendered(TRUE);

Tímto zavoláním vynecháme prvek z vykreslování. Formulář ho pak považuje za vykreslený, tudíž ho nevykreslí, a
my si jej pak můžeme vykreslit v šabloně ručně. To se hodí v případech, kdy chceme například vykreslit
samostatně více odesílacích tlačítek vedle sebe.

Příklad více formulářů na jedné stránce

class SomePresenter extends BasePresenter


{
public function formASubmitted(AppForm $form)
{
// zpracování formA
Debug::dump($form->getValues());
}

public function formBSubmitted(AppForm $form)


{
// zpracování formB
Debug::dump($form->getValues());
}

protected function createComponentFormA($name)


{
$form = new AppForm($this, $name);
// ... definice formuláře A
$form->onSubmit[] = array($this, 'formASubmitted');
}

protected function createComponentFormB($name)


{
$form = new AppForm($this, $name);
// ... definice formuláře B
$form->onSubmit[] = array($this, 'formBSubmitted');
}
}
}

Defaultně action formuláře ukazuje na stejný view/action se stejnými parametry, jen se do URL přidá signál (
?do=formA-submit), který Nette předá příslušnému formuláři a ten se podle toho zařídí (zavolá si metody
definované v onSubmit). Pokud se má formulář odesílat do jiného view, tak je potřeba ten signál přidat do
argumentů.

Iterování nad formulářem


K iterování nad formulářem stejně jako nad polem svádí zápisy $form['name']->... tedy:

foreach ($form as $name => $component) { ... }

což je samozřejmě možné, ale zvažme, že ne každá komponenta $form je prvek FormControl. Můžou tam být
jakékoliv jiné komponenty, nebo kontainery, které teprve obsahují prvky formuláře, případně další kontainery atd.
Pro iterování je lepší použít šikovnou metodu $form->getComponents($deep = FALSE, $type = NULL), kde
první parametr říká, zda se má iterovat do hloubky (tj. projít i prvky konteinerů) a druhý nastavuje volitelný filtr:

foreach ($form->getComponents(TRUE, 'Nette\Forms\IFormControl') as $control) {


$control->setValue(...);
}

Viz také:

● Best practice: Formulářová tlačítka


● Vlastní vykreslování formulářů
● Formuláře, podmínky a pravidla
● Zprávy z modelu do formuláře
● Nette\Application\AppForm API reference

« Neplatné odkazy PresenterComponent »


Nette\Application\PresenterComponent
Základní třída (rodič) pro všechny komponenty používané v spansenteru.

Komponenty spansenteru jsou persistentní objekty, které si spansenter uchovává počas svého životního cyklu.

Mají schopnost vzájemně ovlivňovat ostatní poděděné komponenty, ukládat své stavy jako persistentní
(IStatePersistent) a odpovídat na uživatelské příkazy (ISignalReceiver), ale nejsou vykreslitelné.

Viz také:

● Nette\Application\PresenterComponent
● PresenterComponent API reference
● ISignalReceiver API reference
● IStatePersistent API reference
● Nette\Application\Presenter

« AppForm PresenterRequest »
Nette\Application\PresenterRequest
Objekt zapouzdřující požadavky určené a následně předané spansenteru, a metody pro
manipulaci s nimi.

Objekt PresenterRequest je důsledně zapouzdřenou respanzentací všech dotazů, které byly přijaty prohlížečem
od uživatele jako požadavek pro načtení stránky, a také vlastností a parametrů aktuální Routy, která byla označena
jako vyhovující masce těchto požadavků.

Konkrétně jde o první fázi životního cyklu aplikace, kdy Router z URL vytváří objekt PresenterRequest, který
nese i informace jaký spansenter bude požadavek obsluhovat. Tento objekt si poté uchovává aplikace i výsledný
spansenter.

Modifikací tohoto objektu riskujete nefunkčnost Vašich aplikací!

Objekt PresenterRequest v sobě drží, kromě výše zmíněných informací, také informace o datech odeslaných
metodou POST, o případných uploadovaných souborech a o tom, jakou metodou byl požadavek zpracován (GET,
POST, …).

K těmto vlastnostem má samozřejmě příslušné metody:

// uměle nastavíme proměnné


$_POST = array('a' => 'variable1', 'b' => 'variable2', 'c' => 'variable3')

// pomocí třídy Environment získáme objekt PresenterRequest


$request = Environment::getApplication()->getPresenter()->getRequest();

// byl požadavek vyvolán metodou POST ?


$request->isMethod('post'); // ekvivalentně $request->isPost();

// získání obsahu globální proměnné $_POST


$request->getPost(); // array('a' => 'variable1', 'b' => 'variable2', 'c' => 'variable3')

// modifikace položky globální proměnné $_POST


$request->modify('post', 'b', 'new-variable2');
$request->getPost(); // array('a' => 'variable1', 'b' => 'new-variable2', 'c' => 'variable3')

$request->isMethod('get'); // byl požadavek vyvolán metodou GET ?


$request->getFiles(); // získání obsahu globální proměnné $_FILES

// získání parametrů poskytnutých spansenteru (obvykle přes URL)


$request->getParams(); // např: array('view' => 'default')

// jméno spansenteru ve formátu Module:Presenter


$request->getPresenterName(); // např: "Front:Homepage"

Viz také:
● PresenterRequest API reference
● Presenter
● Routování

« PresenterComponent Action vs. View »


Action vs. View
Na počátku životního cyklu aplikace stojí požadavek na akci (t.j. fully qualified action). Aplikace vytvoří příslušný
spansenter a předá mu řízení. Ten má za úkol akci „odspanzentovat“. V nejjednodušším případě to znamená načíst
stejnojmennou šablonu a vykreslit ji.

Existují však situace, kdy se pod jednou akcí mohou vykreslit diametrálně odlišné stránky. Příkladem je třeba
akce zobrazit zboží v e-shopu. Presenter vyhledá položku v databázi a zjistí, že:

a. v databázi je a může se zobrazit


b. v databázi je, ale má příznak „smazáno“ – zobrazí se informace o nedostupnosti a nabídnou se podobné
produkty
c. v databázi není – zobrazí se informace o chybějící stránce a nabídne např. vyhledávací formulář

Klíčové je si uvědomit, že tyto tři možnosti jsou tři různé výsledky jediné akce – zobrazení zboží. Presenter nemá
akce jako „zobrazit zboží jako nedostupné“ nebo „zobrazit zboží jako neexistující“. Má pouze akci „zobrazit zboží“,
to aplikační logika rozhodne, zda-li zboží existuje, je smazáno, nebo neexistuje. Výsledkem této logiky je pohled na
zboží (v našem příkladu jeden z tří).

Akce je tedy jen jedna (např. Product:show) a tato se rozpadá na tři pohledy. Kód by vypadal asi takto:

class ProductPresenter
{
// action 'show' calls method 'actionShow'
function actionShow($id)
{
$row = dibi::query('SELECT * FROM products WHERE id=%s', $id)->fetch();
if (!$row) {
$this->view = 'notfound';
} elseif ($row->deleted) {
$this->view = 'deleted';
} else {
// není potřeba, je to předvolené nastavení
// $this->view = 'show';
}
}

function renderShow()
{ ... }

function renderNotfound()
{ ... }

function renderDeleted()
{ ... }
}

Volání $this->view = ... tedy ovlivní, které metody spanpareXYZ() a renderXYZ() budou volány a která
šablona se načte. Jinak prostě platí, že se pro pohled použije stejný název jako pro akci.
« PresenterRequest Routování URL »
Routování
Moderní dynamické webové aplikace vyžadují sbírat požadavky zvenčí a na jejich základě přistupovat ke zdrojům
dat, které poté patřičně odspanzentují. Aby aplikace věděla jaké akce má vykonávat při různých požadavcích, musí
se nejprve určit nějaká pravidla. Této technice se říká routování a typickým požadavkem zvenčí je pro webové
aplikace URL adresa.

U webových aplikací založených na architektuře (nebo chcete-li) vzoru MVC a MVP je routování (nebo-li
směrování) spojníkem právě mezi požadavkem zvenčí a správným řadičem, který požadavek zpracuje a
naservíruje očekávaný výstup.

Právě tvar routy určuje, kterému spansenteru a pohledu bude požadavek přidělen a jak bude zpracován.
V současné době se setkáváme s „lidštějšími URL“ (cool, spantty nebo user-friendly URL), které jsou použitelnější a
zapamatovatelnější než jejich „syroví“ předchůdci, ale daleko větší důležitost sehrávají v současně aktuálních
optimalizací webů pro vyhledávače (SEO). Nette Framework na současné trendy myslí a vychází v tomto
vývojářům plně vstříc. Routování navíc odstiňuje vývojáře od direktiv pro mod_rewrite, tudíž nemusíme definovat
tvar rout na více místech – tím předcházíte i určitému procentu chybovosti aplikace.

Obecně lze 90% všech tvarů požadavků v běžné webové aplikaci charakterizovat takto:

● zavolej určitou metodu (pohled) nějakého řadiče (objektu)


● předej jí nějaké další identifikátory nebo parametry

Z pohledu OOP programování, nám tento způsob umožňuje přistupovat rovnou k metodám objektů, předávat jim
ony parametry a pokochat se výsledkem.

Příklady tvarů URL požadavků:

● example.com/article/show/5
● example.com/article/edit/5
● example.com/article/delete/5

nebo

● example.com/category/show
● example.com/category/show/5

První entita, na kterou v URL narazíme, je zároveň první dělící čárou aplikace – který řadič převezme požadavek?
V případě příkladů to bude nejspíše, jak už je z názvu patrné, řadič pro manipulaci s články, v druhém případě pro
manipulaci s logickým prvkem webu – kategorií.
Druhá entita je další dělící čárou – která metoda (akce) tohoto řadiče převezme požadavek? Zde postupně metoda
pro výpis, editaci a smazání článku, v druhém případě pro zobrazení výpisu v kategorii.
A konečně, této metodě je třeba předat nějaké parametry – např. ID stránky nebo konkrétní pozici ve stránkování.

V jiných systémech má routování obrovských význam, naopak v Nette se jím netřeba zabývat. To je věc, která se
může řešit až když je aplikace hotová. Navíc není vůbec žádný problém kdykoliv úplně změnit veškeré cesty
pouze přepsáním rout – je tomu tak, protože routování je obousměrné, slouží jak k parsování, tak
k generování cest.
Není tudíž problém kdykoliv docílit toho, aby požadavek example.com/article/my-new-article vedl na
stejnou stránku jako „kdysi“ example.com/article/show/5 nebo si zjednodušit stránkování výpisu kategorií do
následujícího tvaru: example.com/category/5.

Můžeme také označit některé, například starší, tvary cest za nepoužívané a zastaralé a říct tak aplikaci aby
takovéto odkazy negenerovala, ale jen přijala a přesměrovala na správný (aktuální) tvar URL požadavku.

Výhoda Nette oproti některým jiným MVC frameworkům je v tom, že nemusí pojmenovávat routy. Routy mají
minimální závislosti mezi vrstavama (např. není vztah mezi pořadím parametrů v routě a argumenty metody
spansenteru).

Viz také:

● Model-View-Presenter
● Nette\Application\Route
● Nette\Application\MultiRouter
● Nette\Application\SimpleRouter
● Nette\Application\Presenter

« Action vs. View SimpleRouter »


Nette\Application\SimpleRouter
Jednoduchá dvousměrná routa pro triviální routování přes základní tvar query stringu.

SimpleRouter je jednoduchou implementací rozhraní IRouter. Má stejné možnosti jako Route, tj. generovat
i přijímat URL adresy, nastavit jim příznak pro zabezpečené schéma (https) a jednosměrky (požadavky jsou jen
přijímány). Co však s Route nemá společné, je možnost vytvářet „cool URL“, protože SimpleRouter nemá jako
jeden z parametrů masku tvaru vstupní/výstupní URL. Ty pak mají tvar klasického query stringu.

SimpleRouter stejně jako Route na začátku svého životního cyklu naparsuje vstupní požadavek (tedy query string)
a vytvoří z něj objekt PresenterRequest a stejně jako Route také generuje URL adresy z tohoto objektu
PresenterRequest, pokud je dvousměrný.

Pokud není aplikaci určena žádná uživatelská definice routy, jako výchozí se použije právě
SimpleRouter, který předá požadavek ke zpracování spansenteru Default a pohledu default.

Příklad deklarace SimpleRoutu a jeho předání aplikaci:

// získáme objekt MultiRouter, který slouží jako úložiště pro routy


$router = Environment::getApplication()->getRouter();

// přidání dvousměrné routy do aplikace


$router[] = new SimpleRouter(array(
'module' => 'Front',
'spansenter' => 'Article',
'action' => 'show',
'id' => NULL,
));

// přidání jednoduché jednosměrné routy do aplikace


// je vhodné použít s nějakou další routou pro stejný
// modul a spansenter, pokud z něj chceme i generovat odkazy
$router[] = new SimpleRouter(array(
'module' => 'Front',
'spansenter' => 'Rss',
'action' => 'display',
), SimpleRouter::ONE_WAY);

// nebo příklad routy pro https schéma


$route = new SimpleRouter(array(
'module' => 'Admin',
'spansenter' => 'Dashboard',
'action' => 'default',
'id' => NULL,
), SimpleRouter::SECURED);

Pěkná ukázka použití SimpleRouteru ve spolupráci s objektem PresenterRequest je v adresáři tests v distribuci.
Viz také:

● SimpleRouter API reference


● IRouter API reference
● Routování
● Route
● MultiRouter
● PresenterRequest
● Fórum: Příklady routeru
● Fórum: Routovací tipy a triky

« Routování URL Route »


Nette\Application\Route
Úkolem routy je nejen URL adresu naparsovat a vytvořit z ní interní požadavek, ale
i přesný opak – z interního požadavku vygenerovat URL.

Třída Route implementující rozhraní IRouter je nástrojem pro tvorbu user-friendly URL adres. Že nejde o nic
složitého se můžete přesvědčit sami níže.

Definice cest
Prvním parametrem při tvorbě routy je maska cesty. Ta může být doplněna o validační podmínky ve formě
klasických regulárních výrazů. Druhým parametrem je pole výchozích a fixních hodnot.

// akceptuje URL cestu ve tvaru např. admin/edit/10 nebo catalog/


$router[] = new Route('<spansenter>/<action>/<id [0-9]+>', array(
'spansenter' => 'Article',
'action' => 'show',
'id' => NULL,
));

Příklad ukazuje masku sestávající ze tří parametrů, přičemž všechny jsou volitelné, neboť mají definovánu
výchozí hodnotu. Parametr id má navíc specifikovánu validační podmínku [0-9]+, tj. akceptuje jen číslo
(u ostatních parametrů je použita výchozí podmínka [^/]+).

Definice rout obvykle umístíme do souboru bootstrap.php:

$application = Environment::getApplication();
$router = $application->getRouter();

// nebo all-in-one row


$router = Environment::getApplication()->getRouter();

// vytvoření jednosměrné routy, která bude odchytávat


// všechny dotazy směrující na index.php
// a přesměrovávat na routu níže do cool-url tvaru
// automaticky zohledňuje SEO,
// takže se vám tyto stránky nezaindexují dvakrát
$router[] = new Route('index.php', array(
'spansenter' => 'Article',
'action' => 'show',
), Route::ONE_WAY);

// deklarace obecné dvousměrné routy s cool-url tvarem


$router[] = new Route('<spansenter>/<action>/<id>', array(
'spansenter' => 'Article',
'action' => 'show',
'id' => NULL,
));

// vytvoříme odkaz
$spansenter->link('Article:'); // ekvivalentní s Article:show

Pokud jsou routy inicializovány jako ty v příkladu výše (uvedení výchozích hodnot spansenteru a action), nemusí
se v metodách pro tvorbu odkazů uvádět celá maska routy. Action není potřeba uvádět vůbec – výchozí hodnota je
default zcela automaticky.

Routa s maskou index.php určuje, že při požadavku na stránku index.php se otevře spansenter Article a
action show. Příznak Route::ONE_WAY (jednosměrka) zajistí, že routa může požadavek přijmout (stránka
index.php existuje), ale aplikace takové URL nevytvoří. Tedy při generování URL pro spansenter Article a
action show se použije vhodná následující routa.

Jednosměrné routy se používají třeba pro zachování zpětné kompatibility – pokud na web již existují odkazy ve
tvaru http://example.com/index.php, budou tyto nadále funkční. Navíc dojde k automatickému
přesměrování na nový tvar URL (tzv. kanonizace).

Routy nemusí být striktně SEO friendly, nic nám nebrání napsat tvar klasického query stringu. Můžeme při tom
využívat plné síly regulárních výrazů.

$router[] = new Route('/(hledat|hledat.php) ? find=<find [A-Za-z0-9]*> & in=<in \s{1,3}>', ...);

Má-li být parametrem routy cesta filesystému, pak maskou .*? povolíme všechny znaky včetně
lomítek. Například: new Route('/storage/<path .*?>', ...)

V případě nenalezení routy se vyhodí výjimka.


Pokud se žádná routa nenastaví, tak se o správné chování postará automaticky SimpleRouter.

Uvnitř aplikace se odkazuje tak, jako když jsou volány metody v OOP: Presenter::action($arg1, $arg2).
Konkrétně třeba Product:detail($id). Voláme metodu detail třídy Product a předáme jí parametr $id.
Podrobně je filosofie routování popsána v jiném článku.

Kanonizace
Kanonizace je proces přesměrování URL na výchozí (kanonickou) formu. Jednoduše řeřeno, kanonická URL je ta,
kterou vygeneruje router. Je úkolem třídy Presenter toto zajistit a ve výchozím nastavení je automatická
kanonizace zapnuta. Lze ji vypnout přes $spansenter->autoCanonicalize = FALSE.

Pokud k cíli vede několik možných URL, tak jedno z nich je kanonické a ostatní se na ně přesměrují.

Funguje to tak, že router převede HTTP požadavek na objekt PresenterRequest. Aplikace poté z tohoto objektu
zpětně vygeneruje URL a pokud není ekvivalentní s aktuálním, dojde k přesměrování.

Příklad kanonizace:
$router[] = new Route('index.php', array(
'spansenter' => 'Blog',
'action' => 'default',
), Route::ONE_WAY);

$router[] = new Route('blog/(|index\.php) ? <id> & <comments>', array(


'spansenter' => 'Blog',
'action' => 'show',
'id' => NULL,
'comments' => NULL,
), Route::ONE_WAY);

$router[] = new Route('blog/<id>', array(


'spansenter' => 'Blog',
'action' => 'show',
'id' => NULL,
));

$router[] = new SimpleRouter(array(


'spansenter' => 'Blog',
'action' => 'default',
'id' => NULL,
), SimpleRouter::ONE_WAY);

Při tomto tvaru rout budou všechny níže uvedené adresy přesměrovány na adresu
http://example.com/blog/nejaky-clanek, což se hodí při zachování zpětné kompatibility odkazů, které již
vedou na váš web z internetu.

● http://example.com/index.php?spansenter=blog&id=nejaky-clanek
● http://example.com/?spansenter=blog&id=nejaky-clanek
● http://example.com/blog/index.php?id=nejaky-clanek
● http://example.com/blog/?id=nejaky-clanek
● http://example.com/blog/nejaky-clanek/

Počet rout má vliv na rychlost aplikace, zejména při generování odkazů.

K přesměrování nedojde při AJAXovém nebo POST požadavku (protože by došlo ke ztrátě dat, které jsou
posílány), takže se nemusíte bát, že za cenu SEO optimalizovaných adres budete muset něco obětovat.

Foo parametry
Foo parametry rozšiřují možnosti definice rout. Narozdíl od klasických parametrů nemají název (místo něj se
použije otazník), nepředávají se spansenteru a slouží k tomu, aby bylo možné do masky přidat regulární výraz.

Příklad: jednosměrná routa akceptující index.html, index.htm a index.php.

$router[] = new Route('index<? \.html?|\.php>', array(


'spansenter' => 'Homepage',
'action' => 'default',
), Route::ONE_WAY);

Pokud by uvedená routa byla obousměrná, generovala by cestu index, kterou však sama neumí akceptovat.
Výraz by se proto musel rozšířit i o prázdnou hodnotu na 'index<? \.html?|\.php|>'.

Nebo lze explicitně definovat řetězec, který bude při generování cesty použit (obdoba výchozí hodnoty
u skutečných parametrů). Řetězec se vloží ihned za otazník:

$router[] = new Route('feed<?.xml>', array(


'spansenter' => 'Feed',
'action' => 'rss',
));

Tato routa akceptuje cesty feed.xml a feed, přičemž generuje feed.xml.

Volitelné sekvence
V routovací masce třídy Route lze označovat tzv. volitelné sekvence. Ty se uzavírají do hranatých závorek:

$route = new Route('[<lang [a-z]{2}>/]<name>', array());

//Akceptuje cesty:
// /cs/download => lang=cs, name=download
// /download => lang=NULL, name=download

Výhodou je, že volitelný parameter se může nacházet i uprostřed masky, ale především lze definovat jeho okolí,
v tomto případě znak lomítka, které musí parametr, pokud je uveden, obklopovat. To lze využít například
u volitelných subdomén:

$router[] = new Route('//[<module>.]example.com/<spansenter>/<action>', array(


'spansenter' => 'Homepage',
'action' => 'default',
));

Závorky je možné libovolně zanořovat:

$route = new Route('[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page>]', array(


'page' => 0,
));

// Akceptuje cesty:
// /cs/stranka
// /en-us/stranka
// /stranka
// /stranka/page-12

Přitom uvnitř volitelné sekvence nemusí být ani žádný parametr:

$route = new Route('index[.html]', array());

// Akceptuje cesty:
// /index.html
// /index
//
// Generuje:
// /index

Generování URL
Při generování cest se používá pravidlo nejkratšího URL, takže všechno, co lze vynechat, se vynechá. Právě
proto routa index[.html] generuje, jak jsem naznačil výše, cestu index.

Pokud byste naopak chtěli generování volitelné sekvence vynutit, napište za levou závorku vykřičník:

$route = new Route('index[!.html]', array());

// Akceptuje cesty:
// /index.html
// /index

// Generuje:
// /index.html

Zanořování a parametry
Interní poznámka: volitelné parametry (tj. parametry mající výchozí hodnotu) mimo hranaté závorky se chovají
v podstatě tak, jako by byly uzávorkovány následujícím způsobem:

// tento zápis
$route = new Route('<spansenter>/<action>/<id>', array(
'spansenter' => 'Dashboard',
'action' => 'default',
'id' => NULL,
));

// odpovídá funkčně tomuto:


$route = new Route('[<spansenter>/[<action>/[<id>]]]', array(
'spansenter' => 'Dashboard',
'action' => 'default',
// 'id' => NULL, neni potřeba uvádět
));

Pokud byste chtěli ovlivnit chování zpětných lomítek, tj. aby se místo např. dashboard/view/ generovalo
dashboard/view, lze toho docílit takto:

$route = new Route('[<spansenter>[/<action>[/<id>]]]', array(


'spansenter' => 'Dashboard',
'action' => 'default',
));

Což ale není z logiky URL-tvorby správné.

Volitelné sekvence vs. foo-parametery


Ačkoliv možnosti foo-parametrů a volitelných sekvencí se trošku překrývají, navzájem se nenahrazují. Účelem
foo-parametrů je dostat do masky regulární výrazy, naopak volitelné sekvence s nimi vůbec nepracují.

Překladový slovník pro Route


Pokud píšete aplikaci v angličtině a web má běžet v českém prostředí, tak vám nemusí dostačovat jednoduché
routování typu:

$router[] = new Route('<spansenter>/<action>/<id>', array(


'spansenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
));

A to z důvodu, že spansentery Product, Basket, Customer chcete mít v URL respanzentované jako
produkt, kosik, zakaznik. Věc je možno řešit buď vytvořením více rout (což však bude zpomalovat aplikaci),
nebo definicí vlastních filtračních funkcí:

Route::setStyleProperty('spansenter', Route::FILTER_IN, 'myFunctionIn');


Route::setStyleProperty('spansenter', Route::FILTER_OUT, 'myFunctionOut');

function myFunctionIn($s) {
return ...;
}

function myFunctionOut($s) {
return ...;
}

Což je zase poměrně komplikované. Třída Route proto nabízí možnost nastavit překladovou tabulku:
Route::setStyleProperty('spansenter', Route::FILTER_TABLE, array(
'produkt' => 'Product',
'kosik' => 'Basket',
'zakaznik' => 'Customer',
));

Tabulku tvoří páry "řetězec v URL" ⇒ "spansenter".

Zajímavost: jeden spansenter může být uveden pod více ruznými kliči. Pak k němu povedou všechny varianty
(tedy vytvoří se aliasy), s tím, že za kanonickou se považuje ta poslední.

Filtry rout
Pokud používáte jako parametr v routách řetězec, který obsahuje encodovatelné znaky (například lomítko či
mezeru), jsou tyto znaky nahrazeny implicitně funkcí rawurlencode při generování, tak jak je nastaveno ve třídě
Route v proměnné styles. Klasickým případem je Routa pro soubory, kde je parametr path může nabývá tvarů
filesystemu, tzn. řetězec s lomítky.

$router[] = new Route('//files.example.com/ ', array(


'spansenter' => 'File',
'action' => 'default',
'path' => NULL,
));

Takováto routa by generovala v parametru cesty s lomítkem přeloženým za %2F, jelikož výchozí styl má
nastaveno self::FILTER_OUT => 'rawurlencode'.

Jelikož jde o statickou proměnnou, můžeme chování jednoduše upravit, nebo definovat styl vlastní přímo pro
parametr path:

Route::$styles['path'] = array(
Route::PATTERN => '.*?',
);

$router[] = new Route('//files.example.com/ ', array(


'spansenter' => 'File',
'action' => 'default',
'path' => NULL,
));

Všimněte si, že již není dále třeba u parametru path určovat filtr (což je regulární výraz .*?) explicitně v definici
routy. Téhož výsledku lze dosáhnout i takto:

Route::addStyle('path', NULL);
Route::setStyleProperty('path', Route::PATTERN, '.*?');
Route::setStyleProperty dělá to samé jako <url .*>, ale klíčový rozdíl je právě v tom, že druhý parametr
Route::addStyle říká, že nechceme zdědit standartní filtrování pomocí rawurlencode.

Dynamické přidávání rout


Nyní si ukážeme, jak zaroutovat do naší aplikace nějaký existující modul.

Dejme tomu, že do website programované v Nette chceme přidat fórum. Stačí, aby fórum disponovalo instalační
funkcí createRoutes():

class Forum
{
function createRoutes($router, $spanfix)
{
$router[] = new Route($spanfix . 'index.php', array(
'spansenter' => 'Forum:Homepage',
'action' => 'default',
));
$router[] = new Route($spanfix . 'admin.php', array(
'spansenter' => 'Forum:Admin',
'action' => 'default',
));
...
}
}

„Zaroutování“ fóra do existující aplikace je pak velmi jednoduché. bootstrap.php:

$router = $application->getRouter();
// přidáme své routy
...
// přidáme modul forum
Forum::createRoutes($router, '//forum.example.com/');

Multijazyčnost
Nette Framework nikomu nevnucuje konkrétní řešení. Otázkou mnohých složitějších systémů je multijazyčnost.
Základem k jejímu vyřešení je dobrý návrh rout. Možností jak vyřešit tento požadavek je spostu a vše záleží jen na
Vaší představivosti. Můžete se inspirovat na pár příkladech:

// nejjednodušší způsob řešení


$router[] = new Route('/article/<id>', array(
'spansenter' => 'Article',
'lang' => 'en',
));
$router[] = new Route('/clanek/<id>', array(
'spansenter' => 'Article',
'lang' => 'cs',
));

// další z možností: název jazyka ve tvaru doménu 3. řádu


$router[] = new Route('//<lang {?cs|en}>.example.com/<id>/<action>', array( ... ), Route::ONE_WAY);
$router[] = new Route('//<lang [a-z]{2}>.example.com/<id>/<action>', array( ... ), Route::ONE_WAY);

// další možnost: povinné a volitelné parametry


// - module je zadán a není v masce => fixní
// - lang není zadán a podléhá masce => povinný
$router[] = new Route('<lang [a-z]{2}>/<id>/<action>', array(
'module' => 'Front',
'spansenter' => 'Homepage',
'action' => 'default',
'id' => NULL // takto definovaný parametr je volitelný
));
// poté někde ve startup() zavoláme: $this->lang = $this->getParam('lang');

Viz také:

● Routování
● Fórum: Příklady routeru
● Fórum: Routovací tipy a triky

« SimpleRouter MultiRouter »
Nette\Application\MultiRouter
Hromadné úložiště pro routy aplikace.

Jelikož Nette narozdíl od jiných frameworků nepojmenovává routy, je zde důmyslný mechanismus, který
z jednoho globálního úložiště na routy vybere odpovídající routu (objekty Route a SimpleRouter), pro kterou
požadavek nejvíce vyhovuje. To, zda-li se vybere úspěšně ta routa, kterou jsme zamýšleli, závisí na našich
konkrétních definicích masek rout. Obecně platí pravidlo, že routy deklarujeme postupně od těch nejvíce
specifických po ty obecné. Tím se vyhneme případným kolizím.

Kolik regulárů používáš v .htaccess, s tolika routami si vystačíš v Nette!

MultiRouter je v základu potomek objektů z jmenného prostoru Nette\Collections, konkrétně objektu ArrayList,
který je přímým potomkem Collection. Sám implementuje i rozhraní IRouter stejně jako Route a SimpleRouter.

Tato hierarchie poskytuje vlastnosti, díky kterým je routování v Nette čistě a rychle napsáno (rychlost parsování
URL je ekvivalentní jako RewriteRules z .htaccess souborů), proto nemá cenu například nastavení MultiRouteru
kešovat. Naopak – funguje rychleji, než když se načítá z keše! Pokud tedy negenerujete routy z databáze, nemá
cenu se keší zabývat.

Na závěr se sluší dodat, že jako vše v Nette je možno napsat vlastní router a implementovat si routovací techniky
dle libosti, stačí implementovat daná rozhraní.

Jednoduchá ukázka použití MutliRouteru a tvaru routy:

// získáme instanci objektu MultiRouter, který slouží jako úložiště pro routy
$router = Environment::getApplication()->getRouter();

// přidáme routu, objekt Route do MultiRouteru


$router[] = new Route('/clanek/<id>', array(
'spansenter' => 'Article',
'view' => 'article',
'lang' => 'cs',
));

Další příklady rout lze nalézt v dokumentacích tříd Route a SimpleRouter a na fóru.

Viz také:

● MultiRouter API reference


● IRouter API reference
● Routování
● Route
● SimpleRouter
● Fórum: Příklady routeru
● Fórum: Routovací tipy a triky
« Route Autentizace, přihlašování uživatelů »
Autentizace – Přihlašování uživatelů
Autentizací se rozumí přihlašování uživatelů, tedy proces, při kterém se ověřuje, zda je uživatel opravdu tím, za
koho se vydává. V drtivé většině aplikací se ověřuje uživatelské jméno a heslo. Naopak při autorizaci se zjišťuje,
zda má již autentizovaný uživatel dostatečná oprávnění pro přístup k určitému souboru či pro provedené nějaké
akce. Autorizaci si necháme do příštího pokračování.

Přihlašování uživatelů je oblast velmi úzce související s ochranou osobních údajů a zabezpečením aplikace. Jelikož
PHP nenabízí žádnou standardní implementatici, jde také bohužel o oblast bezbřehé programátorské „kreativity“.
Lze se setkat s odstrašujícími případy, kdy programátoři například ukládají hesla do cookies a nebo vytvářejí jiné
sofistikované bezpečnostní díry.

Nette Framework se snaží tuto díru zacelit. A zároveň přihlašování zjednodušit až na naprosté minimum. Tím jsou
dvě metody login() (přihlásit) a logout() (odhlásit), plus dotazovací metoda isLoggedIn() sdělující, zda je
uživatel nyní přihlášen.

Ve starších verzích se můžete setkat se starším pojmenováním metod: authenticate() →


login(), signOut() → logout(), isAuthenticated() → isLoggedIn(),
getSignOutReason() → getLogoutReason().

O realizační stránku se stará třída Nette\Web\User. Ta je, stejně jako v případě Nette\Web\Session, singleton,
proto nevytváříme její instanci přímo, ale vrátí ji metoda Environment::getUser(). Používá se zhruba tímto
způsobem:

require 'Nette/loader.php';

$user = Environment::getUser();

// přihlášení uživatele
$username = ...
$password = ...
$user->login($username, $password);

// je přihlášen?
echo $user->isLoggedIn() ? 'ano' : 'ne';

// odhlášení
$user->logout();

Aby příklad fungoval, je potřeba napsat rutinu, která provede ověření uživatelského jména a hesla. Této rutině se
říká autentizační handler a jde o objekt implementující rozhraní Nette\Security\IAuthenticator. To má jedinou
metodu login(). Implementace, která ověřuje přihlašovací údaje oproti databázové tabulce, může vypadat
třeba takto:

require 'Nette/loader.php';

// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:


// use Nette\Object, Nette\Security\IAuthenticator, Nette\Security\AuthenticationException, Nette\Security\Id
entity;

class MyAuthenticator extends Object implements IAuthenticator


{

public function authenticate(array $credentials)


{
$username = $credentials[self::USERNAME];
$password = sha1($credentials[self::PASSWORD] . $credentials[self::USERNAME]);

// přečteme záznam o uživateli z databáze


$row = dibi::fetch('SELECT realname, password FROM users WHERE login=%s', $username);

if (!$row) { // uživatel nenalezen?


throw new AuthenticationException("User '$username' not found."
, self::IDENTITY_NOT_FOUND);
}

if ($row->password !== $password) { // hesla se neshodují?


throw new AuthenticationException("Invalid password.", self::INVALID_CREDENTIAL);
}

return new Identity($row->realname); // vrátíme identitu


}

Autentizační handler si zaslouží hlubší rozbor. Z pohledu návrhu aplikace podle vzoru MVP jde o součást modelu,
přičemž samotnou autentizaci zpravidla iniciuje spansenter. Nette Framework vás tak vede k oddělení ověření
údajů od spanzentační vrstvy.

Úkolem handleru je buď vrátit tzv. identitu v případě úspěchu, nebo vyhodit výjimku. Nette definuje výjimku
Nette\Security\AuthenticationException a několik chybových kódu, které můžete využít k formálnímu popisu
vzniklé chyby. (Nicméně na to, jakou výjimku vyhodíte, se žádná omezení nekladou, nakonec bude je zachytávat a
ošetřovat opět váš kód.)

V případě úspěšné autentizace vrácí handler identitu, což je objekt implementující rozhraní Nette\Security\I-
Identity a popisující aktuálního uživatele. Popis může obsahovat libovolné údaje, povinné je uživatelské jméno (což
nemusí být nutně totéž, jako přihlašovací jméno) a role (o těch si povíme více v příštím dílu). K identitě se
dostaneme přes getter getIdentity():

$user = Environment::getUser();
if ($user->isLoggedIn()) {
echo 'Prihlášen uživatel: ', $user->getIdentity()->getName();
} else {
echo 'Uživatel není přihlášen';
}
Ve verzi 1.0 byla metoda getName() nahrazena metodou getId().

Odhlášení
Jak už jsem zmínil, uživatele odhlásí metoda logout(). Při odhlášení se však nesmaže uživatelská identita,
kterou máme i nadále k dispozici. Pokud bychom chtěli identitu explicitně smazat, odhlásíme uživatele voláním
logout(TRUE).

Kromě manuálního odhlášení nabízí Nette Framework i automatické odhlášení po uplynutí časového intervalu
nebo zavření okna prohlížeče. K tomu slouží metoda setExpiration(), kterou volejte vždy před samotnou
autentizací. Metoda setExpiration() jako parametr akceptuje relativní čas v sekundách nebo UNIX timestamp,
v aktuální verzi frameworku je možné použít i velmi srozumitelný textový zápis. Druhý parametr stanoví, zda se má
uživatel odhlásit při zavření okna prohlížeče:

// přihlášení vyprší po 30 minutách neaktivity nebo zavření okna prohlížeče


$user->setExpiration('+ 30 minutes');

// přihlášení vyprší po 2 dnech


$user->setExpiration('+ 2 days', FALSE);

Dokonce je možné zjistit, z jakého důvodu k poslednímu odhlášení došlo (viz. metoda getLogoutReason).

Shrnutí
Kompletní postup přihlašování uživatele pak vypadá asi takto:

require 'Nette/loader.php';

require 'MyAuthenticator.php';

// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:


// use Nette\Environment, Nette\Security\AuthenticationException;

// přihlašovací údaje
$username = ...
$password = ...

$user = Environment::getUser();

// zaregistrujeme autentizační handler


$user->setAuthenticationHandler(new MyAuthenticator);

// nastavíme expiraci
$user->setExpiration('+ 30 minutes');

try {
// pokusíme se přihlásit uživatele...
$user->login($username, $password);
// ...a v případě úspěchu spansměrujeme na další stránku
Environment::getHttpResponse()->redirect('index.php');

} catch (AuthenticationException $e) {


echo 'Chyba: ', $e->getMessage();
}

« MultiRouter Nette\Web\User »
Nette\Web\User
Zajišťuje přihlašování a odhlašování uživatelů, popisuje jejich identitu a ověřuje práva
na základě rolí.

Autentizace (přihlášení)
Přihlášení a odhlášení uživatele:

$user = new User;

// přihlášení
$user->login($userName, $password); // předáme přihlašovací jméno a heslo

// ověření, zda je uživatel přihlášen


if ($user->isLoggedIn()) ...

// jednoduché odhlášení
$user->logout();

Přihlašování vyžaduje u uživatele povolené cookies; jiná metoda přihlašování není bezpečná!

Oveření uživatelského jména a hesla provádí autentizační handler, což je objekt implementující rozhraní
Nette\Security\IAuthenticator. Jeho triviální implementací je třída
Nette\Security\SimpleAuthenticator, která dostane v konstruktoru seznam uživatelů a hesel jakožto
asociativní pole. Úkolem handleru je v metodě login(array $credentials) ověřit, zda uživatelské jméno a
heslo odpovídá a v případě úspěchu vrátit tzv. identitu. Neúspěch indikuje vyhozením výjimky
Nette\Security\AuthenticationException s popisem důvodu. Lze využít i připravené konstanty
IAuthenticator::IDENTITY_NOT_FOUND nebo IAuthenticator::INVALID_CREDENTIAL.

$authenticator = new SimpleAuthenticator(array(


'john' => 'IJ^%4dfh54*',
'kathy' => '12345', // Kathy, this is very weak password!
));

$user->setAuthenticationHandler($authenticator);

Automatické odhlášení
Ve výchozím nastavení je uživatel odhlášen v okamžiku, kdy zavře okno prohlížeče. Chování lze změnit metodou
setExpiration(). První parametr určuje čas v sekundách, za který bude uživatel v případě neaktivity odhlášen.
Druhý parametr říká, zda uživatele odhlásit v okamžiku zavření okna prohlížeče. Třetí parametr stanoví, zda při
odhlášení smazat identitu.

// uživateli zůstane při odhlášení identita


$user->logout(); // nebo $user->logout(FALSE);

// uživatelova identita bude smazána - hodí se u případů,


// kdy je počítač sdílený a chci se odhlásit
// a nezanechat po sobě žádné osobní údaje
$user->logout(TRUE);

// odhlásit uživatele po 14 dnech neaktivity


$user->setExpiration(1209600, FALSE);

// odhlásit uživatele po jednom dni neaktivity, nebo až zavře prohlížeč


$user->setExpiration(86400, TRUE);

// odhlásit uživatele až zavře prohlížeč (bez časového limitu)


$user->setExpiration(0, TRUE);

// automatické odhlášení uživatele po 15 minutách neaktivity


// nebo zavření prohlížeče s odstraněním identity
$user->setExpiration(15*60, TRUE, TRUE);

Pokud při ověřování autentizace uživatele zjistíme, že není přihlášen, můžeme jít ještě dál a zjistit příčinu
odhlášení. Nette umožňuje zjistit metodou getLogoutReason() základní příčiny odhlášení jako zavření okna
prohlížeče, neaktivita nebo manuální odhlášení uživatele. Můžeme tak například pomocí flash zpráviček dát
uživateli vědět o příčině jeho odhlášení.

Z jakého důvodu byl uživatel odhlášen prozradí metoda $user->getLogoutReason(). Pokud vypršel časový
limit vrací User::INACTIVITY, pokud uživatel zavřel okno prohlížeče vrací User::BROWSER_CLOSED a pokud byl
uživatel odhlášen voláním metody logout() vrací User::MANUAL.

// user authentication
$user = Environment::getUser();
if (!$user->isLoggedIn()) {
if ($user->getLogoutReason() === User::INACTIVITY) {
$this->flashMessage('You have been logged out due to inactivity. Please login again.');
}

// stejným způsobem zjistíme User::BROWSER_CLOSED a User::MANUAL


}

Dále je možno využít událostí onLoggedIn a onLoggedOut například pro jednoduchého přidání callbacku pro
logování autorizačních aktivit na webu. Událost onLoggedIn je volána po úspěšném přihlášení. Událost
onLoggedOut je zpracována po odhlášení uživatele, ať už se odhlásil sám nebo z důvodu neaktivity či zavření
prohlížeče.

$user->onLoggedIn[] = array($logger, 'loginMethod');


$user->onLoggedOut[] = array($logger, 'logoutMethod');

Expirace Nette\Web\Session musí být nastavena na stejnou nebo vyšší hodnotu, jakou má
expirace přihlášení

Identita
Identita je objekt implementující rozhraní Nette\Security\IIdentity. Nette\Web\User jej udržuje v session.
Jeho výchozí implementace Nette\Security\Identity udržuje informaci o jméně, rolích a dalších uživatelských
datech (na uživatelská data si můžeme šáhnout přes Environment::getUser()->getIdentity()->promena).

Ačkoliv má uživatel identitu, nemusí být přihlášený! Identita se sice obvykle získá při přihlášení, ale i když vás
systém po nějaké době odhlásí ($user->logout()), stále si ji pamatuje (jméno, obsah košíku na eshopu, …).

Pokud se to hodí, můžeme při odhlášení identitu vymazat: $user->logout(TRUE).

Autorizace
Autorizace = zjištění, zda má uživatel právo to či ono udělat. Rozhoduje se na základě rolí a toho, zda je uživatel
přihlášen. V nejjednodušších případech si vystačíme právě s indikátorem přihlášení:

if ($user->isLoggedIn()) ..

Silnější mechanismus je rozhodování na základě rolí:

● každý uživatel může mít v jednu chvíli přiřazeno více rolí


● nepřihlášený uživatel má automaticky roli $user->guestRole (výchozí hodnota 'guest')
● autentizovaný (tj. přihlášený) uživatel bez identity má automaticky roli $user->authenticatedRole (výchozí
hodnota 'authenticated')
● autentizovaný uživatel s identitou ($user->getIdentity()) vychozí roli nezíská, o role se stará
$user->getIdentity()->getRoles()

if ($user->isInRole('editor')) ..

S tím si u většiny Běžných Aplikací™ vystačíte.

Nejsilnější mechanismus poskytuje autorizační handler ($user->authorizationHandler), tj. objekt


implementující rozhraní Nette\Security\IAuthorizator s metodou isAllowed(). Jeho implementací je
právě třída Nette\Security\Permission, do hry tak kromě rolí vstupují i parametery resource & privilege.

if ($user->isAllowed($resource, $privilege)) ..
Protože uživatel může mít více rolí, povolení dostane, pokud alespoň jedna role má povolení. Oba parametry jsou
volitelné, výchozí hodnota nese význam všechny. Takže pokud např. parametr $privilege nevyužijeme, můžeme ho
vynechat.

Ještě upozornění: pokud uživateli po odhlášení zůstane identita, tak i včetně všech rolí – získatelných přes
$user->getIdentity()->getRoles(). Nicméně metoda $user->getRoles() stav přihlášení zohledňuje.
Stejně tak i dotazy isInRole() a isAllowed() – proto je není nutné kombinovat s dotazem isLoggedIn().

Odkud se handlery berou?


Globální úložiště pro služby (services) poskytuje Nette\Environment::getServiceLocator(), zkratkou pro získání
objektu User je Environment::getUser(). Objekt Nette\Web\User má settery pro autorizační a autentizační handlery,
ale aby to celé pracovalo hezky líně (tj. objekty se vytvářejí, až když jsou skutečně potřeba), pokouší se
Nette\Web\User získat handlery opět přes metodu Nette\Environment::getService(), kde je identifikátorem název
interface.

Stačí tedy nastavit:

Environment::getServiceLocator()->addService($authenHandler, 'Nette\Security\IAuthenticator');
Environment::getServiceLocator()->addService($authorHandler, 'Nette\Security\IAuthorizator');

// kde handler je buď hotový objekt (pak to ale není lazy), jméno třídy nebo callback na továrnu

Službu lze nastavit i skrze config.ini:

service.Nette-Security-IAuthenticator = MyAuthenticator

Více aplikací v jednom prostoru


V rámci jedné aplikace (serveru, session) může fungovat více aplikací, s tím, že si každá spravuje přihlašování
samostatně. Stačí každé nastavit vlastní jmenný prostor:

$user->setNamespace('forum');

Viz také:

● Nette\Web\User
● Nette\Security
● API reference

« Autentizace, přihlašování uživatelů Identity »


Nette\Security\Identity
Udržuje informace o uživatelově identitě a jeho právech.

Nette\Security\Identity je třída implementující rozhraní Nette\Security\IIdentity a její funkcí je


udržovat informace o jméně, rolích a dalších uživatelských datech.

Třída disponuje pouze dvěma veřejnými metodami getName(), která vrací jméno identity, a getRoles(), která
vrátí seznam všech rolí, kterých identita nabyla. Také lze přistupovat k uživatelským datům jako k property.

Nette\Web\User udržuje tuto identitu v session a slouží ke všem autorizačním a autentizačním procesům. Identita
se sice obvykle získá při přihlášení, ale i když vás systém po nějaké době odhlásí, stále si ji pamatuje (toto chování
lze upravit a můžeme při odhlášení identitu vymazat).

Viz také:

● Nette\Security\Identity API reference


● Nette\Security\IIdentity API reference
● Nette\Web\User

« Nette\Web\User SimpleAuthenticator »
Nette\Security\SimpleAuthenticator
Triviální implementace autentizačního handleru.

Autentizace
Autentizace je proces ověření proklamované identity subjektu.

Patří k bezpečnostním opatřením a zajišťuje ochranu před falšováním identity, kdy se subjekt vydává za někoho,
kým není. V Nette rozlišujeme autentizaci entity (osoby).

Ověření uživatelského jména a hesla provádí autentizační handler, což je objekt implementující rozhraní
Nette\Security\IAuthenticator.

Jeho triviální implementací je třída Nette\Security\SimpleAuthenticator, která dostane v konstruktoru


seznam uživatelů a hesel jakožto asociativní pole.

Úkolem handleru je ověřit, zda uživatelské jméno a heslo odpovídá a v případě úspěchu vrátit tzv. identitu.

Neúspěch indikuje vyhozením výjimky Nette\Security\AuthenticationException s popisem důvodu. Lze


využít i připravené konstanty IAuthenticator::IDENTITY_NOT_FOUND nebo
IAuthenticator::INVALID_CREDENTIAL.

Příklad použití:

$authenticator = new SimpleAuthenticator(array(


'john' => 'IJ^%4dfh54*',
'kathy' => '12345', // Kathy, this is a very weak password!
));

$user = new User;


$user->setAuthenticationHandler($authenticator);

// přihlášení
$user->login('kathy', '12345'); // předáme přihlašovací jméno a heslo

// ověření, zda je uživatel přihlášen


if ($user->isAuthenticated()) { ... }

// jednoduché odhlášení
$user->logout();

Viz také:

● Nette\Security\Identity
● Nette\Security\Permission
● Nette\Security\SimpleAuthenticator API reference
● Nette\Security\IAuthenticator API reference
● Dynamická správa rolí a zdrojů

« Identity Autorizace, ověřování oprávnění »


Autorizace – ověřování oprávnění
Zatímco pod autentizací se rozumí přihlašování uživatelů, tedy proces, při kterém se ověřuje, zda je uživatel
opravdu tím, za koho se vydává, při autorizaci se zjišťuje, zda má již autentizovaný uživatel dostatečná oprávnění
pro přístup k určitému souboru či pro provedení nějaké akce.

Autorizace se může v Nette Framework vyhodnocovat na základě členství uživatele v určitých skupinách či
přidělených rolích. Začněme ale od úplného začátku.

Triviální autorizace
Zopakuji, že autorizace předpokládá předchozí úspěšnou autentizaci, tj. že uživatel je spolehlivě přihlášen.
U jednoduchých webů s administrací, do které se přihlašuje jen jeden uživatel, je možné jako autorizační kritérium
použít již známou metodu isAuthenticated(). Řečeno srozumitelnějším jazykem: jakmile je uživatel přihlášen,
má veškerá oprávnění a naopak.

// use Nette\Environment;

$user = Environment::getUser();

if ($user->isAuthenticated()) { // je uživatel přihlášen?


deleteItem(); // pak má k operaci oprávnění
}

Autorizace na základě rolí


Jemnější řízení oprávnění nabízí tzv. role (nebo též skupiny). Každému uživateli hned při přihlášení přiřkneme
jednu či více rolí, ve kterých bude vystupovat. Role mohou být pojmenovány například admin, member, guest,
apod. Upravíme autentizační handler tak, aby při úspěšném přihlášení předal identitě i aktuální roli uživatele:

class MyAuthenticator extends Object implements IAuthenticator


{

public function authenticate(array $credentials)


{
...
$row = dibi::fetch('SELECT realname, password, role FROM users WHERE login=%s',
$username);
...
return new Identity($row->realname, $row->role); // vrátíme identitu včetně role
}

Druhým parametrem konstruktoru Identity je buď řetězec s názvem role, nebo pole řetězců – rolí.
Jako autorizační kritérium nyní použijeme metodu isInRole(), která prozradí, zda-li uživatel vystupuje
v dané roli:

$user = Environment::getUser();

if ($user->isInRole('admin')) { // je uživatel v roli admina?


deleteItem(); // pak má k operaci oprávnění
}

Výhodou a smyslem rolí je nabídnout přesnější řízení oprávnění, ale zůstat nezávislý na uživatelském jméně.

Ještě musím zmínit jednu důležitou věc. Jak už totiž víte, po odhlášení uživatele nemusí dojít ke smazání jeho
identity. Tedy i nadále metoda getIdentity() vrací objekt Identity, včetně všech udělených rolí. Nette
Framework vyznávající princip „less code, more security“, kdy méně psaní vede k více zabezpečenému kódu,
nechce nutit programátora všude psát if ($user->isAuthenticated() && $user->isInRole('admin'))
a proto metoda isInRole() pracuje s efektivními rolemi. Pokud uživatel je přihlášen, vrací se role udělené
v autentizačním handleru, pokud přihlášen není, má virtuální roli guest.

Autorizační handler
Představuje nejjemnější možné řízení oprávnění. Autorizační handler je objekt implementující rozhraní
Nette\Security\IAuthorizator. To má jedinou metodu isAllowed() s úkolem rozhodnout, zda má daná role
povolení provést určitou operaci s určitým zdrojem. Rámcová podoba implementace vypadá takto:

require 'Nette/loader.php';

// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:


// use Nette\Object, Nette\Security\IAuthorizator;

class MyAuthorizator extends Object implements IAuthorizator


{
public function isAllowed($role = self::ALL, $resource = self::ALL, $privilege = self::ALL)
{
return ...; // vrací bool
}
}

A následuje příklad použití:

$user = Environment::getUser();

// zaregistrujeme autorizační handler


$user->setAuthorizationHandler(new MyAuthorizator);

if ($user->isAllowed('file')) { // má uživatel oprávnění ke zdroji 'file'?


useFile();
}
if ($user->isAllowed('file', 'delete')) { // má uživatel oprávnění ke zdroji 'file' a operaci 'delete'?
deleteFile();
}

V tuto chvíli záleží čistě na vás, jak implementujete autorizační handler. Nicméně Nette Framework disponuje
i jednou poměrně silnou předpřipravenou implementací.

Access control list


Jde o třídu Nette\Security\Permission implementující model Access control list. Práce s ní spočívá v definici rolí,
zdrojů a jednotlivých oprávnění. Přičemž role a zdroje umožňují vytvářet hierarchie. Příklad:

// use Nette\Environment, Nette\Security\Permission;

$acl = new Permission;

// definujeme role
$acl->addRole('guest');
$acl->addRole('member');
$acl->addRole('administrator', 'member'); // administrator je potomkem member

// definujeme zdroje
$acl->addResource('file');
$acl->addResource('article');

// pravidlo: host může jen prohlížet články


$acl->allow('guest', 'article', 'view');

// pravidlo: člen může prohlížet vše, soubory i články


$acl->allow('member', Permission::ALL, 'view');

// administrátor dědí práva od člena, navíc má právo vše editovat


$acl->allow('administrator', Permission::ALL, array('view', 'edit'));

// zaregistrujeme autorizační handler


Environment::getUser()->setAuthorizationHandler($acl);

Na opravnění se poté opět dotazujeme metodou isAllowed(), jak bylo ukázáno výše.

« SimpleAuthenticator Permission »
Nette\Security\Permission
Ověřuje práva a přístupy k objektům na základě rolí.

Nette\Security\Permission je objekt implementující rozhraní Nette\Security\IAuthorizator


poskytující programátorovi lehkou a flexibilní ACL vrstvu pro řízení práv a přístupu.

Tato vrstva velmi úzce souvisí s objektem Nette\Web\User a jejím základem je definování pravidel (rules).

Autorizace (zjištění, zda má uživatel právo to či ono udělat) rozhoduje se na základě rolí (roles), zdrojů/objektů
(resources) a práv/akcí (privileges), která v celé aplikaci určí kdo může přistupovat k chráněnému objektu a jaké
akce s ním může vykonávat.

Lépe poslouží jako vysvětlení následující příklad webové aplikace:

● Nepřihlášený (neautentizovaný) návštěvník webu (což je výchozí role Nette\Web\User) může číst a procházet
veřejnou část webu, tzn. číst články, komentáře, novinky a volit v anketách.
● Oproti tomu přihlášený uživatel, který je registrován může dostávat novinky mailem a komentovat.
● Další uživatelé mají přístup k administrační části a mohou provádět úkony jako psát a spravovat své příspěvky
a provádět různé změny v aplikaci (třeba změnit vzhed, záhlaví, zápatí) a mají samozřejmě i práva procházet
veřejnou část a dostávat novinky mailem.

Nadefinovali jsme si tedy určité role (guest, registered a administrator) a objekty (news, article,
poll, comments, backend), ke kterým mohou uživatelé s nějakou rolí přistupovat nebo provádět určité akce (
view, vote, comment, feed, edit).

Jednoduše řečeno
● role (role) je vlastnost uživatele, který může přistupovat k objektům/zdrojům
● zdroj (resource) je objekt který je kontrolován
● práva (privilege) jsou akce, které může s objektem provádět uživatel s rolí

Vzájemné vazby kdo co může s čím dělat ale určují až pravidla (rules), která jsou uchovávána právě objektem
Permission. Ten toho umí samozřejmě víc, než jen uchovávat pravidla.

Zároveň je tu ještě jedna vazba, možná ne na první pohled zcela zřejmá, a to dědičnost rolí, která zajistí, že
uživatel s rolí administrátor může dělat i to co obyčejný návštěvník webu.

role unikátní práva rodič


guest view, vote NULL
registered feed, comment guest
administrator edit registered

Poznámka: práva administrátora lze nadefinovat i jako ‚bez omezení‘ tzn. bez rodičů od kterých by dědil nějaká
omezení (viz níže).
Začínáme
Nejprve si ukážeme, jak nadefinovat objektu Permission uživatelské role.
Ještě před tím je ale třeba vytvořit instanci třídy, se kterou budeme pracovat.

$acl = new Permission();

Role (Roles)
Jak již bylo řečeno, budeme postupně vytvářet stromovou strukturu rolí, která dědí oprávnění od svých rodičů.
Použijeme výše zmíněný příklad webové aplikace o třech rolích. Mějmě tedy role guest, registered a
administrator.

$acl->addRole('guest');
$acl->addRole('registered', 'guest');
$acl->addRole('administrator', 'registered');

Docela triviální že? Tímto zajístíme, že se nám vlastnosti přenášejí z rodičovské role na potomky.
Rodiče lze zadat i jako pole s více rolemi.

Za zmínku stojí metoda getRoleParents(), která vrací pole se všemi rodičovskými rolemi a také metoda
roleInheritsFrom(), která zjistí, zda-li od sebe dědí dvě role. Jejich použití:

$acl->roleInheritsFrom('administrator', 'guest'); // TRUE


$acl->getRoleParents('administrator'); // array('guest', 'registered')

Zdroje neboli objekty (Resources)


Nyní je čas nadefinovat i seznam objektů, ke kterým mohou uživatelé s rolemi přistupovat.

$acl->addResource('news');
$acl->addResource('article');
$acl->addResource('poll');
$acl->addResource('comments');
$acl->addResource('backend');

I zdroje/objekty mohou vytvářet stromovou strukturu.


Využítí? Napadá mě možná e-shop, kde by kategorie dědila produkt.
Metody pro manipulaci se zdroji jsou podobné jako s objekty, liší se jen názvy: resourceInheritsFrom(),
removeResource() atd.
Práva a pravidla (Privileges & Rules)
A teď to nejdůležitější. Samotné role a objekty by nám byly k ničemu, musíme mezi nimi vytvořit ještě pomocí
práv/akcí vazby (neboli pravidla).

// host může prohlížet obsah jen veřejné části, hlasovat v anketách


$acl->allow('guest', array('news', 'article', 'poll', 'comments'), 'view');

// předchozí příkaz alternativně ve dvou příkazech: (ale trochu přes koleno)


$acl->allow('guest', NULL, 'view');
$acl->deny('guest', 'backend', 'view');
$acl->allow('guest', 'poll', 'vote');

// registrovaný dědí právo view od hosta, ale má i právo komentovat a dostávat novinky mailem
$acl->allow('registered', 'comments', 'comment');
$acl->allow('registered', 'news', 'feed');

// dědí se i omezení, takže aby měl administrator přístup do administrace,


// který jsme zamezili hostovi a registrovanému, musíme mu to výslovně povolit
// a na víc ke všemu dostane práva view a edit
$acl->allow('administrator', NULL, array('view', 'edit'));
$acl->allow('administrator', 'backend', 'view');

Autorizace
Nyní když máme vytvořený seznam pravidel, můžeme jednoduše provádět autorizaci.

$acl->isAllowed('guest', 'article', 'view') ? "allowed" : "denied"; // allowed


$acl->isAllowed('guest', 'article', 'edit') ? "allowed" : "denied"; // denied
$acl->isAllowed('guest', 'backend', 'view') ? "allowed" : "denied"; // denied
$acl->isAllowed('guest', 'poll', 'vote') ? "allowed" : "denied"; // allowed

// registrovaný dědí od hosta jak práva tak omezení


$acl->isAllowed('registered', 'article', 'view') ? "allowed" : "denied"; // allowed
$acl->isAllowed('registered', 'comments', 'comment') ? "allowed" : "denied"; // allowed
$acl->isAllowed('registered', 'backend', 'view') ? "allowed" : "denied"; // denied

// administrátor nemá nyní žádné omezení


$acl->isAllowed('administrator', 'poll', 'vote') ? "allowed" : "denied"; // allowed
$acl->isAllowed('administrator', 'backend', 'view') ? "allowed" : "denied"; // allowed

Práva administrátora lze nadefinovat i jako ‚bez omezení‘ tzn. bez rodičů od kterých by dědil nějaká omezení.
Vypadalo by to asi takto:

$acl->removeRole('administrator'); // odeberu roli z pravidel


$acl->addRole('administrator'); // vytvořím roli znova, ale bez předků
$acl->allow('administrator');
// nastavím pravidlo: všechna práva a všechny zdroje pro administrátora bez omezení

Můžete si všimnout že kdykoliv za běhu aplikace můžeme i odebrat roli. A nejenom ji, odebírat ze seznamu
pravidel lze objekty: removeResource(), removeAllResources(); ale i samotná pravidla: removeAllow(),
removeDeny(). Máme zde i metody na kontrolu přítomnosti nějaké role nebo objektu: needRole(),
needResource(); které vyhodí výjimku InvalidStateException pokud není zjištěna jejich přítomnost.

Jak již bylo řečeno, role může dědit od jiné role či od více rolí. Co se ale stane pokud má jeden předek akci
zakázanou a druhý povolenou? Jaké budou práva potomka? Určuje se to podle váhy role – poslední uvedená role
v seznamu předků má největší váhu, první uvedená role tu nejmenší. Více názorné je to z příkladu:

$acl = new Permission();


$acl->addResource('backend');
$acl->addRole('admin');
$acl->addRole('guest');

$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');

// případ A: role admin má menší váhu než role guest


$acl->addRole('john', array('admin', 'guest'));
$acl->isAllowed('john', 'backend'); // FALSE

// případ B: role admin má větší váhu než guest


$acl->addRole('mary', array('guest', 'admin'));
$acl->isAllowed('mary', 'backend'); // TRUE

Napojení na Nette\Web\User
Na začátku jsem zmiňoval, že celá třída Nette\Security\Permission velmi úzce navazuje na
Nette\Web\User. Dá se používat zcela bez něj, ale spolu dosáhnou maximální efektivity.

Pro začátek je potřeba zaregistrovat autorizační a autentizační handler:

Environment::getServiceLocator()->addService(new MyAuthenticator, 'Nette\Security\IAuthenticator');


Environment::getServiceLocator()->addService(new Permission, 'Nette\Security\IAuthorizator');

nebo v config.ini

service.Nette-Security-IAuthenticator = MyAuthenticator
service.Nette-Security-IAuthorizator = Permission

Autentizační handler MyAuthenticator může vypadat například takto:

class MyAuthenticator implements IAuthenticator


{
/**
* @param array
* @return IIdentity
* @throws AuthenticationException
*/
function authenticate(array $credentials)
{
// jméno, heslo i role mohou být získány třeba z databáze
$username = 'john';
$password = 'xxx';
$roles = array('admin', 'editor');

if ($credentials['username'] !== $username) {


throw new AuthenticationException('Unknown user', self::IDENTITY_NOT_FOUND);
}

if ($credentials['password'] !== $password) {


throw new AuthenticationException('Password not match', self::INVALID_CREDENTIAL);
}

return new Identity('John Doe', $roles); // zde je důležité právě předání rolí
}

Kdekoliv v aplikaci, nebo v nějakém vašem modelu, komponentě stačí zavolat:

// s využitím třídy Environment


$acl = Environment::getService('Nette\Security\IAuthorizator');
$user = Environment::getUser();

// bez Environment
$acl = new Permission;
$user = new User;

// následně naplňíme autorizační handler pravidly


$acl->addRole('editor');
$acl->addRole('admin');
$acl->addResource('file');
$acl->addResource('jany');
$acl->allow('admin', 'file', 'delete_file');
$acl->allow('editor', 'jany', 'say_hello');
$acl->deny('editor', 'jany', 'sleep_with_jany');

// pokud nepoužíváme služby použijeme k nastavení handlerů metody


$user->setAuthenticationHandler(new MyAuthenticator);
$user->setAuthorizationHandler($acl);

// samotná autorizace
$user->isAllowed('file', 'delete_file'); // TRUE
$user->isAllowed('jany', 'sleep_with_jany'); // FALSE
$user->isAllowed('jany', 'say_hello'); // TRUE

// obecně
$user->isAllowed($resource, $privilege);

Metoda isAllowed() má nyní jen dva parametry, role se sama doplní pomocí metody getRoles() a v cyklu
se projdou všechny role a při prvním kladném vyhodnocení se kladně vyhodnotí i podmínka výše. To vše je již
implementováno v Nette\Web\User.
U proměnné $user můžeme dále používat veškeré metody uvedené v Nette\Web\User.

Viz také:

● Nette\Security\Permission API reference


● Dynamická správa rolí a zdrojů

« Autorizace, ověřování oprávnění Dynamická správa rolí a zdrojů »


Dynamická správa rolí a zdrojů

Ukázková struktura databáze pro


dynamickou správu rolí a zdrojů

V praxi u složitějších aplikací (v aplikacích kde chceme umožňovat právě dynamickou úpravu rolí, zdrojů) si
nejspíše nevystačíme se základním objektem Nette\Security\Permission. V malých aplikacích můžeme bez
problémů „natvrdo“ nadefinovat seznam rolí, zdrojů a pravidel někde v aplikaci, ale dojde-li na nějaké úpravy nebo
rozšíření rolí, budeme muset ručně do aplikace zasahovat.

Nyní si ukážeme implementaci dynamické správy rolí.

U jednoduchých případů lze zaregistrovat jako autorizační handler přímo třídu Nette\Security\Permission. Pro naši
potřebu si ale vytvoříme jejího potomka, třídu Acl, kterou obohatíme o konstruktor, který sestaví všechna pravidla,
zdroje a role.

U příkladu budeme používat databázový layer dibi.

Uvažujeme se strukturou databáze stejnou jako na obrázku. Umožňuje zadat v tabulce acl resource_id
i privilege_id NULL, taková situace znamená, že se pravidlo aplikuje na všechny dostupné resources či privileges.

Následuje kód třídy Acl:

class Acl extends Permission {

public function __construct() {


$model = new AclModel();

foreach($model->getRoles() as $role)
$this->addRole($role->name, $role->parent_name);

foreach($model->getResources() as $resource)
$this->addResource($resource->name);

foreach($model->getRules() as $rule)
$this->{$rule->allowed == 'Y' ? 'allow' : 'deny'}($rule->role, $rule->resource, $rule
->privilege);
}

Pro načítání rolí a zdrojů z databáze použijeme třídu AclModel:

class AclModel extends Object {

const ACL_TABLE = 'users_acl';


const PRIVILEGES_TABLE = 'users_privileges';
const RESOURCES_TABLE = 'users_resources';
const ROLES_TABLE = 'users_roles';

public function getRoles() {


return dibi::fetchAll('SELECT r1.name, r2.name as parent_name
FROM ['. self::ROLES_TABLE . '] r1
LEFT JOIN ['. self::ROLES_TABLE . '] r2 ON (r1.parent_id = r2.id)
');
}

public function getResources() {


return dibi::fetchAll('SELECT name FROM ['. self::RESOURCES_TABLE . '] ');
}

public function getRules() {


return dibi::fetchAll('
SELECT
a.allowed as allowed,
ro.name as role,
re.name as resource,
p.name as privilege
FROM [' . self::ACL_TABLE . '] a
JOIN [' . self::ROLES_TABLE . '] ro ON (a.role_id = ro.id)
LEFT JOIN [' . self::RESOURCES_TABLE . '] re ON (a.resource_id = re.id)
LEFT JOIN [' . self::PRIVILEGES_TABLE . '] p ON (a.privilege_id = p.id)
ORDER BY a.id ASC
');
}
}

Nakonec nesmíme zapomenout zaregistrovat autorizační handler:

Environment::getServiceLocator()->addService('Nette\Security\IAuthorizator', new Acl);

…nebo v config.ini:

service.Nette-Security-IAuthorizator = Acl

Pokud chceme objekt třídy Acl cachovat (nesmíme zapomenout invalidovat cache při každé změně ACL
v databázi):

$cache = Environment::getCache();
if (!isset($cache['acl'])) $cache['acl'] = new Acl();
Environment::getServiceLocator()->addService('Nette\Security\IAuthorizator', $cache['acl']);

Tím jsme si z tabulky vygenerovali objekt, který je potomkem Nette\Security\Permission a umožňuje nám
v aplikaci používat dynamickou správu rolí, a zaregistrovali potřebnou službu.

Viz také:

● Nette\Security\Permission
● Nette\Security\Permission API reference

« Permission AJAX »
Ajax & snippety
AJAXový požadavek lze detekovat metodou třídy (resp. služby) zapouzdřující HTTP požadavek:
Environment::getHttpRequest()->isAjax() (detekuje podle HTTP hlavičky X-Requested-With). Uvnitř
spansenteru je k dispozici „zkratka“ v podobě metody $spansenter->isAjax().

AJAXový požadavek se nijak neliší od klasického požadavku – je zavolán spansenter s určitým view a parametry.
Je také věcí spansenteru, jak bude na něj reagovat: může použít vlastní rutinu, která vrátí nějaký fragment HTML
kódu (HTML snippet), XML dokument, JSON objekt nebo kód v JavaScriptu. K tomu lze využít i předpřipravený
objekt šablony – například tak, že při detekci AJAXu zvolí speciální šablonu:

public function handleClick($param)


{
if ($this->isAjax()) {
$this->template->setFile('...ajax.phtml');
}
...
}

Nicméně daleko silnější nástroj představuje vestavěná podpora AJAXu. Díky ní lze udělat z obyčejné aplikace
AJAXovou prakticky několika řádky kódu.

Myšlenka vychází z toho, že při prvotním (tedy neAJAXovém) požadavku se přenese celá stránka a poté se při
každém již AJAXovém subrequestu (= požadavku na stejný spansenter a view) přenáší pouze kód změněných částí.
K tomu slouží dva mechanismy: invalidace a renderování snippetů.

Invalidace
Každý objekt třídy Control (což je i samotný Presenter) si umí zapamatovat, jestli při subrequestu došlo ke
změnám, které si vyžadují jej překreslit. K tomu slouží triptych metod invalidateControl(),
validateControl() a isControlInvalid(). Příklad:

public function handleLogin($user)


{
// po přihlášení uživatele se musí objekt překreslit
$this->invalidateControl();
...
}

Nette však nabízí ještě jemnější rozlišení, než na úrovni komponent. Uvedené metody mohou totiž jako parametr
nabývat název tzv. „snippetu“, nebo-li ústřižku. Lze tedy invalidovat/validovat na úrovni těchto snippetů (každý
objekt může mít libovolné množství snippetů). Pokud se invaliduje celá komponenta, tak je i každý snippet
považován za invalidní. Komponenta je invalidní i tehdy, pokud je invalidní některá její subkomponenta.

echo $this->isControlInvalid(); // -> FALSE


$this->invalidateControl('header'); // invaliduje snippet 'header'
echo $this->isControlInvalid('header'); // -> TRUE
echo $this->isControlInvalid('footer'); // -> FALSE
echo $this->isControlInvalid(); // -> TRUE, alespoň jeden snippet je invalid

$this->invalidateControl(); // invaliduje celou komponentu, každý snippet


echo $this->isControlInvalid('footer'); // -> TRUE

Komponenta, která přijímá signál, je automaticky označena za invalidní.

Díky invalidaci snippetů přesně víme, které části kterých prvků bude potřeba překreslit.

Renderování snippetů
Nette je založeno na myšlence logických, nikoliv grafických prvků, tj. objekt třídy Control nepředstavuje
pravoúhlou oblast ve stránce, ale logickou komponentu, která se může renderovat i do více podob (např. DataGrid
může mít jednu metodu pro vykreslení mřížky a druhou pro vykreslení „stránkovadla“ apod). Každý prvek může být
navíc na stránce vykreslen vícekrát, nebo podmíněně, nebo pokaždé s jinou šablonou atd.

Není tedy možné jednoduše zavolat metodu render na každém invalidním objektu, vlastně ani žádný interface
metodu render nedefinuje. K vykreslování je nutné přistupovat tak, jako když se kreslí celá stránka. (Přesněji
řečeno, je možné jít touto cestou a vykreslit pouze invalidní snippety invalidních objektů, ale vaše aplikace musí
být k tomu účelu vhodně navržena, kdežto vestavěná podpora AJAXu musí fungovat obecně).

Vykreslování stránky probíhá velmi podobně, jako u neAJAXového požadavku, načtou se tytéž šablony atd.
Klíčovým úkolem však je vypustit ty části, které se na výstup vůbec dostat nemají, a ty, které se vykreslit mají,
přidružit s identifikátorem a poslat klientovi ve formátu, kterému bude obslužný JavaScript rozumět.

Pokud se uvnitř šablony nachází control nebo snippet, musíme jej obalit párovou značkou {snippet} ...
{/snippet} – ty totiž zajistí, že se vykreslený snippet vystřihne a předá AJAXovému ovladači. Také jej obalí
pomocnou značkou <div> (lze použít i jinou značku). V uvedeném příkladě je snippet pojmenován jako header a
může představovat i například šablonu controlu:

{snippet header}
<h1>Hello .... </h1>
{/snippet}

Při využití této techniky je nutné si dát pozor na vkládané šablony pomocí {include $template}, které
obsahují nějaké snippety, aby nebyly přeskočeny. Tomu lze zabránit přidáním zavináče před složené závorky, tedy
použitím @{include $template}, který nechá na snippetu rozhodnutí, zda-li se má překreslit (rozhodne se
podle proměnné SnippetHelper::$outputAllowed) a vkládanou šablonu správně obalí podmínkami. Šablona,
do které vkládáme jinou šablonu obsahující snippet by vypadala například takto:

<html>
<body>
@{include $template}
</body>
</html>
Více informací k zavináčové magii.

Komunikace s prohlížečem
Jednotlivé snippets společně s dalšími (i uživatelskými daty) se předají do úložište, které je dostupné přes
$spansenter->payload. Presenter předává data ve formátu JSON.

Jak to celé funguje pohromadě demostruje příklad Fifteen, jehož kód najdete v distribuci.

« Dynamická správa rolí a zdrojů Konfigurace prostředí »


Nette\Environment

Propojení s Nette\Config
Třída Config má na starosti načtení konfigurace prostředí složené z kombinací nastaveních, služeb, proměnných a
konstant. Doporučuje se používat konfiguraci uloženou ve formátech ini a xml, jelikož parsery pro tyto soubory
jsou nativně podporovány v PHP a jsou velmi rychlé. Dokonce tak rychlé, že naparsovanou konfiguraci se nevyplatí
ani kešovat.

Příklad config.ini souboru:

[common] php.date.timezone = "Europe/Prague" php.iconv.internal_encoding = "%encoding%"


php.mbstring.internal_encoding = "%encoding%" php.include_path = "%appDir%;%libsDir%"
variable.tempDir = %appDir%/cache variable.foo = %bar% world variable.bar = hello
const.PROJECT = eshop [production < common] database.driver = sqlite database.file =
"%modelsDir%/demo.db" database.lazy = TRUE service.Nette-Security-IAuthenticator =
Users [development < production] database.profiler = TRUE

Příklad config.ini souboru s rozšířenou syntaxí:

V názvu sekce lze použít tečky pro vytvoření podsekcí.

[production.database.params] host = db.example.com username = dbuser password = secret

Dědění podsekcí.

[production.test < production.database] host = localhost

V rámci sekce pak lze vypnout dělení klíčů pomocí tečky – za název sekce se přidá vykřičník – tečky jsou poté
brány jako součást názvu direktivy.

[common.ip!] 127.0.0.1 = localhost 192.168.1.1 = router

Následující blok kódu demonstruje, jak lze s konfiguračními soubory pracovat:

// načtení a kontrolní vypsání konfigurace


$config = Config::fromFile('config.ini');
Debug::dump((array) $config);

// současnou konfiguraci můžeme i ukládat,


// do souboru se přidá poznámka, že byl vygenerován
$config->save('config_generated.ini', 'production');

// Config::fromFile() pouze načte nastavení ze souboru a uchová jej do objektu,


// oproti tomu Environment::loadConfig() jej načte a aplikuje
Environment::loadConfig('config.ini');
// nebo ekvivalentně:
Config::loadConfig($config);

// změny v prostředí PHP můžeme zkontrolovat


phpinfo();
if (defined('PROJECT')) echo PROJECT;

Poznámka k set.include_path: V linuxových systémech se používá jiný oddělovač cest než na Windows
systémech. Nette\Config řeší tento problém použitím univerzálního oddělovače cest v konfiguračním souboru
(středník), pak při běhu nahradí tento oddělovač za oddělovač cest konkrétní platformy, tudíž nedochází k žádným
nekompatibilitám.

Název prostředí
Prostředí je zjednodušeně název počítače, na kterém aplikace právě běží. Může to být jeden z počítačů, kde
probíhá vývoj, může to být produkční server. Každé prostředí má jiné parametry (cesty k adresářům, připojení
k databázi, …), mohu si je pojmenovat a podle názvu prostředí načíst kupříkladu konfiguraci:

Environment::setName('mujpocitac');
...
Environment::loadConfig(); // načte z config.ini sekci [mujpocitac]

Název prostředí je libovolný řetězec, na kterém žádná logika v Nette nestojí.

V případě většího množství prostředí není nutné přepisovat všechna společná nastavení do každého prostředí
zvlášť. Následující kód (zkopírovaný z ukázkového souboru na začátku této stránky) zajistí, že i v prostředí
production se použijí nastavení z prostředí common

[production < common]

Režimy prostředí
Režim neboli mód je indikátor, určující nějaký parametr daného prostředí. Módy lze nastavovat buď přes config.ini,
nebo přímo voláním Environment::setMode('mujmod', $bool), zjišťovat stav lze přes
Environment::getMode('mujmod').

Autodetekce
Třída Nette\Environment disponuje vestavěnou autodetekcí pro režimy production, debug, console a pro
název prostředí (a jako téměř vše v Nette ji lze rozšířit nebo přepsat). Asi nejdůležitější mód production určuje,
jestli aplikace běží na ostrém (produkčním) serveru nebo ne. Proto také existuje zkratka, místo
Environment::getMode('production') lze volat výstižnější Environment::isProduction(). Pro režim
console existuje obdobná zkratky Environment::isConsole().

Autodetekce pracuje na principu zjištění IP adresy serveru ( ze $_SERVER['SERVER_ADDR'] ) a v případě shody


s některou z klasických IP adres používaných v intranetu se režim nastaví na debug. Manuální nastavení režimu
v konfiguračním souboru má vyšší prioritu než autodetekce, a na zkušebním serveru (běžícím například na
localhostu tj. na 127.0.0.1) ho lze vyvolat pomocí příkazu
mode.{režim} = TRUE

tj. například produkční mód se aktivuje přes

mode.production = TRUE

Autodetekce názvu prostředí úzce souvisí s detekcí režimů production & console, protože právě na základě
nich se název zvolí z variant Environment::DEVELOPMENT, Environment::PRODUCTION nebo
Environment::CONSOLE.

Příklad
…a přímo ze života: mám jednu aplikaci, která běží v pěti různých prostředích:

1. 2× na lokálním serveru, kde probíhá vývoj (můj počítač + virtuální testovací stroj)
2. 1× na serveru tojeono.cz (jako texy.info)
3. 2× na serveru hostmonster.com (jako nette.org a dibiphp.com)

Každé prostředí může mít jiný název. Prostředí 2) a 3) budou mít nejspíš vždy aktivní režim production.
V prostředí 1) budu vyvíjet nejčastěji v „neživém“ režimu, ale před nahráním na server si mohu mód production
ručně aktivovat a ověřit, jestli všechno funguje v pořádku. Mezi názvy prostředí a režimy tedy není žádná přímá
souvislost, krom autodetekce.

Viz také:

● Nette\Config API reference


● Nette\Configurator API reference
● Nette\Environment API reference
● Nette\ServiceLocator API reference

Vylepšení zápisu phpdoc direktiv (rev. 481) – changelog

« AJAX Komponenty »

You might also like