Destruktoren und Session-Daten in der Datenbank

01. Mai 2012 um 14:39 Uhr von Wolfgang Stengel zu PHP, MySQL
Eines meiner Projekte verwendet einen eigenen Session-Handler um die Session-Daten in die Datenbank zu schreiben anstatt in's Dateisystem. Das Einhängen und der Aufruf eigener Funktionen mit session_set_save_handler() ist dafür der erste Schritt.

Jedoch trat hierbei das Problem auf dass PHP zum Zeitpunkt des Aufrufs der Write-Funktion bereits die meisten Objekte im Speicher zerstört hatte, inklusive des Objekts welches ich für den Datenbankzugriff verwende. Hierbei handelt es sich um ein bekanntes und dokumentiertes Problem. Die PHP-Dokumentation schlägt vor, den Write-Handler manuell zu triggern mit folgender Zeile:

register_shutdown_function('session_write_close');
Shutdown-Funktionen werden vor dem Zerstören der Objekte aufgerufen. Dadurch ist mein Datenbankobjekt zum Zeitpunkt des Schliessens der Session noch vorhanden und die Daten können in die Datenbank gespeichert werden.

Das nächste Problem war dann jedoch dass die Shutdown-Funktionen auch vor den Destruktoren aufgerufen werden. Was passiert also wenn ein Destruktor eines Objekts Daten in der Session verändert? In meinem Beispiel war es ein Warenkorbobjekt eines Shop-Systems. Der Warenkorb sollte seinen Inhalt am Ende des Scripts in der Session speichern. Durch das Verwenden des register_shutdown_function()-Tricks wird der Warenkorb jedoch erst in die Session geschrieben nachdem diese bereits über session_write_close() geschlossen wurde.

Daher habe ich mir folgenden Trick ausgedacht:

class SessionShutdownHelper
{
    function __destruct() { session_write_close(); }
}
register_shutdown_function(function()
{
    $one=new SessionShutdownHelper();
    $one->one=$one;
});
Hier wird session_write_close() nicht direkt über eine Shutdown-Funktion aufgerufen, sondern indirekt über einen Destruktor eines ansonsten leeren Objekts. Durch die zirkuläre Referenz wird der PHP-Interpreter gezwungen das SessionShutdownHelper-Objekt bis ganz zum Ende des Scripts im Speicher zu halten, weil solche Kombinationen erst durch die Cycles-Collection entfernt werden können. Dadurch wird also session_write_close() erst nach allen anderen Destruktoren aufgerufen, und mein Warenkorb landet sicher in der Datenbank.
PHP Wolfgang Stengel