Objekte in Sessions

Bei der Programmierung eines User Systems und der damit verbundenen Nutzung von Sessions bin ich gestern auf ein Problem gestoßen, welches mich einiges an Zeit und Nerven gekostet hat. Ich möchte Euch mit diesem Artikel natürlich nicht vorenthalten, worin dieses Problem bestand und wie man dieses Problem umgehen kann.

In der Anwendung, die ich programmiere, gibt es ein User Objekt, welches alle Daten eines Benutzers verwaltet und zu dieser Aufgabe auch einige Methoden bereitstellt. Die Daten des Benutzers sollen natürlich in einer Session gespeichert werden, so dass diese in der gesamten Anwendung über eine bestimmte Zeit verfügbar sind. Eben so, wie ihr es von einer guten Anwendung kennt: Ihr meldet Euch im System an und habt aufgrund Eurer Anmeldung Zugang zu Inhalten, die ihr ohne Anmeldung nicht sehen würdet. Sobald der Benutzer sich also mit seinem Benutzernamen und seinem Passwort authentifiziert hat, wird das User Objekt in eine Session gelegt. Soweit der Plan. Wir arbeiten strikt objektorientiert. Der PHP Code hierfür könnte wie folgt aussehen:

// Unser User Objekt in User.class.php
class User extends DataObject implements Serializable {
    /**
     * PDO Datenbankverbindung
     * @param PDO
     */
    protected $dbHandle = null;

    /**
     * Erzeugt ein neues User Datenobjekt
     *
     * @param PDO     $dbHandle  PDO Datenbankverbindung
     * @param integer $userID    ID eines Benutzerdatensatzes
     * @param string  $username  Benutzername
     */
    public function __construct(PDO $dbHandle, $userID = null, $username = null) {
        $this->dbHandle = $dbHandle;

        // Datenbankabfrage anhand User ID oder Benutzername
        try {
            $sql = "...";
            $stmt = $this->dbHandle->prepare($sql);
            $stmt->execute();
            $row = $stmt->fetch();
        } chatch (PDOException $e) {
            throw new SystemException();
        }

        parent::__construct($row);
    }

    /**
     * @see Serializable::serialize()
     */
    public function serialize() {
        return serialize($this->data);
    }
    
    /**
     * @see Serializable::unserialize()
     */
    public function unserialize($serializedData) {
        $this->data = unserialize($serializedData);
    }
}

// Anmeldung eines Users in index.php?form=Login
...
session_start();
require_once(ROOT_PATH . 'inc/data/user/User.class.php');
$user = new User($dbHandle, null, 'Marcel');
$_SESSION['user'] = $user;
...

// Abruf der Session in index.php?page=AndereSeite
...
session_start();
$user = $_SESSION['user'];
...

Wie bereits hier beschrieben, binden wir das Serializable Interface in die User Klasse ein, um nicht serialisierbare Eigenschaften, wie z.B. die PDO Klasse, auszuschließen. In der Login Instanz melden wir uns mal mit dem Namen Marcel an und bilden ein neues User Objekt, welches wir dann in einer Session speichern, so dass das User Objekt dann auch in einer anderen Instanz „AndereSeite“ zu einem späteren Zeitpunkt verfügbar ist. Genau hier beginnt mein Problem: Class __PHP_Incomplete_Class has no unserializer in …
Um diese Fehlermeldung deuten zu können, muss man verstehen, wie PHP Sessions Objekte behandeln.

Wie werden Objekte von einer Session behandelt?

Sofern man Objekte in einer Session ablegt, werden diese automatisch am Ende jeder PHP Anwendung serialisiert. Bei der Serialisierung von Objekten werden lediglich der Name des Objektes und die dazugehörigen Eigenschaften des Objektes gespeichert. Die im Objekt enthaltenen Methoden werden nicht gespeichert. Man erhält bei der Serialisierung also nur einen String, der sich auf die genannte Klasse bezieht und nicht die komplette Klasse. Beim Aufruf einer neuen PHP Seite werden die Objekte, die sich in der Session befinden, automatisch deserialisiert.

Genau hier liegt das Problem begraben, welches oben genannten Fehler verursacht. Weil die Session nur den Namen der Klasse benennt und nicht die komplette Klassendefinition enthält, kann ich in meinem Fall auch nicht auf meine unserialize() Methode zurückgreifen. Wenn ich also eine neue Seite aufrufe und auf die Session zurückgreifen möchte, ist das Objekt eigentlich gar nicht bekannt, weil es nicht definiert ist. Dies bedeutet, dass jedes Mal, bevor die Session via session_start() initialisiert wird, immer erst die Klassendefinition eingebunden werden muss. Machen wir das nicht, weist PHP dem Objekt in der Session die Klasse __PHP_Incomplete_Class_Name zu, welche keine Methoden besitzt und somit unbrauchbar wird. In diesem Fall erhalten wir also den oben genannten Fehler.

Lösungen?

Die Lösung gibt PHP in seiner Dokumentation schon selbst vor. Unsere User Klasse muss also in jeder Datei eingebunden werden, in der wir auch auf die Session zugreifen. In meinem Fall muss die User Klasse also global verfügbar sein, da die User Session ja irgendwie auch auf jeder Seite benötigt wird. Konkret bedeutet das also für die Beispielseite aus dem oben dargestellten Codebeispiel:

// Fehlerfreier Abruf der Session in index.php?page=AndereSeite
...
require_once(ROOT_PATH . 'inc/data/user/User.class.php');
session_start();
$user = $_SESSION['user'];
...

Die Session wird erst gestartet, wenn die User Klassendefinition per require_once() eingebunden ist. Somit ist die Klassendefinition bekannt und unser User Objekt in der Session kann richtig deserialisiert werden. Das Serialisieren und Deserialisieren findet weiterhin voll automatisch anhand unserer definierten Methoden im Objekt statt. Unser Fehler tritt nicht mehr auf.

Also immer daran denken: Die Session erst starten, wenn alle Klassendefinitionen der Objekte in der Session bekanntgegeben wurden. Ansonsten hagelt ’s Fehler en masse.
Haben wir wieder was gelernt. 😉

2 Gedanken zu „Objekte in Sessions“

  1. Hi,

    auch wenn der Beitrag schon etwas älter ist:

    Wie übergibst du denn dem User-Objekt nach der Deserialisierung wieder das DB Handle?

    session_start();
    $user = $_SESSION[‚user‘];

    Machst du das dann nach jeder Deserialisierung manuell?

    Antworten

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.