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'); // 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.

201012021200_prirucka-programatora

Embed Size (px)

Citation preview

Page 1: 201012021200_prirucka-programatora

Nette\ObjectNette\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 PHPnic takového neexistuje. Jde o třídu dostatečně transparentní, neměla by způsobovat žádné kolize, proto ji můžetejako základní třídu pro své třídy používat i vy.

class MyClass extends Nette\Object{

}

ReflexeZá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í chybPHP reaguje na přístup k nedeklarovaným členům značně nekonzistentně:

$obj->undeclared = 1; // projde bez hlášeníecho $obj->undeclared2; // generuje NoticeMyClass::$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ýjimkuMemberAccessException.

Gettery a setteryTermí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řístupk ní máme plně pod kontrolou. Můžeme tak validovat vstupy nebo generovat výstupy až v případě potřeby.

Page 2: 201012021200_prirucka-programatora

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á seona – 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; }

Page 3: 201012021200_prirucka-programatora

}

$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 propertiesnemusí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álostiPokud 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

Page 4: 201012021200_prirucka-programatora

Rozšiřovací metodyPokud 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 »

Page 5: 201012021200_prirucka-programatora

Nette\ComponentJá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 prosamotný 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 neborozhraní. Například $component->lookup('Nette\Application\Presenter') vrací spansenter, pokud jek 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 komponentyvůči spansenteru.

Monitorování změnJak 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ímmetody 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 tennemusí 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)

Page 6: 201012021200_prirucka-programatora

{ $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é promaximální výkon.

Viz také:

Nette\Component API reference●

Nette\ComponentContainer●

Nette\ComponentContainer API reference●

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

Page 7: 201012021200_prirucka-programatora

Nette\Application\ControlControl 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é sivyžadují jej překreslit. K tomu slouží triptych metod invalidateControl(), validateControl() aisControlInvalid(), 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, žeje 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 jeinvalidní i tehdy, pokud je invalidní některá její subkomponenta. Komponenta, která přijímá signál, je automatickyoznač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, alelogickou komponentu, která se může renderovat i do více podob. Každá komponenta může být navíc na stráncevykreslena 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 slovasmyslu. Jen metody attached() a detached() umožňují detekovat, kdy byl Control připojen k nebo odpojenod rodiče (spansenteru či jiné komponenty).

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

ŠablonyTří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,

Page 8: 201012021200_prirucka-programatora

* 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 obsahovathlavičku popisující proměnné, které v ní lze využívat.

Strom komponentKaž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 nebojaký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. Podlestruktury 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 uvedemev parametru konstruktoru instanci jejího rodiče a řetězec se jménem komponenty. Uvedením stejné komponentypod 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ávyVizuá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:

Page 9: 201012021200_prirucka-programatora

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í, budei 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 zasebou 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ávamiTento způsob vede k lepší znovupoužitelnosti komponenty, na událost si její vlastník reaguje zcela po svém.

class Komponenta extends Control{

Page 10: 201012021200_prirucka-programatora

/** @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 »

Page 11: 201012021200_prirucka-programatora

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

Zachytávání chyb a výjimekZachytá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 nae-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 sexypodobu:

Page 12: 201012021200_prirucka-programatora

Nezachycená výjimka v provedení Nette\Debug

Takto vypadá výjimka, takto vypadá vygenerovaná chyba. To je pak jiné ladění, co? Takto zobrazeny jsouautomaticky všechny fatální chyby. Pro ještě hlubší odvšivování lze zapnout striktnější mód. Pak budou stejnýmzpů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 dumpKaž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()

Page 13: 201012021200_prirucka-programatora

$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í časuDalší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ódDebug::timer('rss-generating');

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

ProfilerProfiler 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:

Page 14: 201012021200_prirucka-programatora

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 »

Page 15: 201012021200_prirucka-programatora

Logování chybVypisová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 tomunáhodou došlo, má integrovaný systém pro skrytí citlivých políček, např. hesel). Na produkčním serveru je všakmož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', '[email protected]');

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).●

AutodetekceLadě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. Pokudnejsme ve vývojovém režimu, zvolí logování do souboru %logDir%/php_error.log tudíž v Nette\Environmentmusí být nastavena proměnná prostředí logDir nebo appDir. (nicméně Nette\Debug lze používat stálesamostatně, 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 budezasí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étodetekci 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' => '[email protected]', 'To' => '[email protected]', '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);

Page 16: 201012021200_prirucka-programatora

Jako %message% se doplní případná zpráva vyjímky.

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

Page 17: 201012021200_prirucka-programatora

FirebugKomunikace 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éž jemož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í.

je vyžadován Firefox verze 2 nebo 31.stáhněte si rozšíření Firebug2.stáhněte si rozšíření FirePHP (minimálně ve verzi 0.2)3.zapněte si FirePHP v FirePHP menu a aktivujte Firebug Net panel4.

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 FirebuguDebug::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);}

Page 18: 201012021200_prirucka-programatora

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é:

Page 19: 201012021200_prirucka-programatora

FirePHP●

« Logování chyb Sessions »

Page 20: 201012021200_prirucka-programatora

Nette\Web\SessionU 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ží jakoklíč k session datům. Narozdíl od cookies, které se uchovávají na straně prohlížeče, jsou data v session uchovávánana straně serveru.

V Nette ke správě session slouží třída Nette\Web\Session a manipulaci s daty zajišťujeNette\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á $countif (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 prostoruProměnné se používají jako obyčejné proměnné objektu:

Page 21: 201012021200_prirucka-programatora

$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í expiracePromě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:

Page 22: 201012021200_prirucka-programatora

$namespace->remove();

Práce se jmennými prostoryVypsá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 tonejlé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

Page 23: 201012021200_prirucka-programatora

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

Platnost autentizace na subdoménáchPlatnost 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řesEnvironment::getSession()->isStarted(). Pozdější volání nemá efekt. Sezení se otevírá například připřístupu ke jmennému prostoru (třeba voláním $namespace = Environment::getSession('myapp'), nebopouží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ímpř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 apři zobrazení informační hlášky kontrolujeme existenci této proměnné a na základě toho se rozhodneme, zda-lihlášku ještě zobrazit/nezobrazit

Viz také:

Nette\Web\Session API reference●

Nette\Web\SessionNamespace API reference●

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

Page 24: 201012021200_prirucka-programatora

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

Příklad použití:

$mail = new Mail;$mail->setFrom('Franta <[email protected]>');// nebo $mail->setFrom('[email protected]', 'Franta');$mail->addTo('[email protected]');$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 <[email protected]>')->addTo('[email protected]')->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í lzevypnout 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 <[email protected]>');$mail->addTo('[email protected]');$mail->setHtmlBody($template); // nebo $mail->setBody($template) pro textovou šablonu$mail->send();

Page 25: 201012021200_prirucka-programatora

Do šablony bude automaticky vložená proměnná $mail, je tedy možné přímo v šabloně nastavit další hlavičkyemailu.

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. variantaMail::$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ů »

Page 26: 201012021200_prirucka-programatora

Nette\ImageTřída Nette\Image je určena pro základní manipulaci s obrázky. Zjednodušujenejč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(); // šířkaecho $image->getHeight(); // výška

Změna velikostiObrá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);

Page 27: 201012021200_prirucka-programatora

$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');

Page 28: 201012021200_prirucka-programatora

$image->place($watermark, '50%', '75%', 30);

Uložení obrázkuObrá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ší funkceNette\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é:

Page 29: 201012021200_prirucka-programatora

Nette\Image API reference●

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

Page 30: 201012021200_prirucka-programatora

Nette\CachingKnihovna 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 cacheunset($cache['data']);// nebo$cache['data'] = NULL;

// ověření, zda je položka v kešiif (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 proukládání obsahu funkci $cache->save($key, $data, $options). Parametr $options je pole, které můžemít tyto klíče:

expire => (int) čas, kdy obsah vyexpiruje sliding => (bool) má se expirace prodlužovat? files => (array) seznamsouborů, 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í

Page 31: 201012021200_prirucka-programatora

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 jetřeba používat absolutní cesty). V praxi to používám třeba pro kešování konfigurace nebo šablon – jakmile sepří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 vyexpirovatvš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'),));

Page 32: 201012021200_prirucka-programatora

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í tytozdroje 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 bymohlo být systémově pomalé. Jako řešení se nabízí implementace na databázi, nebo také při kešování nastavovatprioritu.

$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ůsobzpracování keše v úložišti, můžete využít připraveného objektu DummyStorage, což je úložiště, které veskuteč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 »

Page 33: 201012021200_prirucka-programatora

Nette\Web\HttpRequestTří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;

MetodygetMethod() 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 prioritykterou 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

Page 34: 201012021200_prirucka-programatora

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

Filtrování URI adresFiltrová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\UriTří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 jefunkce `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; // httpecho $uri->authority; // nette.orgecho $uri->getHostUri(); // http://nette.org

echo $uri->path; // /cs/dokumentaceecho $uri->query; // action=historyecho $uri->fragment; // footer

Jsou podporovány i jiná schémata než http, např. file, ftp nebo https.

Page 35: 201012021200_prirucka-programatora

Propojení třídy Uri s třídou HttpRequestNette\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í NetteEnvironment::getHttpRequest()->uri->absoluteUri;

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

Viz také:

Nette\Web\HttpRequest API reference●

« Kešování response »

Page 36: 201012021200_prirucka-programatora

Nette\Web\HttpResponseTřída zapouzdřuje a sjednocuje funkce pro obsluhu HTTP odpovědi serveru. Umožňujezměnit stavový kód odpovědi, určit typ obsahu a jeho kódování, měnit HTTP hlavičky aspravovat 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ědiKó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 HodnotaS200_OK 200S204_NO_CONTENT 204S300_MULTIPLE_CHOICES 300S301_MOVED_PERMANENTLY 301S302_FOUND 302S303_SEE_OTHER 303S303_POST_GET 303S304_NOT_MODIFIED 304S307_TEMPORARY_REDIRECT 307S400_BAD_REQUEST 400S401_UNAUTHORIZED 401S403_FORBIDDEN 403S404_NOT_FOUND 404S410_GONE 410S500_INTERNAL_SERVER_ERROR 500S501_NOT_IMPLEMENTED 501S503_SERVICE_UNAVAILABLE 503

Page 37: 201012021200_prirucka-programatora

Změna typu obsahuMetoda setContentType mění hlavičku Content-type:

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

Nastavení expirace dokumentuPro 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čekPro 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ší metodyPokud 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.

CookiesPro 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');

Page 38: 201012021200_prirucka-programatora

Tyto dvě metody přijimájí ještě další parametry: $path (podadresář kde bude cookie dostupná), $domain a$secure.

KomspanseChcete-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 »

Page 39: 201012021200_prirucka-programatora

Nette\LoadersProgramový 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ůlipř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ýmzpů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 jednakodstranilo obě slabá místa, zároveň co nejvíce zjednodušilo programátorovi život a hlavně nekladlo nová omezeníči pravidla.

Nette\Loaders\RobotLoaderZá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 seautomaticky 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●

Page 40: 201012021200_prirucka-programatora

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 RobotLoaderuRoborLoader 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 tojde v konfiguračním souboru config.ini. Například takto:

service.Nette-Loaders-RobotLoader.factory = Nette\Configurator::createRobotLoaderservice.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 »

Page 41: 201012021200_prirucka-programatora

AnotaceAnotace 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ůlizrychlení scriptu „mažou“ komentáře.

Ukázkový kódToto 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.

Page 42: 201012021200_prirucka-programatora

@secured1.@author John Doe2.@privilege(Bar, bar)3.

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í tomuvž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časjsou případy, kdy potřebujete tuto anotaci uvést s hodnotou false.

Práce s anotacemi

Ověření existence anotaceZda 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 torenderable).

$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í anotaceData anotace získáme zavoláním metody Nette\Reflection\ClassReflection::getAnnotation() kde jako parametr

Page 43: 201012021200_prirucka-programatora

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) "TomasMarny" } "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í siukáž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.

Page 44: 201012021200_prirucka-programatora

ReflexeNette\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 reflexeRozliš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í reflexeReflexi 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');

Page 45: 201012021200_prirucka-programatora

Usnadnění práce s ReflexemiTří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‎ »

Page 46: 201012021200_prirucka-programatora

Nette\IO\SafeStreamCo 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 nikdynastat. 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íliprová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říduNette\IO\SafeStream, která právě toto zajistí.

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

// zaregistrujeme protokolSafeStream::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 smazatunlink('safe://test.txt');

Page 47: 201012021200_prirucka-programatora

// a vůbec používat všechny známé funkcefile_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ápisOtevř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 souboruSoubor 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 souboruNení 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 appendProtož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í souboruSoubor 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●

Page 48: 201012021200_prirucka-programatora

« reflexe FTP »

Page 49: 201012021200_prirucka-programatora

Nette\FormsTřídy Nette\Forms usnadňují vytváření a zpracování webových formulářů ve vašichaplikacích.

Třída Nette\Forms\Form je určena pro samostatné použití mimo aplikaci Nette. Pokud chcetepouží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 oblastimá 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émtextovém políčku atd.

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

Vytvoření formulářeZač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');

Page 50: 201012021200_prirucka-programatora

$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ářeExistují 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 lzepouží 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 metodamigetControlPrototype() 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í pravidlaMetody 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')

Page 51: 201012021200_prirucka-programatora

->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ýmJavaScriptový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í metodygetControlPrototype() 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

Page 52: 201012021200_prirucka-programatora

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éhosémantického významu. Význam jí tedy dodá až například renderovací rutina, která prvky vykreslí seskupené doelementů 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ářeFormulář 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 objekts 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:');

Page 53: 201012021200_prirucka-programatora

$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ůžebý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ůlichybě v Internet Exploreru, bude Nette za odesílací tlačítko považovat první tlačítko formuláře. Tudíž obsluha přesudá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

Page 54: 201012021200_prirucka-programatora

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 taknechtě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 kontrolujetoken, 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íceoknech najednou (v rámci jedné session). Platnost je však možné zkrátit na počet sekund, které se uvedou jakodruhý 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 »

Page 55: 201012021200_prirucka-programatora
Page 56: 201012021200_prirucka-programatora

Nette\Web\FtpPří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●

Page 57: 201012021200_prirucka-programatora

« Atomické operace‎ Pro řetězce »

Page 58: 201012021200_prirucka-programatora

Nette\StringNette\String je statická třída s užitečnými funkcemi pro práci s řetězci. Je navrženaspeciálně pro práci s řetězci v kódování UTF-8.

String::checkEncodingZjistí, 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::fixEncodingVrací 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::startsWithVrací 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";

Page 59: 201012021200_prirucka-programatora

String::startsWith($haystack, $needle); // true

String::endsWithVrací 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::normalizeOdstraní z textu pravostranné mezery a sjednotí oddělovače řádků.

public static string normalize (string $s)

Příklad:

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

String::webalizeUpraví ř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.

Page 60: 201012021200_prirucka-programatora

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::truncateOří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ýrazyTří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.

Page 61: 201012021200_prirucka-programatora

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_EMPTYnebo 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 jemožné uvést PREG_OFFSET_CAPTURE nebo PREG_PATTERN_ORDER, viz dokumentace PHP. (Narozdíl od funkcespang_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ímvý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]); });

Page 62: 201012021200_prirucka-programatora

// 'invrP, yhurd,itert'

Viz také:

Nette\String API reference●

« FTP HTML elementy »

Page 63: 201012021200_prirucka-programatora

Nette\Web\HtmlNette\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 NetteFrameworku 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í atributuecho $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';

Page 64: 201012021200_prirucka-programatora

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::elAtributy 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ímbude text, tak nastavíte vnitřní text elementu.

$strong = Html::el("strong", "Barnes & Noble");$input = Html::el("input", array("type" => "text"));

Výpis elementuNejjednodušší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.

Page 65: 201012021200_prirucka-programatora

$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

Page 66: 201012021200_prirucka-programatora

Nette\Templates\TemplateTří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 spansenterechNejvhodnějším způsobem, jak zaregistrovat filtr v spansenterech (resp. v Controlech), je přepsání metody

templatePrepareFilters.

Page 67: 201012021200_prirucka-programatora

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.

HelperyDo š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šíhozápisu pomocí Latte filteru.

HelperLoaderManuální registraci velkého množství helperů lze nahradit registrací jednoho či více HelperLoaderů. Jako

Page 68: 201012021200_prirucka-programatora

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ě, žehelper 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 »

Page 69: 201012021200_prirucka-programatora

Latte filterLatte 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!

RegistraceVe 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í makraZá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.

{link ...} Odkaz nad komponentou (v šabloně spansenteru ekvivalentní s makremplink)

{if ?} ... {elseif ?} ... {/if} <?php if (?): ?> ... <?php elseif (?): ?> ... <?phpendif ?>{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ýchVš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.

Page 70: 201012021200_prirucka-programatora

<h1>{$title}</h1> {* Escapovaný titulek *}{!$content} {* Neescapovaný obsah stránky *}

Přizpůsobení escapování podle lokálního typu obsahuVelmi 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é zcelanativně 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řigenerová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řitvorbě odkazu vynechat *}{plink Products:detail, $productId}

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

Page 71: 201012021200_prirucka-programatora

{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 cyklusForeach 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é údajeo 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()}

Page 72: 201012021200_prirucka-programatora

</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ýchNě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 tatoznač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.

Page 73: 201012021200_prirucka-programatora

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

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

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

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

<ul>{foreach $items as $item}<li>{$item}</li>{/foreach}</ul>

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

<p>{if $strong}<strong>{/if}...{if$strong}</strong>{/if}</p>

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

<ul>{foreach $items as $item}<li>A {$item}</li><li>B {$item}</li>{/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>

Page 74: 201012021200_prirucka-programatora

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?Pokročilá makra1.Dědičnost2.Snippety3.Vlastní makra4.

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

Page 75: 201012021200_prirucka-programatora

Pokročilá makraZá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();

Page 76: 201012021200_prirucka-programatora

// ...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 HtmlFiltr 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}>

Page 77: 201012021200_prirucka-programatora

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í implementaciSnippetHelper, 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ýchsouborů.

Dále je podporováno vnořování značek {cache}. Části šablony se pak invalidují, když se invaliduje kterákolivvnoř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 jekeš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 jev tom, že při generování odkazu se detekuje, jestli odkaz míří na aktuální stránku. Výsledek pak vrátí

Page 78: 201012021200_prirucka-programatora

$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čkamihttp://forum.nette.org/…agie-v-praxi

Grid rendering a pravidelné operace při iteracihttp://forum.nette.org/…id-rendering

Viz také:

Nette\Templates\TemplateFilters API reference●

Nette\Templates\LatteFilter API reference●

« helperů Dědičnost »

Page 79: 201012021200_prirucka-programatora

Dědičnost šablonDě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.

BlokyBloky slouží pro označení části šablony. Mohou být buď anonymní nebo pojmenované (ty se používají

především).

DefiniceK 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řideklaraci bloku vynechat.{/block}

Kromě makra {block} lze pro definování pojmenovaného bloku ve specifických situacích využít také atributyn: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ětekvivalentní:

<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ženautomaticky 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říkladyDovnitř tagu <title> bude vložen obsah tagu <h1>.

...<title>{include #title} | Example.com</title> <!-- Vypíše "Registrace | Example.com"

Page 80: 201012021200_prirucka-programatora

-->...<h1 n:inner-block="title">Registrace</h1> <!-- Definován blok #title. Vyjmut z šablonya následně automaticky vložen. -->...

Platnost proměnnýchPř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 napří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.

Page 81: 201012021200_prirucka-programatora

Dědičnost

ZákladyZač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 astrá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>

Page 82: 201012021200_prirucka-programatora

Pomocí makra {layout} (nebo jeho aliasu – {extends}) označíme, která šablona bude sloužit jako layout, apomocí 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 layoutuNě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žeexistuje).

Načtení přepsaného blokuUvnitř 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>

Page 83: 201012021200_prirucka-programatora

<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ý layoutLayout šablony může sám používat další layout. Toho lze využít pro tvorbu komplikovanějších layoutů.

Shrnutí

SyntaxeDefinice 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}

PravidlaPokud 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 »

Page 84: 201012021200_prirucka-programatora

Vlastní makraTří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

Page 85: 201012021200_prirucka-programatora

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

LatteLatte 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, aletaké 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.

NetteLinksPř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');

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

Registrace:

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

Page 86: 201012021200_prirucka-programatora

RemovePhpOdstraní ze šablony veškerý PHP kód.

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

Hello <?php nebezpecnaFunkce() ?> World

Registrace:

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

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

Ukázka použití v šabloně<texy>Text **tučně**, [odkaz | example.com] atd.</texy>

RegistraceAby 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

Page 87: 201012021200_prirucka-programatora

$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ů »

Page 88: 201012021200_prirucka-programatora

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ísmenkaupper String::upper() Převede text na velká písmenka

capitalize String::capitalize() Převede text na malá písmenka, přičemž první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é datumbytes TemplateHelpers::bytes() Lidsky přívětivé vyjádření velikost v bajtechtruncate 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ězcestrip TemplateHelpers::strip() Odstraní bílé znaky (mezery)stripTags strip_tags Odstraní HTML tagynl2br nl2br Zamění odřádkování za <br />translate Přeloží text do jiného jazykureplace 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 znakyescapeUrl 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.

TruncateOří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… -->

Page 89: 201012021200_prirucka-programatora

{$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.

BytesPř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 -->

DateVlastní 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 instancitří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ě? »

Page 90: 201012021200_prirucka-programatora

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ř. dosložky libs/Nette. Dále si připravte adresář pro dočasné soubory (například temp) a ujistěte se (hlavně naserveru), ž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 frameworkrequire_once dirname(__FILE__) . '/libs/Nette/loader.php';

// volitené, pro šikovnější ladění aplikaceDebug::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>

Page 91: 201012021200_prirucka-programatora

{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 prozarovná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}.

Page 92: 201012021200_prirucka-programatora

Nette\ApplicationZastřešuje chování Model View Presenteru.

Životní cyklus aplikaceŽivotní cyklus aplikace se dá rozdělit do těchto bodů:

Router z URL vytvoří objekt PresenterRequest (obsahuje jméno spansenteru);1.PresenterLoader ze jména spansenteru odvodí třídu a případně název souboru;2.Presenter volá metody podle aktuálního action & view (případně i subrequestu);3.Presenter načítá šablony, ve hře je název spansenteru a view;4.Renderování: v tomto a předchozím bodě se obvykle vytváří odkazy na jiné spansentery a jejich action & view,5.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í lzebuď úpravou pole $styles, nebo přímo v definici routy použitím modifikátoru: <spansenter #mymod>. Filtry pakpopisuje 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 naimplementovatvlastní router. Jediný požadavek je implementace rozhraní IRouter.

PresenterLoaderVý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 nebonahrazením loaderu za svůj.

Page 93: 201012021200_prirucka-programatora

Vyvolání metod podle aktuálního action & viewPresenter 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í šablonPresenter 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 číslav 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/[email protected]

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. Abysprávně fungovalo vynechávání defaultních hodnot, je potřeba i defaultní hodnoty zapsat správně.

UdálostiJde 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

Page 94: 201012021200_prirucka-programatora

$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 aonError.

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ímonShutdown 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 objektPresenterRequest, který je aplikací zpracovávánonError 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 »

Page 95: 201012021200_prirucka-programatora

Nette\Application\PresenterReaguje 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

Životní cyklus spansenteru je rozdělen do několikačástí představovaných voláním volitelně existujícíchmetod. Jde o action{Action}, handle{Signal} arender{View}. Každá metoda se hodí na něco jiného.Ty které mají společné znaky řadíme do společnýchfází životního cyklu.

Charakteristika fází:výkonná (execution)1.změny vnitřních stavů (interaction)2.vykreslovací (rendering)3.ukončení činnosti (shutdown)4.

Následující obrázek ilustruje, jak jsou postupněvykonávány metody spansenteru v jeho životním cyklua 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

Popis jednotlivých metod

Fáze výkonná (execution)

startup je vyvolána na začátku životního cyklu spansenteru. Může obsahovat například zajištění připojení1.k databázi. Během životního cyklu aplikace se může spustit více spansenterů, metoda startup() se můževolat vícekrát.action{Action} by měla obsahovat vykonání operací, po kterých může následovat přesměrování. Zde probíhá2.například automatické přesměrování na jinou jazykovou verzi (např. podle detekce z prohlížeče). Také zde můžebý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

Page 96: 201012021200_prirucka-programatora

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, taki 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íchparametrů.Př.: pokud zde nastavím perzistentnímu parametru $lang hodnotu 'cs', pak se i tato hodnota zohlední v novémpož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)

handle{Signal} : zpracování signálů neboli subrequestů. Určeno pro uživatelskou interakci a zpracování1.AJAXových požadavků.

Fáze vykreslovací (rendering)

beforeRender může obsahovat například společné nastavení filtrů pro všechny vykreslovače a nastavení1.společných proměnných pro šablony všech vykreslovačů.render{View} má na starosti vykreslení a věci s tím spojené (tvorba odkazů v šablonách, přiřazení2.proměnných do konkrétních šablon, …).

Fyzické vykreslení šablonyulož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ích1.proměnných.vykreslení šablony na výstup2.

Ukončení činnosti (shutdown)

shutdown je vyvolána při ukončení životního cyklu spansenteru. Zde můžeme ukončit databázové připojení,1.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ě navrhovatje řečeno v článku Návrh struktury spansenters/views. V takovýchto strukturách nelze abstraktní spansenter kvůlibezpeč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 jevhodné 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

Page 97: 201012021200_prirucka-programatora

Nette\Application\RenderResponse. Více informací ohledně této změny naleznete na fóru .

Signál aneb subrequestSigná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čenvykř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ánAJAXem) a vyvolá metodu signalReceived($signal), jejíž výchozí implementace ve třídě PresenterComponentse 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, ak argumentům se podle jména dosadí parametry z URL a pokusí se danou metodu zavolat. Např. jako prametr $idse 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řipř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, bloks 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 $destinationpředáme řetězec {signal}! a jako $args pole argumentů, které chceme signálu předat. Signál se vždy volá naaktuální view s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává hned na začátkuparametr ?do, který určuje signál.

Jeho formát je buď {signal}, nebo {signalReceiver}-{signal}. {signalReceiver} je názevkomponenty v spansenteru. Proto nemůže být v názvu komponenty pomlčka – používá se k oddělení názvukomponenty 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 jakopříjemce signálu (pokud není určen příjemce signálu, je to spansenter samotný) a pošle jí signál.

Page 98: 201012021200_prirucka-programatora

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. requestRozdí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/[email protected].●

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ázevlayoutu. Např. setLayout('extra') bude místo souboru [email protected] hledat [email protected].

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žkya není potřeba psát žádné (prázdné) metody. A naopak, view jsou na šablonách nezávislé, můžeme je zpracovatdřív, než na kreslení šablony dojde. Detailnější popis k šablonám lze nalézt v Nette\Templates.

Obyčejné a perzistentní parametryObyč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íkvšech parametrů je uložen v poli $params každé komponenty, dá se k nim přistupovat také metodougetParam(...).

class SomePresenter extends Presenter{ /** @persistent */ public $persistentParam = 0;

Page 99: 201012021200_prirucka-programatora

...

public function handleExpand($param1, $param2, $param3) { ... }

...}

Výše uvedená syntax pro označení perzistentních parametrů může selhat při zapnutémeAccelatoru na hostingu. Pro tyto případy je možné perzistentní parametry deklarovatalternativně.

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ápisodkazů. 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říkladrenderDefault($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 phpDocsyntaxi komentáře proměnné:

Page 100: 201012021200_prirucka-programatora

/** @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ányv 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 (nejenperzistentní, ale i ty, které přenášíme v metodách render apod.), inicializovat výchozí hodnotou, stejně jakový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ý odkazPř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 komponentyJelikož 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žadovanoukomponentu podle názvu nebo cesty, a createComponent<Name>(), což je továrnička na komponenty. Výhodatová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ýchobjektů.

Ukládání vnitřních stavů komponenty má na starosti předek spansenteru PresenterComponent. Díky tomui jednotlivé komponenty (např. PresenterComponent) mohou používat továrničky. Vnitřní stavy komponent seuklá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) */

Page 101: 201012021200_prirucka-programatora

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íchparametrů.

Proč je to nutné? V podstatě z technického důvodu. Mezi spansentery se předávají jen data, která jsou jimspolečná, tedy která jsou deklarována na úrovni společných předků. Ale jak zjistit, že komponentu gamedeklarovala právě metoda třídy DefaultPresenter a ne nějaký její předek nebo potomek? To zjistit nelze. Lzeale 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 navstupní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étokomponenty 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čí aplikacevýjimkou o neexistujícím příjemci signálu. Příkladem této chyby je vytvoření komponenty až metodách renderjiný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 }

...

Page 102: 201012021200_prirucka-programatora

}

Svázání komponenty s spansenteremZde 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 (respektivenení potřeba ani dědit z PresenterComponent nebo Control). Nicméně původní konstruktor by se měl vždyrozhodně 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 komponentyTová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í komponentyprávě lazy loading / on-demand způsobem. V této metodě buď komponentu rovnou připojíte k spansenteru (nebonapří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

Page 103: 201012021200_prirucka-programatora

{ 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íkytomu, že jsou všechny komponenty definovány na jednom místě dochází k lepší přehlednosti. Komponentyz továrničky se také stávají lépe znovupoužitelnými, stačí si je jen kdekoliv přičarovat zápisem $this[...] apří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žítjednu komponentu na stránce vícekrát, rozliší se názvem, který také pomáhá určovat správnou komponentu jakopříjemce signálu.

V šabloně je možné získat a vykreslit komponentu pomocí makra widget nebo control. Není protopotř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ů »

Page 104: 201012021200_prirucka-programatora

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 metodarenderShow($id, $lang, ...) nebo metoda spanpareShow($id, $lang, ...), kde ono ‚show‘ v názvuodpoví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říkladlze 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á metoduCatalogPresenter::renderShow(10, 'en'). Jaké se vytvoří URL v tu chvíli nehraje roli. To je úkol oddělenévrstvy – routování.

Page 105: 201012021200_prirucka-programatora

Odkazování v šablonáchRozliš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 seprohledá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 asistencefiltru curlyBrackets může odkaz nad komponentou zkrátit pomocí značky {link} a odkaz nad spansenterempomocí {plink}.

V šabloně spansenteru jsou zápisy $spansenter->link() a $control->link() ekvivalentní (platí i prozkrácený zápis plink a link), protože i spansenter je komponentou a jako navázaný spansenter uvažujesám sebe.

Rozdíl se projeví v šablonách komponent, kde zápis {link Home:default} nevygeneruje odkaz, ale signál nakomponentu, 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íceinformací 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četparametrů neznáme, předáme parametry polem. Při předávání parametrů polem ale musíme využít nezkrácenéhozá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}

Page 106: 201012021200_prirucka-programatora

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 linkzkrá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 komponentURL 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 aforward jen přepošle zpracování jinam.

Viz také:

Fully qualified action●

Routování●

« Presenter Neplatné odkazy »

Page 107: 201012021200_prirucka-programatora

Neplatné odkazyPokud 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ývattě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říkladZa 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')));

...

Page 108: 201012021200_prirucka-programatora

}

« Generování odkazů AppForm »

Page 109: 201012021200_prirucka-programatora

Nette\Application\AppFormTří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 aaž jako druhý rodičovský IComponentContainer). Třída Form má parametr $parent uvedený až „bokem“ proto, žetam, 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 cykluspansenteru.

Metoda onSubmit se volá jen v případě, že je formulář skutečně odeslán, není potřeba znovu kontrolovat přesisSubmitted. Maximálně pokud můžeme zjistit, jakým prvkem byl formulář odeslán. onSubmit se provede pouzepokud 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 metodouhandle{Signal}. Takovou klasikou bývá {componentName}{event-occured}, tedy třebaloginFormSubmitted. To ale záleží na uvážení programátora. Tato metoda také pobírá jeden argument, kterýmje instance formuláře.

Nikdy neregistrujte AppForm ve fázi render! Na registraci v této fázi je již příliš pozdě (AppFormnemůž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()) { ... } }

Page 110: 201012021200_prirucka-programatora

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ýtzavolá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ůlichybě v Internet Exploreru, bude Nette za odesílací tlačítko považovat první tlačítko formuláře. Tudíž obsluha přesudálosti onClick je spolehlivá.

Kontrola odeslání formulářePouž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 sevyhodnotí 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()) { ... }

Page 111: 201012021200_prirucka-programatora

Validace jednotlivých prvků formulářeU 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í, amy si jej pak můžeme vykreslit v šabloně ručně. To se hodí v případech, kdy chceme například vykreslitsamostatně 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');

Page 112: 201012021200_prirucka-programatora

} }}

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 metodydefinované v onSubmit). Pokud se má formulář odesílat do jiného view, tak je potřeba ten signál přidat doargumentů.

Iterování nad formulářemK 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ýtjaké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), kdeprvní 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 »

Page 113: 201012021200_prirucka-programatora

Nette\Application\PresenterComponentZá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 »

Page 114: 201012021200_prirucka-programatora

Nette\Application\PresenterRequestObjekt zapouzdřující požadavky určené a následně předané spansenteru, a metody promanipulaci s nimi.

Objekt PresenterRequest je důsledně zapouzdřenou respanzentací všech dotazů, které byly přijaty prohlížečemod uživatele jako požadavek pro načtení stránky, a také vlastností a parametrů aktuální Routy, která byla označenajako 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ýchmetodou 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é:

Page 115: 201012021200_prirucka-programatora

PresenterRequest API reference●

Presenter●

Routování●

« PresenterComponent Action vs. View »

Page 116: 201012021200_prirucka-programatora

Action vs. ViewNa 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číststejnojmennou š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řebaakce zobrazit zboží v e-shopu. Presenter vyhledá položku v databázi a zjistí, že:

v databázi je a může se zobrazita.v databázi je, ale má příznak „smazáno“ – zobrazí se informace o nedostupnosti a nabídnou se podobnéb.produktyv databázi není – zobrazí se informace o chybějící stránce a nabídne např. vyhledávací formulářc.

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 nazboží (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.

Page 117: 201012021200_prirucka-programatora

« PresenterRequest Routování URL »

Page 118: 201012021200_prirucka-programatora

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-lisměrování) spojníkem právě mezi požadavkem zvenčí a správným řadičem, který požadavek zpracuje anaserví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ší azapamatovatelnější než jejich „syroví“ předchůdci, ale daleko větší důležitost sehrávají v současně aktuálníchoptimalizací webů pro vyhledávače (SEO). Nette Framework na současné trendy myslí a vychází v tomtovývojářům plně vstříc. Routování navíc odstiňuje vývojáře od direktiv pro mod_rewrite, tudíž nemusíme definovattvar 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 jimony 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ě promanipulaci 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ě metodapro 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á semůže řešit až když je aplikace hotová. Navíc není vůbec žádný problém kdykoliv úplně změnit veškeré cestypouze přepsáním rout – je tomu tak, protože routování je obousměrné, slouží jak k parsování, takk generování cest.

Page 119: 201012021200_prirucka-programatora

Není tudíž problém kdykoliv docílit toho, aby požadavek example.com/article/my-new-article vedl nastejnou stránku jako „kdysi“ example.com/article/show/5 nebo si zjednodušit stránkování výpisu kategorií doná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 abytakové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 metodyspansenteru).

Viz také:

Model-View-Presenter●

Nette\Application\Route●

Nette\Application\MultiRouter●

Nette\Application\SimpleRouter●

Nette\Application\Presenter●

« Action vs. View SimpleRouter »

Page 120: 201012021200_prirucka-programatora

Nette\Application\SimpleRouterJednoduchá 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. generovati přijímat URL adresy, nastavit jim příznak pro zabezpečené schéma (https) a jednosměrky (požadavky jsou jenpřijímány). Co však s Route nemá společné, je možnost vytvářet „cool URL“, protože SimpleRouter nemá jakojeden 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 objektuPresenterRequest, 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.

Page 121: 201012021200_prirucka-programatora

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 »

Page 122: 201012021200_prirucka-programatora

Nette\Application\RouteÚkolem routy je nejen URL adresu naparsovat a vytvořit z ní interní požadavek, alei 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 nicsložitého se můžete přesvědčit sami níže.

Definice cestPrvní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ánuvý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,

Page 123: 201012021200_prirucka-programatora

));

// 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 jedefault zcela automaticky.

Routa s maskou index.php určuje, že při požadavku na stránku index.php se otevře spansenter Article aaction show. Příznak Route::ONE_WAY (jednosměrka) zajistí, že routa může požadavek přijmout (stránkaindex.php existuje), ale aplikace takové URL nevytvoří. Tedy při generování URL pro spansenter Article aaction 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 vetvaru http://example.com/index.php, budou tyto nadále funkční. Navíc dojde k automatickémupř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 tomvyuží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.

KanonizaceKanonizace 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 objektuzpětně vygeneruje URL a pokud není ekvivalentní s aktuálním, dojde k přesměrování.

Příklad kanonizace:

Page 124: 201012021200_prirucka-programatora

$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 adresuhttp://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é jsouposílány), takže se nemusíte bát, že za cenu SEO optimalizovaných adres budete muset něco obětovat.

Foo parametryFoo 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',

Page 125: 201012021200_prirucka-programatora

'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í hodnotyu 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é sekvenceV 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říkladu 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

Page 126: 201012021200_prirucka-programatora

// /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í URLPř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 parametryInterní 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

Page 127: 201012021200_prirucka-programatora

));

Pokud byste chtěli ovlivnit chování zpětných lomítek, tj. aby se místo např. dashboard/view/ generovalodashboard/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-parameteryAč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 RoutePokud 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é jakoprodukt, 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:

Page 128: 201012021200_prirucka-programatora

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 routPokud 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 proparametr 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 definicirouty. Téhož výsledku lze dosáhnout i takto:

Route::addStyle('path', NULL);Route::setStyleProperty('path', Route::PATTERN, '.*?');

Page 129: 201012021200_prirucka-programatora

Route::setStyleProperty dělá to samé jako <url .*>, ale klíčový rozdíl je právě v tom, že druhý parametrRoute::addStyle říká, že nechceme zdědit standartní filtrování pomocí rawurlencode.

Dynamické přidávání routNyní 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 forumForum::createRoutes($router, '//forum.example.com/');

MultijazyčnostNette 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 naVaší 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',));

Page 130: 201012021200_prirucka-programatora

$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 »

Page 131: 201012021200_prirucka-programatora

Nette\Application\MultiRouterHromadné ú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 kteroupožadavek nejvíce vyhovuje. To, zda-li se vybere úspěšně ta routa, kterou jsme zamýšleli, závisí na našichkonkrétních definicích masek rout. Obecně platí pravidlo, že routy deklarujeme postupně od těch nejvícespecifický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í MultiRouterukeš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í technikydle 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●

Page 132: 201012021200_prirucka-programatora

« Route Autentizace, přihlašování uživatelů »

Page 133: 201012021200_prirucka-programatora

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 jsoudvě metody login() (přihlásit) a logout() (odhlásit), plus dotazovací metoda isLoggedIn() sdělující, zda jeuž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ímtozpů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á jedinoumetodu login(). Implementace, která ověřuje přihlašovací údaje oproti databázové tabulce, může vypadattřeba takto:

require 'Nette/loader.php';

// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:

Page 134: 201012021200_prirucka-programatora

// use Nette\Object, Nette\Security\IAuthenticator, Nette\Security\AuthenticationException, Nette\Security\Identity;

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ýjimkuNette\Security\AuthenticationException a několik chybových kódu, které můžete využít k formálnímu popisuvzniklé chyby. (Nicméně na to, jakou výjimku vyhodíte, se žádná omezení nekladou, nakonec bude je zachytávat aoš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ě sedostaneme 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';}

Page 135: 201012021200_prirucka-programatora

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ímlogout(TRUE).

Kromě manuálního odhlášení nabízí Nette Framework i automatické odhlášení po uplynutí časového intervalunebo zavření okna prohlížeče. K tomu slouží metoda setExpiration(), kterou volejte vždy před samotnouautentizací. 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...

Page 136: 201012021200_prirucka-programatora

$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 »

Page 137: 201012021200_prirucka-programatora

Nette\Web\UserZajišťuje přihlašování a odhlašování uživatelů, popisuje jejich identitu a ověřuje právana 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ášenif ($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řídaNette\Security\SimpleAuthenticator, která dostane v konstruktoru seznam uživatelů a hesel jakožtoasociativní pole. Úkolem handleru je v metodě login(array $credentials) ověřit, zda uživatelské jméno aheslo odpovídá a v případě úspěchu vrátit tzv. identitu. Neúspěch indikuje vyhozením výjimkyNette\Security\AuthenticationException s popisem důvodu. Lze využít i připravené konstantyIAuthenticator::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

Page 138: 201012021200_prirucka-programatora

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říčinuodhlášení. Nette umožňuje zjistit metodou getLogoutReason() základní příčiny odhlášení jako zavření oknaprohlížeče, neaktivita nebo manuální odhlášení uživatele. Můžeme tak například pomocí flash zpráviček dátuž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 byluž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 prologování autorizačních aktivit na webu. Událost onLoggedIn je volána po úspěšném přihlášení. UdálostonLoggedOut 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');

Page 139: 201012021200_prirucka-programatora

$user->onLoggedOut[] = array($logger, 'logoutMethod');

Expirace Nette\Web\Session musí být nastavena na stejnou nebo vyšší hodnotu, jakou máexpirace přihlášení

IdentitaIdentita 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ýchdatech (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ássysté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).

AutorizaceAutorizace = 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. objektimplementující rozhraní Nette\Security\IAuthorizator s metodou isAllowed(). Jeho implementací jeprávě třída Nette\Security\Permission, do hry tak kromě rolí vstupují i parametery resource & privilege.

if ($user->isAllowed($resource, $privilege)) ..

Page 140: 201012021200_prirucka-programatora

Protože uživatel může mít více rolí, povolení dostane, pokud alespoň jedna role má povolení. Oba parametry jsouvolitelné, výchozí hodnota nese význam všechny. Takže pokud např. parametr $privilege nevyužijeme, můžeme hovynechat.

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ší seNette\Web\User získat handlery opět přes metodu Nette\Environment::getService(), kde je identifikátorem názevinterface.

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 prostoruV 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 »

Page 141: 201012021200_prirucka-programatora

Nette\Security\IdentityUdrž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í jeudrž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. Identitase 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 »

Page 142: 201012021200_prirucka-programatora

Nette\Security\SimpleAuthenticatorTriviální implementace autentizačního handleru.

AutentizaceAutentizace 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 konstruktoruseznam 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. Lzevyužít i připravené konstanty IAuthenticator::IDENTITY_NOT_FOUND neboIAuthenticator::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ášenif ($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●

Page 143: 201012021200_prirucka-programatora

Dynamická správa rolí a zdrojů●

« Identity Autorizace, ověřování oprávnění »

Page 144: 201012021200_prirucka-programatora

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 čipřidělených rolích. Začněme ale od úplného začátku.

Triviální autorizaceZopakuji, ž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ériumpouží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í.

Page 145: 201012021200_prirucka-programatora

Jako autorizační kritérium nyní použijeme metodu isInRole(), která prozradí, zda-li uživatel vystupujev 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í jehoidentity. Tedy i nadále metoda getIdentity() vrací objekt Identity, včetně všech udělených rolí. NetteFramework 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í handlerPř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á rolepovolení 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();}

Page 146: 201012021200_prirucka-programatora

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 disponujei jednou poměrně silnou předpřipravenou implementací.

Access control listJde 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í handlerEnvironment::getUser()->setAuthorizationHandler($acl);

Na opravnění se poté opět dotazujeme metodou isAllowed(), jak bylo ukázáno výše.

« SimpleAuthenticator Permission »

Page 147: 201012021200_prirucka-programatora

Nette\Security\PermissionOvěřuje práva a přístupy k objektům na základě rolí.

Nette\Security\Permission je objekt implementující rozhraní Nette\Security\IAuthorizatorposkytují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ázetveř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čenorole (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ě objektemPermission. 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í, žeuž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 NULLregistered feed, comment guestadministrator 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).

Page 148: 201012021200_prirucka-programatora

ZačínámeNejprve 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 aadministrator.

$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é metodaroleInheritsFrom(), 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.

Page 149: 201012021200_prirucka-programatora

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');

AutorizaceNyní 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');

Page 150: 201012021200_prirucka-programatora

// 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 seznamupravidel 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 akcizakázanou a druhý povolenou? Jaké budou práva potomka? Určuje se to podle váhy role – poslední uvedená rolev 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\UserNa 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 = MyAuthenticatorservice.Nette-Security-IAuthorizator = Permission

Autentizační handler MyAuthenticator může vypadat například takto:

class MyAuthenticator implements IAuthenticator{ /**

Page 151: 201012021200_prirucka-programatora

* @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ě

Page 152: 201012021200_prirucka-programatora

$user->isAllowed($resource, $privilege);

Metoda isAllowed() má nyní jen dva parametry, role se sama doplní pomocí metody getRoles() a v cykluse 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ů »

Page 153: 201012021200_prirucka-programatora

Dynamická správa rolí a zdrojů

Ukázková struktura databáze prodynamickou 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ů) sinejspíše nevystačíme se základním objektem Nette\Security\Permission. V malých aplikacích můžeme bezproblémů „natvrdo“ nadefinovat seznam rolí, zdrojů a pravidel někde v aplikaci, ale dojde-li na nějaké úpravy neborozšíř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šipotř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_idi 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)

Page 154: 201012021200_prirucka-programatora

$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

Page 155: 201012021200_prirucka-programatora

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ámv 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 »

Page 156: 201012021200_prirucka-programatora

Ajax & snippetyAJAXový 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 HTMLkó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é aplikaceAJAXovou 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řikaž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ů.

InvalidaceKaž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 parametrnabý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ý snippetpovažován za invalidní. Komponenta je invalidní i tehdy, pokud je invalidní některá její subkomponenta.

echo $this->isControlInvalid(); // -> FALSE

Page 157: 201012021200_prirucka-programatora

$this->invalidateControl('header'); // invaliduje snippet 'header'echo $this->isControlInvalid('header'); // -> TRUEecho $this->isControlInvalid('footer'); // -> FALSEecho $this->isControlInvalid(); // -> TRUE, alespoň jeden snippet je invalid

$this->invalidateControl(); // invaliduje celou komponentu, každý snippetecho $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ř. DataGridmůže mít jednu metodu pro vykreslení mřížky a druhou pro vykreslení „stránkovadla“ apod). Každý prvek může býtnaví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ý interfacemetodu 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 amůž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, tedypoužitím @{include $template}, který nechá na snippetu rozhodnutí, zda-li se má překreslit (rozhodne sepodle 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>

Page 158: 201012021200_prirucka-programatora

Více informací k zavináčové magii.

Komunikace s prohlížečemJednotlivé 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í »

Page 159: 201012021200_prirucka-programatora

Nette\Environment

Propojení s Nette\ConfigTří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 souboryjsou 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 = helloconst.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 aplikujeEnvironment::loadConfig('config.ini');// nebo ekvivalentně:Config::loadConfig($config);

// změny v prostředí PHP můžeme zkontrolovat

Page 160: 201012021200_prirucka-programatora

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 Windowssysté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ýmnekompatibilitá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řesEnvironment::getMode('mujmod').

AutodetekceTří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ístoEnvironment::getMode('production') lze volat výstižnější Environment::isProduction(). Pro režimconsole existuje obdobná zkratky Environment::isConsole().

Autodetekce pracuje na principu zjištění IP adresy serveru ( ze $_SERVER['SERVER_ADDR'] ) a v případě shodys některou z klasických IP adres používaných v intranetu se režim nastaví na debug. Manuální nastavení režimuv konfiguračním souboru má vyšší prioritu než autodetekce, a na zkušebním serveru (běžícím například nalocalhostu tj. na 127.0.0.1) ho lze vyvolat pomocí příkazu

Page 161: 201012021200_prirucka-programatora

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 neboEnvironment::CONSOLE.

Příklad…a přímo ze života: mám jednu aplikaci, která běží v pěti různých prostředích:

2× na lokálním serveru, kde probíhá vývoj (můj počítač + virtuální testovací stroj)1.1× na serveru tojeono.cz (jako texy.info)2.2× na serveru hostmonster.com (jako nette.org a dibiphp.com)3.

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 productionruč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 »