Daten zwischenspeichern

Erläuterungen zu den verschiedenen Techniken, Daten kurzfristig und mittelfristig zu cachen

1. Daten innerhalb einer Klasse oder eines Objekts zwischenspeichern


Eine Möglichkeit des Zwischenspeicherns von Daten ist es, diese in Klassen- oder Objektvariablen zu speichern.
Die zwischengespeicherten Daten gehen in diesem Fall spätestens dann verloren, wenn die Ausführung des Skripts endet.
Diese Form des cachens lohnt sich insbesondere dann, wenn sich Daten über eine Seitenansicht hinweg nicht ändern werden, gleichzeitig aber auch für jede Seitenansicht neu bestimmt werden sollen (z.B. weil sie vom Besucher oder der besuchten Unterseite abhängig sind).

Zischenspeichern per Objektvariable:
PHP-Code
<?php
    class Beispiel {
		private $functionCache = null;
		
		public function eineAufwendigeFunktion() {
			if ($this->functionCache === null) {
				$a = 0;
				sleep(5);
				$a++;
				
				$this->functionCache = $a;
			}
			return $this->functionCache;
		}
	}

	$bsp = new Beispiel();
	$a = 0;
	$start = time();

	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach erstem Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach zweitem Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach dritten Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach viertem Aufruf: '.(time() - $start)." Sek. (a: $a)");
?>
Ausgabe
Zeit nach erstem Aufruf: 5 Sek. (a: 1)
Zeit nach zweitem Aufruf: 5 Sek. (a: 2)
Zeit nach dritten Aufruf: 5 Sek. (a: 3)
Zeit nach viertem Aufruf: 5 Sek. (a: 4)

Es kann auch per statischer Variable zwischengespeichert werden.
Das ist immer dann zu bevorzugen, wenn das Ergebnis sich zwischen den verschiedenen Objekten nicht unterscheidet.
Es gibt zwei Wege dies zu erreichen.

Der normale Weg, per Klassenvariable:
PHP-Code
<?php
	class Beispiel {
		private static $functionCache = null;
		
		public function eineAufwendigeFunktion() {
			if (self::$functionCache === null) {
				$a = 0;
				sleep(5);
				$a++;
				
				self::$functionCache = $a;
			}
			return self::$functionCache;
		}
	}

	$bsp1 = new Beispiel();
	$bsp2 = new Beispiel();
	$a = 0;
	$start = time();

	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach erstem Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach zweitem Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach dritten Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach viertem Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");

	// Jetzt folgt die zweite Instanz ($bsp2).
	// Da die Variable statisch ist und bereits festgelegt wurde, muss die
	// zweite Instanz die Berechnung nicht nochmal durchführen
	$a = 0;
	echo("Frage bsp2 ab (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach ersten Aufruf (bsp2): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach zweitem Aufruf (bsp2): '.(time() - $start)." Sek. (a: $a)");
?>
Ausgabe
Zeit nach erstem Aufruf (bsp1): 5 Sek. (a: 1)
Zeit nach zweitem Aufruf (bsp1): 5 Sek. (a: 2)
Zeit nach dritten Aufruf (bsp1): 5 Sek. (a: 3)
Zeit nach viertem Aufruf (bsp1): 5 Sek. (a: 4)
Frage bsp2 ab (a: 0)
Zeit nach ersten Aufruf (bsp2): 5 Sek. (a: 1)
Zeit nach zweitem Aufruf (bsp2): 5 Sek. (a: 2)

Der zweite Weg ist seltener: Die statische Variable wird stattdessen direkt in der Funktion definiert.
PHP-Code
<?php
	class Beispiel {	
		public function eineAufwendigeFunktion() {
			// Achtung: null wird der Variable nur beim ersten Mal zugewiesen, danach
			// wird die Zuweisung nicht mehr ausgeführt, da die statische Variable bereits
			// deklariert ist. Daher kann die Bedingung in der if-Abfrage auch false ergeben.
			static $functionCache = null;

			if ($functionCache === null) {
				$a = 0;
				sleep(5);
				$a++;
				
				$functionCache = $a;
			}

			return $functionCache;
		}
	}

	$bsp1 = new Beispiel();
	$bsp2 = new Beispiel();
	$a = 0;
	$start = time();

	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach erstem Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach zweitem Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach dritten Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach viertem Aufruf (bsp1): '.(time() - $start)." Sek. (a: $a)\n");

	$a = 0;
	echo("Frage bsp2 ab (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach ersten Aufruf (bsp2): '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp1->eineAufwendigeFunktion();
	echo('Zeit nach zweitem Aufruf (bsp2): '.(time() - $start)." Sek. (a: $a)");
?>
Ausgabe
Zeit nach erstem Aufruf (bsp1): 5 Sek. (a: 1)
Zeit nach zweitem Aufruf (bsp1): 5 Sek. (a: 2)
Zeit nach dritten Aufruf (bsp1): 5 Sek. (a: 3)
Zeit nach viertem Aufruf (bsp1): 5 Sek. (a: 4)
Frage bsp2 ab (a: 0)
Zeit nach ersten Aufruf (bsp2): 5 Sek. (a: 1)
Zeit nach zweitem Aufruf (bsp2): 5 Sek. (a: 2)

Der Vorteil der zweiten Methode ist, dass die Variable direkt in der Funktion deklariert wird, die sie auch verwendet.
Der Nachteil ist, dass die Variable dann auch von keiner anderen Funktion verwendet werden kann und dass diese Art der Deklaration vielen Entwicklern unbekannt ist.


2. Cachen in Dateien


Zum Cachen von Daten in Dateien sind folgende Funktionen nützlich:
  • file_get_contents($filepath): Gibt den Inhalt der Datei mit Pfad $filepath zurück.
  • file_put_contents($filepath, $content): Schreibt den Inhalt der Variable $content in die Datei unter $filepath. Falls die Datei noch nicht existiert wird sie automatisch angelegt.
  • file_exists($filepath): Prüft, ob die Datei unter $filepath bereits angelegt wurde.
  • serialize($var): Serialisiert eine Variable. Das heißt, dass die übergebene Variable in einen String umgewandelt wird, der sich dann abspeichern lässt.
  • unserialize($var): Kehrt die Serialisierung von serialize() wieder um, sodass aus dem String die ursprüngliche Variable erzeugt wird.

Die vorherige Beispielklasse, angepasst an das Cachen per Datei, könnte dann wie folgt aussehen:
PHP-Code
<?php
	class Beispiel {
		public function eineAufwendigeFunktion() {
			// Prüfen ob der Cache bereits angelegt wurde
			if (!file_exists(__DIR__.'/eineAufwendigeFunktionCache.txt')) {
				$a = 0;
				sleep(5);
				$a++;
				
				// Cache schreiben
				file_put_contents(__DIR__.'/eineAufwendigeFunktionCache.txt', strval($a));

				return $a;
			} else {
				// Cache-Datei auslesen und Inhalt in einen Integer-Wert umwandeln
				$a = intval( file_get_contents(__DIR__.'/eineAufwendigeFunktionCache.txt') );

				return $a;
			}
		}
	}

	$bsp = new Beispiel();
	$a = 0;
	$start = time();

	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach erstem Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach zweitem Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach dritten Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach viertem Aufruf: '.(time() - $start)." Sek. (a: $a)");
?>
Ausgabe
Zeit nach erstem Aufruf: 5 Sek. (a: 1)
Zeit nach zweitem Aufruf: 5 Sek. (a: 2)
Zeit nach dritten Aufruf: 5 Sek. (a: 3)
Zeit nach viertem Aufruf: 5 Sek. (a: 4)

In diesem Beispiel werden serialize() und unserialize() nicht verwendet, da nur ein Integer gespeichert werden soll, welcher auch per strval() in einen String umgewandelt werden kann.
Bei dieser Implementierung des Cachings per Datei sollte zudem bedacht werden, dass die Cache-Datei nicht mehr automatisch gelöscht wird.
Es ist daher sinnvoll, in der Datei zusätzlich den Zeitpunkt der Erstellung zu vermerken und nach x Sekunden/Minuten/Stunden/Tagen die Datei zu löschen.


3. Caching per Datenbank


Genauso wie die Daten in einer Datei gelagert werden können ist auch die Speicherung in einer Datenbank möglich.
Angenommen es existiert eine Tabelle in einer MySQL-Datenbank, die etwa folgenden Aufbau hat:
cache_name cache_content
calc.f17 14372321435194
users.query.dshzsb2nx91 steve1;fishblob;doo;venice_raider
... ...
Wobei für die Spalte "cache_name" gilt: PRIMARY INDEX, VARCHAR(250) und für die Spalte cache_content: TEXT (String mit maximal ~65.000 Zeichen).
Es könnte dann folgender Code zum Schreiben und Lesen verwendet werden (die Ausgabe ist identisch mit dem Ergebnis des Datei-Cachings):

PHP-Code
<?php
	// Einfache beispielhafte Klasse zum Kapseln der aktuellen DB-Verbindung
	class DbConnection {
		private static $connection;
		
		public static function getConnection() {
			if (self::$connection === null) {
				self::$connection = new mysqli('localhost', 'user', 'passwort', 'datenbank');
			}
			return self::$connection;
		}
	}


	class Beispiel {
		public function eineAufwendigeFunktion() {
			$conn = DbConnection::getConnection();
			$sql = sprintf('SELECT cache_content FROM caches WHERE cache_name=\'Beispiel.meineAufwendigeFunktion\'');
			$result = $conn->query($sql);
			$row = $result->fetch_assoc();
			$result->free();

			if ($row===null) {
				$a = 0;
				sleep(5);
				$a++;
				
				// Cache schreiben
				$insertSql = sprintf('INSERT INTO caches (cache_name, cache_content) VALUES (\'Beispiel.meineAufwendigeFunktion\', \'%s\')', strval($a));
				$result = $conn->query($insertSql);

				return $a;
			} else {
				return intval($row['cache_content']);
			}
		}
	}

	$bsp = new Beispiel();
	$a = 0;
	$start = time();

	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach erstem Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach zweitem Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach dritten Aufruf: '.(time() - $start)." Sek. (a: $a)\n");
	$a += $bsp->eineAufwendigeFunktion();
	echo('Zeit nach viertem Aufruf: '.(time() - $start)." Sek. (a: $a)");
?>
Ausgabe
Zeit nach erstem Aufruf: 5 Sek. (a: 1)
Zeit nach zweitem Aufruf: 5 Sek. (a: 2)
Zeit nach dritten Aufruf: 5 Sek. (a: 3)
Zeit nach viertem Aufruf: 5 Sek. (a: 4)

Auch in diesem Fall ist zu beachten, dass der Cache — so wie die Funktion hier implementiert ist — nicht mehr automatisch gelöscht wird.

4. Zwischenspeichern mit Memcache


Ist Memcache auf dem eigenen Server/Webspace installiert, dann kann die Beispielklasse wie folgt angepasst werden:
PHP-Code
<?php
	class Beispiel {	
		public function eineAufwendigeFunktion() {
			$mc = new Memcache();
			$mc->connect('localhost', 11211);
			$cache = $mc->get('Beispiel.eineAufwendigeFunktion');

			if ($cache === false) {
				$a = 0;
				sleep(5);
				$a++;
				
				// MEMCACHE_COMPRESSED heißt, dass der Wert komprimiert gespeichert wird
				// und somit weniger Speicherplatz belegt. Für einzelne Zahlen (wie hier)
				// lohnt sich das eigentlich nicht, wird also nur beispielhaft verwendet.
				// Falls keine Komprimierung verwendet werden soll muss false eingetragen werden.
				// 
				// 60*60 heißt, dass das Ergebnis 3600 Sekunden bzw. eine Stunde zwischengespeichert
				// bleiben soll.
				$mc->set('Beispiel.eineAufwendigeFunktion', strval($a), MEMCACHE_COMPRESSED, 60*60);
				return $a;
			} else {
				return intval($cache);
			}
		}
	}
?>

Anmerkungen:
  • Memcache erzwingt es nicht, dass ein maximales Alter für den Cache übergeben wird (im Beispiel 3600 Sekunden). Dennoch empfiehlt es sich, das immer zu machen, da sich praktisch alle Daten, die zwischengespeichert werden sollen, früher oder später ändern - und sei es, dass die Funktion, die auf diese Daten zurückgreift, aus dem Code entfernt wird.
  • Für den Namen des Cache-Eintrags (hier: Beispiel.eineAufwendigeFunktion()) sollte immer ein Prefix verwendet werden, das in der Klasse oder der Komponente angepasst werden kann. Andernfalls passiert es schnell, dass zwei verschiedene Klassen versuchen, Caches unter den selben Namen abzulegen und zu lesen.
  • Die maximale Größe der abzuspeichernden Daten beträgt etwa ein MB.

5. Zwischenspeichern mit APC


Sofern auf dem Server/Webspace APC installiert ist, kann auch dieses verwendet werden. Die Anwendung erfolgt ähnlich
zu Memcache:
PHP-Code
<?php
	class Beispiel {	
		public function eineAufwendigeFunktion() {
			$cache = apc_get('Beispiel.eineAufwendigeFunktion');

			if (!is_string($cache)) {
				$a = 0;
				sleep(5);
				$a++;
				
				apc_add('Beispiel.eineAufwendigeFunktion', strval($a), 60*60);
				return $a;
			} else {
				return intval($cache);
			}
		}
	}
?>