Contenido intelligent cache Engine
Contenido intelligent cache Engine
Die "Contenido Intelligent Cache Engine" ermöglicht das cachen von Modulen oder Modulteilen. Sie basiert auf emergences simple Cache Engine (http://contenido.org/forum/viewtopic.php?t=9420). Durch das cachen werden die betreffenden Teile des Codes nicht so häufig ausgeführt wie ohne Caching, das beschleuningt den Seitenaufbau im Frontend.
Diese Cache Engine arbeitet auf Modul (oder Sub-Modul) Ebene, was den Vorteil hat, dass der Webentwickler entscheidet welche Teile gecached werden und welche nicht. So kann z.B. ein Modul, dass einen Zugriffszähler beinhaltet bei jeden Seitenaufruf ausgewertet werden, während ein Modul das den Namen des Artikel-Autors ausgibt gecached wird.
Die Erweiterung gegenüber der simple Cache Engine von emergence besteht darin, dass jeder cache-Abschnitt eine eigene Lifetime erhält, d.h. die Zeit nachdem der Cache seine Gültigkeit verliert, und der Abschnitt neu evaluiert wird. Dadurch kann der Cache nun auch für Navigationsmodule o.ä. eingesetzt werden.
Durch die Cache Engine ist es zudem möglich CMS_Type-Platzhalter in Schleifen einzusetzten.
Vielen Dank an emergence für den Ansatz. Die Anleitung und den Code habe ich wegen großer Überschneidungen teilweise aus deinem Post abgekupfert.
Zu cachende Bereiche werden im Modul durch <!cache=<lifetime> cache!> markiert. Diese Tags sind wie <?php und ?> zu behandeln, also anstelle dessen das man
Code: Alles auswählen
<?php echo "was auch immer"; ?>
Code: Alles auswählen
<!cache=600 echo "was auch immer"; cache!>
Normaler php Code wird as is in der con code gesichert... also
Code: Alles auswählen
<?php echo "was auch immer"; ?>
Code: Alles auswählen
<?php
if ((time() > 483475987+600) and (!$edit)):
ob_start();
echo "was auch immer";
$cached_code=ob_get_flush();
conUpdateCacheCode(15,1,1,154887,$cached_code);
else:?>
<!--cached code id 154887>
was auch immer
<!--cached code id 154887>
<?endif;?>
Für <lifetime> setzt man einfach die gewünschte Lebenszeit in Sekunden ein. Ein Wert von 0 führt dazu, dass nicht gecached wird. Ein negativer Wert ergibt einen unendlich gültigen Cache, entspricht damit exakt der Funktionsweise der Simple Cache Engine.
Wird =<lifetime> weggelassen, wird eine unendliche Lebenszeit angenommen.
Das Ganze wurde so implementiert das auch innerhalb von dieser gecachten Code-passagen CMS_VALUE verwendet werden kann...
Die grundsätzliche Vorgangsweise wie code nun in der con_code erzeugt wird sieht jetzt wie folgt aus
1. innerhalb der konfigurierten module wird zu allererst CMS_VALUE durch den konfigurationswert ersetzt...
2. anschließend werden die module in dem entsprechenden layout eingebettet
3. (neu) gefundene <!cache cache!> passagen innerhalb des gesamten bisherigen erzeugen codes werden evaluiert
4. anschließend werden die cms_type platzhalter ersetzt
5. code wird in der con_code hinterlegt
6. gesamter code wird in der front_content bei aufruf evaluiert
<!cache=<lifetime> cache!> funktioniert sowohl im layout als auch in modulen...
eine kombination aus beiden also normaler php code und gecachten code ist ebenso möglich...
damit ein großteil der bisherigen module ebenso als gecachte version lauffähig sind wird ein grundstock an contenido variablen zur verfügung gestellt....
welche da wären:
$idcat, $idart, $idcatart, $idartlang, $lang, $client, $cfg, $cfgClient, $edit, $sess, $perm, $auth, $encoding und eine eigene $db instanz...
die als gecachte version ausgeführten module können ebenso eine variable $cache abfragen, welche auf true gesetzt ist...
gewisse kleinigkeiten bei dieser arbeitsweise mit dem code werden nicht (ohne weiteres) funktionieren... zb auswertung von $_GET,$_POST,$_REQUEST werten...
Bekannte Fehler und Einschränkungen:
- Es stehen nicht alle Contenido-Variablen zur Verfügung
- Es stehen keine $_GET, $_POST, $_REQUEST - Variablen zur Verfügung, bzw. nicht die des aktuellen Aufrufs. (Sondern die des Aufrufs als der Cache das letzte Mal abgelaufen war.)
- Bedenkt immer das gecachete Blöcke zu einer völlig anderen Zeit evaluiert werden, als der Rest des Codes. Der Code im <!cache=<lifetime> cache!>-Block muss also für sich alleine Sinn ergeben.
- Eine Übergabe von Variablen zwischen dem gecachten und anderen Code-Teilen ist nicht möglich.
- (gelöst =>)Laufen mehrere Cache-Blöcke in einem Artikel gleichzeitig ab, wird nur der letzte Cache-Block erneut gecached. Die andren Blöcke werden aber korrekt ausgeführt, nur werden die Ergebnisse nicht gecached
- (gelöst =>)Laufen mehrere Cache-Blöcke in einem Artikel gleichzeitig ab, werden überflüssige SQL-Abfragen an die Datenbank gesendet (=>Performance-Problem)
- (gelöst=>by emergence)Es kann zu Problemen mit Modulen kommen, die den Wert der Variablen $code ändern. Liegen diese Änderungen in einem gecachten Block werden diese unter Umständen in die con_Code geschrieben. (Was bei einer Standardinstallation nicht passieren kann) z.B. das Modul Snippets
- (gelöst =>)Innerhalb von Cache-Blöcken können keine <?php und ?> Tags verwendet werden. Dies kann zu Problemen bei Verwendung von <!cache=<lifetime> cache!> in Layouts führen, wenn im Cache-Block Container liegen die PHP-Code enthalten.
- (gelöst =>)Aufgrund eines Bugs in PHP-Preg-Ausdrücken kann es bei großen Cache-Blöcken zu Apache-Abstürzen kommen. Workaround: mehrere kleine Blöcke einsetzen.
- Beim Einsatz im Layout muss mindestens 1 HTML-Tag zwischen <!cache und dem nachfolgenden <container /> liegen, sonst wird der Container nicht als solcher erkannt.
- Ein weiteres Problem, dass mir gerade auffällt, ist, dass die Listen in diesem Forum keine Bullet-Points haben, und so nur schwer als Listen zu erkennen sind.
Diese Anleitung sollte mit allen Versionen zwischen contenido-cvs-2005-09-02.tar und 4.6.15 funktionieren.
cms/front_content.php
nach
Code: Alles auswählen
unset($edit); // disable editmode
Code: Alles auswählen
unset($cache); // disable cachemode
functions.mod.php
bei function modTestModule
nach
Code: Alles auswählen
$code = str_replace('CMS_VALUE','$CMS_VALUE', $code);
$code = str_replace('CMS_VAR','$CMS_VAR', $code);
Code: Alles auswählen
$code = preg_replace('/<!cache=-?\d*/','<'.'?php',$code);
$code = str_ireplace(Array('<!cache','cache!>'),Array('<'.'?php','?'.'>'), $code);
neue funktion
Code: Alles auswählen
/**
* Generates the code for cached elements
*
* @param int $idcat Id of category
* @param int $idart Id of article
* @param int $idcatart Id of categoryarticle
* @param int $idartlang Id of articlelang
* @param int $lang Id of language
* @param int $client Id of client
* @param int $output code fragment which may include <!cache cache!> elements
*
* @author Martin Horwath <horwath@dayside.net>, Tobias Nothdurft <tono@online.de>(lifetime)
* @copyright dayside.net <www.dayside.net>
*/
function conGenerateCacheCode ($idcat, $idart, $idcatart, $idartlang, $lang, $client, &$output) {
// cache prototyp engine
if (strpos ($output, "<!cache") !== false && strpos ($output, "cache!>") !== false) {
global $cfg, $cfgClient, $edit, $sess, $perm, $auth, $encoding;
$db = new DB_Contenido();
// very simple ... very fast
$output = stripslashes($output);
$output = '<'.'?php $_cachedCode = Array(); $_cachedCode["update"] = false; $_cachedCode["code"] = $code; ?'.'>'."\n".$output;
$output.= '<'.'?php if ($_cachedCode["update"] == true) conUpdateCacheCode('."$idcatart,$lang,$client,0,'',true".'); ?'.'>'."\n";
$output = str_replace (Array("<"."?","?".">"), Array("<!?","?!>"), $output);
srand(time());
// Emulate lazy matching with ereg.
$parts=explode('<!cache',$output);
foreach ($parts as $nr=>$part)
{
$match=array();
if (ereg('=([-]?[[:digit:]]+)[[:space:]](.*)cache!>(.*)',$part,$match))
{
$cacheid=rand(1,999999);
switch (true)
{
case ($match[1]==0):
$parts[$nr]= '<!?php '.$match[2].' ?!>'.$match[3];
break;
case ($match[1]<0):
$unprotected = str_replace (Array("<!?","?!>"), Array("<"."?","?".">"), $match[2]);
$parts[$nr]= '<?php '.$unprotected.' ?>'.$match[3];
break;
case ($match[1]>0):
$unprotected = str_replace (Array("<!?","?!>"), Array("<"."?","?".">"), $match[2]);
$tag = "<!-- cached code id $cacheid -->"; //The CacheBlock-Delimiter
$parts[$nr]= '<!?php if ((time() > '.time().'+'.$match[1].') and (!$edit)):// cache id '."$cacheid\n".
'ob_start();'."\n".
"$match[2]\n".
'$_cachedCode["update"] = true;'."\n".
'conUpdateCacheCode('."$idcatart,$lang,$client,$cacheid".',ob_get_flush());'."\n".
'else:?!>'."\n".
$tag."\n".
'<?php'."\n".
"$unprotected\n".
'?>'."\n".
$tag."\n".
'<!?endif;?!>'.
$match[3];
}
}
}
$output = implode($parts);
$cache = true;
ob_start();
eval("?>\n".$output."\n<?php\n");
$output = ob_get_contents();
ob_end_clean();
$output = str_replace (Array("<!?","?!>"), Array("<"."?","?".">"), $output);
$output = addslashes($output);
}
}
Code: Alles auswählen
if (strpos ($output, "<!cache") !== false && strpos ($output, "cache!>") !== false) {
noch eine neue Funktion (functions.con2.php)
Code: Alles auswählen
/**
* Update the code for cached elements
*
* @param int $idcatart Id of categoryarticle
* @param int $lang Id of language
* @param int $client Id of client
* @param int $cacheid ID of the cache Block
* @param int $cached_code the newly evaluated Output
*
* @author Tobias Nothdurft <tono@online.de>
* @copyright Tobias Nothdurft, 2006
*/
function conUpdateCacheCode($idcatart,$lang,$client,$cacheid,$newCodeBlock, $update = false)
{
global $_cachedCode;
if (!$update) {
$tag = "<!-- cached code id $cacheid -->"; // the cache block delimiter
$codearr = explode($tag, $_cachedCode['code']); // explode the old code and ...
$_cachedCode['code'] = $codearr[0].$tag.$newCodeBlock.$tag.$codearr[2]; //...glue together the new one
$_cachedCode['code'] = preg_replace('%if \(\(time\(\) > \d*\+(\d*)\) and \(!\$edit\)\):// cache id '.$cacheid.'%',"if ((time() > ".time().'+$1) and (!$edit)):// cache id '.$cacheid, $_cachedCode['code']); // fit it into the code for expiration checking
} else {
echo "<!-- CACHED PARTS UPDATED //-->";
global $cfg;
$db = new DB_Contenido();
$sql = "UPDATE ".$cfg["tab"]["code"]." SET `code`='".addslashes(addslashes($_cachedCode['code']))."' WHERE `idcatart` = ".$idcatart." AND `idlang` = ".$lang." AND `idclient` = ".$client.";";// do not forget to addslash twice.
$db->query($sql);
}
}
selbe datei
function conGenerateCode
nach
Code: Alles auswählen
/* Replace new containers */
$code = preg_replace("/<container( +)id=\\\\\"$value\\\\\"(.*)>(.*)<\/container>/i", "CMS_CONTAINER[$value]", $code);
$code = preg_replace("/<container( +)id=\\\\\"$value\\\\\"(.*)\/>/i", "CMS_CONTAINER[$value]", $code);
$code = str_ireplace("CMS_CONTAINER[$value]", "<?php $CiCMS_VALUE ?>\r\n".$output, $code);
$fedebug = "";
}
Code: Alles auswählen
conGenerateCacheCode ($idcat, $idart, $idcatart, $idartlang, $lang, $client, $code);
nach
Code: Alles auswählen
/* Long syntax with closing tag */
$code = preg_replace("/<container( +)id=\\\\\"$value\\\\\"(.*)>(.*)<\/container>/i", "CMS_CONTAINER[$value]", $code);
/* Short syntax */
$code = preg_replace("/<container( +)id=\\\\\"$value\\\\\"(.*)\/>/i", "CMS_CONTAINER[$value]", $code);
$code = str_ireplace("CMS_CONTAINER[$value]", "<?php $CiCMS_VALUE ?>\r\n".$output, $code);
}
Code: Alles auswählen
conGenerateCacheCode ($idcat, $idart, $idcatart, $idartlang, $lang, $client, $code);
(Dieser Beitrag ist zurzeit nicht aktuell, und dient nur der Verdeutlichung, bitte den aktuellen Code oben verwenden)
in functions.con2.php Funktion conGenerateCacheCode
3 reguläre Ersetzungen und den Zufallsgenerator für die CacheID hinzugefügt:
Code: Alles auswählen
srand(time());
$cacheid=rand(1,999999);
//Sonderfall lifetime = 0
$output = preg_replace("/<!cache=0\s(.*)cache!>/s",
'<!? $1 ?!>',$output);
//Sonderfall lifetime negativ
$output = preg_replace("/<!cache=-\d*\s(.*)cache!>/s",
'<? $1 ?>',$output);
//Standardfall
$output = preg_replace("/<!cache=(\d*)\s(.*)cache!>/s",
'<!? if ((time() > '.time().'+$1) and (!$edit)):'."\n".
'ob_start();'."\n".
'$2'."\n".
'$cached_code=ob_get_flush();'."\n".
'conUpdateCacheCode('."$idcatart,$lang,$client,$cacheid".',$cached_code);'."\n".
'else:?!>'."\n".
'<!--cached code id '."$cacheid>\n".
'<?'."\n".
'$2'."\n".
'?>'."\n".
'<!--cached code id '."$cacheid>\n".
'<!?endif;?!>',$output);
Code: Alles auswählen
/**
* Update the code for cached elements
*
* @param int $idcatart Id of categoryarticle
* @param int $lang Id of language
* @param int $client Id of client
* @param int $cacheid ID of the cache Block
* @param int $cached_code the newly evaluated Output
*
* @author Tobias Nothdurft <tono@online.de>
* @copyright Tobias Nothdurft, 2006
*/
function conUpdateCacheCode($idcatart,$lang,$client,$cacheid,$cached_code)
{
global $code, $cfg, $db;
$tag = "<!--cached code id $cacheid>"; //The CacheBlock-Delimiter
$codearr = explode($tag,$code); //Explode the old code and ...
$newcode = $codearr[0].$tag.$cached_code.$tag.$codearr[2]; //...glue together the new one
$newtime = time(); //get actual time and...
$newcode = addslashes(addslashes(preg_replace("/if \(time\(\) > \d*\+(\d*)\)/","if (time() > $newtime+$1)",$newcode))); //fit it into the code for expiration checking. do not forget to addslash twice.
$sql = "UPDATE con_code SET `code`='".$newcode."' WHERE `idcatart` = ".$idcatart." AND `idlang` = ".$lang." AND `idclient` = ".$client.";"
$db->query($sql);
}
neue Zeile wegen geänderter Syntax:
Code: Alles auswählen
$code = preg_replace('/<!cache=-?\d*/','<'.'?php',$code);

einige anmerkungen
conUpdateCacheCode fordert ja die variable $code via global an,
tja und im edit modus sieht der inhalt der variable etwas anders aus, als im frontend. d.h man müsste $edit berücksichtigen sodas keinesfalls ein update der db tabelle vorgenommen wird.
die andere sache was passiert wenn $code im evaluierten code umdefiniert wird (zb ein modul ändert den inhalt der variable) ?
wenn es mehrere unterschiedliche passagen gibt die code zur selben zeit updaten sollen, werden vermutlich nicht alle passagen in der db korrekt vorgenommen... (da ja immer wieder auf die $code vor dem update zurückgegriffen wird...)
ähm da würd ich vorschlagen es anders zu handeln..Für <lifetime> setzt man einfach die gewünschte Lebenszeit in Sekunden ein. Ein Wert von 0 führt dazu, dass nicht gecached wird. Ein negativer Wert ergibt einen unendlich gültigen Cache, entspricht damit exakt der Funktionsweise der Simple Cache Engine.
Wird =<lifetime> weggelassen, wird eine unendliche Lebenszeit angenommen.
<!cache sollte <!cache=0 entsprechen... -> sollte somit unendlich gültig sein
<!cache=-1 sollte ungecached sein...
also <!cache=0 und <!cache=-1 vertauschen...
ist für mich logischer, geb ich nichts an ist es das selbe als ob ich =0 definiere...
ne kleine zusatz info:
bin vor kurzen auf ein problem genagelt, das zu einem ziemlich unangenehmen nebeneffekt führen kann...
wenn ich sowas wie das hier habe
Code: Alles auswählen
$output = preg_replace("/<!cache=-\d*\s(.*)cache!>/s", '<? $1 ?>',$output);
(ne lösung für das hab ich aber leider auch nicht...)
Dankeemergence hat geschrieben:...coole erweiterung...![]()

Es ist ohnehin nicht sinnvoll im Edit-Modus gecachte Inhalte zu zeigen. Deshalb werde ichemergence hat geschrieben:conUpdateCacheCode fordert ja die variable $code via global an, tja und im edit modus sieht der inhalt der variable etwas anders aus, als im frontend. d.h man müsste $edit berücksichtigen sodas keinesfalls ein update der db tabelle vorgenommen wird.
Code: Alles auswählen
and (!$edit)
Dann wird dieser geänderte Code die Grundlage für das Caching. Allerdings kann der Modulentwickler nicht davon ausgehen, dass seine Änderungen am $code zurück in die DB geschrieben werden. Deshalb wird das vermutlich Probleme verursachen.emergence hat geschrieben:die andere sache was passiert wenn $code im evaluierten code umdefiniert wird (zb ein modul ändert den inhalt der variable) ?
Ist das eine theoretische Frage, oder kennst Du so ein Modul?
Korrekt. Es wird immer nur der letzte Cache-Block in der DB bleiben. Beim nächsten Aufruf ist dann der vorletzte Block dran, vorausgesetzt der letzte ist nicht schon wieder abgelaufen. Es wird allerdings nie veralteter Inhalt ausgegeben, denn die vorderen Cache-Blöcke bleiben ja als abgelaufen markiert, und werden bei jedem Aufruf evaluiert. Es ergibt sich allerdings ein Performance-Probelm, weil sinnlose UPDATE-Anfragen zur DB geschickt werden.emergence hat geschrieben:wenn es mehrere unterschiedliche passagen gibt die code zur selben zeit updaten sollen, werden vermutlich nicht alle passagen in der db korrekt vorgenommen... (da ja immer wieder auf die $code vor dem update zurückgegriffen wird...)
Ich schreibs unter "Bekannte Einschränkungen" und warte auf einen Geistesblitz.

Na ja, der Parameter heißt ja lifetime. Und eine Cache-Lebenszeit von 0 heißt, der Cache ist schon abgelaufen, wenn er erstellt wird. Also kein Caching. Oder sieh es so: Dem Cache wird der Wert 0 zugewiesen und er wird somit deaktiviert.emergence hat geschrieben:ähm da würd ich vorschlagen es anders zu handeln..
<!cache sollte <!cache=0 entsprechen... -> sollte somit unendlich gültig sein
<!cache=-1 sollte ungecached sein...
also <!cache=0 und <!cache=-1 vertauschen...
ist für mich logischer, geb ich nichts an ist es das selbe als ob ich =0 definiere...
Desweiteren ist es programiertechnische Praxis den Wert unendlich durch eine negetive Zahl darzustellen. Also -1 = unendlicher Cache.
Die Verwendung von <!cache ohne lifetime habe ich eingentlich nur wegen Abwärtskompatibilität dringelassen.
Ich würde lieber bei dieser Syntax bleiben.
Dieser Fehler lässt sich durchemergence hat geschrieben:ne kleine zusatz info:
bin vor kurzen auf ein problem genagelt, das zu einem ziemlich unangenehmen nebeneffekt führen kann...
wenn ich sowas wie das hier habeund $1 überschreitet eine gewisse string größe... (ca. 2000-3000 zeichen) bricht der php interpreter ab (leider mit keinerlei fehlermeldung)...Code: Alles auswählen
$output = preg_replace("/<!cache=-\d*\s(.*)cache!>/s", '<? $1 ?>',$output);
(ne lösung für das hab ich aber leider auch nicht...)![]()
Code: Alles auswählen
$output = preg_replace("/<!cache=-\d*\s(.*){,2000}(.*){,2000}(.*){,2000}(.*)cache!>/s", '<? $1$2$3$4 ?>',$output);
ähm ja -> snippetstono hat geschrieben:Dann wird dieser geänderte Code die Grundlage für das Caching. Allerdings kann der Modulentwickler nicht davon ausgehen, dass seine Änderungen am $code zurück in die DB geschrieben werden. Deshalb wird das vermutlich Probleme verursachen.
Ist das eine theoretische Frage, oder kennst Du so ein Modul?
kannst du ja... war ja nur ein vorschlag meinerseits..tono hat geschrieben:Desweiteren ist es programiertechnische Praxis den Wert unendlich durch eine negetive Zahl darzustellen. Also -1 = unendlicher Cache.
Die Verwendung von <!cache ohne lifetime habe ich eingentlich nur wegen Abwärtskompatibilität dringelassen.
Ich würde lieber bei dieser Syntax bleiben.
das ändert leider nix, es ist ne limitierung von preg anweisungen...tono hat geschrieben:Dieser Fehler lässt sich durchvielleicht abmildern. Ungetestet.Code: Alles auswählen
$output = preg_replace("/<!cache=-\d*\s(.*){,2000}(.*){,2000}(.*){,2000}(.*)cache!>/s", '<? $1$2$3$4 ?>',$output);
hier auch beschrieben -> http://bugs.php.net/bug.php?id=27070
Hab ich oben unter bekannte Einschränkungen ergänzt. Das ist natürlich richtig großer Mist.emergence hat geschrieben: das ändert leider nix, es ist ne limitierung von preg anweisungen...
hier auch beschrieben -> http://bugs.php.net/bug.php?id=27070

Das Snippets-Modul muss ich mir mal anschauen und überlegen was man da machen könnte.
stimmt... wobei das noch ne untertreibung ist...tono hat geschrieben:Das ist natürlich richtig großer Mist.
ähm... getestet, ja der fehler ist bei ereg nicht reprozierbar..
intressant wirds ja erst wenn mal php6 rauskommt, da wird auf ereg verzichtet (bzw. nur mehr als extension angeboten)
Der $code wird jetzt nach dem Cache update zurück in den globalen scope geschrieben, deshalb werden jetzt immer alle Blöcke in der DB aktualisiert. Allerdings werde immer noch zu viele UPDATE-Anfragen gemacht. Geplanter Workarround: Wenn notwendig, erst nach der gesammten Code-Evaluierung eine DB-Abfrage machen.
Zu Snippets komm ich heut nicht mehr.

da fehlt ne kleinigkeit in der conGenerateCacheCode
Code: Alles auswählen
global $cfg, $cfgClient, $edit, $sess, $perm, $auth, $encoding;
$db = new DB_Contenido();
der geplante workaround erst am ende alle teile upzudaten und somit nur ein query anzustarten halte ich für ne sehr gute idee...
für das mit $code -> da hab ich ne idee...
nach dem teil hier
Code: Alles auswählen
// very simple ... very fast
$output = stripslashes($output);
Code: Alles auswählen
<?php
if (!$_cachedCode) {
$_cachedCode = $code;
}
?>
dann mit $_cachedCode arbeiten...