Pojďme si ukázat několik způsobů jak cachovat data v jednotlivých Nette komponentách a jejich šablonách.
Cachování v komponentách se dá vyřešit mnoha způsoby, zkusím popsat všechny mně známé a vysvětlit, na co se hodí a kdy je dobré je použít.
Typy, o kterých budu mluvit.
- cachování v šabloně
- v místě použítí
- v šabloně komponenty
- cachování v komponentě
- vynucené
- volitelné
- cachování při vytváření komponenty
Cachování v šabloně
> v místě použití
use Nette\Application\UI\Control;
class Foo extends Control
{
/**
* @param mixed $list
*/
public function __construct($list) {
$this->list = $list;
}
public function render() {
$this->template->list = $this->list;
$this->template->setFile(__DIR__ . '/foo.latte');
$this->template->render();
}
}
protected function createComponentFoobar() {
return new Foo($this->list);
}
Když máme $list
jako pole, klasicky se nám hodí první způsob, a to cachovaní v šabloně.
Využijeme makra cache
, kterému dáme unikátní název a vhodnou dobu expirace. Pro přípád pokročilého cachování, můžene využít i tagy.
Normálně se používá na většinu výpisů, klasicky při načítání z databáze jako alternativa ke cachování v komponentě nebo při vytváření komponenty.
// @layout.latte, default.latte, etc..
{cache 'foo/bar', expire => '+1 hour'}
{control foobar}
{/cache}
> v šabloně komponenty
use Nette\Application\UI\Control;
class Foo extends Control
{
public function getList() {
// some operations..
}
public function render()
$this->template->list = $this->getList();
$this->template->setFile(__DIR__ . '/foo.latte');
$this->template->render();
}
}
protected function createComponentFoobar() {
return new Foo();
}
Cachování v šabloně komponety se obvykle používá, pokud máte jednoučelovou komponentu, která třeba počítá různé statistiky nebo vypisuje data s velkou časovou náročností.
// @layout.latte, default.latte, etc..
{control foobar}
// foo.latte
{cache 'foo', expire => '+1 day'}
{foreach $list as $item}
{* render *}
{/foreach}
{/cache}
Cachování v komponentě
> vynucené
use Nette\Application\UI\Control;
use Nette\Caching\IStorage;
use Nette\Caching\Cache;
class Foo extends Control
{
/**
* @param object $facade
* @param IStorage $storage
*/
public function __construct($facade, IStorage $storage) {
$this->facade = $facade;
$this->cache = new Cache($storage, 'foo');
}
/**
* @return array
*/
public function getCachedList() {
return $this->cache->load('foo', function(& $dependencies) {
$dependencies['expire'] = '+ 1 day';
// some dark magic
// ...
return $list;
});
}
public function render() {
$this->template->list = $this->getCachedList();
$this->template->setFile(__DIR__ . '/foo.latte');
$this->template->render();
}
}
protected function createComponentFoobar() {
return new Foo($this->list);
}
Cachování v komponentě je defakto uplně stejné jako v šabloně, akorát se dějě na logicky jiném místě. Výhodu v tom vídím, že si člověk sám definuje, co chce cahovat, může si to vyladit přímo na míru, nastavit ruzné tagy, závislosti na jiných datech / souborech apod.
Mnozí by mohli argumentovat, že cachovat by se mělo jenom v komponentě a v šabloně nikoli. Že by to mohlo porušovat principy MVC. Já v tom takovou vědu nevidím. Každý si může využít to, co se mu hodí.
> volitelné
use Nette\Application\UI\Control;
use Nette\Caching\IStorage;
use Nette\Caching\Cache;
class Foo extends Control
{
/**
* @param IStorage $storage
*/
pubic function setCacheStorage(IStorage $storage) {
$this->cache = new Cache($storage, 'foo');
}
public function render() {
if ($this->cache) {
$this->template->list = $this->getCachedList();
} else {
$this->template->list = $this->getList();
}
$this->template->setFile(__DIR__ . '/foo.latte');
$this->template->render();
}
}
protected function createComponentFoobar() {
return new Foo($this->list);
}
V ideálním případě byste mohli cache nastavovat přes setter
a komponenta už si sama rozhodne, zda-li cache využije nebo ne.
Pokud se vám použítí přes setter
nelíbí, tak pro testovací učely je možné využít Nette\Caching\Storages\DevNullStorage
, které simuluju cache, ale nic necachuje.
Cachování při vytváření komponenty
Tenhle způsob ja osobně moc nepoužívam, protože nerad špiním Presenter
y dalšími závislostmi.
Každopádně to lze také.
/**
* @return array
*/
public function getCachedList() {
return $this->cache->load('foo', function(& $dependencies) {
$dependencies['expire'] = '+ 1 day';
// some dark magic
// ...
return $list;
});
}
protected function createComponentFoobar() {
return new Foo($this->getCachedList());
}
V této variantě by bylo nejspíš lepší daná data cachovat ve facade
/ DAO
/ service
. Na tento přípád se to hodí perfektně.
Naposledy, co jsem řešil cache bylo na Componette. Měl jsem data z databáze a chtěl jsem je cachovat, protože se skoro nemění. Rozhodl jsem se využít latte makra cache
, aby to bylo pohodlné.
Moje komponenta vypadala nějak takto:
public function render()
{
$this->template->categories = $this->tagRepository->findWithHighPriority();
$this->template->tags = $this->ensure($this->tagRepository->findWithLowPriority());
$this->template->setFile(__DIR__ . '/templates/menu.latte');
$this->template->render();
}
A její šablona:
{cache menu, expire => '+1 day'}
{* rendering *}
{/cache}
Jenže to nefungovalo, jak jsem chtěl. Sice dotaz do databáze byl lazy-loading
, ale já nad daty dělal nějakou chytristiku (vyhazoval položky z menu, které nemají připojený ani jeden tag) v metodě ensure
.
Komponenta byla cachovaná, ale při renderování to dělalo vždy 1 dotaz.
Mohl jsem využít cachování v komponentě, ale nechtěl jsem další závislost. Mohl jsem cachovat v @layout.latte
, ale já to chtěl mít přímo v šabloně komponenty.
Vyřešil jsem to tedy malým trikem.
public function render()
{
$this->template->_categories = function () {
return $this->tagRepository->findWithHighPriority();
};
$this->template->_tags = function () {
return $this->ensure($this->tagRepository->findWithLowPriority());
};
$this->template->setFile(__DIR__ . '/templates/menu.latte');
$this->template->render();
}
A její šablona:
{cache menu, expire => '+1 day'}
{var $categories => $_categories()}
{var $tags => $_tags()}
{* rendering *}
{/cache}
Je to velmi jednoduché, při vytváření cache mi anonymní funkce $_categories
a $_tags
vrácí správná data a ja je můžů krásně vyrenderovat.
Podruhé už se použije cache a člověk nemusí nic řešit.
Celkem tedy 0 dotazů. Mission complete.
Pokud máte nějaké vlastní typy, ukázky nebo workaroundy, budu rád, když se o ně podělíte.