Datenbank basiertes Session-Management

Hier dreht sich alles um die auf der Webseite veröffentlichten Tutorials. // This forum is all about the APF tutorials.
Antworten
Avedo

Datenbank basiertes Session-Management

Beitrag von Avedo » 29.03.2010, 21:48:23

Guten Abend!

Habe gerade noch etwas die Seiten des APFs durchforstet und binauf den Session-Manager gestoßen. Da ich meine Sessions gerne in einer Datenbank ablege, würde mich interessieren, ob das auch der Session-Manager unterstützt oder nicht. War aus der Dokumentation leider nicht ersichtlich. Habe zu diesem Thema selbst ein kleines Tutorial Datenbank basiertes Session-Management als Teil der Reihe PHP & OOP geschrieben.

Liebe Grüße,

Andreas

Benutzeravatar
dr.e.
Administrator
Beiträge: 4527
Registriert: 04.11.2007, 16:13:53

Re: Datenbank basiertes Session-Management

Beitrag von dr.e. » 29.03.2010, 22:01:14

Hallo Andreas,

der SessionManager ist nicht mehr als eine Facade auf das $_SESSION-Array. Es erleichtert dir lediglich das Namespace-basierte Verwalten von Session-Inhalten und bietet eine einfache API zum Hinzufügen, Bearbeiten und Löschen von Inhalten. Es sieht nicht nur besser aus im Code wenn du den SessionManager nutzt, sondern ist auch hinsichtlich der Implementierung transparenter.

Was du suchst ist ein Aufsatz auf das PHP-Session-Handling, sprich Session-Inhalte in einer Datenbank oder einem memcached-Server abzulegen. Das wiederum ist aber eine andere Baustelle - konkret gesprochen: das andere Ende der Session-Verwaltung. Das kannst du jedoch auch sehr einfach implementieren. Dazu gibt es einige Beispiele im Netz. Ich persönlich würde jedoch Inhalte von Sessions nicht in der Datenbank, sondern höchstens auf einem shared Filesystem oder einem memcached-Server lagern, das ist deutlich performanter und genauso skalierbar für den Einsatz von mehreren Applikations-Servern.
Habe zu diesem Thema selbst ein kleines Tutorial Datenbank basiertes Session-Management als Teil der Reihe PHP & OOP geschrieben.
Genau sowas meinte ist. :)
Viele Grüße,
Christian

Avedo

Re: Datenbank basiertes Session-Management

Beitrag von Avedo » 02.04.2010, 13:54:45

Guten Morgen!

Ich habe mich gestern und heute mal etwas mit diesem Thema beschäftigt und meine Klasse mal etwas umgemodelt, sodass sie nun mit den Komponenten des APFs arbeitet. Getestet habe ich sie leider bisher noch nicht, sollte aber so funktionieren, auch wenn ich noch ein paar Probleme mit den SQL-Controllern habe. Ich würde jedoch auch gerne die durch den SessionHandler des APF festgelegten Schnittstellen erfüllen, um eine schnelle und einfache Umstellung zu ermöglichen. Ich bin mir aber nicht sicher, wie ich das am besten angehe. Würde mich daher über Anregungen und Kommentare freuen.

Liebe Grüße,

Andreas

PS: Es wäre toll, wenn es möglich wäre *.php und *.html Dateien als Anhänge anzufügen.
EDIT: Eventuell wäre auch eine Zentrale Schnittstelle (Interface) sinnvoll.
Anhang:

Code: Alles auswählen

<?php

import('core::database','MySQLiHandler');

/**
 * @package core::session
 * @class DatabaseSession
 *
 * The DatabaseSession class implements all methods to use a
 * database based session management instead of using text files.
 * This has the benefit that all session data can be accessed
 * at a central place. This class is supported since the PHP version 5.0.5
 * because it uses the register_shutdown_function() function to ensure that
 * all session values are stored before the PHP representation is destroyed.
 *
 * Provides advances session handling with namespaces. Example:
 * <pre>$sessMgr = new SessionManager('<namespace>');
 * $sessMgr->loadSessionData('<key>');
 * $sessMgr->saveSessionData('<key>','<value>');</pre>
 * Further, you do not have to take care of starting or persisting sessions.
 *
 * @author Andreas Wilhelm
 * @version
 * Version 0.1, 01.04.2010<br />
 */
final class DatabaseSession {

	/**
	 * @private
	 * The MySQLi database connection handle.
	 */
	private $mysqli;

	/**
	 * @private
	 * The namespace of the current instance of the session manager.
	 */
	private $namespace;

	/**
	 * @public
	 *
	 * Initializes the namespace of the current instance and starts the
	 * session in case it is not started yet.
	 *
	 * @param string $namespace The desired namespace.
	 * @throws Exception In case, the namespace is empty.
	 *
	 * @author Andreas Wilhelm
	 * @version
	 * Version 0.1, 01.04.2010<br />
	 */
	public function DatabaseSession($namespace = '') {

		// Check if an namespace was given ...
		if(empty($namespace)) {
			// ... and throw an exception if not.
			throw new Exception('Database Session cannot be created without specifiing a namespace!',E_USER_ERROR);
		}

		// Set the given namespace.
		$this->namespace = $namespace;

		// Load an instance of the database class.
		$this->mysqli = $this->__getServiceObject('core::database','MySQLiHandler');

		// Switch to user defined session handling ...
		ini_set('session.save_handler', 'user');
    
		// ... and assign the session handler to the class methods.
		session_set_save_handler(
			array(&$this, '_open'),
			array(&$this, '_close'),
			array(&$this, '_read'),
			array(&$this, '_write'),
			array(&$this, '_destroy'),
			array(&$this, '_gc')
		);

		// Check if a session was already started ...
		if(isset($_SESSION) === false) {
			// ... and do so if not.
			session_start();
		}

		// Finally make shure that all sessions are stored before closing.
		register_shutdown_function('session_write_close');

		// Init the session if it does not exist.
		if(!isset($_SESSION[$namespace])) {
			$_SESSION[$namespace] = array();
		}
	}
    
	/**
	 * @public
	 *
	 * Is called to open a session. The method
	 * does nothing because we do not want to write
	 * into a file so we doesn't need to open one.
	 *
	 * @param string $save_path The save path.
	 * @param string $session_name The name of the session.
	 * @return Returns always true.
	 *
	 * @author Andreas Wilhelm
	 * @version
	 * Version 0.1, 01.04.2010<br />
	 */
	public function _open($save_path, $session_name) {
		return true;
	}
    
	/**
	 * @public
	 *
	 * Is called when the reading in a session is
	 * completed. The method calls the garbage collector.
	 *
	 * @return Returns always true.
	 *
	 * @author Andreas Wilhelm
	 * @version
	 * Version 0.1, 01.04.2010<br />
	 */
        public function _close() {
		// Call the garbage collector.
		$this->_gc(100);
        
		return true;
	}
    
	/**
	 * @public
	 *
	 * Is called to read data from a session.
	 *
	 * @param Integer $id The id of the current session
	 * @return Returns the fetched data.
	 *
	 * @author Andreas Wilhelm
	 * @version
	 * Version 0.1, 01.04.2010<br />
	 */
        public function _read($id) {

		// Create a select statement to get the session data.
		$select = '
			SELECT
				*
			FROM
				`sessions`
			WHERE
				`sessions`.`id` = \'' . $id . '\'
			LIMIT 1;';

		// Send the select statement, ...
		$result = $this->mysqli->executeTextStatement($select);

		// ... fetch the session data ...
		$data = $this->mysqli->fetchData($result);

		// ... and return it.
		return $data['value'];
	}
    
	/**
	 * @public
	 *
	 * Writes data into a session rather
	 * into the session record in the database.
	 *
	 * @param Integer $id The id of the current session.
	 * @param String $sess_data The data of the session.
	 * @return Returns a boolean statement, that tells if the data could be stored or not.
	 *
	 * @author Andreas Wilhelm
	 * @version
	 * Version 0.1, 01.04.2010<br />
	 */
	public function _write($id, $sess_data) {

		// Check if some data was given ...
		if( $sess_data == null ) {
			// ... and return true if not.
			return true;
		}
    
		// Create the update statement, ...
		$update = '
			UPDATE
				`sessions`
			SET
				`sessions`.`last_updated` = \'' .time() . '\',
				`sessions`.`value` = \'' . $sess_data . '\'
			WHERE
				`sessions`.`id` = \'' . $id . '\';';

		// ... send it ...
		$result = $this->mysqli->executeTextStatement($update);
        
		// ... and check for a database error.
		if( $result === false ) {
			return false;
		}
        
		// Check if the update was successful.
		if( $this->mysqli->getAffectedRows() > 0 ) {
			return true;
		}
        
		// The session does not exists, so create it ...
		$insert = '
			INSERT INTO
				`sessions`
				(id, last_updated, start, value)
			VALUES
				(\'' . $id . '\', \'' . time() . '\', \'' . time() . '\', \'' . $sess_data . '\');';

		// ... and send it.
		$result = $this->mysqli->executeTextStatement($insert);
        
		return $result;
	}
    
	/**
	 * @public
	 *
	 * Ends a session and deletes it.
	 *
	 * @param Integer $id The id of the current session.
	 * @return Returns a boolean statement, that tells if the data could be deleted or not.
	 *
	 * @author Andreas Wilhelm
	 * @version
	 * Version 0.1, 02.04.2010<br />
	 */
	public function _destroy($id) {

		// Create the delete statement ...
		$delete = '
			DELETE FROM
				`sessions`
			WHERE
				`sessions`.`id` = \'' . $id . '\';';

		// ... and send it.
		$result = $this->mysqli->executeTextStatement($delete);
        
		return $result;
	}
    
	/**
	 * @public
	 *
	 * The garbage collector deletes all sessions from the database
	 * that where not deleted by the session_destroy function.
	 * so your session table will be stay clean.
	 *
	 * @param Integer $maxlifetime The maximum session lifetime
	 * @return Returns a boolean statement, that tells if the data could be deleted or not.
	 *
	 * @author Andreas Wilhelm
	 * @version
	 * Version 0.1, 02.04.2010<br />
	 */
	public function _gc($maxlifetime) {

		// Calculate the period after that a session pass off, ...
		$maxlifetime = strtotime("-20 minutes");
        
		// ... create the delete statement ...
		$delete = '
			DELETE FROM
				`sessions`
			WHERE
				`sessions`.`last_updated` < \'' . $maxlifetime . '\';';

		// ... and send it.
		$result = $this->mysqli->executeTextStatement($delete);
        
		return $result;
	}
}

?>

Benutzeravatar
dr.e.
Administrator
Beiträge: 4527
Registriert: 04.11.2007, 16:13:53

Re: Datenbank basiertes Session-Management

Beitrag von dr.e. » 05.04.2010, 19:13:56

Hallo Andreas,

den "neuen" Ansatz finde ich sehr schön. Es abstrahiert eigentlich alle notwendigen Funktionen komplett in der Klasse und bietet eine API, die sich am SessionHandler orientiert. Hier meine Anmerkungen:
  • Code: Alles auswählen

    DatabaseSession($namespace = '')
    würde ich zu

    Code: Alles auswählen

    DatabaseSession($namespace)
    abändern, dann kann der Anwender erst gar nicht keinen Namespace übergeben. Leer kann er ntürlich immer noch sein, aber dafür hast du ja eine Prüfung drin. Hier ist es jedoch üblich eine InvalidArgumentException (in PHP bereits vorhanden) zu werfen.
  • Warum erzeugst du den MySQLiHandler mit dem Service-Manager - also per

    Code: Alles auswählen

    $this->mysqli = $this->__getServiceObject('core::database','MySQLiHandler');
    und nicht über den ConnectionManager? Die Implementierung des MySQLiHandler ist eigentlich nicht (mehr) dafür ausgelegt standalone erzeugt zu werden. Schließlich musst du ja eine DB-Verbindung auch initialisieren. Das sehe ich im Code nicht.
EDIT: Eventuell wäre auch eine Zentrale Schnittstelle (Interface) sinnvoll.
Du meinst ein Interface für SessionHandler und den DatabaseSessionHandler? Das ließe sich sehr einfach machen.
PS: Es wäre toll, wenn es möglich wäre *.php und *.html Dateien als Anhänge anzufügen.
Das lasse ich aus Sicherheitsgründen nicht zu. Es gab schon security issues bei phpBB deswegen. Bitte einfach zippen und dann anhängen.

Abschließend: hast du Interesse den Code in das APF zu integrieren? Falls ja, würde ich diesen als neues Feature aufnehmen und mit 1.12 ausliefern. Voraussetzung ist natürlich a) dein Einverständnis und b) eine Doku. :)
Viele Grüße,
Christian

Avedo

Re: Datenbank basiertes Session-Management

Beitrag von Avedo » 06.04.2010, 01:55:58

Guten Abend ... mhh Morgen!

Wie müsste denn die Erzeugung mit Hilfe des ConnectionManagers aussehen?
... hast du Interesse den Code in das APF zu integrieren? Falls ja, würde ich diesen als neues Feature aufnehmen und mit 1.12 ausliefern. Voraussetzung ist natürlich a) dein Einverständnis und b) eine Doku ...
... Ja ... super ... hast du ... bekomm ich hin, kann aber dauern.

Liebe Grüße,

Andreas

Benutzeravatar
dr.e.
Administrator
Beiträge: 4527
Registriert: 04.11.2007, 16:13:53

Re: Datenbank basiertes Session-Management

Beitrag von dr.e. » 06.04.2010, 09:22:14

Hallo Andreas,

die Doku des ConnectionManagers findest du hier. Für dich ist die Änderung nicht groß, es ist lediglich wichtig, dass der DatabaseSessionHandler den Context der Anwendung kennt. Darüber - und über die relevante Konfigurations-Sektion identifiziert der ConnectionManager, welche Datenbank-Verbindung erzeugt werden soll. Die Übergabe des Contextes ist jedoch in der Implementierung bisher (noch) nicht vorgesehen und müsste entweder noch in den Konstruktor als Argument eingefügt werden oder der DatabaseSessionHandler muss auch über den Service-Manager erzeugt werden.

Bedenken habe ich hier jedoch ein bischen wegen des Verhaltens von PHP beim Lesen und Speichern der Session in der Datenbank. Denn: PHP weiß zunächst nichts vom APF und dessen Context. Wie funktioniert das aktuell bei dir? Wird der Session-Save-Handler exakt nur 1x innerhalb eines Request erzeugt?
... Ja ... super ... hast du ... bekomm ich hin, kann aber dauern.
Freut mich. Zeitlich gibt es absolut keinen Zwang, wenn nicht 1.12, dann eben 1.13. ;)
Viele Grüße,
Christian

Antworten

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast