* @link http://www.yiiframework.com/ * @copyright 2008-2013 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * CDbHttpSession extends {@link CHttpSession} by using database as session data storage. * * CDbHttpSession stores session data in a DB table named 'YiiSession'. The table name * can be changed by setting {@link sessionTableName}. If the table does not exist, * it will be automatically created if {@link autoCreateSessionTable} is set true. * * The following is the table structure: * *
 * CREATE TABLE YiiSession
 * (
 *     id CHAR(32) PRIMARY KEY,
 *     expire INTEGER,
 *     data BLOB
 * )
 * 
* Where 'BLOB' refers to the BLOB-type of your preffered database. * * Note that if your session IDs are more than 32 characters (can be changed via * session.hash_bits_per_character or session.hash_function) you should modify * SQL schema accordingly. * * CDbHttpSession relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to access database. * * By default, it will use an SQLite3 database named 'session-YiiVersion.db' under the application runtime directory. * You can also specify {@link connectionID} so that it makes use of a DB application component to access database. * * When using CDbHttpSession in a production server, we recommend you pre-create the session DB table * and set {@link autoCreateSessionTable} to be false. This will greatly improve the performance. * You may also create a DB index for the 'expire' column in the session table to further improve the performance. * * @property boolean $useCustomStorage Whether to use custom storage. * * @author Qiang Xue * @package system.web * @since 1.0 */ class CDbHttpSession extends CHttpSession { /** * @var string the ID of a {@link CDbConnection} application component. If not set, a SQLite database * will be automatically created and used. The SQLite database file is * is protected/runtime/session-YiiVersion.db. */ public $connectionID; /** * @var string the name of the DB table to store session content. * Note, if {@link autoCreateSessionTable} is false and you want to create the DB table manually by yourself, * you need to make sure the DB table is of the following structure: *
	 * (id CHAR(32) PRIMARY KEY, expire INTEGER, data BLOB)
	 * 
* @see autoCreateSessionTable */ public $sessionTableName='YiiSession'; /** * @var boolean whether the session DB table should be automatically created if not exists. Defaults to true. * @see sessionTableName */ public $autoCreateSessionTable=true; /** * @var CDbConnection the DB connection instance */ private $_db; /** * Returns a value indicating whether to use custom session storage. * This method overrides the parent implementation and always returns true. * @return boolean whether to use custom storage. */ public function getUseCustomStorage() { return true; } /** * Updates the current session id with a newly generated one. * Please refer to {@link http://php.net/session_regenerate_id} for more details. * @param boolean $deleteOldSession Whether to delete the old associated session file or not. * @since 1.1.8 */ public function regenerateID($deleteOldSession=false) { $oldID=session_id(); // if no session is started, there is nothing to regenerate if(empty($oldID)) return; parent::regenerateID(false); $newID=session_id(); $db=$this->getDbConnection(); $row=$db->createCommand() ->select() ->from($this->sessionTableName) ->where('id=:id',array(':id'=>$oldID)) ->queryRow(); if($row!==false) { if($deleteOldSession) $db->createCommand()->update($this->sessionTableName,array( 'id'=>$newID ),'id=:oldID',array(':oldID'=>$oldID)); else { $row['id']=$newID; $db->createCommand()->insert($this->sessionTableName, $row); } } else { // shouldn't reach here normally $db->createCommand()->insert($this->sessionTableName, array( 'id'=>$newID, 'expire'=>time()+$this->getTimeout(), 'data'=>'', )); } } /** * Creates the session DB table. * @param CDbConnection $db the database connection * @param string $tableName the name of the table to be created */ protected function createSessionTable($db,$tableName) { switch($db->getDriverName()) { case 'mysql': $blob='LONGBLOB'; break; case 'pgsql': $blob='BYTEA'; break; case 'sqlsrv': case 'mssql': case 'dblib': $blob='VARBINARY(MAX)'; break; default: $blob='BLOB'; break; } $db->createCommand()->createTable($tableName,array( 'id'=>'CHAR(32) PRIMARY KEY', 'expire'=>'integer', 'data'=>$blob, )); } /** * @return CDbConnection the DB connection instance * @throws CException if {@link connectionID} does not point to a valid application component. */ protected function getDbConnection() { if($this->_db!==null) return $this->_db; elseif(($id=$this->connectionID)!==null) { if(($this->_db=Yii::app()->getComponent($id)) instanceof CDbConnection) return $this->_db; else throw new CException(Yii::t('yii','CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.', array('{id}'=>$id))); } else { $dbFile=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'session-'.Yii::getVersion().'.db'; return $this->_db=new CDbConnection('sqlite:'.$dbFile); } } /** * Session open handler. * Do not call this method directly. * @param string $savePath session save path * @param string $sessionName session name * @return boolean whether session is opened successfully */ public function openSession($savePath,$sessionName) { if($this->autoCreateSessionTable) { $db=$this->getDbConnection(); $db->setActive(true); try { $db->createCommand()->delete($this->sessionTableName,'expire<:expire',array(':expire'=>time())); } catch(Exception $e) { $this->createSessionTable($db,$this->sessionTableName); } } return true; } /** * Session read handler. * Do not call this method directly. * @param string $id session ID * @return string the session data */ public function readSession($id) { $db=$this->getDbConnection(); if($db->getDriverName()=='sqlsrv' || $db->getDriverName()=='mssql' || $db->getDriverName()=='dblib') $select='CONVERT(VARCHAR(MAX), data)'; else $select='data'; $data=$db->createCommand() ->select($select) ->from($this->sessionTableName) ->where('expire>:expire AND id=:id',array(':expire'=>time(),':id'=>$id)) ->queryScalar(); return $data===false?'':$data; } /** * Session write handler. * Do not call this method directly. * @param string $id session ID * @param string $data session data * @return boolean whether session write is successful */ public function writeSession($id,$data) { // exception must be caught in session write handler // http://us.php.net/manual/en/function.session-set-save-handler.php try { $expire=time()+$this->getTimeout(); $db=$this->getDbConnection(); if($db->getDriverName()=='sqlsrv' || $db->getDriverName()=='mssql' || $db->getDriverName()=='dblib') $data=new CDbExpression('CONVERT(VARBINARY(MAX), '.$db->quoteValue($data).')'); if($db->createCommand()->select('id')->from($this->sessionTableName)->where('id=:id',array(':id'=>$id))->queryScalar()===false) $db->createCommand()->insert($this->sessionTableName,array( 'id'=>$id, 'data'=>$data, 'expire'=>$expire, )); else $db->createCommand()->update($this->sessionTableName,array( 'data'=>$data, 'expire'=>$expire ),'id=:id',array(':id'=>$id)); } catch(Exception $e) { if(YII_DEBUG) echo $e->getMessage(); // it is too late to log an error message here return false; } return true; } /** * Session destroy handler. * Do not call this method directly. * @param string $id session ID * @return boolean whether session is destroyed successfully */ public function destroySession($id) { $this->getDbConnection()->createCommand() ->delete($this->sessionTableName,'id=:id',array(':id'=>$id)); return true; } /** * Session GC (garbage collection) handler. * Do not call this method directly. * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up. * @return boolean whether session is GCed successfully */ public function gcSession($maxLifetime) { $this->getDbConnection()->createCommand() ->delete($this->sessionTableName,'expire<:expire',array(':expire'=>time())); return true; } }