Das Zwischenspeichern von generierten Inhalten ist inzwischen ein sehr wichtiges Thema. Vor allem in TYPO3. Durch etliche Extensions und unzählige Abfragen zum Generieren der Inhalte, bietet das Caching von TYPO3 einen erheblichen Performance-Boost.
Auf das TYPO3 generelle Caching möchte ich in diesem Beitrag nicht näher eingehen. Es ist sozusagen eine Welt für sich und kann sehr ausarten, dies näher zu beschreiben.
Stattdessen geht es um die API für das seit 4.3 eingeführte Caching-Framework, das seit 4.5 grundsätzlich aktiviert ist. Anhand eines Beispiels möchte ich euch heute einen Anwendungsfall und dessen nutzen näher bringen.
Eine klassische Extbase Extension mit zum Beispiel einer „list“ und einer „show“ Action wird in der Regel durch TYPO3 zwischengespeichert (Cached). Dies ist soweit auch praktisch und ermöglicht eine einfache Entwicklung.
Jetzt haben wir aber noch den Fall, dass wir einen Filter bzw. eine Suche integriert haben. Dies kann schnell zu Problemen führen. Der Nutzer möchte durch den Filter bzw. einen Suchbegriff ein anderes Ergebnis angezeigt bekommen, jedoch holt TYPO3 die Informationen aus dem „Cache“ und nichts hat sich an der Ausgabe geändert.
In der Regel kann dies umgangen werden, indem die Action in der ext_localconf.php als „nonCacheableAction“ definiert wird. Bei sehr komplexen Abfragen mit vielen verknüpften Objekten zu dem Datensatz, führt diese Variante jedoch zu erhebliche Performance Einbußen. In einem Projekt führte dies zu Ladezeiten von bis zu 2 Minuten.
Hier kommt das Caching-Framework (CF) zum Einsatz. Wir belassen die Action als „non-cacheable“, und definieren unsere eigenen Caching Bedingungen in der Action.
Damit das CF benutzt werden kann, müssen wir TYPO3 anweisen, für unsere Extension die Caching-Tabellen etc. korrekt anzulegen. Hier gibt es mehrere Varianten. Man kann Strings, Variablen, uvm. cachen. Ich persönlich bevorzuge den Cache für Variablen.
Dies geschieht wie folgt in der „ext_localconf.php“. In diesem Beispiel speziell für das Frontend. Auch für das Backend kann das Caching Framework benutzt werden.
// Caching framework if( !is_array($GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY] ) ) { $GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY] = array(); } // Hier ist der entscheidende Punkt! Es ist der Cache von Variablen gesetzt! if( !isset($GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['frontend'] ) ) { $GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['frontend'] = 'TYPO3\\CMS\\Core\\Cache\\Frontend\\VariableFrontend'; } //if( !isset($GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['backend'] ) ) { // $GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['backend'] = 'TYPO3\\CMS\\Core\\Cache\\Backend\\DatabaseBackend'; //} // Wie lange soll der Cache haltbar sein? (1 Stunde) if( !isset($GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['options'] ) ) { $GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['options'] = array('defaultLifetime' => 3600); } if( !isset($GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['groups'] ) ) { $GLOBALS['TYPO3_CONF_VARS'] ['SYS']['caching']['cacheConfigurations'][$_EXTKEY]['groups'] = array('pages'); }
Zu Beginn wird das Caching initialisiert. Am einfachsten im Konstruktor oder in der initializeAction() Funktion unserer Controller-Class:
/** * cacheUtility * * @var \TYPO3\CMS\Core\Cache\CacheManager */ protected $cacheInstance; /** * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer */ protected $cObj; public function initializeAction() { $this->cacheInstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager')->getCache("myExtKey"); $this->cObj = $this->configurationManager->getContentObject(); }
Jetzt kann es verwendet werden. Das entscheidende ist, dass anhand einer eindeutigen Kennung der Cache abgefragt und geschrieben wird.
In einer listAction() mit Filter Funktionen könnte dies nun wie folgt aussehen:
/** * list action * * @param int $filterA * @param int $filterB * @validate $filterA Integer,NumberRange(minimum=1) * @validate $filterB Integer,NumberRange(minimum=1) * @return void */ public function listAction($filterA = NULL, $filterB = NULL) { // Überprüfen ob Filter gesetzt sind. // und einen eindeutigen identifier für unseren cache generieren abhängig der filter. If ( ($filterA !== NULL) && ($filterB !== NULL) ) { $cacheIdentifier =md5( $GLOBALS['TSFE']->id . "-" . $this->cObj->data['uid']. "-" . $GLOBALS['TSFE']->sys_language_uid . "-" . $this->actionMethodName . "-" . $filterA . "-" . $filterB ); } else $cacheIdentifier = =md5( $GLOBALS['TSFE']->id . "-" . $this->cObj->data['uid']. "-" . $GLOBALS['TSFE']->sys_language_uid . "-" . $this->actionMethodName ); }
Es wird ein eindeutiger Hash erzeugt, anhand der übermittelten Parameter, so dass der Cache geladen werden kann. In diesem Beispiel gibt es einen eindeutigen Hash der unseren Cache identifiziert, wenn nichts gefiltert wurde und einen Cache auf Basis der Filter Konfiguration. Fazit: Es kann auch ein Suchergebnis aus dem Cache kommen und damit die Performance steigern.
Als nächstes muss nun geprüft werden, ob ein Cache Eintrag auf Basis dieser Identifizierung bereits vorhanden ist. Wenn dies der Fall ist, wird der Cache geladen. Ist dies nicht der Fall wird die Ausgabe erzeugt und zusätzlich ein neuer Cache Eintrag erstellt.
If ( $this->cacheInstance->has($cacheIdentifier) ) { // Cache vorhanden $items = $this->cacheInstance->get($cacheIdentifier); } else { $items = $this->itemRepository->findAll(); // bzw. findByFilter() usw. // In diesem Beispiel wird das Ergebnis des Repositories im Cache gespeichert. // Es ist natürlich möglich noch viel mehr zu speichern. $this->cacheInstance->set($cacheIdentifier, $items, array(‚myItemTag‘)); } $this->view->assign(‚items‘, $items);
Im Wesentlichen war es dies bereits. Die Items kommen bei Bedarf aus dem Cache. Wie bereits erwähnt lassen sich natürlich noch weitere Informationen im Cache speichern, bis hin zur gesamten Action bzw. View. Dies hängt von dem Bedarf ab.
Zusätzlich wurde bei dem Aufruf der „set()“ Funktion noch ein „Tag“ hinterlegt. Hier können auch mehrere Tags definiert werden. Anhand von Tags, ist es möglich den Cache im Frontend mit der Funktion „flushCachesByTag($tag)“ gezielt zu leeren. Auch im Backend lässt sich per TSconfig (z.B. im Sysfolder mit den Datensätzen) gezielt der Cache leeren bei Veränderung. Das TSconfig sieht dann z.B. wie folgt aus:
TCEMAIN.clearCacheCmd = cacheTag:myItemTag
Grundsätzlich war dies bereits der Einsatz des Caching Frameworks und damit eine Leistungssteigerung. Es ist nicht sonderlich schwer und sehr einfach zu bedienen. Weitere Varianten und Anwendungsfälle sind natürlich möglich. Für einen kurzen Einblick und Inspiration hat dies euch hoffentlich weitergeholfen.
Viel Spaß beim Ausprobieren!