[2010–09–10] Vylepšená makra {cache} … {/cache} (verze 2)
- David Grudl
- Nette Core | 8212
V šablonách je nová implementace maker {cache}
. Lze je
používat třeba takto:
{cache $id, expire => '+20 minutes', tags => array(tag1, tag2)}
...
{/cache}
Obsah cache je vázán na hodnotu proměnné $id
, je mu
nastavena expirace a tagy pro zneplatnění. Samozřejmě všechny položky jsou
volitelné, takže nemusíte uvádět ani expiraci, ani tagy, nakonec ani
vstupní podmínky (tedy třeba parametr $id). Podmínek je možné uvést
i více, oddělených čárkou.
Cache se automaticky invaliduje při změně souboru. Pokud je uvnitř bloku
{cache}...{/cache}
vnořený jiný kešovaný blok a ten
zneplatníme (například tagem), zneplatní se i blok nadřazaný. Také pokud
je uvnitř bloku inkludovaná jiná šablona makrem {include}
,
změna šablony změna způsobí zneplatnění keše.
- David Grudl
- Nette Core | 8212
Kešování lze také podmínit pomocí if
:
{cache $id, expire => '+20 minutes', if => $date < $limit}
...
{/cache}
Položka se načte/uloží do cache pouze tehdy, je-li splněna podmínka uvedená za if.
- Honza Marek
- Člen | 1664
David Grudl napsal(a):
Kešování lze také podmínit pomocí
if
:{cache $id, expire => '+20 minutes', if => $date < $limit} ... {/cache}
Položka se načte/uloží do cache pouze tehdy, je-li splněna podmínka uvedená za if.
Jestli to má být zkratka za
{if $date < $limit}{cache $id, expire => '+20 minutes'}{/if}
...
{if $date < $limit}{/cache}{/if}
tak mi to přijde extrémně neintuitivní. Protože než jsem to pochopil, myslel jsem, že je to jen nevhodné pojmenování pro nastavování expirace položky v cache podle nějaké podmínky (což by ostatně asi byla užitečnější záležitost než toto).
- Jan Tvrdík
- Nette guru | 2595
Já s intuitivností problém nemám, alternativní výklad mě nenapadl. Dost možná proto, že něco takového jsem si chtěl napsat sám.
Jedinou větší nevýhodu, kterou teď v makru {cache}
spatřuji je nemožnost zjistit v presenteru, zda je v cache daný záznam
podle jeho ID (viz toto
vlákno). Musí se použít tagy. Nebo mi něco uniká?
Drobné vylepšení, které mě napadá, je možnost ovlivnit jmenný prostor, kam se cache uloží. Hodí se to v případě, že makro používám na více místech a chci v tom mít pořádek.
Editoval Jan Tvrdík (10. 11. 2010 14:49)
- David Grudl
- Nette Core | 8212
Honza Marek napsal(a):
Jestli to má být zkratka za
V podstatě ano, ale tohle bude nemožné v okamžiku použití kontroly křížení značek.
tak mi to přijde extrémně neintuitivní.
Fajn, a nějaké jiné návrhy?
expirace položky v cache podle nějaké podmínky (což by ostatně asi byla užitečnější záležitost než toto).
Expirace podle podmínky není problém
{cache expire => $cond ? 10 : 1000}
, zatímco {if} bez
křížení nelze udělat, toliko k tomu, co je užitečnější.
- Honza Marek
- Člen | 1664
David Grudl napsal(a):
Expirace podle podmínky není problém
{cache expire => $cond ? 10 : 1000}
, zatímco if bez křížení nelze udělat.
Ostatně ani if by tím pádem neměl být problém, ne?
{cache expire => $cond ? 10 : -1}
I když tady nevím, jestli to funguje stejně jako if, pokud se už položka v keši nachází.
toliko k tomu, co je užitečnější
Keší podle podmínky jsem měl na mysli něco jiného. Pokud by podmínka byla true, vzala by se položka z keše, pokud false, tak by se invalidovala. Tolik na vysvětlenou. Jestli je to užitečné, o tom teď pochybuju i já. Kdysi jsem potřeboval invalidaci podle data poslední změny, ale teď si uvědomuju, že podmínka to neřeší, pokud si nemám kam uložit datum poslední změny v době zakešování :)
- David Grudl
- Nette Core | 8212
Mám takový pocit, že se bavíme o tom stejném. Příklad: chci kešovat formulář, ale nechci, abys se bral z keše (a ani aby se ukládal do keše), pokud je odesílán:
{cache if => !$form->submitted}
<div id="form">
{control form}
</div>
{/cache}
- Honza Marek
- Člen | 1664
To makro mi teď vůbec nefunguje.
Mám v šabloně toto:
{cache tags => $page->cacheKey}{!$page->text|texy}{/cache}
a zobrazí se mi pokaždé to, co se uložilo do cache jako první. Bez ohledu na tag. Dříve to šlo a nemůžu přijít na to, kvůli čemu to přestalo fungovat.
- jansfabik
- Člen | 193
Řekl bych, že by to mělo vypadat spíš nějak takhle…
{cache $page->cacheKey, tags => $page->cacheKey}{!$page->text|texy}{/cache}
Protože pokud vím, tak tagy se uloží do tabulky, aby se podle nich pak dalo mazat, ale pokud nespecifikuješ id, tak se bude vždycky ukládat pod stejným (prázdným) a to nebude fungovat.
- Honza Marek
- Člen | 1664
Což taky nejde a podle https://api.nette.org/…ros.php.html#699 mi ani nepřipadá, že by to mohlo jít. Nemluvě o tom, že dřív to šlo i bez id.
- cuga
- Člen | 210
zkouším tímhle makrem cachovat navigaci, ale na localhostu se tomu nechce fungovat… v šabloně mám
<nav>
{cache 'navCategories', expire => '+20 minutes'}
{widget $navCategories}
{/cache}
</nav>
{dump $cache['navCategories']}
v $navCategories je render addonu Navigation
v dumpu je null, nikde nevypadá, že by se něco cachovalo… mám pro to v bootstrapu něco povolit? z dokumentace jsem nevyčetl, že by něco chybělo…
Editoval cuga (12. 12. 2010 21:52)
- cuga
- Člen | 210
v $cache je Environment::getCache(‚Nette.Template.Cache‘);
nicmene uvedomil jsem si par chyb, ktere z tohoto plynou, takze to moje nastinene reseni nemuze byt funkcni… nemuzu si nacachovat drobeckouvou navigaci jednou, musim ji cachovat pro kazdou stranku, takze jsem zkusil
<nav>
{cache $linkThis."-navigation", expire => '+20 minutes'}
{widget navigation}
{/cache}
</nav>
<nav>
{cache $linkThis."-breadcrumbs", expire => '+20 minutes'}
{widget navigation:breadcrumbs}
{/cache}
</nav>
<nav>
{cache $linkThis."-categories", expire => '+20 minutes'}
{widget categories}
{/cache}
</nav>
coz jsem predpokladal zalozi cache pro kazdou stranku, takze drobky, navigace i produktove menu bude se spravne nastavenyma aktivnima polozkama… ovsem problem… cachne se to jednou a slus :)
- Filip Procházka
- Moderator | 4668
zkus tohle :)
{cache [$presenter->getAction(TRUE), "navigation"], expire => '+20 minutes', tags => ["navigation"]}
{control navigation}
{/cache}
{cache [$presenter->getAction(TRUE), "breadcrumbs"], expire => '+20 minutes', tags => ["breadcrumbs"]}
{control navigation:breabcrumbs}
{/cache}
{cache [$presenter->getParam('parametr-ktery-ovlivnuje-kategorie'), "categories"], expire => '+20 minutes', tags => ["categories"]}
{control categories, $preseter->getParam('parametr-ktery-ovlivnuje-kategorie')}
{/cache}
PS: doporučuju umístit třeba do beforeRender, nebo createTemplate ;)
protected function beforeRender()
{
$this->template->cacheKeyFqa = $presenter->getAction(TRUE);
$this->template->cacheKeyCategories = $presenter->getParam('parametr-ktery-ovlivnuje-kategorie');
}
Editoval HosipLan (13. 12. 2010 0:32)
- David Grudl
- Nette Core | 8212
cuga napsal(a):
coz jsem predpokladal zalozi cache pro kazdou stranku,
Nikoliv, je to cache pro každou šablonu. Takže v klíči musí být i rozlišovací znak stránky.
- cuga
- Člen | 210
Mám DefaultPresenter, který v actionView natáhne stránku, přes explode rozpadne $url a dotazem do DB určí, která šablona se má použít, takže $url je pro mě rozhodující parametr. Zkusil sem:
<nav>
{cache [String::webalize($presenter->getParam('url')), time(), "navigation"], tags => ["navigation"]}
{widget navigation}
{/cache}
</nav>
<nav>
{cache [String::webalize($presenter->getParam('url')), time(), "breadcrumbs"], tags => ["navigation"]}
{widget navigation:breadcrumbs}
{/cache}
</nav>
<nav>
{cache [String::webalize($presenter->getParam('url')), time(), "categories"], tags => ["navigation"]}
{widget categories}
{/cache}
</nav>
Jak jsem to pochopil tak je potřeba mít unikátní klíč, takže time() by měl na každé stránce vynutit nové generování cache. Ale funguje to pořád stejně kešne se to jednou a pak je to pořád stejné.
Chci tomu dát jeden společný tag, ať při změně v administraci můžu jednoduše cache invalidovat.
Takže co dělám špatně?
- David Grudl
- Nette Core | 8212
Kolik je rozdílných klíčů, tolik se vytvoří rozdílných keší. Takže pokud do klíče dáš time(), nemá takové kešování žádný smysl. Dej tam jen rozhodující parametry a nic víc. Stačí:
<nav>
{cache [$presenter->getParam('url'), "navigation"]}
{widget navigation}
{/cache}
</nav>
- Filip Procházka
- Moderator | 4668
iguana007 napsal(a):
zajímalo by mě, jak se potom taková cache invaliduje?
buďto jí dáš tagy, nebo ji budeš vytvářet tak, aby nebylo potřeba ji
invalidovat.
Tj tak že použiješ do klíče parametr, podle kterého se navigace rozbaluje.
Vytvoří se ti pak postupně cache pro všechny varianty rozbalení.
- Michalek
- Člen | 211
Existuje jednoduchá možnost, jak v presenteru zjistit, zda obsah
nacachovaný přes {cache 'nazev'}
existuje? Před úpravou na
novou verzi makra bylo jednoduché ho upravit, ale teď už se dvě hodiny
nechytám…
Proč to potřebuju: v presenteru je složitý sql dotaz na výpis
článků. V šabloně to poprvé nacachuju a už to nechci v presenteru znovu
vybírat. Takže jsem dřív používal
if(!isset([$cache['nazev']]))
. Předávání proměnné do šablony
a spouštění dotazu z ní není možné.
Nebo to dělám celé blbě? Jak mám ověřit, že není potřeba spouštět složité sql dotazy když existuje cache?
Editoval Michalek (9. 2. 2011 21:34)
- bojovyletoun
- Člen | 667
Přesně tohle mě taky trápí. Výpočetně dlouhé získávání dat v renderDefault() –100ms. výpočetně dlouhé vykreslování (2D tabulka 20*50) – 100ms.
makro {cache} ukládá pod speciálním klíčem ve tvaru {RandomString pro každou kompilovanou šablonu}:N, kde n je pořadí makra cache v šabloně. Tento klíč nelze ani změnit ani ovlivnit.
Takže bych to viděl v presenteru si definovat property
$this->cache=Environment::getCache('WherigoCache')
, tu předat
do šablony a v šabloně opustit makro cache a nahradit nějakou logikou ve
stylu
{if isset $cache[$key]}{načti z cache}{else vypiš data od presenteru}
.
Klíč bych si taky zvolil v presenteru. V presenteru bych taky na začátku
sql dotazu kontroloval, zda je v keši záznam.
Připadá mi to trochu krkolomné, takže asi existuje něco elegantnějšího. napadá vás něco?
- maio
- Člen | 7
redhead napsal(a):
Není nahodou s novou cache možnost psát if jako parametr??
{cache $id, if => podminka} ... {/cache}
osobne mi ten zapis if moc nesedi a vyvolava WTF :)
hlavne vzhledem k tomu jak se pise normalni if a pak i to, ze pokud uz chce clovek pouzit podminene cachovani tak je podminka podle ktere se to bude rozhodovat dulezitejsi nez treba ID kam se to bude ukladat, takze by ta podminka nemusela byt jako x-ty optional parametr.
{if $neco >= 10}
{cache 'test', expire => '+20 minutes', if => $neco >= 10}
{cache_if $neco >= 10, 'test', expire => '+20 minutes'}
- redhead
- Člen | 1313
To o čet tu mluvíš už je docela detail. Já v tom problém nevidím, ani žádné WTF.
Další možností je, vytvořit si na to control, který bude vypisovat data, které bude získávat samotný control, nikoli presenter. Pak by stačilo jednoduché
{cache neco}
{control dataRenderer}
{/cache}
class DataRenderer extends Control
{
public function render() {
$template = $this->createTemplate();
...
$template->data = $model->getData();
$template->render();
}
}
Nebo, pokud ti to nevadí, můžeš data získat přímo v šabloně:
{cache neco}
{var data = $model->getData()}
{* ... vypis dat ... *}
{/cache}
Tenhle způsob já ale nemám rád.
Editoval redhead (10. 2. 2011 11:57)
- Michalek
- Člen | 211
redhead napsal(a):
{cache neco} {var data = $model->getData()} {* ... vypis dat ... *} {/cache}
Ha, tak teď mi při testování docvaklo, že jsem na to šel blbě
(neuvědomil jsem si možnost volat sql jinde než v
renderDefault
u) a navíc si myslel, že se to provede znovu
i když je šablona nacachovaná. Taky teď presenter nezanáším
„zbytečnými a nesouvisejícími“ if
y.
Můj problém je tím vyřešen, i když chápu, proč to nemusí být z určitého pohledu „správně“. Děkuji mnohokrát.
Editoval Michalek (10. 2. 2011 16:13)
- bojovyletoun
- Člen | 667
právě mi jede cachování template ↔ presenter:
Příklad Presenter:
class Homepage presenter extends presenter{
public $cache;
public $key;
public $dpfile;
public function startup(){
parent::startup();
$this->template->cache=$this->cache=\Nette\Environment::getCache('powercache');
}
function renderDefault(){
$key=$this->template->key=array($this->filters->f, size_of_my_shoes(),$this->some_id); // nejake klice(parametry)
$this->template->dpfile=$this->model->file; //sqlite.db pri zapisu a tedy změně dat
if($this->cache[$key]){
$this->template->cached='Jen pro informaci, že cachujeme';
$this->sendTemplate();
}
$this->model->fi=$this->filters->f; //nejake parametriky, razeni atd
$this->template->clm = $this->model->ClovekAggr(); //expensive
}
}
template:
<table n:snippet="table">
{if !$cache[$key]}
{?ob_start()}
<thead>
foreach... foreach ...
</tbody>
{?$cache->save($key,ob_get_flush(),['expire'=>3600*6,'files'=>[$dpfile]])}
{else}
{!$cache[$key]} <!-- zde poznámka* -->
{/if}
</table>
Poznámka: na místě se volá echo $cache[$key];
,třeba
100kb… nebylo by lepší include (vím, že v těch souborech jsou nějaké
hlavičky) nebo je to jedno (potíží jsem si ale žádných nevšiml)
Mimochodem: nevíte proč nb hlásí syntax chybu zde?
{?$cache->save($key,ob_get_flush(),['expire'=>3600*6,'files'=>[$dpfile]])}
EDIT: ještě jedna věcička- teĎ si nemohu vzpomenout, ale jak přidám do závislostí, aby cachovaný obsah platil do té doby než se šablona změní (zkompiluje). jde něco jako ‚consts‘=>$_->uniq. Jenže to není konstanta. Prostě závislost na nějaké hodnotě.
Editoval bojovyletoun (10. 2. 2011 19:23)