Diese Anzeige ist nur für
Nicht-Mitglieder sichtbar!

Jetzt kostenlos registrieren

Datenbankbasierter SessionHandler: Fatal Error

Fragen zum Thema PHP können hier gestellt werden

Moderatoren: Basti, Guillermo, Andreas W.

Datenbankbasierter SessionHandler: Fatal Error

Beitragvon Shanair am 26.11.2011, 23:03

Guten Abend Kollegen:

Was ist an diesem datenbankbasierten SessionHandler falsch?
Die Methode _open löst einen Fehler aus. Und zwar wenn keine $_SESSION Variable definiert ist.
Sprich nur der SessionHandler mit new eingebunden wird.
Es handelt sich um eine Race Hazard Unterdrückung welche von den Buchautoren vorgeschlagen wird.
Kommentiere ich in der _open Methode die Race Hazard Unterdrückung aus und gebe nur true zurück, funktioniert alles.
Aja, es liegt nicht an der while schleife :)


Er stammt aus dem Buch PHP 5.3 und MySQL 5.5 (3. Auflage).
Ich habe lediglich statt eine MySQL Klasse eine PDO Klasse zur Datenbankanbindung verwendet. Diese sollte korrekt implementiert sein. Für updates nutze ich die PDO->exec Methode, für querys eben die PDO->query (erstmal ohne preparedStatements) zum Test.



Code: Alles auswählen
<?php
//Namespace der Klasse
namespace System;

//TODO: Beim Schreiben von Daten überprüfen, ob nicht Daten überschrieben werden.
//DEADLOCK bzw. A öffnen, B öffnen , B schreiben , A schreiben -> Problem!
//Passiert nur bei zwei offenen Fenstern des selben Users

//Wie?
//http://www.zend.com/zend/spotlight/code-gallery-wade8.php?article=code-gallery-wade8&kind=sl&id=2752&open=1&anc=0&view=1#notes

class SessionHandler {

   private $DB = null;

   /**
    * Konstruktor
    */
   public function __construct()
   {   
        $this->DB = $GLOBALS['DB'];

      // Den SessionHandler auf die Methoden
      // dieser Klasse setzen
      session_set_save_handler(array ($this, '_open'),
                         array ($this, '_close'),
                         array ($this, '_read'),
                         array ($this, '_write'),
                         array ($this, '_destroy'),
                         array ($this, '_gc'));
      

      // Session starten
      session_start();
      echo "Session gestartet und erzeugt".session_id()."<br />";
      register_shutdown_function('session_write_close');
   }

   /**
    * Öffnen der Session
    */
   
   public function _open($path, $name) {
      
    #echo "_open";
       echo "In Session _open: "."<br />";
      $locked = true;
      $loop  = 0;
      
      
      while($locked && ($loop < 20))
      {
         $sessionStatement = "UPDATE sessions SET locked = 1"
                               ." WHERE id = '".session_id()."' AND locked = 0";
            $result = $this->DB->exec($sessionStatement);
         if ($result === 1)
         {
            #echo "Not locked.<br />";
            $locked = false;
         }else
         {
            //Überprüfen, ob überhaupt vorhanden
            $sessionStatement = "SELECT * FROM sessions"
                                   ." WHERE id = '".session_id()."'";
                $result = $this->DB->query($sessionStatement);
            
            if(count($result) != 0)
            {
               //Datensatz vorhanden aber wohl (dem Statement oben entsprechend)
               //gelockt.
               #echo "Locked.<br />";
                $loop++;
                 usleep(100000);
                 if($loop == 19)
               {
                 //Behandlungsroutine für komplett gelockte Tables?!
                 die();}
                
            }else
            {
               //Muss erst angelegt werden...also ok.
               #echo "not locked...muss angelegt werden. Und zwar hier drin!";
               //Ansonsten muss eine neue Session erstellt werden
                 $sessionStatement = "INSERT INTO sessions "."(id, locked)"." VALUES ('".session_id()."',1)";
                 $result = $this->DB->exec($sessionStatement);
               echo 'NEUE SESSION IN DATENBANK ABGELEGT';
                 //Wenn hier keine Zeile geschaffen wurde, hat dieses Skript die Race Conditions verloren
                 //dies erfolgreich war....die Session wurde zwischenzeitlich von einem anderen Skript angelegt.
               
                 if($result === 1)
               {
                  
                   $locked = false;
                 }
                    
            }
         }
      
      }
         
      
      return true;
   }

   /**
    * Session schließen
    */
   public function _close() {
       echo "In Session _close: "."<br />";
      
       $sessionStatement = "UPDATE sessions SET locked = 0"
                               ." WHERE id = '".session_id()."' AND locked = 1";
            $result = $this->DB->exec($sessionStatement);
         echo "Result: ".$result;
         if($result === 1)
         {
            echo 'von 1 auf 0 gesetzt';
         }
      //Ruft den Garbage-Collector auf.
      $this->_gc(0);
      return true;
   }

   /**
    * Session-Daten aus der Datenbank auslesen
    *
    */
   public function _read($sesID) {
      
      echo "In Session _read: "."<br />";
      
      $sessionStatement = "SELECT * FROM sessions"." WHERE id = '$sesID'";
      $result = $this->DB->query($sessionStatement);
      if ($result === false) {
         return '';
      }      
      if (count($result) > 0) {
         echo 'SESSION IN DATENBANKGEFUNDEN_read()';
         return $result[0]["value"];
      } else {
         return '';
      }
   }

   /**
    * Neue Daten in die Datenbank schreiben
    *
    * @param varchar eindeutige Sessionid
    * @param Array Alle Daten der Session
    */
   public function _write($sesID, $data) {
       
       echo "In Session _write: "."<br />";
        if($data == null){
           return true;
        }
       
      //Statement um eine bestehende Session "upzudaten"
      $sessionStatement = "UPDATE sessions "."SET validTill='".time()."', value='$data' WHERE id='$sesID'";
      $result = $this->DB->exec($sessionStatement);

      //Ergebnis prüfen
      if ($result === false) {
         //Fehler in der Datenbank
         return false;
      }
      if ($result === 1) {
         //bestehende Session "upgedated"
         return false;
      }
       
      //Ansonsten muss eine neue Session erstellt werden
      $sessionStatement = "INSERT INTO sessions "."(id, validTill, start, value,locked)"." VALUES ('$sesID', '".time()."', '".time()."', '$data',1)";
      $result = $this->DB->exec($sessionStatement);

      //Ergebnis prüfen
      if ($result === false) {
         //Datenbankfehler...nicht erreichbar..Mail an Admin!!
         echo 'Datenbankfehler';
         return false;
      } else {
         //Session wurde angelegt.
         return true;
      }
   }

   /**
    * Session aus der Datenbank löschen
    *   
    * @param varchar eindeutige Session-Nr.
    */
   public function _destroy($sesID) {
      echo "In Session _destroy: "."<br />";
      
      $sessionStatement = "DELETE FROM sessions "."WHERE id = '$sesID'";
      $result = $this->DB->exec($sessionStatement);
      if ($result === false) {
         return false;
      } else {
         echo 'SESSION AUS DATENBANK ENTFERNT';
         return true;
         
      }
      
   }

   /**
    * Müll-Sammler ;-)
    *
    * Löscht abgelaufene Sessions aus der Datenbank
    */
   public function _gc($life) {
      
      echo "In Session _gc: "."<br />";
       //Zeitpunkt, zu dem die Session als abgelaufen gilt.
      //Hier 15 min
      $sessionLife = strtotime("-15 minutes");

      $sessionStatement = "DELETE FROM sessions "."WHERE validTill < $sessionLife";
      $result = $this->DB->query($sessionStatement);

      if ($result === false) {
         //Datenbankfehler:
         return false;
      } else {         
         echo 'gelöscht';
         return true;
      }
   }
}
?>


Und hier die DB Klasse dazu (meine Modifikationen gegenüber dem Buch sollten korrekt sein)
Code: Alles auswählen
<?php
//Namespace der Klasse
namespace System\Database;

/**
* Abstraktionsschicht für die Datenbank
*
* Verbindet zur Datenbank und kapselt alle Anfragen an die
* Datenbank. Nutzbar mit diversen Datenbanken (MySQL, SQLite, Oracle, etc.)
*
*/

class PDO
{

    //Datenbankverbindungsobjekt
    public $PDO = null;
    //aktuelles preparedStatement
    public $preparedStatement = null;

    /**
     * Verbindet zur Datenbank und gibt ggf. eine
     * Fehlermeldung zurück.
     *
     */
    public function __construct($dsn, $user, $password)
    {
        try
        {
            //Neues PDO-Objekt
            $this->PDO = new \PDO($dsn,$user,$password);
            //Fehlermeldungen sollen "geworfen" werden
            $this->PDO->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);   
        }
        catch (PDOException $e)
        {
            //Fehlerbehandlung (bspw. E-Mail an Admin)
            die('<div style="color:red;">'.$e->getMessage().'</div>');   
        }
   
    }

    /**
     * Führt eine SQL-Anfrage durch.
     *
     * @param text Die SQL-Anfrage
     *
     * @return Array Gibt eine Ergebnismenge zurück
     */
    public function query($sql)
    {
        try
        {
            //PDO-Anfrage durchführen
            $pdoStmt  = $this->PDO->query($sql);
            //Liegt eine leere Ergebnismenge vor?
         
            if($pdoStmt->rowCount()==0)
         {
             $return = array();
         } else
         {
            //Array mit den Daten
            $return = $pdoStmt->fetchAll();
         }
            //Statement schließen
            $pdoStmt->closeCursor();
            return $return;   
               
        }
        catch(PDOException $e)
        {
            //Fehlerbehandlung (bspw. E-Mail an Admin)
            echo '<div style="color:red;">'.$e->getMessage().'</div>';   
            return false;               
        }
   
    }
   
    /**
     * Legt ein "prepared Statement" an
     *
     * @param String Statement mit Platzhalter-Parametern
     */
    public function prepareStatement($statement)
    {
        //Prepared Statement vorbereiten
        $this->preparedStatement = $this->PDO->prepare($statement);
        if($this->preparedStatement===false)
        {
            //Fehlerbehandlung (bspw. E-Mail an Admin)
            echo '<div style="color:red;">Prepared Statement konnte nicht vorbereitet werden.</div>';
        }           
    }
   
    /**
     * Führt ein zuvor angelegtes preparedStatement aus
     *
     * @param Array Die Parameter für das prepared Statement.
     *
     * @return Array Ergebnis der Anfrage
     */
    public function execute($params = array())
    {
        //Wenn noch kein Statement angelegt ist, wird hier abgebrochen.
        if($this->preparedStatement==null)return false;
       
        try
        {
            //PDO-Anfrage durchführen
            $this->preparedStatement->execute($params);
            //Wenn keine Daten zurück kamen
            if($this->preparedStatement->columnCount()==0)
         {
            return array();
         } else
         {
            //Andernfalls die Daten als Array zurückgeben
            return $this->preparedStatement->fetchAll();
         }
               
        }
        catch(PDOException $e)
        {
            //Fehlerbehandlung (bspw. E-Mail an Admin)
            echo '<div style="color:red;">'.$e->getMessage().'</div>';   
            return false;               
        }   
    }
   
   public function exec($sql)
   {
      $affectedRows = $this->PDO->exec($sql);
      return $affectedRows;   
   }

   
}
?>



Aja, die DB hat folgende Tabelle:

sessions
Mit id, value, start, lastUpdated(nicht verwendet) und validTill

Es gibt für die index.php ein globales Datenbankobjekt und der SessionHandler wird per new SessionHandler eingebunden.

Die Umgebung, dass Einbinden und die Datenbankzugriffe sind korrekt, da hier keine Fehler erzeugt werden.

Am Schluss des Skriptes erhalte ich :

Fatal error: Exception thrown without a stack frame in Unknown on line 0

Warum?
AMD Phenom II X4 920 2,8Ghz, 3GB Ram, Windows 7 Prof., Ubuntu 10, Geforce 260 Gtx, LED HD 22", Adobe Web Premium CS5.5, Notepad++, Eclipse(Android), FlashDevelop, OpenOffice, Skype, Gimp, Firebug, BlueJ, Dia::: Individuelle Web Entwicklung!
Benutzeravatar
Shanair
Mitglied
 
Beiträge: 126
Registriert: 05.11.2009, 18:39
Wohnort: nahe Regensburg
Diese Anzeige ist nur für
Nicht-Mitglieder sichtbar!
 
Jetzt kostenlos registrieren

Re: Datenbankbasierter SessionHandler: Fatal Error

Beitragvon hoytwins am 28.11.2011, 00:58

hallo, hast du inzwischen eine lösung gefunden habe auch probleme, dass er mir die daten in die db schreibt?

g
hoytwins
Mitglied
 
Beiträge: 1
Registriert: 28.11.2011, 00:57

Re: Datenbankbasierter SessionHandler: Fatal Error

Beitragvon Shanair am 28.11.2011, 15:27

Servus!

Ich habe vom Author des Buches die Informationen, dass es Fehler im SessionHandler2 gibt.

In der _open Methode nuss $sesID mit session_id() ausgetauscht werden.

Der Fatal Error allerdings bleibt offen.
Dieser verschwindet nur wenn mindestens 1 $_SESSION Variable deklariert wird.

Die SessionHandler (1) Version aus dem Buch funktioniert ebenfalls nicht richtig.
Auch nicht, wenn man 1 zu 1 den Quellcode des Buches verwendet...

Ich teste noch ein wenig.
AMD Phenom II X4 920 2,8Ghz, 3GB Ram, Windows 7 Prof., Ubuntu 10, Geforce 260 Gtx, LED HD 22", Adobe Web Premium CS5.5, Notepad++, Eclipse(Android), FlashDevelop, OpenOffice, Skype, Gimp, Firebug, BlueJ, Dia::: Individuelle Web Entwicklung!
Benutzeravatar
Shanair
Mitglied
 
Beiträge: 126
Registriert: 05.11.2009, 18:39
Wohnort: nahe Regensburg

Re: Datenbankbasierter SessionHandler: Fatal Error

Beitragvon Andreas W. am 30.11.2011, 22:45

Guten Abend!

Schau dir zu diesem Thema doch mal mein Tutorial unter PHP & OOP - Datenbankbasiertes Session-Management an. Im Manual unter session_set_save_handler() findest du außerdem noch viele weitere Hinweise.

So auf die Schnelle konnte ich in deinem Code keinen Fehler finden, er scheint mir an manchen Stellen doch etwas umständlich. Außerdem würde ich weder mit $_GLOBALS arbeiten noch das Datenbank-Objekt innerhalb der Klasse laden.

Falls dich eine Alternative zu $_GLOBALS interessiert schau dir doch mal mein Tutorial zum Thema PH & OOP - Das Registry Pattern an.

Liebe Grüße

Andreas
Association for Valid wEb DevelOpment - Informatik, Programmierung & Webdesign
http://www.avedo.net
Benutzeravatar
Andreas W.
Web Moderator
 
Beiträge: 1352
Registriert: 09.12.2007, 20:12
Wohnort: Göttingen


Zurück zu PHP

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast