Nette Documentation Preview

syntax
Nette Caching
*************

<div class=perex>

A Cache felgyorsítja az alkalmazást azáltal, hogy az egyszer nehezen megszerzett adatokat elmenti a későbbi felhasználásra. Megmutatjuk:

- hogyan használjuk a cache-t
- hogyan változtassuk meg a tárolót
- hogyan érvénytelenítsük helyesen a cache-t

</div>

A cache használata a Nette-ben nagyon egyszerű, miközben nagyon fejlett igényeket is lefed. Teljesítményre és 100%-os ellenállóságra tervezték. Alapból adaptereket talál a leggyakoribb háttértárolókhoz. Lehetővé teszi a tag-ek alapján történő érvénytelenítést, az időbeli lejárást, védelmet nyújt a cache stampede ellen stb.


Telepítés
=========

A könyvtárat a [Composer|best-practices:composer] eszközzel töltheti le és telepítheti:

```shell
composer require nette/caching
```


Alapvető használat
==================

A cache-sel vagy gyorsítótárral való munka középpontjában az [api:Nette\Caching\Cache] objektum áll. Létrehozunk egy példányt belőle, és paraméterként átadjuk a konstruktornak az úgynevezett tárolót. Ez egy olyan objektum, amely azt a helyet képviseli, ahol az adatok fizikailag tárolódnak (adatbázis, Memcached, fájlok a lemezen, ...). A tárolóhoz úgy juthatunk hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével kérjük át a `Nette\Caching\Storage` típussal. Minden lényegeset megtudhat a [Tárolók szakaszban |#Tárolók].

.[warning]
A 3.0-s verzióban az interfésznek még volt `I` előtagja, tehát a neve `Nette\Caching\IStorage` volt. Továbbá a `Cache` osztály konstansai nagybetűkkel voltak írva, tehát például `Cache::EXPIRE` a `Cache::Expire` helyett.

A következő példákhoz feltételezzük, hogy létrehoztunk egy `Cache` aliast, és a `$storage` változóban van a tároló.

```php
use Nette\Caching\Cache;

$storage = /* ... */; // instance of Nette\Caching\Storage
```

A cache valójában egy *key–value store*, tehát az adatokat kulcsok alatt olvassuk és írjuk, ugyanúgy, mint az asszociatív tömböknél. Az alkalmazások számos független részből állnak, és ha mindegyik ugyanazt a tárolót használná (képzeljünk el egyetlen könyvtárat a lemezen), előbb-utóbb kulcsütközés következne be. A Nette Framework ezt a problémát úgy oldja meg, hogy az egész teret névtérekre (alkönyvtárakra) osztja. Minden programrész ezután a saját, egyedi nevű terét használja, és így már nem fordulhat elő ütközés.

A névtér nevét a Cache osztály konstruktorának második paramétereként adjuk meg:

```php
$cache = new Cache($storage, 'Full Html Pages');
```

Most már a `$cache` objektum segítségével olvashatunk a gyorsítótárból és írhatunk bele. Mindkettőre a `load()` metódus szolgál. Az első argumentum a kulcs, a második pedig egy PHP callback, amely akkor hívódik meg, ha a kulcs nem található a cache-ben. A callback generálja az értéket, visszaadja, és az elmentődik a cache-be:

```php
$value = $cache->load($key, function () use ($key) {
	$computedValue = /* ... */; // költséges számítás
	return $computedValue;
});
```

Ha a második paramétert nem adjuk meg `$value = $cache->load($key)`, akkor `null`-t ad vissza, ha az elem nincs a cache-ben.

.[tip]
Nagyszerű, hogy a cache-be bármilyen szerializálható struktúrát tárolhatunk, nem csak stringeket. És ugyanez igaz még a kulcsokra is.

Az elemet a gyorsítótárból a `remove()` metódussal töröljük:

```php
$cache->remove($key);
```

Elemet a gyorsítótárba a `$cache->save($key, $value, array $dependencies = [])` metódussal is menthetünk. Azonban a fentebb bemutatott `load()` használata preferált.


Memoizáció
==========

A memoizáció egy függvény vagy metódus hívásának eredményének gyorsítótárazását jelenti, hogy legközelebb újra felhasználhassuk anélkül, hogy újra kiszámítanánk ugyanazt.

Metódusokat és függvényeket memoizáltan hívhatunk a `call(callable $callback, ...$args)` segítségével:

```php
$result = $cache->call('gethostbyaddr', $ip);
```

A `gethostbyaddr()` függvény így minden `$ip` paraméterre csak egyszer hívódik meg, és legközelebb már a cache-ből adódik vissza az érték.

Lehetőség van arra is, hogy egy memoizált burkolót hozzunk létre egy metódus vagy függvény köré, amelyet később hívhatunk meg:

```php
function factorial($num)
{
	return /* ... */;
}

$memoizedFactorial = $cache->wrap('factorial');

$result = $memoizedFactorial(5); // először kiszámítja
$result = $memoizedFactorial(5); // másodszor a cache-ből
```


Lejárat & érvénytelenítés
=========================

A cache-be való mentéskor felmerül a kérdés, hogy a korábban elmentett adatok mikor válnak érvénytelenné. A Nette Framework egy mechanizmust kínál az adatok érvényességének korlátozására vagy azok irányított törlésére (a keretrendszer terminológiájában „érvénytelenítésére”).

Az adatok érvényességét a mentéskor állítjuk be a `save()` metódus harmadik paraméterével, pl.:

```php
$cache->save($key, $value, [
	$cache::Expire => '20 minutes',
]);
```

Vagy a `load()` metódus callbackjének referenciaként átadott `$dependencies` paraméterével, pl.:

```php
$value = $cache->load($key, function (&$dependencies) {
	$dependencies[Cache::Expire] = '20 minutes';
	return /* ... */;
});
```

Vagy a `load()` metódus 3. paraméterével, pl.:

```php
$value = $cache->load($key, function () {
	return ...;
}, [Cache::Expire => '20 minutes']);
```

A további példákban a második változatot feltételezzük, és így a `$dependencies` változó létezését.


Lejárat
-------

A legegyszerűbb lejárat az időkorlát. Így 20 perces érvényességgel mentünk adatokat a cache-be:

```php
// elfogadja a másodpercek számát vagy UNIX timestamp-et is
$dependencies[Cache::Expire] = '20 minutes';
```

Ha minden olvasással meg szeretnénk hosszabbítani az érvényességi időt, azt a következőképpen érhetjük el, de vigyázat, a cache rezsije ezzel megnő:

```php
$dependencies[Cache::Sliding] = true;
```

Ügyes lehetőség, hogy az adatokat akkor járassuk le, amikor egy fájl vagy több fájl közül valamelyik megváltozik. Ezt például akkor használhatjuk, ha ezeknek a fájloknak a feldolgozásából származó adatokat mentjük a cache-be. Használjon abszolút elérési utakat.

```php
$dependencies[Cache::Files] = '/path/to/data.yaml';
// vagy
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];
```

Lejárathatunk egy elemet a cache-ben akkor, amikor egy másik elem (vagy több másik közül valamelyik) lejár. Ezt akkor használhatjuk, ha például egy egész HTML oldalt mentünk a cache-be, és más kulcsok alatt annak töredékeit. Amint egy töredék megváltozik, az egész oldal érvénytelenné válik. Ha a töredékeket pl. `frag1` és `frag2` kulcsok alatt tároljuk, használjuk ezt:

```php
$dependencies[Cache::Items] = ['frag1', 'frag2'];
```

A lejáratot saját függvényekkel vagy statikus metódusokkal is vezérelhetjük, amelyek minden olvasáskor eldöntik, hogy az elem még érvényes-e. Így például lejárathatunk egy elemet mindig, amikor a PHP verziója megváltozik. Létrehozunk egy függvényt, amely összehasonlítja az aktuális verziót a paraméterrel, és a mentéskor hozzáadjuk a függőségek közé a `[függvény neve, ...argumentumok]` formátumú tömböt:

```php
function checkPhpVersion($ver): bool
{
	return $ver === PHP_VERSION_ID;
}

$dependencies[Cache::Callbacks] = [
	['checkPhpVersion', PHP_VERSION_ID] // járjon le, ha checkPhpVersion(...) === false
];
```

Természetesen minden kritérium kombinálható. A cache akkor jár le, ha legalább egy kritérium nem teljesül.

```php
$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';
```


Érvénytelenítés tag-ekkel
-------------------------

Nagyon hasznos érvénytelenítő eszközök az úgynevezett tag-ek. Minden cache-beli elemhez hozzárendelhetünk egy tag-listát, amelyek tetszőleges stringek. Legyen például egy HTML oldalunk egy cikkel és hozzászólásokkal, amelyet gyorsítótárazni fogunk. Mentéskor megadjuk a tag-eket:

```php
$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];
```

Lépjünk át az adminisztrációba. Itt találunk egy űrlapot a cikk szerkesztéséhez. A cikk adatbázisba mentésével együtt meghívjuk a `clean()` parancsot, amely törli a cache-ből az elemeket a tag alapján:

```php
$cache->clean([
	$cache::Tags => ["article/$articleId"],
]);
```

Ugyanígy az új hozzászólás hozzáadásának (vagy egy hozzászólás szerkesztésének) helyén ne felejtsük el érvényteleníteni a megfelelő tag-et:

```php
$cache->clean([
	$cache::Tags => ["comments/$articleId"],
]);
```

Mit értünk el ezzel? Azt, hogy a HTML cache érvénytelenné válik (törlődik), amikor a cikk vagy a hozzászólások megváltoznak. Ha egy 10-es ID-jú cikket szerkesztünk, akkor kényszerített érvénytelenítés történik az `article/10` tag-re, és a HTML oldal, amely ezt a tag-et hordozza, törlődik a cache-ből. Ugyanez történik egy új hozzászólás beszúrásakor a megfelelő cikk alá.

.[note]
A tag-ekhez úgynevezett [#Journal] szükséges.


Érvénytelenítés prioritással
----------------------------

Az egyes cache-elemekhez beállíthatunk prioritást, amellyel törölhetjük őket, ha például a cache meghalad egy bizonyos méretet:

```php
$dependencies[Cache::Priority] = 50;
```

Töröljük az összes elemet, amelyek prioritása 100 vagy annál kisebb:

```php
$cache->clean([
	$cache::Priority => 100,
]);
```

.[note]
A prioritásokhoz úgynevezett [#Journal] szükséges.


Cache törlése
-------------

A `Cache::All` paraméter mindent töröl:

```php
$cache->clean([
	$cache::All => true,
]);
```


Tömeges olvasás
===============

A cache-ből való tömeges olvasásra és írásra a `bulkLoad()` metódus szolgál, amelynek átadunk egy kulcstömböt, és egy értéktömböt kapunk vissza:

```php
$values = $cache->bulkLoad($keys);
```

A `bulkLoad()` metódus hasonlóan működik, mint a `load()`, a második paraméter callbackkel is, amelynek átadódik a generált elem kulcsa:

```php
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
	$computedValue = /* ... */; // költséges számítás
	return $computedValue;
});
```


Használat PSR-16-tal .{data-version:3.3.1}
==========================================

A Nette Cache PSR-16 interfésszel való használatához használhatja a `PsrCacheAdapter` adaptert. Lehetővé teszi a zökkenőmentes integrációt a Nette Cache és bármely olyan kód vagy könyvtár között, amely PSR-16 kompatibilis cache-t vár.

```php
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
```

Most már használhatja a `$psrCache`-t PSR-16 cache-ként:

```php
$psrCache->set('key', 'value', 3600); // 1 órára menti az értéket
$value = $psrCache->get('key', 'default');
```

Az adapter támogatja az összes PSR-16-ban definiált metódust, beleértve a `getMultiple()`, `setMultiple()` és `deleteMultiple()` metódusokat is.


Kimenet gyorsítótárazása
========================

Nagyon elegánsan lehet a kimenetet elfogni és gyorsítótárazni:

```php
if ($capture = $cache->capture($key)) {

	echo ... // kiírjuk az adatokat

	$capture->end(); // elmentjük a kimenetet a cache-be
}
```

Abban az esetben, ha a kimenet már a cache-ben van, a `capture()` metódus kiírja azt és `null`-t ad vissza, tehát a feltétel nem teljesül. Ellenkező esetben elkezdi a kimenet elfogását és visszaadja a `$capture` objektumot, amelynek segítségével végül elmentjük a kiírt adatokat a cache-be.

.[note]
A 3.0-s verzióban a metódus neve `$cache->start()` volt.


Gyorsítótárazás Latte-ban
=========================

A sablonokban való gyorsítótárazás a [Latte|latte:]-ban nagyon egyszerű, csak a sablon egy részét kell `{cache}...{/cache}` tagekkel körbevenni. A cache automatikusan érvénytelenné válik, amikor a forrás sablon megváltozik (beleértve az esetlegesen beillesztett sablonokat a cache blokkon belül). A `{cache}` tagek egymásba ágyazhatók, és ha egy beágyazott blokk érvénytelenné válik (például egy tag miatt), akkor a fölérendelt blokk is érvénytelenné válik.

A tagben megadhatók kulcsok, amelyekhez a cache kötődni fog (itt a `$id` változó), és beállítható a lejárat és a [címkék az érvénytelenítéshez |#Érvénytelenítés tag-ekkel].

```latte
{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
	...
{/cache}
```

Minden elem opcionális, így nem kell megadnunk sem a lejáratot, sem a címkéket, végül még a kulcsokat sem.

A cache használata feltételhez is köthető az `if` segítségével - a tartalom csak akkor lesz gyorsítótárazva, ha a feltétel teljesül:

```latte
{cache $id, if: !$form->isSubmitted()}
	{$form}
{/cache}
```


Tárolók
=======

A tároló egy objektum, amely azt a helyet képviseli, ahol az adatok fizikailag tárolódnak. Használhatunk adatbázist, Memcached szervert, vagy a leginkább elérhető tárolót, ami a lemezen lévő fájlok.

|-----------------
| Tároló | Leírás
|-----------------
| [#FileStorage] | alapértelmezett tároló, amely a lemezen lévő fájlokba ment
| [#MemcachedStorage] | `Memcached` szervert használ
| [#MemoryStorage] | az adatok ideiglenesen a memóriában vannak
| [#SQLiteStorage] | az adatok SQLite adatbázisba mentődnek
| [#DevNullStorage] | az adatok nem mentődnek, tesztelésre alkalmas

A tároló objektumhoz úgy juthat hozzá, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével kéri át a `Nette\Caching\Storage` típussal. Alapértelmezett tárolóként a Nette a FileStorage objektumot biztosítja, amely az adatokat az [ideiglenes fájlok |application:bootstrapping#Ideiglenes fájlok] könyvtárában lévő `cache` alkönyvtárba menti.

A tárolót a konfigurációban módosíthatja:

```neon
services:
	cache.storage: Nette\Caching\Storages\DevNullStorage
```


FileStorage
-----------

A cache-t fájlokba írja a lemezen. A `Nette\Caching\Storages\FileStorage` tároló nagyon jól optimalizált a teljesítményre, és mindenekelőtt biztosítja a műveletek teljes atomicitását. Mit jelent ez? Azt, hogy a cache használatakor nem fordulhat elő, hogy olyan fájlt olvassunk be, amelyet egy másik szál még nem írt ki teljesen, vagy hogy valaki "a kezünk alól" törölje azt. A cache használata tehát teljesen biztonságos.

Ez a tároló egy fontos beépített funkcióval is rendelkezik, amely megakadályozza a CPU extrém kihasználtságának növekedését abban a pillanatban, amikor a cache törlődik vagy még nincs felmelegítve (azaz létrehozva). Ez a "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede elleni védelem. Előfordul, hogy egy időben több párhuzamos kérés érkezik, amelyek ugyanazt a dolgot akarják a cache-ből (pl. egy drága SQL lekérdezés eredményét), és mivel az nincs a gyorsítótárban, minden folyamat ugyanazt az SQL lekérdezést kezdi el végrehajtani. A terhelés így megsokszorozódik, és akár az is előfordulhat, hogy egyetlen szál sem tud válaszolni az időkorláton belül, a cache nem jön létre, és az alkalmazás összeomlik. Szerencsére a Nette cache úgy működik, hogy több párhuzamos kérés esetén egy elemre csak az első szál generálja azt, a többiek várnak, majd felhasználják a generált eredményt.

Példa a FileStorage létrehozására:

```php
// a tároló a '/path/to/temp' könyvtár lesz a lemezen
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');
```


MemcachedStorage
----------------

A [Memcached|https://memcached.org] szerver egy nagy teljesítményű, elosztott memóriában történő tárolási rendszer, amelynek adaptere a `Nette\Caching\Storages\MemcachedStorage`. A konfigurációban megadjuk az IP-címet és a portot, ha az eltér a standard 11211-től.

.[caution]
Szükséges a `memcached` PHP kiterjesztés.

```neon
services:
	cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
```


MemoryStorage
-------------

A `Nette\Caching\Storages\MemoryStorage` egy olyan tároló, amely az adatokat egy PHP tömbben tárolja, és így a kérés befejeztével elvesznek.


SQLiteStorage
-------------

Az SQLite adatbázis és az `Nette\Caching\Storages\SQLiteStorage` adapter lehetőséget kínál a cache egyetlen fájlba történő mentésére a lemezen. A konfigurációban megadjuk ennek a fájlnak az elérési útját.

.[caution]
Szükséges a `pdo` és `pdo_sqlite` PHP kiterjesztés.

```neon
services:
	cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
```


DevNullStorage
--------------

A tároló speciális implementációja a `Nette\Caching\Storages\DevNullStorage`, amely valójában egyáltalán nem tárol adatokat. Így tesztelésre alkalmas, amikor ki akarjuk küszöbölni a cache hatását.


Cache használata a kódban
=========================

A cache kódban való használatakor kétféleképpen járhatunk el. Az első az, hogy [dependency injection |dependency-injection:passing-dependencies] segítségével átkérjük a tárolót, és létrehozunk egy `Cache` objektumot:

```php
use Nette;

class ClassOne
{
	private Nette\Caching\Cache $cache;

	public function __construct(Nette\Caching\Storage $storage)
	{
		$this->cache = new Nette\Caching\Cache($storage, 'my-namespace');
	}
}
```

A második lehetőség az, hogy közvetlenül a `Cache` objektumot kérjük át:

```php
class ClassTwo
{
	public function __construct(
		private Nette\Caching\Cache $cache,
	) {
	}
}
```

A `Cache` objektumot ezután közvetlenül a konfigurációban hozzuk létre ezzel a módszerrel:

```neon
services:
	- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )
```


Journal
=======

A Nette a címkéket és prioritásokat az úgynevezett journalba menti. Alapértelmezés szerint ehhez SQLite-ot és a `journal.s3db` fájlt használja, és **szükséges a `pdo` és `pdo_sqlite` PHP kiterjesztés.**

A journalt a konfigurációban módosíthatja:

```neon
services:
	cache.journal: MyJournal
```


DI szolgáltatások
=================

Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez:

| Név           | Típus                        | Leírás
|----------------------------------------------------------
| `cache.journal`  |  [api:Nette\Caching\Storages\Journal]  |  journal
| `cache.storage`  |  [api:Nette\Caching\Storage]           |  tároló


Cache kikapcsolása
==================

Az alkalmazásban a cache kikapcsolásának egyik módja, ha a [#DevNullStorage]-t állítjuk be tárolóként:

```neon
services:
	cache.storage: Nette\Caching\Storages\DevNullStorage
```

Ez a beállítás nincs hatással a sablonok gyorsítótárazására a Latte-ban vagy a DI konténerben, mivel ezek a könyvtárak nem használják a nette/caching szolgáltatásait, és önállóan kezelik a cache-t. Egyébként a cache-üket [nem szükséges kikapcsolni |nette:troubleshooting#Hogyan kapcsoljuk ki a cache-t fejlesztés közben] fejlesztői módban.


{{leftbar: nette:@menu-topics}}

Nette Caching

A Cache felgyorsítja az alkalmazást azáltal, hogy az egyszer nehezen megszerzett adatokat elmenti a későbbi felhasználásra. Megmutatjuk:

  • hogyan használjuk a cache-t
  • hogyan változtassuk meg a tárolót
  • hogyan érvénytelenítsük helyesen a cache-t

A cache használata a Nette-ben nagyon egyszerű, miközben nagyon fejlett igényeket is lefed. Teljesítményre és 100%-os ellenállóságra tervezték. Alapból adaptereket talál a leggyakoribb háttértárolókhoz. Lehetővé teszi a tag-ek alapján történő érvénytelenítést, az időbeli lejárást, védelmet nyújt a cache stampede ellen stb.

Telepítés

A könyvtárat a Composer eszközzel töltheti le és telepítheti:

composer require nette/caching

Alapvető használat

A cache-sel vagy gyorsítótárral való munka középpontjában az Nette\Caching\Cache objektum áll. Létrehozunk egy példányt belőle, és paraméterként átadjuk a konstruktornak az úgynevezett tárolót. Ez egy olyan objektum, amely azt a helyet képviseli, ahol az adatok fizikailag tárolódnak (adatbázis, Memcached, fájlok a lemezen, …). A tárolóhoz úgy juthatunk hozzá, hogy dependency injection segítségével kérjük át a Nette\Caching\Storage típussal. Minden lényegeset megtudhat a Tárolók szakaszban.

A 3.0-s verzióban az interfésznek még volt I előtagja, tehát a neve Nette\Caching\IStorage volt. Továbbá a Cache osztály konstansai nagybetűkkel voltak írva, tehát például Cache::EXPIRE a Cache::Expire helyett.

A következő példákhoz feltételezzük, hogy létrehoztunk egy Cache aliast, és a $storage változóban van a tároló.

use Nette\Caching\Cache;

$storage = /* ... */; // instance of Nette\Caching\Storage

A cache valójában egy key–value store, tehát az adatokat kulcsok alatt olvassuk és írjuk, ugyanúgy, mint az asszociatív tömböknél. Az alkalmazások számos független részből állnak, és ha mindegyik ugyanazt a tárolót használná (képzeljünk el egyetlen könyvtárat a lemezen), előbb-utóbb kulcsütközés következne be. A Nette Framework ezt a problémát úgy oldja meg, hogy az egész teret névtérekre (alkönyvtárakra) osztja. Minden programrész ezután a saját, egyedi nevű terét használja, és így már nem fordulhat elő ütközés.

A névtér nevét a Cache osztály konstruktorának második paramétereként adjuk meg:

$cache = new Cache($storage, 'Full Html Pages');

Most már a $cache objektum segítségével olvashatunk a gyorsítótárból és írhatunk bele. Mindkettőre a load() metódus szolgál. Az első argumentum a kulcs, a második pedig egy PHP callback, amely akkor hívódik meg, ha a kulcs nem található a cache-ben. A callback generálja az értéket, visszaadja, és az elmentődik a cache-be:

$value = $cache->load($key, function () use ($key) {
	$computedValue = /* ... */; // költséges számítás
	return $computedValue;
});

Ha a második paramétert nem adjuk meg $value = $cache->load($key), akkor null-t ad vissza, ha az elem nincs a cache-ben.

Nagyszerű, hogy a cache-be bármilyen szerializálható struktúrát tárolhatunk, nem csak stringeket. És ugyanez igaz még a kulcsokra is.

Az elemet a gyorsítótárból a remove() metódussal töröljük:

$cache->remove($key);

Elemet a gyorsítótárba a $cache->save($key, $value, array $dependencies = []) metódussal is menthetünk. Azonban a fentebb bemutatott load() használata preferált.

Memoizáció

A memoizáció egy függvény vagy metódus hívásának eredményének gyorsítótárazását jelenti, hogy legközelebb újra felhasználhassuk anélkül, hogy újra kiszámítanánk ugyanazt.

Metódusokat és függvényeket memoizáltan hívhatunk a call(callable $callback, ...$args) segítségével:

$result = $cache->call('gethostbyaddr', $ip);

A gethostbyaddr() függvény így minden $ip paraméterre csak egyszer hívódik meg, és legközelebb már a cache-ből adódik vissza az érték.

Lehetőség van arra is, hogy egy memoizált burkolót hozzunk létre egy metódus vagy függvény köré, amelyet később hívhatunk meg:

function factorial($num)
{
	return /* ... */;
}

$memoizedFactorial = $cache->wrap('factorial');

$result = $memoizedFactorial(5); // először kiszámítja
$result = $memoizedFactorial(5); // másodszor a cache-ből

Lejárat & érvénytelenítés

A cache-be való mentéskor felmerül a kérdés, hogy a korábban elmentett adatok mikor válnak érvénytelenné. A Nette Framework egy mechanizmust kínál az adatok érvényességének korlátozására vagy azok irányított törlésére (a keretrendszer terminológiájában „érvénytelenítésére”).

Az adatok érvényességét a mentéskor állítjuk be a save() metódus harmadik paraméterével, pl.:

$cache->save($key, $value, [
	$cache::Expire => '20 minutes',
]);

Vagy a load() metódus callbackjének referenciaként átadott $dependencies paraméterével, pl.:

$value = $cache->load($key, function (&$dependencies) {
	$dependencies[Cache::Expire] = '20 minutes';
	return /* ... */;
});

Vagy a load() metódus 3. paraméterével, pl.:

$value = $cache->load($key, function () {
	return ...;
}, [Cache::Expire => '20 minutes']);

A további példákban a második változatot feltételezzük, és így a $dependencies változó létezését.

Lejárat

A legegyszerűbb lejárat az időkorlát. Így 20 perces érvényességgel mentünk adatokat a cache-be:

// elfogadja a másodpercek számát vagy UNIX timestamp-et is
$dependencies[Cache::Expire] = '20 minutes';

Ha minden olvasással meg szeretnénk hosszabbítani az érvényességi időt, azt a következőképpen érhetjük el, de vigyázat, a cache rezsije ezzel megnő:

$dependencies[Cache::Sliding] = true;

Ügyes lehetőség, hogy az adatokat akkor járassuk le, amikor egy fájl vagy több fájl közül valamelyik megváltozik. Ezt például akkor használhatjuk, ha ezeknek a fájloknak a feldolgozásából származó adatokat mentjük a cache-be. Használjon abszolút elérési utakat.

$dependencies[Cache::Files] = '/path/to/data.yaml';
// vagy
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];

Lejárathatunk egy elemet a cache-ben akkor, amikor egy másik elem (vagy több másik közül valamelyik) lejár. Ezt akkor használhatjuk, ha például egy egész HTML oldalt mentünk a cache-be, és más kulcsok alatt annak töredékeit. Amint egy töredék megváltozik, az egész oldal érvénytelenné válik. Ha a töredékeket pl. frag1 és frag2 kulcsok alatt tároljuk, használjuk ezt:

$dependencies[Cache::Items] = ['frag1', 'frag2'];

A lejáratot saját függvényekkel vagy statikus metódusokkal is vezérelhetjük, amelyek minden olvasáskor eldöntik, hogy az elem még érvényes-e. Így például lejárathatunk egy elemet mindig, amikor a PHP verziója megváltozik. Létrehozunk egy függvényt, amely összehasonlítja az aktuális verziót a paraméterrel, és a mentéskor hozzáadjuk a függőségek közé a [függvény neve, ...argumentumok] formátumú tömböt:

function checkPhpVersion($ver): bool
{
	return $ver === PHP_VERSION_ID;
}

$dependencies[Cache::Callbacks] = [
	['checkPhpVersion', PHP_VERSION_ID] // járjon le, ha checkPhpVersion(...) === false
];

Természetesen minden kritérium kombinálható. A cache akkor jár le, ha legalább egy kritérium nem teljesül.

$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';

Érvénytelenítés tag-ekkel

Nagyon hasznos érvénytelenítő eszközök az úgynevezett tag-ek. Minden cache-beli elemhez hozzárendelhetünk egy tag-listát, amelyek tetszőleges stringek. Legyen például egy HTML oldalunk egy cikkel és hozzászólásokkal, amelyet gyorsítótárazni fogunk. Mentéskor megadjuk a tag-eket:

$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];

Lépjünk át az adminisztrációba. Itt találunk egy űrlapot a cikk szerkesztéséhez. A cikk adatbázisba mentésével együtt meghívjuk a clean() parancsot, amely törli a cache-ből az elemeket a tag alapján:

$cache->clean([
	$cache::Tags => ["article/$articleId"],
]);

Ugyanígy az új hozzászólás hozzáadásának (vagy egy hozzászólás szerkesztésének) helyén ne felejtsük el érvényteleníteni a megfelelő tag-et:

$cache->clean([
	$cache::Tags => ["comments/$articleId"],
]);

Mit értünk el ezzel? Azt, hogy a HTML cache érvénytelenné válik (törlődik), amikor a cikk vagy a hozzászólások megváltoznak. Ha egy 10-es ID-jú cikket szerkesztünk, akkor kényszerített érvénytelenítés történik az article/10 tag-re, és a HTML oldal, amely ezt a tag-et hordozza, törlődik a cache-ből. Ugyanez történik egy új hozzászólás beszúrásakor a megfelelő cikk alá.

A tag-ekhez úgynevezett Journal szükséges.

Érvénytelenítés prioritással

Az egyes cache-elemekhez beállíthatunk prioritást, amellyel törölhetjük őket, ha például a cache meghalad egy bizonyos méretet:

$dependencies[Cache::Priority] = 50;

Töröljük az összes elemet, amelyek prioritása 100 vagy annál kisebb:

$cache->clean([
	$cache::Priority => 100,
]);

A prioritásokhoz úgynevezett Journal szükséges.

Cache törlése

A Cache::All paraméter mindent töröl:

$cache->clean([
	$cache::All => true,
]);

Tömeges olvasás

A cache-ből való tömeges olvasásra és írásra a bulkLoad() metódus szolgál, amelynek átadunk egy kulcstömböt, és egy értéktömböt kapunk vissza:

$values = $cache->bulkLoad($keys);

A bulkLoad() metódus hasonlóan működik, mint a load(), a második paraméter callbackkel is, amelynek átadódik a generált elem kulcsa:

$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
	$computedValue = /* ... */; // költséges számítás
	return $computedValue;
});

Használat PSR-16-tal

A Nette Cache PSR-16 interfésszel való használatához használhatja a PsrCacheAdapter adaptert. Lehetővé teszi a zökkenőmentes integrációt a Nette Cache és bármely olyan kód vagy könyvtár között, amely PSR-16 kompatibilis cache-t vár.

$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);

Most már használhatja a $psrCache-t PSR-16 cache-ként:

$psrCache->set('key', 'value', 3600); // 1 órára menti az értéket
$value = $psrCache->get('key', 'default');

Az adapter támogatja az összes PSR-16-ban definiált metódust, beleértve a getMultiple(), setMultiple() és deleteMultiple() metódusokat is.

Kimenet gyorsítótárazása

Nagyon elegánsan lehet a kimenetet elfogni és gyorsítótárazni:

if ($capture = $cache->capture($key)) {

	echo ... // kiírjuk az adatokat

	$capture->end(); // elmentjük a kimenetet a cache-be
}

Abban az esetben, ha a kimenet már a cache-ben van, a capture() metódus kiírja azt és null-t ad vissza, tehát a feltétel nem teljesül. Ellenkező esetben elkezdi a kimenet elfogását és visszaadja a $capture objektumot, amelynek segítségével végül elmentjük a kiírt adatokat a cache-be.

A 3.0-s verzióban a metódus neve $cache->start() volt.

Gyorsítótárazás Latte-ban

A sablonokban való gyorsítótárazás a Latte-ban nagyon egyszerű, csak a sablon egy részét kell {cache}...{/cache} tagekkel körbevenni. A cache automatikusan érvénytelenné válik, amikor a forrás sablon megváltozik (beleértve az esetlegesen beillesztett sablonokat a cache blokkon belül). A {cache} tagek egymásba ágyazhatók, és ha egy beágyazott blokk érvénytelenné válik (például egy tag miatt), akkor a fölérendelt blokk is érvénytelenné válik.

A tagben megadhatók kulcsok, amelyekhez a cache kötődni fog (itt a $id változó), és beállítható a lejárat és a címkék az érvénytelenítéshez.

{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
	...
{/cache}

Minden elem opcionális, így nem kell megadnunk sem a lejáratot, sem a címkéket, végül még a kulcsokat sem.

A cache használata feltételhez is köthető az if segítségével – a tartalom csak akkor lesz gyorsítótárazva, ha a feltétel teljesül:

{cache $id, if: !$form->isSubmitted()}
	{$form}
{/cache}

Tárolók

A tároló egy objektum, amely azt a helyet képviseli, ahol az adatok fizikailag tárolódnak. Használhatunk adatbázist, Memcached szervert, vagy a leginkább elérhető tárolót, ami a lemezen lévő fájlok.

Tároló Leírás
FileStorage alapértelmezett tároló, amely a lemezen lévő fájlokba ment
MemcachedStorage Memcached szervert használ
MemoryStorage az adatok ideiglenesen a memóriában vannak
SQLiteStorage az adatok SQLite adatbázisba mentődnek
DevNullStorage az adatok nem mentődnek, tesztelésre alkalmas

A tároló objektumhoz úgy juthat hozzá, hogy dependency injection segítségével kéri át a Nette\Caching\Storage típussal. Alapértelmezett tárolóként a Nette a FileStorage objektumot biztosítja, amely az adatokat az ideiglenes fájlok könyvtárában lévő cache alkönyvtárba menti.

A tárolót a konfigurációban módosíthatja:

services:
	cache.storage: Nette\Caching\Storages\DevNullStorage

FileStorage

A cache-t fájlokba írja a lemezen. A Nette\Caching\Storages\FileStorage tároló nagyon jól optimalizált a teljesítményre, és mindenekelőtt biztosítja a műveletek teljes atomicitását. Mit jelent ez? Azt, hogy a cache használatakor nem fordulhat elő, hogy olyan fájlt olvassunk be, amelyet egy másik szál még nem írt ki teljesen, vagy hogy valaki „a kezünk alól“ törölje azt. A cache használata tehát teljesen biztonságos.

Ez a tároló egy fontos beépített funkcióval is rendelkezik, amely megakadályozza a CPU extrém kihasználtságának növekedését abban a pillanatban, amikor a cache törlődik vagy még nincs felmelegítve (azaz létrehozva). Ez a cache stampede elleni védelem. Előfordul, hogy egy időben több párhuzamos kérés érkezik, amelyek ugyanazt a dolgot akarják a cache-ből (pl. egy drága SQL lekérdezés eredményét), és mivel az nincs a gyorsítótárban, minden folyamat ugyanazt az SQL lekérdezést kezdi el végrehajtani. A terhelés így megsokszorozódik, és akár az is előfordulhat, hogy egyetlen szál sem tud válaszolni az időkorláton belül, a cache nem jön létre, és az alkalmazás összeomlik. Szerencsére a Nette cache úgy működik, hogy több párhuzamos kérés esetén egy elemre csak az első szál generálja azt, a többiek várnak, majd felhasználják a generált eredményt.

Példa a FileStorage létrehozására:

// a tároló a '/path/to/temp' könyvtár lesz a lemezen
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');

MemcachedStorage

Memcached szerver egy nagy teljesítményű, elosztott memóriában történő tárolási rendszer, amelynek adaptere a Nette\Caching\Storages\MemcachedStorage. A konfigurációban megadjuk az IP-címet és a portot, ha az eltér a standard 11211-től.

Szükséges a memcached PHP kiterjesztés.

services:
	cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')

MemoryStorage

A Nette\Caching\Storages\MemoryStorage egy olyan tároló, amely az adatokat egy PHP tömbben tárolja, és így a kérés befejeztével elvesznek.

SQLiteStorage

Az SQLite adatbázis és az Nette\Caching\Storages\SQLiteStorage adapter lehetőséget kínál a cache egyetlen fájlba történő mentésére a lemezen. A konfigurációban megadjuk ennek a fájlnak az elérési útját.

Szükséges a pdo és pdo_sqlite PHP kiterjesztés.

services:
	cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')

DevNullStorage

A tároló speciális implementációja a Nette\Caching\Storages\DevNullStorage, amely valójában egyáltalán nem tárol adatokat. Így tesztelésre alkalmas, amikor ki akarjuk küszöbölni a cache hatását.

Cache használata a kódban

A cache kódban való használatakor kétféleképpen járhatunk el. Az első az, hogy dependency injection segítségével átkérjük a tárolót, és létrehozunk egy Cache objektumot:

use Nette;

class ClassOne
{
	private Nette\Caching\Cache $cache;

	public function __construct(Nette\Caching\Storage $storage)
	{
		$this->cache = new Nette\Caching\Cache($storage, 'my-namespace');
	}
}

A második lehetőség az, hogy közvetlenül a Cache objektumot kérjük át:

class ClassTwo
{
	public function __construct(
		private Nette\Caching\Cache $cache,
	) {
	}
}

A Cache objektumot ezután közvetlenül a konfigurációban hozzuk létre ezzel a módszerrel:

services:
	- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )

Journal

A Nette a címkéket és prioritásokat az úgynevezett journalba menti. Alapértelmezés szerint ehhez SQLite-ot és a journal.s3db fájlt használja, és szükséges a pdo és pdo_sqlite PHP kiterjesztés.

A journalt a konfigurációban módosíthatja:

services:
	cache.journal: MyJournal

DI szolgáltatások

Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez:

Név Típus Leírás
cache.journal Nette\Caching\Storages\Journal journal
cache.storage Nette\Caching\Storage tároló

Cache kikapcsolása

Az alkalmazásban a cache kikapcsolásának egyik módja, ha a DevNullStorage-t állítjuk be tárolóként:

services:
	cache.storage: Nette\Caching\Storages\DevNullStorage

Ez a beállítás nincs hatással a sablonok gyorsítótárazására a Latte-ban vagy a DI konténerben, mivel ezek a könyvtárak nem használják a nette/caching szolgáltatásait, és önállóan kezelik a cache-t. Egyébként a cache-üket nem szükséges kikapcsolni fejlesztői módban.