[2010–09–10] Vylepšená makra {cache} … {/cache} (verze 2)

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8205
+
0
-

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 | 8205
+
0
-

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.

Filip Procházka
Moderator | 4668
+
0
-

to if vypadá vskutku děsivě

norbe
Backer | 405
+
0
-

Můžu se zeptat k čemu to je dobré? Nějak mne nenapadlo žádné využití…

Honza Marek
Člen | 1664
+
0
-

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
+
0
-

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 | 8205
+
0
-

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
+
0
-

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 | 8205
+
0
-

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
+
0
-

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
+
0
-

Ř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
+
0
-

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.

David Grudl
Nette Core | 8205
+
0
-

fixed

cuga
Člen | 210
+
0
-

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)

David Grudl
Nette Core | 8205
+
0
-

A co je v $cache?

cuga
Člen | 210
+
0
-

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
+
0
-

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 | 8205
+
0
-

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
+
0
-

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 | 8205
+
0
-

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>
cuga
Člen | 210
+
0
-

tak jak si napsal, tak se mi to kešne jednou a dál při změně stránky to stale bere to prvni nakešovane a další keše to nebere…

ten time() jsem přidal protože jsem chtěl otestovat, jestli hraje roli unikátnost klíčů… a i s time() v klíči se to kešne jednou a šlus!

iguana007
Člen | 970
+
0
-

zajímalo by mě, jak se potom taková cache invaliduje? Např. bych rád cachoval stromovou strukturu navigace, která se v podstatě nemění a invalidace by se měla provést pouze při změně něktéré z položek navigace.

Filip Procházka
Moderator | 4668
+
0
-

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

cuga
Člen | 210
+
0
-

AHA! je mozne, ze mohl byt v nonprefixed verzi z 12.11.2010 bug v Cache makru? Protoze ted uz mi to funguje.

Michalek
Člen | 211
+
0
-

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
+
0
-

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
+
0
-

+1

David Grudl napsal(a):

Honza Marek napsal(a):
tak mi to přijde extrémně neintuitivní.

Fajn, a nějaké jiné návrhy?

Neslo by treba:

{conditional_cache podminka, id, ....}
  body
{/conditional_cache}
{cache_if podminka, id, ...}

Editoval maio (9. 2. 2011 22:43)

redhead
Člen | 1313
+
0
-

Není nahodou s novou cache možnost psát if jako parametr??

{cache $id, if => podminka}
...
{/cache}
maio
Člen | 7
+
0
-

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
+
0
-

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
+
0
-

Kvůli WTF efektu jsem to osobně nezačal řešit, vždyť if už je zavedené dlouho :-) Spíš to, jak získávat znalost o uložení/neuložení cache v presenteru… Nápad s controlem je zajímavý, jen nevím jestli to bude úplně fungovat. Vyzkouším.

analytik
Člen | 1
+
0
-

maio wrote:

osobne mi ten zapis if moc nesedi a vyvolava WTF :)

Osobne mi {cache … if} prislo citelne na prvni pohled.

Michalek
Člen | 211
+
0
-

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 renderDefaultu) 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“ ify.

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)

redhead
Člen | 1313
+
0
-

Pokud si dělal SQL dotazy v presenteru, tak je to velká chyba. Podívej se na nějaké tutoriály nebo Quick Start, měl bys naštudovat, co je to model a jak ho používat.

Editoval redhead (10. 2. 2011 16:23)

Michalek
Člen | 211
+
0
-

Neboj, modely používám důsledně, jen jsem si prostě neuvědomil, že data z modelu můžu tahat v jiné funkci presenteru než zrovna renderDefault. Prostě zkrat :)

bojovyletoun
Člen | 667
+
0
-

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)