IngmarBoddington
10/25/2012 - 1:48 PM

PHP Database Session Handler

PHP Database Session Handler

<?php
/**
 * SessionHandler Class - Uses DB for session handling (for multiserver environment)
 *
 * @package glowingminds
 * @author Ingmar Boddington
 */
class SessionHandler {
	
	const TABLENAME = 'sessions';
	private $_databaseConnection;
	
	/**
	 * Setup with PDO
	 *
	 * @param PDO $_databaseConnection
	 */
	public function __construct(PDO $_databaseConnection) {
		$this->_databaseConnection = $_databaseConnection;
	}
	
	/**
	 * Ready Database Table
	 *
	 * @param string $savePath
	 * @param int $sessionId
	 * @throws Exception
	 * @return boolean
	 */
	public function open($savePath, $sessionId) {
		//Create table if it does not exist
		try {
			$sql = '
				CREATE TABLE IF NOT EXISTS '.self::TABLENAME.'
				(
					sessionId VARCHAR(100) PRIMARY KEY,
					sessionLastSave INT NULL,
					sessionData TEXT NULL		
				);
			';
			$statement = $this->_databaseConnection->prepare($sql);
			if (!$statement) {
				throw new Exception('Failed to prepare statement for generation of ' . self::TABLENAME . ', errorInfo = ' . implode('|', $this->_databaseConnection->errorInfo()));
			} 
			$success = $statement->execute();
			if (!$success) {
				throw new Exception('Failed to execute sql for generation of ' . self::TABLENAME . ', errorInfo = ' . implode('|', $statement->errorInfo()) . ', debugDumpParams = ' . implode('|', $statement->debugDumpParams()));
			}
			$statement = NULL;
		} catch (Exception $e) {
			//rethrow the exception for control code
			throw $e;
		}
		
		return true;
		
	}
	
	/**
	 * Cleanup
	 *
	 * @return boolean
	 */
	public function close() {		
		$this->_databaseConnection = NULL;		
		return true;		
	}
	
	/**
	 * Read Session Data
	 *
	 * @param int $sessionId
	 * @throws Exception
	 * @return String
	 */
	public function read($sessionId) {
		//Return matching session data
		try {
			$sql = '
				SELECT sessionData
				FROM ' . self::TABLENAME . '
				WHERE sessionId = ?;
			';
			$statement = $this->_databaseConnection->prepare($sql);
					if (!$statement) {
				throw new Exception('Failed to prepare statement for session data fetch in table ' . self::TABLENAME . ' where sessionId = ' . $sessionId . ', errorInfo = ' . implode('|', $this->_databaseConnection->errorInfo()));
			}
			$success = $statement->execute(array($sessionId));
			if (!$success) {
				throw new Exception('Failed to execute sql for session data fetch in table ' . self::TABLENAME . ' where sessionId = ' . $sessionId . ', errorInfo = ' . implode('|', $statement->errorInfo()) . ', debugDumpParams = ' . implode('|', $statement->debugDumpParams()));
			}
			/* This will cause failure, as session handler appears to attempt read before 1st write
			if ($statement->rowCount() != 1) {
				throw new Exception('Failed to fetch 1 row (found ' . $statement->rowCount() . ') for session data fetch in table ' . self::TABLENAME . ' where sessionId = ' . $sessionId . ', errorInfo = ' . implode('|', $statement->errorInfo()));
			}
			*/
			list($sessionData) = $statement->fetch(PDO::FETCH_NUM);
			$statement = NULL;
		} catch (Exception $e) {
			//rethrow the exception for control code			
			throw $e;
		}
		
		return $sessionData;
	}
	
	/**
	 * Write Session Data
	 *
	 * @param int $sessionId
	 * @param string $sessionData
	 * @throws Exception
	 * @return boolean
	 */
	public function write($sessionId, $sessionData) {		
		try {
			$sessionLastSave = time();
			//Update record
			$sql = '
				REPLACE INTO ' . self::TABLENAME . '
				(sessionId, sessionLastSave, sessionData)
				VALUES
				(?, ?, ?);
			';
			$statement = $this->_databaseConnection->prepare($sql);
			if (!$statement) {
				throw new Exception('Failed to prepare statement for session data update in table ' . self::TABLENAME . ' where sessionId = ' . $sessionId . ', errorInfo = ' . implode('|', $this->_databaseConnection->errorInfo()));
			}
			$success = $statement->execute(array($sessionId, $sessionLastSave, $sessionData));
			if (!$success) {
				throw new Exception('Failed to execute sql for session data update in table ' . self::TABLENAME . ' where sessionId = ' . $sessionId . ', errorInfo = ' . implode('|', $statement->errorInfo()) . ', debugDumpParams = ' . implode('|', $statement->debugDumpParams()));
			}	
			$statement = NULL;					
		} catch (Exception $e) {
			//rethrow the exception for control code
			throw $e;			
		}
		
		return true;
	}
	
	/**
	 * Destroy A Session
	 *
	 * @param int $sessionId
	 * @throws Exception
	 * @return boolean
	 */
	public function destroy($sessionId) {
		//Delete record
		try {
			$sql = '
				DELETE FROM ' . self::TABLENAME . '
				WHERE sessionId = ?
			';
			$statement = $this->_databaseConnection->prepare($sql);
			if (!$statement) {
				throw new Exception('Failed to prepare statement for session data removal in table ' . self::TABLENAME . ' where sessionId = ' . $sessionId . ', errorInfo = ' . implode('|', $this->_databaseConnection->errorInfo()));
			}
			$success = $statement->execute(array($sessionId));
			if (!$success) {
				throw new Exception('Failed to execute sql for session data removal in table ' . self::TABLENAME . ' where sessionId = ' . $sessionId . ', errorInfo = ' . implode('|', $statement->errorInfo()) . ', debugDumpParams = ' . implode('|', $statement->debugDumpParams()));
			}	
			$statement = NULL;	
		} catch (Exception $e) {
			//rethrow the exception for control code
			throw $e;
		}
		
		return true;
	}
	
	/**
	 * Garbage Collection
	 *
	 * @param string $maxLifetime
	 * @throws Exception
	 * @return boolean
	 */
	public function gc($maxLifetime) {		
		//Delete old records
		try {
			$checkStamp = time() - $maxLifetime;
			$sql = '
				DELETE FROM ' . self::TABLENAME . '
				WHERE sessionLastSave < ?
				OR sessionLastSave IS NULL;
			';
			$statement = $this->_databaseConnection->prepare($sql);
			if (!$statement) {
				throw new Exception('Failed to prepare statement for session data cleanup in table ' . self::TABLENAME . ' where checkStamp = ' . $checkStamp . ', errorInfo = ' . implode('|', $this->_databaseConnection->errorInfo()));
			}
			$success = $statement->execute(array($checkStamp));
			if (!$success) {
				throw new Exception('Failed to execute sql for session data cleanup in table ' . self::TABLENAME . ' where checkStamp = ' . $checkStamp . ', errorInfo = ' . implode('|', $statement->errorInfo()) . ', debugDumpParams = ' . implode('|', $statement->debugDumpParams()));
			}
			$statement = NULL;			
		} catch (Exception $e) {
			//rethrow the exception for control code
			throw $e;
		}
		
		return true;
	}

}