Wenn eine Web Applikation einen gewissen Besucherstand angezogen hat und die Seite merklich langsamer wird, ist es eine gute Idee einen Cache zu installieren.
Für diesen Fall kann man z.B. Memcache verwenden. Memcache ist ein Key-Value-Store der die Cache-Daten im RAM hält und so extrem schnell zur Verfügung steht.
Dadurch können z.B. rechenintensive Ergebnis oder aufwändige Datenbankabfragen zwischen gespeichert werden.

Als einfaches Beispiel nehmen wir einen Blog, bei dem die Datenbankabfragen mit Hilfe eines Caches auf ein Minimum reduzieren werden sollen.
Wie in den meisten Blogs üblich, besteht die Startseite aus einer Auswahl an Artikeln, die einen Teil des Inhalts preisgeben. Desweiteren gibt es den eigentlichen Artikel, Kommentare etc.

Einen Artikel im Memcache abzulegen ist relativ einfach. Man stellt die Verbindung zum Memcache her und schreibt die Daten, die aus der DB kommen als String in die Key-Value-Datenbank. Wichtig hierbei ist, dass der Key einzigartig ist, da ansonsten Daten überschrieben werden!

In unserem Beispiel schreiben wir für eine Liste von vier Artikeln folgende Cache-Keys in den Memcache: [“article_erster_artikel”, “article_zweiter_artikel”, “article_scandio_test_memcache”, article_noch_einer”]. Der Cache-Key wird anhand des Artikelnamens generiert: article_{articleName}.
Nun ist bereits ersichtlich, vor welchem Problem wir stehen: Der Cache kann nur geändert werden, wenn auch den Artikelname vorhanden ist. Wenn man nun aber eine Liste von Artikeln hat, die nur aus ID und Beschreibung bestehen, können die Cache-Keys nicht ohne Datenbankverbindung gelöscht werden! Ausserdem wollen wir nur die Daten im Cache löschen, die auch wirklich davon betroffen sind. Dazu müssen wir zuerst den Artikel aus der Datenbank holen um dann alle Keys zu löschen. Dies bricht natürlich mit der Aufgabe, möglichst wenige Datenbankzugriffe zu generieren.

Die folgende Lösung basiert darauf, dass der Entwickler einen Tag für alle Cache-Keys vergibt, die gruppiert werden sollen. Dadurch kann der Entwickler den Tag invalidieren und löscht somit alle Cache-Keys ohne den kompletten Cache zu leeren.

Die Klasse hierzu:

namespace Scandio;
class ScandioCache
{
    private static $memcache = null;
	private static $configs = array(
		'host' => 'localhost',
		'port' => 11211,
		'expires' => '+1 day',
		'global_tag' => '__global_tag_set'
	);
    /**
     * Singleton
     * @throws \Exception
     */
    public static function getMemcacheInstance()
    {
        if (self::$memcache === null) {
            $memcache = new \Memcache;
            $isConnected = $memcache->connect(
                self::$configs['memcache']['host'],
                self::$configs['memcache']['port']
            );
            if (!$isConnected) {
                throw new \Exception('Could not conntect to Memcache instance!');
            }
            self::$memcache = $memcache;
        }
        return self::$memcache;
    }
    /**
     * Writes data to the cache
     * @param string $key
     * @param mixed $value
     * @param string $tag
     * @param string $expiration
     */
    public function write($key, $value, $tag = null, $expiration = '+5 minutes', $compressed = false)
    {
		$memcache = self::getMemcacheInstance();
		if (!empty(self::$configs['expires'])) {
			$expiration = self::$configs['expires'];
		}
		$result = $memcache->set($key, $value, $compressed, strtotime($expiration));
		if (!empty($tag)) {
			$this->setTagByKey($key, $tag);
		}
        return $result;
    }
    /**
     * Reads an Object from the Cache
     * @param string $key
     */
    public function read($key)
    {
        $memcache = self::getMemcacheInstance();
        return $memcache->get($key);
    }
    /**
     * Flushes the cache
     */
    public function flush()
    {
        $memcache = self::getMemcacheInstance();
        $memcache->flush();
    }
    /**
     * Retrieves all Cache-Keys by tag
     * @param string $tag
     */
    public function getKeysByTag($tag)
    {
        $memcache = self::getMemcacheInstance();
        $keys = $memcache->get(self::$configs['global_tag']);
        if (!empty($keys)) {
            $keys = unserialize($keys);
        } else {
            $keys = array();
        }
        return $keys;
    }
    /**
     * Invalidates all Cache-Keys by Tag
     * @param unknown_type $tag
     */
    public function invalidateTag($tag)
    {
        $memcache = self::getMemcacheInstance();
        $keys = $this->getKeysByTag($tag);
        if (!empty($keys)) {
            foreach ($keys as $key) {
                $memcache->delete($key);
            }
        }
    }
    /**
     * Sets a Tag <-> Key Association
     * @param string $key
     * @param string $tag
     */
    public function setTagByKey($key, $tag)
    {
        $memcache = self::getMemcacheInstance();
        $keys = $this->getKeysByTag($tag);
        if (!empty($keys)) {
            $keys[] = $key;
        } else {
            $keys = array($key);
        }
        $memcache->set(self::$configs['global_tag'], serialize($keys));
    }
}

Und ein Beispiel dazu sieht folgendermaßen aus:

$cache = new ScandioCache();
$article = array('test' => 123, 'super' => array('grandiose' => 'idee'), 'time' => date('d.m.Y H:i:s'));
$articleOne = array('test' => '24terwbsg', 'super' => array('grandiose' => 'idee'), 'time' => date('d.m.Y H:i:s'));
$articleTwo = array('scandio' => date('d.m.Y H:i:s'));
$cache->write('testblabla_699', $article, 'articles');
$cache->write('article_'.time(), $articleOne, 'articles');
$cache->write('scandio', $articleTwo, 'articles');
$cachedArticle = $cache->read('testblabla_699');
$cache->invalidateTag('articles');
$cachedArticleAfterInvalidation = $cache->read('testblabla_699');
print_r($cachedArticle);
print_r($cachedArticleAfterInvalidation);
exit();

Wenn alles geklappt ist die $cachedArticleAfterInvalidation Variable leer.