XCache

Hallo Leute,

wir sind gerade in einem Projekt Caching einzuführen, genauer gesagt geht es dabei um das Caching von großen Objekten (heißt > 100kb). Zuvor haben wir das ganze in der Session abgelegt. Da wir diese aber über CDbHttpSession in der Datenbank halten ist das ganze recht schnell an seine Grenzen gestoßen nämlich zu dem Zeitpunkt als das serialisierte Objekt nicht mehr in das Feld der Datenbank gepasst hat.

Das eigentliche Problem ist das wir ein Objekt über einen längeren Zeitraum immer verfügbar haben müssen und auf diesem Objekt auf verschiedenen Seiten Berechnungen durchgeführt werden, das Objekt geupdatet und so weiter.

Nun wollen wir das ganze mit XCache lösen in dem wir das Objekt in den Cache schreiben und dann rausholen wenn wir es brauchen. Das Problem ist nun das wenn das Objekt aus dem Cache geladen wird und es unserealisiert wird irgendwie die Behaviours auf der Strecke bleiben.

Der Stacktrace ist:




PHP Error


include(TimestampUserBehavior.php): failed to open stream: No such file or directory


/var/www/yii/framework/YiiBase.php(421)


409                 {

410                     foreach(self::$_includePaths as $path)

411                     {

412                         $classFile=$path.DIRECTORY_SEPARATOR.$className.'.php';

413                         if(is_file($classFile))

414                         {

415                             include($classFile);

416                             break;

417                         }

418                     }

419                 }

420                 else

421                     include($className.'.php');

422             }

423             else  // class name with namespace in PHP 5.3

424             {

425                 $namespace=str_replace('\\','.',ltrim($className,'\\'));

426                 if(($path=self::getPathOfAlias($namespace))!==false)

427                     include($path.'.php');

428                 else

429                     return false;

430             }

431             return class_exists($className,false) || interface_exists($className,false);

432         }

433         return true;

Stack Trace

#0	

+  /var/www/yii/framework/YiiBase.php(421): YiiBase::autoload()

#1	

 unknown(0): YiiBase::autoload("TimestampUserBehavior")

#2	

 unknown(0): spl_autoload_call("TimestampUserBehavior")

#3	

+  /var/www/yii/framework/caching/CCache.php(84): unserialize("a:2:{i:0;O:17:"TestDurchfuehrung":15:{s:9:"Anmeldung";O:9:"Anmel...")

#4	

–  /var/www/esft20/protected/controllers/TestController.php(249): CCache->get("4e15588d4b9d77.56854913")

244     {

245         if (!isset(Yii::app()->session['test'])) {

246             $this->redirect(array('index'));

247         }

248         

249         $testdurchfuehrung = Yii::app()->cache->get(Yii::app()->session['test']);

250         

251         if ($testdurchfuehrung->checkClass('DurchfuehrungPause')) {

252             Yii::app()->session['design']->setStyle(

253                 array(

254                     'style'      => 5,

#5	

+  /var/www/yii/framework/web/actions/CInlineAction.php(50): TestController->actionShow()



[update] Das TimestampUserBehavior gibt es und es funktioniert auch ganz normal bis das Objekt serialisiert und unserialisiert wird. [/update]

Was mach ich falsch? Muss man zuerst die Behaviors entfernen und später dann wieder aktivieren? Funktioniert das mit dem XCache überhaupt so wie wir uns das Vorstellen? Ist es dazu geeignet? Gibt es andere Möglichkeiten das zu realisieren was wir haben wollen?

Ich hoffe ihr könnt mir weiterhelfen.

Grüße Beste

Martin

Ich denke die "normalen" Yii Klassen sind nicht dafür geschrieben, komplett (inkl. Behaviors) serialisiert/deserialisiert zu werden. Aber schau dir doch mal die __sleep() und __wakeup() Funktionen an, damit könntest du deine Klasse anreichern und so die Sache selbst in die Hand nehmen:

http://de3.php.net/m…oop5.magic.php

Bzgl. XCache frag ich mich, ob es sich um Daten handelt, die sich pro Benutzer ändern, weil ihr ja bisher die Session verwendet habt. D.h. ihr müsst in den Cache-Keys die User-ID mit einfließen lassen. In solchen Fällen verwend ich meistens meine eigene erweiterte WebUser-Klasse und bau Methoden wie getIrgendwas()/setIrgenwas() ein. Dort kannst du dann den Cache Zugriff regeln und im Getter z.B. auch ein neues Objekt erstellen, falls noch keins im Cache vorliegt. Dann kannst du überall mit Yii::app()->user->irgendwas auf dieses Objekt zugreifen und hast das ganze schön gekapselt.

EDIT:

Das Behavior-Problem scheint doch ein anderes zu sein. Sieht aus, als wäre evtl. deine Konfiguration mit den Imports noch nicht geladen, wenn das Objekt Deserialisiert wird. Hmmm. Evtl. mal etwas im Core debuggen?

Hallo,

ja ich bin immer noch dabei das Problem weiter einzugrenzen. Wenn ich in der selben Action den Wert erst setze und dann wieder lese funktioniert alles wunderbar und völlig Problemlos. Wenn ich aber den Wert in einer anderen Action lesen möchte tritt das oben beschriebene Problem auf.

Ich setze die ID durch PHP uniqid("", true) Funktion und führe die für den einzelnen Benutzer . Sollte also schon einigermaßen eineindeutig sein.

Grüße

Martin

Kann ich leider nicht. Kackt immer ab und ich hab in nem Jahr nicht rausgefunden warum.

Wie sieht denn die Codezeile aus, mit der du versuchst das Objekt zurück aus dem Cache zu lesen? Lass dir an der Stelle mal den PHP include_path anzeigen, um zu sehen, ob das Verzeichnis mit dem Behavior richtig importiert wurde.

Hallo,

also ich speichere das Objekt folgendermaßen in im Cache ab:




Yii::app()->session['test'] = uniqid('', true);

Yii::app()->cache->set(Yii::app()->session['test'], $testdurchfuehrung);



und so hol ich es in einer anderen Controller Action eines anderen Controllers wieder raus:




$testdurchfuehrung = Yii::app()->cache->get(Yii::app()->session['test']);



An der Stelle hab ich wie gesagt hast mal




echo get_include_path();

die();

$testdurchfuehrung = Yii::app()->cache->get(Yii::app()->session['test']);



Eingefügt. Das Ergebnis war:

.:/var/www/esft20/protected/components/pdf:/var/www/esft20/protected/components/mail:/var/www/esft20/protected/components:/var/www/esft20/protected/models:/usr/share/php:/usr/share/pear

Ich hoffe das war richtig bzw. der richtige Befehl. Später kann ich den Befehl nicht mehr aufrufen da eine Exception fliegt.

Grüße

Hilft das hier?

Hab das mal genau so in der php.ini geändert, Server auch neu gestartet aber es zeigt keine Wirkung

php.ini




; The unserialize callback function will be called (with the undefined class'

; name as parameter), if the unserializer finds an undefined class

; which should be instantiated. A warning appears if the specified function is

; not defined, or if the function doesn't include/implement the missing class.

; So only set this entry, if you really want to implement such a

; callback-function.

unserialize_callback_func = 'spl_autoload_call'



Was müsste man da wie implementieren um das zu realisieren?

Grüße

Ich glaube Y!! hat genau das richtige gefunden. Aber du musst den richtigen Autoloader nehmen (den von Yii). Probier doch mal sowas hier, bevor du dein Objekt aus dem Cache holst:


ini_set('unserialize_callback_func',array('YiiBase','autoload'));

EDIT:

Dürfte problematisch sein, da man evtl. keinen Array übergeben kann. Dann musst du eine Hilfsfunktion bauen, und entsprechend ‘mein_autoloader’ registerieren:




function mein_autoloader($classname)

{

    return YiiBase::autoload($className);

}

Wo sollte ich die mein_autoloader() Funktion reinmachen?

Egal wo. Kannst du auch in der index.php definieren. Darf keine Klassenmethode sein, sondern eine gewöhnliche PHP Funktion.

hmmm… das scheint auch nicht zu gehen.

Ich hab in die index.php




function esft_autoloader($classname)

{

    return YiiBase::autoload($className);

}



rein und dann vor dem abruf des Objekts aus dem Cache




ini_set('unserialize_callback_func', 'esft_autoloader');

$testdurchfuehrung = Yii::app()->cache->get(Yii::app()->session['test']);



Ich bekomm aber leider nach wie vor den selben Fehler :-/


$classname!==$className

Ich hab den Fehler korrigiert aber leider immer noch genau die gleiche Exception wie zuvor.

Nicht mein Jahr…

Gibt es noch andere Möglichkeiten?

Ich sehe gerade wenn ich print_r(esft_autoloader(‘TimestampUserBehavior’)); aufrufe dann findet es die Datei auch nicht. Also an der selben Stelle. Scheint also irgendwie ein generelleres Problem zu sein?

Was gibt es denn aus? Der Autoloader müsste true zurückliefern, wenn die Funktion gefunden wird. Kannst du auch mal Testhalber ein die(‘bla’); oder so in deinen Autoloader einbauen, um zu sehen, ob er überhaupt aufgerufen wird?

Genau das hab ich gerade auch gedacht und Probiert. Also wenn ich nach einer Model Klasse wie beispielsweise meiner Benutzerklasse suche dann geht es Problemlos und der Autoloader gibt mit




        ini_set('unserialize_callback_func', 'esft_autoloader');

        

        print_r(esft_autoloader('Benutzer'));



true zurück aber mit den Behaviors geht es nicht. Echt komisch. Muss ich das in der Config noch Festlegen in welchen Verzeichnissen nach Dateien gesucht werden soll durch den Autoloader?

Nein, das macht ja YiiBase::autoload() für dich. Wird denn dein Autoloader überhaupt aufgerufen, wenn du unserialize machst? Falls nicht, klappt die unserialize_callback_func Einstellung nicht. Falls er bei unserialize richtig aufgerufen wird, gib dort mal den Include Pfad aus. Yii::import() (was du in deiner Konfig angibts) macht nichts anderes, als diese Verzeichnisse zum include Pfad hinzuzufügen. Yii’s autoloader macht dann ein einfaches include mit dem Klassennamen.

Wie kann ich das Überprüfen ob der aufgerufen wird oder nicht?

Ich hab mal in einer Action versucht:




        $test = serialize(new Termin);

        

        ini_set('unserialize_callback_func', 'esft_autoloader');

        

        $test = unserialize($test);

        

        print_r($test);



Das Schlägt auch mit dem gleichen Fehler fehl.Termin hat das Behavior TimestampUserBehavior attached.