30.3. Gehobene Benutzung

Wärend die Beispiele für die Basisnutzung völlig akzeptierbar sind, in ihrem Weg Zend Framework Sessions zu Benutzen, ist auch etwas beste Praxis zu bedenken. Bedenkt man etwa das Zend_Auth Beispiel das Zend_Session_Namespace standardmäßig transparent benutzt um Authentifizierungs Teile zu fixieren. Dieses Beispiel zeigt einen Ansatz um schnell und einfach Zend_Session_Namespace und Zend_Auth zu integrieren.

30.3.1. Starten einer Session

Wenn alle Anfragen eine Session besitzen und Zend Framework Sessions benutzen sollen, muß die Session in der Bootstrap Datei gestartet werden:

Beispiel 30.6. Starten einer globalen Session

<?php
...
require_once 'Zend/Session.php';
Zend_Session::start();
...
?>

Durch das Starten der Session in der Bootstrap Datei verhindert man das die Session gestartet werden könnte nachdem die Header an den Browser gesendet wurde, was zu einer Ausnahme und möglicherweise zu einer fehlerhaften Seiten im Browser führen würde. Viele gehobenen Features benötigen zuerst Zend_Session::start(). (Mehr dazu später in den gehobenen Features)

Es gibt vier Wege eine Session zustarten wenn Zend_Session verwendet wird. Zwei sind falsch.

  • 1. Falsch: PHP's session.auto.start Ini-Einstellung darf nicht gesetzt sein, weder in der php.ini noch in .htaccess (http://www.php.net/manual/en/ref.session.php#ini.session.auto-start). Wenn keine Möglichkeit besteht diese Einstellung in php.ini zu deaktivieren, und mod_php (oder ähnliches) verwendet wird, und die Einstellung schon in php.ini aktiviert ist, kann php_value session.auto_start 0 in der .htaccess Datei hinzugefügt werden (normalerweise im HTML Dokument Haupt-Verzeichnis).

  • 2. Falsch: PHP's session_start() Funktion darf nicht direkt verwendet werden. Wenn session_start() direkt, und anschließend Zend_Session_Namespace verwendet wird, wird von Zend_Session::start() eine Ausnahme geworfen ("session has already been started"). Wenn Zend_Session::start() ausgerufen wird, nachdem Zend_Session_Namespace verwendet wird oder Zend_Session::start() explizit verwendet wird, wird ein Fehler vom Level E_NOTICE erzeugt und der Aufruf wird ignoriert.

  • 3. Richtig: Verwenden von Zend_Session::start(). Wenn es gewünscht ist, das alle Anfragen eine Session haben und verwenden, sollte diese Funktion sehr früh, direkt und entscheidungslos in der ZF Bootstrap Datei aufgerufen werden. Session haben einigen Overhead. Wenn einige Anfragen Sessions benötigen aber andere Anfragen keine Sessions verwenden, dann:

    • Entscheidungslos, die strict Option auf wahr setzen (siehe Zend_Session::setOptions() ) im eigenen Bootstrap.

    • Aufruf von Zend_Session::start(), nur für die Anfragen die eine Session verwenden müssen, bevor das erste Mal new Zend_Session_Namespace() aufgerufen wird.

    • Normales verwenden von new Zend_Session_Namespace() wo es benötigt wird, aber sicherstellen das davor Zend_Session::start() ausgerufen wurde.

    Die Option strict verhindert das new Zend_Session_Namespace() automatisch eine Session startet und dabei Zend_Session::start() verwendet. Deshalb hilft diese Option Entwicklern bei eigenen ZF Anwendungen, sich für ein Design entscheiden zu können welches verhindert das für bestimmte Anfragen Sessions verwendet werden, da ein Fehler geworfen würde wenn diese Option verwendet und Zend_Session_Namespace instanziiert wird, bevor Zend_Session::start() explizit aufgerufen wird. Diese Option sollte nicht in ZF Kern-Bibliotheks-Code verwendet werden, da nur Entwickler diese Designentscheidung treffen sollten. Gleichfalls sollten alle "Bibliotheks" Entwickler vorsichtig entscheiden welchen Einfluß die Verwendung von Zend_Session::setOptions() auf Benutzern in deren Bibliothekscode hat, da diese Optionen globale Seiteneffekte hat (genauso wie die darunterliegende Option für ext/session).

  • 4. Richtig: Einfach new Zend_Session_Namespace() verwenden wo dies auch immer notwendig ist, und die Session wird automatisch innerhalb von Zend_Session gestartet. Das bietet eine extrem simple Handhabung die in den meisten Situationen gut funktioniert. Trotzdem ist man dann dafür verantwortlich darauf zu schauen das das erste new Zend_Session_Namespace() passiert bevor irgendeine Ausgabe (z.B. HTTP headers ) von PHP an den Client gesendet wird, wenn standardmäßige, Cookie-basierte Sessions verwendet werden (sehr empfehlenswert). Siehe Abschnitt 30.4.3.1, „Header schon gesendet“ für mehr Informationen.

30.3.2. Gesperrte Session Namensräume

Session Namensräume können gesperrt werden um weitere Veränderungen der Daten in diesem Namensraum zu verhindern. Die Verwendung von Zend_Session_Namespace's lock() macht einen speziellen Namensraum nur-lesbar, unLock() macht einen nur-lesbaren Namensraum les- und schreibbar, und isLocked() prüft ob ein Namensraum vorher gesperrt wurde. Sperren sind flüchtig und bestehen nicht von einer Anfrage zur nächsten. Die Sperre des Namensraumes hat keinen Effekt auf Setz-Methoden von Objekten welche im Namensraum gespeichert sind, aber sie verhindert die Verwendung der Setz-Methoden des Namensraumes welche das gespeicherte Objekt direkt im Namensraum löschen oder ersetzen. Gleichwohl verhindert das Sperren von Zend_Session_Namespace Namensräumen nicht die Verwendung von symbolischen Tabellen-Aliasen auf die gleichen Daten (siehe PHP references).

Beispiel 30.7. Sperren von Session Namensräumen

<?php
    // Annahme:
    $userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace');

    // Die Session als nur-lesbar gesperrt markieren
    $userProfileNamespace->lock();

    // Die nur-lesbar Sperre aufheben
    if ($userProfileNamespace->isLocked()) {
        $userProfileNamespace->unLock();
    }
?>

Es gibt eine Vielzahl von Ideen wie Modelle im MVC Paradigma für das Web gehandhabt werden, welche die Erstellung von Präsentations Modellen für Views inkludieren. Manchmal sind bestehende Daten, egal ob Teil des Domain Modells oder nicht, adequat für diese Aufgabe. Um Views vom Hinzufügen von irgendeiner ausführenden Logik abzuraten, die solche Daten verändert, sollte der Session Namensraum gesperrt werden bevor es Views gestattet wird auf dieses Subset des "Präsentations" Modells zuzugreifen.

Beispiel 30.8. Sperren von Sessions in Views

<?php
class FooModule_View extends Zend_View
{
    public function show($name)
    {
        if (!isset($this->mySessionNamespace)) {
            $this->mySessionNamespace = Zend::registry('FooModule');
        }

        if ($this->mySessionNamespace->isLocked()) {
            return parent::render($name);
        }

        $this->mySessionNamespace->lock();
        $return = parent::render($name);
        $this->mySessionNamespace->unLock();

        return $return;
    }
}
?>

30.3.3. Verfall von Namensräumen

Limits können plaziert werden an der Lebensdauer von beidem, Namensräumen und individuellen Schlüsseln in Namensräumen. Normale Anwendungsfälle beinhalten das durchlaufen von temporären Informationen zwischen Anfragen, und das vermindern der Aufdeckung von vielfältigen Sicherheitsrisiken durch das Entfernen des Zugangs zu potentiell sensitiven Informationen, manchmal nachdem Authentifizierung stettgefunden hat. Das Ende kann auf abgelaufenen Sekunden, oder auf dem Konzept von "Sprüngen" basieren, wobei ein Sprung für jede nachfolgende Anfrage stattfindet die den Namensraum aktiviert, durch mindestens ein $space = new Zend_Session_Namespace('myspace');.

Beispiel 30.9. Beispiel für den Verfall

<?php
$s = new Zend_Session_Namespace('expireAll');
$s->a = 'Apfel';
$s->p = 'Pfirsich';
$s->o = 'Orange';

$s->setExpirationSeconds(5, 'a'); // Der Schlüssel "a" läuft in 5 Sekunden ab

// Der komplette Namensraum läuft in 5 "Sprüngen"
$s->setExpirationHops(5);

$s->setExpirationSeconds(60);
// Der "expireAll" Namensraum wird als "abgelaufen" markiert
// sobald der erste Aufruf empfangen wurde und 60 Sekunden
// vergangen sind, oder in 5 Sprüngen, was auch immer zuerst stattfindet
?>

Wenn mit Daten einer Session gearbeitet wird, die in der aktuellen Anfrage ablaufen, sollte Vorsicht beim Empfangen dieser Daten gehalten werden. Auch wenn diese Daten durch Referenz zurückgegeben werden, wird die Änderung derselben, diese Daten nicht über diese Abfrage hinweg gültig machen. Um die Zeit für das Ablaufen zu "resetieren", müssen die Daten in eine temporäre Variable geholt werden, diese im Namensraum entfernt und anschliessend der entsprechende Schlüssel wieder gesetzt werden.

30.3.4. Kapseln von Sessions und Kontroller

Namensräume können auch verwendet werden um den Zugriff auf Sessions durch Kontroller zu seperieren um Variablen vor Kontaminierung zu schützen. Zum Beispiel könnte der 'Zend_Auth' Kontroller seine Session Daten von allen anderen Kontrollern seperat halten.

Beispiel 30.10. Session Namensräume für Kontroller mit automatischem Verfall

<?php
require_once 'Zend/Session.php';
// Abfrage des View Kontrollers
$testSpace = new Zend_Session_Namespace('testSpace');
$testSpace->setExpirationSeconds(300, "accept_answer"); // Nur diese Variable ablaufen lassen
$testSpace->accept_answer = true;

--

// Anwort des Ausführenden Kontrollers
$testSpace = new Zend_Session_Namespace('testSpace');

if ($testSpace->accept_answer === true) {
    // Innerhalb der Zeit
}
else {
    // Ausserhalb der Zend
}
?>

30.3.5. Limitieren von Zend_Session_Namespace Instanzen auf eine pro Namensraum

Die Verwendung der Session Absperrung wird empfohlen (siehe oberhalb) statt dem untigen Feature, welches dem Entwickler extra Management aufbürdet um Zend_Session_Namespace Instanzen zu beliebigen Funktionen und Objekten zu übergeben welche auf jeden Namensraum zugreifen müssen.

Wenn die erste Instanz von Zend_Session_Namespace für einen speziellen Namensraum erstellt wird, kann Zend_Session_Namespace dazu instruiert werden keine weiteren Instanzen für diesen Namensraum zu machen. Jeder weitere Versuch eine Zend_Session_Namespace Instanz zu erstellen die den gleichen hat wird einen Fehler werfen. Dieses Verhalten ist optional, und nicht das Standardverhalten, bleibt aber verfügbar, für jene, die das Herumreichen eines einzigen Instanz Objektes für jeden Namensraum, bevorzugen. Das erhöht den Schutz vor Änderungen durch Komponenten welche einen speziellen Session Namensraum nicht ändern sollten, weil Sie keinen einfachen Zugriff haben. Trotzdem kann das limitieren eines Namensraumes an eine einzelne Instanz zu mehr und komplexerem Code führen, da der Zugriff auf das gewöhnliche $aNamespace = new Zend_Session_Namespace('aNamespace'); entfernt wird, nachdem die Instanz erstellt wurde, wie im beiliegenden Beispiel gezeigt:

Beispiel 30.11. Limitierung auf einzelne Instanzen

<?php
    require_once 'Zend/Session.php';
    $authSpaceAccessor1 = new Zend_Session_Namespace('Zend_Auth');
    $authSpaceAccessor2 = new Zend_Session_Namespace('Zend_Auth', Zend_Session_Namespace::SINGLE_INSTANCE);
    $authSpaceAccessor1->foo = 'bar';
    assert($authSpaceAccessor2->foo, 'bar'); // gestattet
    doSomething($options, $authSpaceAccessor2); // gib den Zugriff auf wo immer er benötigt wird
    .
    .
    .
    $aNamespaceObject = new Zend_Session_Namespace('Zend_Auth'); // Das wird einen Fehler werfen
?>

Der zweite Parameter oben im Konstruktor sagt Zend_Session_Namespace das jede zukünftige Zend_Session die mit dem 'Zend_Auth' Namensraum initiiert wird, nicht erlaubt ist, und eine Ausnahme hervorruft. Da new Zend_Session_Namespace('Zend_Auth') nicht erlaubt ist nachdem der obige Code ausgeführt wurde, wird der Entwickler darauf aufmerksam gemacht die Instanz des Objektes ($authSpaceAccessor2 im obigen Beispiel) woanders zu speichern, wenn ein Zugriff auf diesen Session Namensraum später, wärend der gleichen Anfrage, benötigt wird. Ein Entwickler könnte, zum Beispiel, die Instanz in einer statischen Variable speichern, oder an eine andere Methode weiterreichen welche Zugriff auf diesen Session Namensraum benötigt. Die Sperre der Session (siehe oben) bietet einen gewöhnlicheren Weg, und ist weniger umständlicher im limitieren der Zugriffe auf Namensräume.

30.3.6. Arbeiten mit Arrays in Namensräumen

Ändern eines Array innerhalb eines Namensraumes funktioniert nicht. Die einfachste Lösung ist es das Array zu speichern nachdem alle gewünschten Werte geändert wurden. ZF-800 dokumentiert ein bekanntes Problem welches viele PHP Anwendungen betrifft die magische Methoden mit Arrays verwenden.

Beispiel 30.12. Bekannte Probleme mit Arrays

<?php
    $sessionNamespace = new Zend_Session_Namespace('Foo');
    $sessionNamespace->array = array();
    $sessionNamespace->array['testKey'] = 1; // funktioniert nicht vor PHP 5.2.1
?>

Wenn ein Array geändert werden soll nachdem es einem Session Namensraum Schlüssel zugewiesen wurde, muß das Array geholt, anschließend geändert und das Array in den Session Namensraum zurück gespeichert.

Beispiel 30.13. Workaround: Holen, Ändern, Speichern

<?php
    $sessionNamespace = new Zend_Session_Namespace('Foo');
    $sessionNamespace->array = array('baum' => 'apfel');
    $tmp = $sessionNamespace->array;
    $tmp['frucht'] = 'pfirsich';
    $sessionNamespace->array = $tmp;
?>

Alternativ kann ein Array, welches Referenzen enthält, in ein gewünschtes Array gespeichert und anschließend indirekt darauf zugegriffen werden.

Beispiel 30.14. Workaround: Speichern des Arrays welche Referenzen enthält

<?php
    $myNamespace = new Zend_Session_Namespace('mySpace');

    // funktioniert, selbst für fehlerhafte PHP Versionen
    $a = array(1,2,3);
    $myNamespace->someArray = array( & $a ) ;
    $a['foo'] = 'bar';
?>

30.3.7. Verwenden von Session mit Authentifizierung

Wenn der Authentifizierung Adapter für Zend_Auth ein Ergebnis zurückgibt wo die Identität der Authentifizierung ein Objekt (nicht benötigt) statt einem Array ist, sollte sichergestellt werden das die Definition der Klasse die die Identität der Authentifizierung enthält vor dem Starten der Session geladen wird. Stattdessen wird empfohlen die berechneten Authentifizierung Id's im Authentifizierungs Adapter innerhalb eines gut bekannten Schlüssels im Session Namensraum zu Speichern. Das Standardverhalten von Zend_Auth, zum Beispiel, plaziert ihn im 'storage' Schlüssel des 'Zend_Auth' Namensraumes.

Wenn Zend_Auth mitgeteilt wird, nicht im Authentifizierungs Token der Session zu bleiben, kann die Authentifizierung Id manuell im Session Namensraum gespeichert werden, innerhalb einer gut bekannten Position in einem Session Namensraum der eigenen Wahl. Oft haben Anwendungen spezielle Notwendigkeiten darüber wo benötige Zeugnisse (wenn vorhanden) und "Authorisierungs" Identitäten abgespeichert werden. Anwendungen mappen oft Authentifizierungs Identitäten (z.B. Benutzernamen) an Authorisierungs Identitäten (z.B. einer eindeutigen zugeordneten Integerzahl) wärend der Authentifizierung, welche in der Zend_Auth Authentifizierungs Adapter Methode authenticate() stattfindet.

Beispiel 30.15. Beispiel: Einfacher Zugriff auf Authorisierungs Ids

<?php
    // Vor der Authentifizierungs Anfrage
    require_once 'Zend/Auth/Adapter/Digest.php';
    $adapter = new Zend_Auth_Adapter_Digest($filename, $realm, $username, $password);
    $result = $adapter->authenticate();
    require_once 'Zend/Session/Namespace.php';
    $namespace = new Zend_Session_Namespace('Zend_Auth');
    if ($result->isValid()) {
        $namespace->authorizationId = $result->getIdentity();
        $namespace->date = time();
    } else {
        $namespace->attempts++;
    }

    // Nachfolgende Anfragen
    require_once 'Zend/Session.php';
    Zend_Session::start();
    $namespace = new Zend_Session_Namespace('Zend_Auth');

    echo "Gültig: ", (empty($namespace->authorizationId) ? 'Nein' : 'Ja'), "\n"';
    echo "Authorisierung / Benutzer Id: ", (empty($namespace->authorizationId)
        ? 'keine' : print_r($namespace->authorizationId, true)), "\n"';
    echo "Authentifizierungs Versuche: ", (empty($namespace->attempts)
        ? '0' : $namespace->attempts), "\n"';
    echo "Authentifizierung am: ",
        (empty($namespace->date) ? 'Nein' : date(DATE_ATOM, $namespace->date), "\n"';
?>

Authorisierungs Ids welche auf der Seite des Clienten gespeichert werden sind Angriffspunkte betreffend der Eskalation von Rechten wenn diese Ids verwendet und der Server diesen glaubt, solange bis zum Beispiel die Id auf Seite des Servers dupliziert (z.B. in der Session) und anschließend gegengeprüft wird mit der Authorisierungs Id welche durch den Client in der effektiven Session angefordert wird. Es wird unterschieden zwischen "Authentifizierungs Ids" (z.B. Benutzernamen) und "Authorisierungs Ids" (z.B. Benutzer ID #101 in der Benutzertabelle der Datenbank).

Das Spätere ist nicht unüblich aus Gründen der Geschwindigkeit, wie etwa Hilfe bei der Auswahl aus einem Pool von Servern welche Session Informationen holen um bei dem Henne-und-Ei Problemen zu helfen. Oft werden Debatten darüber geführt ob die echte Authorisierungs Id in einem Cookie verwendet wird, oder eine Ableitung welche in die echte Authorisierungs Id gemappt werden kann (oder Session oder Server welche die Benutzersession/Profile etc. enthalten), da einige Systemsicherheits Architekten wünschen das Verhindert wird das echte "Datenbank Primärschlüssel" nicht in die Wildnis entlassen werden. Diese Architekten versuchen einige Level von Verschleierung zu erreichen im Fall eines SQL Angriffs auf Ihr System. Nicht jeder verwendet automatisch-addierte Strategien für Authorisierungs Ids.

30.3.8. Verwenden von Sessions mit Unit Tests

Der Zend Framework vertraut auf PHPUnit um das Testen von sich selbst zu ermöglichen. Viele Entwickler erweitern die existierende Sammlung von Unit Tests um den Code in deren Anwendungen anzudecken. Die Ausnahme "Zend_Session ist aktuell als nur-lesbar markiert" wird geworfen wärend Unit Tests durchgeführt werden, wenn irgendeine schreibende Methode verwendet wird nachdem Ende der Session. Trotzdem benötigen Unit Tests die Zend_Session verwenden besondere Aufmerksamkeit weil das Schließen (Zend_Session::writeClose()) oder Zerstören einer Session (Zend_Session::destroy()) weitere Änderungen oder Rücknahmen von Schlüsseln in jedem Zend_Session_Namespace verhindert. Dieses Verhalten ist ein direktes Resultat des darunterliegenden ext/session Mechanismus und PHP's session_destroy() und session_write_close() welche keinen "rückgängig machen" Mechanismus unterstützen um Setup/Teardown innerhalb der Unit Tests zu unterstützen.

Um das Umzuarbeiten, siehe den Unit Test testSetExpirationSeconds() in tests/Zend/Session/SessionTest.php und SessionTestHelper.php welche PHP's exec() verwenden um einen eigenständigen Prozess zu starten. Der neue Prozess simuliert eine zweite Anfrage eines Browsers, viel genauer. Der separate Prozess beginnt mit einer "reinen" Session, genauso wie jede PHP Skript Ausführung für eine web Anfrage. Auch jede Änderung in $_SESSION[] welche im aufrufenden Prozess gemacht wurde, ist im Kind-Prozess verfügbar, ermöglicht wenn der Elternprozess die Session beendet hat, bevor exec() verwendet wird.

Beispiel 30.16. Verwenden von PHPUnit um Code zu testen der Zend_Session* verwendet

<?php
        // testen von setExpirationSeconds()
        require 'tests/Zend/Session/SessionTestHelper.php'; // siehe auch SessionTest.php in trunk/
        $script = 'SessionTestHelper.php';
        $s = new Zend_Session_Namespace('space');
        $s->a = 'apfel';
        $s->o = 'orange';
        $s->setExpirationSeconds(5);

        Zend_Session::regenerateId();
        $id = Zend_Session::getId();
        session_write_close(); // Session freigeben damit der untere Prozess Sie verwenden kann
        sleep(4); // nicht lange genug damit die Dinge ablaufen
        exec($script . "expireAll $id expireAll", $result);
        $result = $this->sortResult($result);
        $expect = ';a === apfel;o === orange;p === pfirsich';
        $this->assertTrue($result === $expect,
            "Iterierung durch standard Zend_Session Namensraum fehlgeschlagen; erwartet result === '$expect', aber '$result' bekommen");

        sleep(2); // lange genug damit die Dinge ablaufen (insgesamt 6 Sekunden warten, aber nach 5 Sekunden abgelaufen)
        exec($script . "expireAll $id expireAll", $result);
        $result = array_pop($result);
        $this->assertTrue($result === '',
            "Iterierung dirch standard Zend_Session Namensraum fehlgeschlagen; erwartet result === '', aber '$result' bekommen)");
        session_start(); // wiederherstellen der vorher eingefrorenen Session

        // Das könnte in einen seperaten Test abgeteilt werden, aber aktuell, wenn irgendwas vom darüberleigenden
        // Test den darunterliegenden Test kontaminiert, ist das auch ein Fehler den wir wissen wollen.
        $s = new Zend_Session_Namespace('expireGuava');
        $s->setExpirationSeconds(5, 'g'); // Versuch nur einen Schlüssel im Namensraum ablaufen zu lassen
        $s->g = 'guava';
        $s->p = 'peach';
        $s->p = 'plum';

        session_write_close(); // Session auflösen damit der untere Prozess sie verwenden kann
        sleep(6); // Nicht lange genug damit die Dinge ablaufen können
        exec($script . "expireAll $id expireGuava", $result);
        $result = $this->sortResult($result);
        session_start(); // Die bestimmte Session wiederherstellen
        $this->assertTrue($result === ';p === plum',
            "Iterierung durch benannte Zend_Session Namensräume fehlgeschlaten (result=$result)");
?>