Nette Documentation Preview

syntax
Zwischenspeichern
*****************

<div class=perex>

Der Cache beschleunigt Ihre Anwendung, indem er Daten, die einmal hart abgerufen wurden, für die spätere Verwendung speichert. Wir zeigen es Ihnen:

- Wie Sie den Cache nutzen
- Wie Sie den Cache-Speicher ändern
- Wie man den Cache richtig ungültig macht

</div>

Die Verwendung des Cache ist in Nette sehr einfach, deckt aber auch sehr fortgeschrittene Cache-Anforderungen ab. Er ist auf Leistung und 100%ige Haltbarkeit ausgelegt. Im Grunde finden Sie Adapter für die gängigsten Backend-Speicher. Ermöglicht Tag-basierte Invalidierung, Cache-Stampede-Schutz, Zeitablauf, etc.


Installation .[#toc-installation]
=================================

Laden Sie das Paket herunter und installieren Sie es mit [Composer |best-practices:composer]:

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


Grundlegende Verwendung .[#toc-basic-usage]
===========================================

Im Mittelpunkt der Arbeit mit dem Cache steht das Objekt [api:Nette\Caching\Cache]. Wir erstellen seine Instanz und übergeben dem Konstruktor als Parameter den sogenannten Speicher. Dabei handelt es sich um ein Objekt, das den Ort repräsentiert, an dem die Daten physisch gespeichert werden (Datenbank, Memcached, Dateien auf der Festplatte, ...). Sie erhalten das Storage-Objekt, indem Sie es per [Dependency Injection |dependency-injection:passing-dependencies] mit dem Typ `Nette\Caching\Storage` übergeben. Alles Wesentliche erfahren Sie im [Abschnitt Storage |#Storages].

.[warning]
In Version 3.0 hatte die Schnittstelle noch den `I` prefix, so the name was `Nette\Caching\IStorage`. Außerdem wurden die Konstanten der Klasse `Cache` großgeschrieben, also zum Beispiel `Cache::EXPIRE` statt `Cache::Expire`.

Für die folgenden Beispiele nehmen wir an, wir haben einen Alias `Cache` und eine Speicherung in der Variablen `$storage`.

```php
use Nette\Caching\Cache;

$storage = /* ... */; // Instanz von Nette\Caching\Storage
```

Der Cache ist eigentlich ein *Schlüsselwertspeicher*, d. h. wir lesen und schreiben Daten unter Schlüsseln, genau wie bei assoziativen Arrays. Anwendungen bestehen aus einer Reihe unabhängiger Teile, und wenn sie alle einen Speicher verwenden würden (z. B. ein Verzeichnis auf einer Festplatte), käme es früher oder später zu einer Schlüsselkollision. Das Nette Framework löst das Problem, indem es den gesamten Speicherplatz in Namensräume (Unterverzeichnisse) unterteilt. Jeder Teil des Programms verwendet dann seinen eigenen Bereich mit einem eindeutigen Namen, und es können keine Kollisionen auftreten.

Der Name des Spaces wird als zweiter Parameter des Konstruktors der Cache-Klasse angegeben:

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

Wir können nun das Objekt `$cache` verwenden, um aus dem Cache zu lesen und zu schreiben. Die Methode `load()` wird für beides verwendet. Das erste Argument ist der Schlüssel und das zweite ist der PHP-Callback, der aufgerufen wird, wenn der Schlüssel nicht im Cache gefunden wird. Der Callback generiert einen Wert, gibt ihn zurück und speichert ihn im Cache:

```php
$value = $cache->load($key, function () use ($key) {
	$computedValue = /* ... */; // schwere Berechnungen
	return $computedValue;
});
```

Wenn der zweite Parameter nicht angegeben wird `$value = $cache->load($key)`, wird `null` zurückgegeben, wenn sich das Element nicht im Cache befindet.

.[tip]
Das Tolle daran ist, dass beliebige serialisierbare Strukturen zwischengespeichert werden können, nicht nur Zeichenketten. Dasselbe gilt auch für Schlüssel.

Das Element wird mit der Methode `remove()` aus dem Cache geleert:

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

Sie können ein Element auch mit der Methode `$cache->save($key, $value, array $dependencies = [])` zwischenspeichern. Die obige Methode mit `load()` ist jedoch vorzuziehen.


Zwischenspeicherung .[#toc-memoization]
=======================================

Memoisierung bedeutet, dass das Ergebnis einer Funktion oder Methode zwischengespeichert wird, so dass Sie es beim nächsten Mal verwenden können, anstatt immer wieder dasselbe zu berechnen.

Methoden und Funktionen können mit `call(callable $callback, ...$args)` memoisiert aufgerufen werden:

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

Die Funktion `gethostbyaddr()` wird nur einmal für jeden Parameter `$ip` aufgerufen und beim nächsten Mal wird der Wert aus dem Cache zurückgegeben.

Es ist auch möglich, einen memoisierten Wrapper für eine Methode oder Funktion zu erstellen, der später aufgerufen werden kann:

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

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

$result = $memoizedFactorial(5); // zählt es
$result = $memoizedFactorial(5); // gibt es aus dem Cache zurück
```


Verfall & Ungültigmachung .[#toc-expiration-invalidation]
=========================================================

Bei der Zwischenspeicherung ist es notwendig, sich mit der Frage zu befassen, dass einige der zuvor gespeicherten Daten mit der Zeit ungültig werden. Nette Framework bietet einen Mechanismus, um die Gültigkeit von Daten zu begrenzen und sie auf kontrollierte Weise zu löschen ("sie ungültig zu machen", um die Terminologie des Frameworks zu verwenden).

Die Gültigkeit der Daten wird zum Zeitpunkt des Speicherns mit dem dritten Parameter der Methode `save()` festgelegt, z. B:

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

Oder über den Parameter `$dependencies`, der als Referenz an den Callback in der Methode `load()` übergeben wird, z. B:

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

Oder unter Verwendung des 3. Parameters in der Methode `load()`, z. B:

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

In den folgenden Beispielen gehen wir von der zweiten Variante und damit vom Vorhandensein einer Variablen `$dependencies` aus.


Verfall .[#toc-expiration]
--------------------------

Die einfachste Verfallszeit ist das Zeitlimit. Hier sehen Sie, wie Sie Daten mit einer Gültigkeit von 20 Minuten zwischenspeichern können:

```php
// er akzeptiert auch die Anzahl der Sekunden oder den UNIX-Zeitstempel
$dependencies[Cache::Expire] = '20 minutes';
```

Wenn wir die Gültigkeitsdauer bei jedem Lesen verlängern wollen, kann dies auf diese Weise erreicht werden, aber Achtung, dies erhöht den Cache-Overhead:

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

Die praktische Option ist die Möglichkeit, die Daten ablaufen zu lassen, wenn eine bestimmte Datei oder eine von mehreren Dateien geändert wird. Dies kann z.B. für die Zwischenspeicherung von Daten verwendet werden, die aus der Verarbeitung dieser Dateien resultieren. Absolute Pfade verwenden.

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

Wir können ein Element im Cache ablaufen lassen, wenn ein anderes Element (oder eines von mehreren anderen) abläuft. Dies kann verwendet werden, wenn wir die gesamte HTML-Seite und Fragmente davon unter anderen Schlüsseln zwischenspeichern. Sobald sich das Snippet ändert, wird die gesamte Seite ungültig. Wenn wir Fragmente unter Schlüsseln wie `frag1` und `frag2` gespeichert haben, verwenden wir:

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

Der Ablauf kann auch über benutzerdefinierte Funktionen oder statische Methoden gesteuert werden, die immer beim Lesen entscheiden, ob das Element noch gültig ist. Zum Beispiel können wir das Element verfallen lassen, wenn sich die PHP-Version ändert. Wir erstellen eine Funktion, die die aktuelle Version mit dem Parameter vergleicht, und beim Speichern fügen wir ein Array in der Form `[function name, ...arguments]` zu den Abhängigkeiten:

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

$dependencies[Cache::Callbacks] = [
	['checkPhpVersion', PHP_VERSION_ID] // ablaufen, wenn checkPhpVersion(...) === false
];
```

Natürlich können alle Kriterien kombiniert werden. Der Cache verfällt dann, wenn mindestens ein Kriterium nicht erfüllt ist.

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


Invalidierung mit Tags .[#toc-invalidation-using-tags]
------------------------------------------------------

Tags sind ein sehr nützliches Werkzeug zur Invalidierung. Wir können jedem im Cache gespeicherten Element eine Liste von Tags zuweisen, bei denen es sich um beliebige Zeichenfolgen handelt. Nehmen wir zum Beispiel an, wir haben eine HTML-Seite mit einem Artikel und Kommentaren, die wir zwischenspeichern wollen. Wir geben also beim Speichern im Cache Tags an:

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

Wechseln wir nun zur Verwaltung. Hier haben wir ein Formular zur Bearbeitung von Artikeln. Zusammen mit dem Speichern des Artikels in einer Datenbank rufen wir den Befehl `clean()` auf, der zwischengespeicherte Artikel nach Tags löscht:

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

An der Stelle, an der wir einen neuen Kommentar hinzufügen (oder einen Kommentar bearbeiten), vergessen wir nicht, das entsprechende Tag zu deaktivieren:

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

Was haben wir erreicht? Dass unser HTML-Cache ungültig gemacht (gelöscht) wird, wenn sich der Artikel oder die Kommentare ändern. Wenn Sie einen Artikel mit ID = 10 bearbeiten, wird der Tag `article/10` zwangsweise ungültig gemacht und die HTML-Seite, die den Tag enthält, aus dem Cache gelöscht. Dasselbe geschieht, wenn Sie einen neuen Kommentar unter dem betreffenden Artikel einfügen.

.[note]
Tags erfordern [Journal |#Journal].


Invalidierung nach Priorität .[#toc-invalidation-by-priority]
-------------------------------------------------------------

Wir können die Priorität für einzelne Elemente im Cache festlegen, und es wird möglich sein, sie kontrolliert zu löschen, wenn der Cache z. B. eine bestimmte Größe überschreitet:

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

Löschen Sie alle Objekte mit einer Priorität gleich oder kleiner als 100:

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

.[note]
Prioritäten erfordern das sogenannte [Journal |#Journal].


Cache löschen .[#toc-clear-cache]
---------------------------------

Der Parameter `Cache::All` löscht alles:

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


Bulk Reading .[#toc-bulk-reading]
=================================

Zum massenhaften Lesen und Schreiben in den Cache wird die Methode `bulkLoad()` verwendet, bei der wir ein Array von Schlüsseln übergeben und ein Array von Werten erhalten:

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

Die Methode `bulkLoad()` funktioniert ähnlich wie `load()` mit dem zweiten Callback-Parameter, an den der Schlüssel des erzeugten Elements übergeben wird:

```php
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
	$computedValue = /* ... */; // schwere Berechnungen
	return $computedValue;
});
```


Verwendung mit PSR-16 .[#toc-using-with-psr-16]
===============================================

Um Nette Cache mit der PSR-16-Schnittstelle zu verwenden, können Sie die `PsrCacheAdapter` verwenden. Sie ermöglicht eine nahtlose Integration zwischen Nette Cache und jedem Code oder jeder Bibliothek, die einen PSR-16-kompatiblen Cache erwartet.

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

Jetzt können Sie `$psrCache` als PSR-16-Cache verwenden:

```php
$psrCache->set('key', 'value', 3600); // speichert den Wert für 1 Stunde
$value = $psrCache->get('key', 'default');
```

Der Adapter unterstützt alle in PSR-16 definierten Methoden, einschließlich `getMultiple()`, `setMultiple()` und `deleteMultiple()`.


Zwischenspeicherung der Ausgabe .[#toc-output-caching]
======================================================

Die Ausgabe kann auf sehr elegante Weise erfasst und zwischengespeichert werden:

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

	echo ... // Drucken einiger Daten

	$capture->end(); // Speichern der Ausgabe im Cache
}
```

Falls die Ausgabe bereits im Cache vorhanden ist, druckt die Methode `capture()` sie aus und gibt `null` zurück, so dass die Bedingung nicht ausgeführt wird. Andernfalls beginnt sie, die Ausgabe zu puffern und gibt das Objekt `$capture` zurück, mit dem wir die Daten schließlich im Cache speichern.

.[note]
In Version 3.0 hieß die Methode `$cache->start()`.


Caching in Latte .[#toc-caching-in-latte]
=========================================

Das Zwischenspeichern in [Latte-Vorlagen |latte:] ist sehr einfach, indem man einen Teil der Vorlage mit Tags umgibt `{cache}...{/cache}`. Der Cache wird automatisch ungültig gemacht, wenn sich die Quellvorlage ändert (einschließlich aller in den `{cache}` -Tags enthaltenen Vorlagen). Tags `{cache}` können verschachtelt werden, und wenn ein verschachtelter Block ungültig wird (z. B. durch ein Tag), wird auch der übergeordnete Block ungültig.

Im Tag können die Schlüssel angegeben werden, an die der Cache gebunden werden soll (hier die Variable `$id`), und die Ablauf- und [Ungültigkeits-Tags |#Invalidation using Tags] gesetzt werden

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

Alle Parameter sind optional, d.h. Sie müssen weder Verfallsdatum noch Tags oder Schlüssel angeben.

Die Verwendung des Cache kann auch durch `if` bedingt werden - der Inhalt wird dann nur dann zwischengespeichert, wenn die Bedingung erfüllt ist:

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


Speicherplätze .[#toc-storages]
===============================

Ein Speicher ist ein Objekt, das darstellt, wo Daten physisch gespeichert werden. Wir können eine Datenbank, einen Memcached-Server oder den am häufigsten verwendeten Speicher, nämlich Dateien auf der Festplatte, verwenden.

|----------------------
| Speicher | Beschreibung
|----------------------
| [FileStorage |#FileStorage] | Standardspeicher mit Speicherung in Dateien auf der Festplatte
| [MemcachedStorage |#MemcachedStorage] | verwendet den `Memcached` Server
| [MemoryStorage |#MemoryStorage] | Daten werden temporär im Speicher abgelegt
| [SQLiteStorage |#SQLiteStorage] | Daten werden in einer SQLite-Datenbank gespeichert
| [DevNullStorage |#DevNullStorage] | Daten werden nicht gespeichert - für Testzwecke

Sie erhalten das Speicherobjekt, indem Sie es mittels [Dependency Injection |dependency-injection:passing-dependencies] mit dem Typ `Nette\Caching\Storage` übergeben. Standardmäßig bietet Nette ein FileStorage-Objekt, das Daten in einem Unterordner `cache` im Verzeichnis für [temporäre Dateien |application:bootstrap#Temporary Files] speichert.

Sie können den Speicherort in der Konfiguration ändern:

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


FileStorage .[#toc-filestorage]
-------------------------------

Schreibt den Cache in Dateien auf der Festplatte. Der Speicher `Nette\Caching\Storages\FileStorage` ist sehr leistungsoptimiert und gewährleistet vor allem eine vollständige Atomisierung der Operationen. Was bedeutet das? Dass es bei der Verwendung des Caches nicht passieren kann, dass wir eine Datei lesen, die von einem anderen Thread noch nicht vollständig geschrieben wurde, oder dass jemand sie "unter der Hand" löscht. Die Nutzung des Caches ist also völlig sicher.

Dieser Speicher hat auch eine wichtige eingebaute Funktion, die einen extremen Anstieg der CPU-Auslastung verhindert, wenn der Cache geleert oder kalt (d.h. nicht erstellt) ist. Dies ist die "Cache-Stampede"-P:https://en.wikipedia.org/wiki/Cache_stampede rävention.
Es kommt vor, dass zu einem Zeitpunkt mehrere gleichzeitige Anfragen das Gleiche aus dem Cache wollen (z. B. das Ergebnis einer teuren SQL-Abfrage), und da es nicht zwischengespeichert ist, beginnen alle Prozesse mit der Ausführung derselben SQL-Abfrage.
Die Prozessorlast vervielfacht sich und es kann sogar passieren, dass kein Thread innerhalb des Zeitlimits antworten kann, der Cache nicht erstellt wird und die Anwendung abstürzt.
Glücklicherweise funktioniert der Cache in Nette so, dass bei mehreren gleichzeitigen Anfragen für ein Element dieses nur vom ersten Thread erzeugt wird, die anderen warten und verwenden dann das erzeugte Ergebnis.

Beispiel für die Erstellung eines FileStorage:

```php
// der Speicher wird das Verzeichnis '/pfad/zu/temp' auf der Festplatte sein
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');
```


MemcachedStorage .[#toc-memcachedstorage]
-----------------------------------------

Der Server [Memcached |https://memcached.org] ist ein hochleistungsfähiges verteiltes Speichersystem, dessen Adapter `Nette\Caching\Storages\MemcachedStorage` ist. Geben Sie in der Konfiguration die IP-Adresse und den Port an, falls dieser vom Standard 11211 abweicht.

.[caution]
Erfordert die PHP-Erweiterung `memcached`.

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


SpeicherStorage .[#toc-memorystorage]
-------------------------------------

`Nette\Caching\Storages\MemoryStorage` ist ein Speicher, der Daten in einem PHP-Array speichert und daher verloren geht, wenn die Anfrage beendet wird.


SQLiteStorage .[#toc-sqlitestorage]
-----------------------------------

Die SQLite-Datenbank und der SQLite-Adapter `Nette\Caching\Storages\SQLiteStorage` bieten eine Möglichkeit, in einer einzigen Datei auf der Festplatte zu speichern. In der Konfiguration wird der Pfad zu dieser Datei angegeben.

.[caution]
Erfordert die PHP-Erweiterungen `pdo` und `pdo_sqlite`.

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


DevNullStorage .[#toc-devnullstorage]
-------------------------------------

Eine spezielle Implementierung des Speichers ist `Nette\Caching\Storages\DevNullStorage`, der eigentlich gar keine Daten speichert. Sie eignet sich daher zum Testen, wenn wir die Wirkung des Cache ausschalten wollen.


Verwendung von Cache im Code .[#toc-using-cache-in-code]
========================================================

Bei der Verwendung von Caching im Code gibt es zwei Möglichkeiten, wie man es machen kann. Die erste ist, dass Sie das Speicherobjekt erhalten, indem Sie es mittels [Dependency Injection |dependency-injection:passing-dependencies] übergeben und dann ein Objekt `Cache` erstellen:

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

Die zweite Möglichkeit ist, dass Sie das Speicherobjekt `Cache` erhalten:

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

Das Objekt `Cache` wird dann direkt in der Konfiguration wie folgt erstellt:

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


Journal .[#toc-journal]
=======================

Nette speichert Tags und Prioritäten in einem sogenannten Journal. Standardmäßig werden dafür SQLite und die Datei `journal.s3db` verwendet, und **PHP-Erweiterungen `pdo` und `pdo_sqlite` sind erforderlich.**

Sie können das Journal in der Konfiguration ändern:

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


DI-Dienste .[#toc-di-services]
==============================

Diese Dienste werden dem DI-Container hinzugefügt:

| Name | Typ | Beschreibung
|----------------------------------------------------------
| `cache.journal` | [api:Nette\Caching\Storages\Journal] | journal
| `cache.storage` | [api:Nette\Caching\Storage] | repository


Cache ausschalten .[#toc-turning-off-cache]
===========================================

Eine Möglichkeit, die Zwischenspeicherung in der Anwendung zu deaktivieren, besteht darin, den Speicher auf [DevNullStorage |#DevNullStorage] zu setzen:

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

Diese Einstellung wirkt sich nicht auf die Zwischenspeicherung von Vorlagen in Latte oder dem DI-Container aus, da diese Bibliotheken nicht die Dienste von nette/caching nutzen und ihren Cache unabhängig verwalten. Außerdem [muss |nette:troubleshooting#how-to-disable-cache-during-development] ihr Cache im Entwicklungsmodus [nicht abgeschaltet werden |nette:troubleshooting#how-to-disable-cache-during-development].


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

Zwischenspeichern

Der Cache beschleunigt Ihre Anwendung, indem er Daten, die einmal hart abgerufen wurden, für die spätere Verwendung speichert. Wir zeigen es Ihnen:

  • Wie Sie den Cache nutzen
  • Wie Sie den Cache-Speicher ändern
  • Wie man den Cache richtig ungültig macht

Die Verwendung des Cache ist in Nette sehr einfach, deckt aber auch sehr fortgeschrittene Cache-Anforderungen ab. Er ist auf Leistung und 100%ige Haltbarkeit ausgelegt. Im Grunde finden Sie Adapter für die gängigsten Backend-Speicher. Ermöglicht Tag-basierte Invalidierung, Cache-Stampede-Schutz, Zeitablauf, etc.

Installation

Laden Sie das Paket herunter und installieren Sie es mit Composer:

composer require nette/caching

Grundlegende Verwendung

Im Mittelpunkt der Arbeit mit dem Cache steht das Objekt Nette\Caching\Cache. Wir erstellen seine Instanz und übergeben dem Konstruktor als Parameter den sogenannten Speicher. Dabei handelt es sich um ein Objekt, das den Ort repräsentiert, an dem die Daten physisch gespeichert werden (Datenbank, Memcached, Dateien auf der Festplatte, …). Sie erhalten das Storage-Objekt, indem Sie es per Dependency Injection mit dem Typ Nette\Caching\Storage übergeben. Alles Wesentliche erfahren Sie im Abschnitt Storage.

In Version 3.0 hatte die Schnittstelle noch den I prefix, so the name was Nette\Caching\IStorage. Außerdem wurden die Konstanten der Klasse Cache großgeschrieben, also zum Beispiel Cache::EXPIRE statt Cache::Expire.

Für die folgenden Beispiele nehmen wir an, wir haben einen Alias Cache und eine Speicherung in der Variablen $storage.

use Nette\Caching\Cache;

$storage = /* ... */; // Instanz von Nette\Caching\Storage

Der Cache ist eigentlich ein Schlüsselwertspeicher, d. h. wir lesen und schreiben Daten unter Schlüsseln, genau wie bei assoziativen Arrays. Anwendungen bestehen aus einer Reihe unabhängiger Teile, und wenn sie alle einen Speicher verwenden würden (z. B. ein Verzeichnis auf einer Festplatte), käme es früher oder später zu einer Schlüsselkollision. Das Nette Framework löst das Problem, indem es den gesamten Speicherplatz in Namensräume (Unterverzeichnisse) unterteilt. Jeder Teil des Programms verwendet dann seinen eigenen Bereich mit einem eindeutigen Namen, und es können keine Kollisionen auftreten.

Der Name des Spaces wird als zweiter Parameter des Konstruktors der Cache-Klasse angegeben:

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

Wir können nun das Objekt $cache verwenden, um aus dem Cache zu lesen und zu schreiben. Die Methode load() wird für beides verwendet. Das erste Argument ist der Schlüssel und das zweite ist der PHP-Callback, der aufgerufen wird, wenn der Schlüssel nicht im Cache gefunden wird. Der Callback generiert einen Wert, gibt ihn zurück und speichert ihn im Cache:

$value = $cache->load($key, function () use ($key) {
	$computedValue = /* ... */; // schwere Berechnungen
	return $computedValue;
});

Wenn der zweite Parameter nicht angegeben wird $value = $cache->load($key), wird null zurückgegeben, wenn sich das Element nicht im Cache befindet.

Das Tolle daran ist, dass beliebige serialisierbare Strukturen zwischengespeichert werden können, nicht nur Zeichenketten. Dasselbe gilt auch für Schlüssel.

Das Element wird mit der Methode remove() aus dem Cache geleert:

$cache->remove($key);

Sie können ein Element auch mit der Methode $cache->save($key, $value, array $dependencies = []) zwischenspeichern. Die obige Methode mit load() ist jedoch vorzuziehen.

Zwischenspeicherung

Memoisierung bedeutet, dass das Ergebnis einer Funktion oder Methode zwischengespeichert wird, so dass Sie es beim nächsten Mal verwenden können, anstatt immer wieder dasselbe zu berechnen.

Methoden und Funktionen können mit call(callable $callback, ...$args) memoisiert aufgerufen werden:

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

Die Funktion gethostbyaddr() wird nur einmal für jeden Parameter $ip aufgerufen und beim nächsten Mal wird der Wert aus dem Cache zurückgegeben.

Es ist auch möglich, einen memoisierten Wrapper für eine Methode oder Funktion zu erstellen, der später aufgerufen werden kann:

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

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

$result = $memoizedFactorial(5); // zählt es
$result = $memoizedFactorial(5); // gibt es aus dem Cache zurück

Verfall & Ungültigmachung

Bei der Zwischenspeicherung ist es notwendig, sich mit der Frage zu befassen, dass einige der zuvor gespeicherten Daten mit der Zeit ungültig werden. Nette Framework bietet einen Mechanismus, um die Gültigkeit von Daten zu begrenzen und sie auf kontrollierte Weise zu löschen („sie ungültig zu machen“, um die Terminologie des Frameworks zu verwenden).

Die Gültigkeit der Daten wird zum Zeitpunkt des Speicherns mit dem dritten Parameter der Methode save() festgelegt, z. B:

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

Oder über den Parameter $dependencies, der als Referenz an den Callback in der Methode load() übergeben wird, z. B:

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

Oder unter Verwendung des 3. Parameters in der Methode load(), z. B:

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

In den folgenden Beispielen gehen wir von der zweiten Variante und damit vom Vorhandensein einer Variablen $dependencies aus.

Verfall

Die einfachste Verfallszeit ist das Zeitlimit. Hier sehen Sie, wie Sie Daten mit einer Gültigkeit von 20 Minuten zwischenspeichern können:

// er akzeptiert auch die Anzahl der Sekunden oder den UNIX-Zeitstempel
$dependencies[Cache::Expire] = '20 minutes';

Wenn wir die Gültigkeitsdauer bei jedem Lesen verlängern wollen, kann dies auf diese Weise erreicht werden, aber Achtung, dies erhöht den Cache-Overhead:

$dependencies[Cache::Sliding] = true;

Die praktische Option ist die Möglichkeit, die Daten ablaufen zu lassen, wenn eine bestimmte Datei oder eine von mehreren Dateien geändert wird. Dies kann z.B. für die Zwischenspeicherung von Daten verwendet werden, die aus der Verarbeitung dieser Dateien resultieren. Absolute Pfade verwenden.

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

Wir können ein Element im Cache ablaufen lassen, wenn ein anderes Element (oder eines von mehreren anderen) abläuft. Dies kann verwendet werden, wenn wir die gesamte HTML-Seite und Fragmente davon unter anderen Schlüsseln zwischenspeichern. Sobald sich das Snippet ändert, wird die gesamte Seite ungültig. Wenn wir Fragmente unter Schlüsseln wie frag1 und frag2 gespeichert haben, verwenden wir:

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

Der Ablauf kann auch über benutzerdefinierte Funktionen oder statische Methoden gesteuert werden, die immer beim Lesen entscheiden, ob das Element noch gültig ist. Zum Beispiel können wir das Element verfallen lassen, wenn sich die PHP-Version ändert. Wir erstellen eine Funktion, die die aktuelle Version mit dem Parameter vergleicht, und beim Speichern fügen wir ein Array in der Form [function name, ...arguments] zu den Abhängigkeiten:

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

$dependencies[Cache::Callbacks] = [
	['checkPhpVersion', PHP_VERSION_ID] // ablaufen, wenn checkPhpVersion(...) === false
];

Natürlich können alle Kriterien kombiniert werden. Der Cache verfällt dann, wenn mindestens ein Kriterium nicht erfüllt ist.

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

Invalidierung mit Tags

Tags sind ein sehr nützliches Werkzeug zur Invalidierung. Wir können jedem im Cache gespeicherten Element eine Liste von Tags zuweisen, bei denen es sich um beliebige Zeichenfolgen handelt. Nehmen wir zum Beispiel an, wir haben eine HTML-Seite mit einem Artikel und Kommentaren, die wir zwischenspeichern wollen. Wir geben also beim Speichern im Cache Tags an:

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

Wechseln wir nun zur Verwaltung. Hier haben wir ein Formular zur Bearbeitung von Artikeln. Zusammen mit dem Speichern des Artikels in einer Datenbank rufen wir den Befehl clean() auf, der zwischengespeicherte Artikel nach Tags löscht:

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

An der Stelle, an der wir einen neuen Kommentar hinzufügen (oder einen Kommentar bearbeiten), vergessen wir nicht, das entsprechende Tag zu deaktivieren:

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

Was haben wir erreicht? Dass unser HTML-Cache ungültig gemacht (gelöscht) wird, wenn sich der Artikel oder die Kommentare ändern. Wenn Sie einen Artikel mit ID = 10 bearbeiten, wird der Tag article/10 zwangsweise ungültig gemacht und die HTML-Seite, die den Tag enthält, aus dem Cache gelöscht. Dasselbe geschieht, wenn Sie einen neuen Kommentar unter dem betreffenden Artikel einfügen.

Tags erfordern Journal.

Invalidierung nach Priorität

Wir können die Priorität für einzelne Elemente im Cache festlegen, und es wird möglich sein, sie kontrolliert zu löschen, wenn der Cache z. B. eine bestimmte Größe überschreitet:

$dependencies[Cache::Priority] = 50;

Löschen Sie alle Objekte mit einer Priorität gleich oder kleiner als 100:

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

Prioritäten erfordern das sogenannte Journal.

Cache löschen

Der Parameter Cache::All löscht alles:

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

Bulk Reading

Zum massenhaften Lesen und Schreiben in den Cache wird die Methode bulkLoad() verwendet, bei der wir ein Array von Schlüsseln übergeben und ein Array von Werten erhalten:

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

Die Methode bulkLoad() funktioniert ähnlich wie load() mit dem zweiten Callback-Parameter, an den der Schlüssel des erzeugten Elements übergeben wird:

$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
	$computedValue = /* ... */; // schwere Berechnungen
	return $computedValue;
});

Verwendung mit PSR-16

Um Nette Cache mit der PSR-16-Schnittstelle zu verwenden, können Sie die PsrCacheAdapter verwenden. Sie ermöglicht eine nahtlose Integration zwischen Nette Cache und jedem Code oder jeder Bibliothek, die einen PSR-16-kompatiblen Cache erwartet.

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

Jetzt können Sie $psrCache als PSR-16-Cache verwenden:

$psrCache->set('key', 'value', 3600); // speichert den Wert für 1 Stunde
$value = $psrCache->get('key', 'default');

Der Adapter unterstützt alle in PSR-16 definierten Methoden, einschließlich getMultiple(), setMultiple() und deleteMultiple().

Zwischenspeicherung der Ausgabe

Die Ausgabe kann auf sehr elegante Weise erfasst und zwischengespeichert werden:

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

	echo ... // Drucken einiger Daten

	$capture->end(); // Speichern der Ausgabe im Cache
}

Falls die Ausgabe bereits im Cache vorhanden ist, druckt die Methode capture() sie aus und gibt null zurück, so dass die Bedingung nicht ausgeführt wird. Andernfalls beginnt sie, die Ausgabe zu puffern und gibt das Objekt $capture zurück, mit dem wir die Daten schließlich im Cache speichern.

In Version 3.0 hieß die Methode $cache->start().

Caching in Latte

Das Zwischenspeichern in Latte-Vorlagen ist sehr einfach, indem man einen Teil der Vorlage mit Tags umgibt {cache}...{/cache}. Der Cache wird automatisch ungültig gemacht, wenn sich die Quellvorlage ändert (einschließlich aller in den {cache} -Tags enthaltenen Vorlagen). Tags {cache} können verschachtelt werden, und wenn ein verschachtelter Block ungültig wird (z. B. durch ein Tag), wird auch der übergeordnete Block ungültig.

Im Tag können die Schlüssel angegeben werden, an die der Cache gebunden werden soll (hier die Variable $id), und die Ablauf- und Ungültigkeits-Tags gesetzt werden

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

Alle Parameter sind optional, d.h. Sie müssen weder Verfallsdatum noch Tags oder Schlüssel angeben.

Die Verwendung des Cache kann auch durch if bedingt werden – der Inhalt wird dann nur dann zwischengespeichert, wenn die Bedingung erfüllt ist:

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

Speicherplätze

Ein Speicher ist ein Objekt, das darstellt, wo Daten physisch gespeichert werden. Wir können eine Datenbank, einen Memcached-Server oder den am häufigsten verwendeten Speicher, nämlich Dateien auf der Festplatte, verwenden.

Speicher Beschreibung
FileStorage Standardspeicher mit Speicherung in Dateien auf der Festplatte
MemcachedStorage verwendet den Memcached Server
MemoryStorage Daten werden temporär im Speicher abgelegt
SQLiteStorage Daten werden in einer SQLite-Datenbank gespeichert
DevNullStorage Daten werden nicht gespeichert – für Testzwecke

Sie erhalten das Speicherobjekt, indem Sie es mittels Dependency Injection mit dem Typ Nette\Caching\Storage übergeben. Standardmäßig bietet Nette ein FileStorage-Objekt, das Daten in einem Unterordner cache im Verzeichnis für temporäre Dateien speichert.

Sie können den Speicherort in der Konfiguration ändern:

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

FileStorage

Schreibt den Cache in Dateien auf der Festplatte. Der Speicher Nette\Caching\Storages\FileStorage ist sehr leistungsoptimiert und gewährleistet vor allem eine vollständige Atomisierung der Operationen. Was bedeutet das? Dass es bei der Verwendung des Caches nicht passieren kann, dass wir eine Datei lesen, die von einem anderen Thread noch nicht vollständig geschrieben wurde, oder dass jemand sie „unter der Hand“ löscht. Die Nutzung des Caches ist also völlig sicher.

Dieser Speicher hat auch eine wichtige eingebaute Funktion, die einen extremen Anstieg der CPU-Auslastung verhindert, wenn der Cache geleert oder kalt (d.h. nicht erstellt) ist. Dies ist die „Cache-Stampede“-P:https://en.wikipedia.org/…che_stampede rävention. Es kommt vor, dass zu einem Zeitpunkt mehrere gleichzeitige Anfragen das Gleiche aus dem Cache wollen (z. B. das Ergebnis einer teuren SQL-Abfrage), und da es nicht zwischengespeichert ist, beginnen alle Prozesse mit der Ausführung derselben SQL-Abfrage. Die Prozessorlast vervielfacht sich und es kann sogar passieren, dass kein Thread innerhalb des Zeitlimits antworten kann, der Cache nicht erstellt wird und die Anwendung abstürzt. Glücklicherweise funktioniert der Cache in Nette so, dass bei mehreren gleichzeitigen Anfragen für ein Element dieses nur vom ersten Thread erzeugt wird, die anderen warten und verwenden dann das erzeugte Ergebnis.

Beispiel für die Erstellung eines FileStorage:

// der Speicher wird das Verzeichnis '/pfad/zu/temp' auf der Festplatte sein
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');

MemcachedStorage

Der Server Memcached ist ein hochleistungsfähiges verteiltes Speichersystem, dessen Adapter Nette\Caching\Storages\MemcachedStorage ist. Geben Sie in der Konfiguration die IP-Adresse und den Port an, falls dieser vom Standard 11211 abweicht.

Erfordert die PHP-Erweiterung memcached.

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

SpeicherStorage

Nette\Caching\Storages\MemoryStorage ist ein Speicher, der Daten in einem PHP-Array speichert und daher verloren geht, wenn die Anfrage beendet wird.

SQLiteStorage

Die SQLite-Datenbank und der SQLite-Adapter Nette\Caching\Storages\SQLiteStorage bieten eine Möglichkeit, in einer einzigen Datei auf der Festplatte zu speichern. In der Konfiguration wird der Pfad zu dieser Datei angegeben.

Erfordert die PHP-Erweiterungen pdo und pdo_sqlite.

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

DevNullStorage

Eine spezielle Implementierung des Speichers ist Nette\Caching\Storages\DevNullStorage, der eigentlich gar keine Daten speichert. Sie eignet sich daher zum Testen, wenn wir die Wirkung des Cache ausschalten wollen.

Verwendung von Cache im Code

Bei der Verwendung von Caching im Code gibt es zwei Möglichkeiten, wie man es machen kann. Die erste ist, dass Sie das Speicherobjekt erhalten, indem Sie es mittels Dependency Injection übergeben und dann ein Objekt Cache erstellen:

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

Die zweite Möglichkeit ist, dass Sie das Speicherobjekt Cache erhalten:

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

Das Objekt Cache wird dann direkt in der Konfiguration wie folgt erstellt:

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

Journal

Nette speichert Tags und Prioritäten in einem sogenannten Journal. Standardmäßig werden dafür SQLite und die Datei journal.s3db verwendet, und PHP-Erweiterungen pdo und pdo_sqlite sind erforderlich.

Sie können das Journal in der Konfiguration ändern:

services:
	cache.journal: MyJournal

DI-Dienste

Diese Dienste werden dem DI-Container hinzugefügt:

Name Typ Beschreibung
cache.journal Nette\Caching\Storages\Journal journal
cache.storage Nette\Caching\Storage repository

Cache ausschalten

Eine Möglichkeit, die Zwischenspeicherung in der Anwendung zu deaktivieren, besteht darin, den Speicher auf DevNullStorage zu setzen:

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

Diese Einstellung wirkt sich nicht auf die Zwischenspeicherung von Vorlagen in Latte oder dem DI-Container aus, da diese Bibliotheken nicht die Dienste von nette/caching nutzen und ihren Cache unabhängig verwalten. Außerdem muss ihr Cache im Entwicklungsmodus nicht abgeschaltet werden.