Doing special actoin on session close / expire

Hello,

Is there a possibility to do some action when a session is being closed or expired ?

Thanks,

Luc

I found the solution. In my case I extends the CDbHttpSession and overrides method destroySession.


public function destroySession($id)

	{

                $result = $this->deleteStock(); /*  my code */

                if ($result)

                    $result = parent::destroySession($id);

		

		return $result;

	} 

That works only when session is closed manualy (by code). In case of an session timeout this don’t work.

Now it works correctly.

Closing the session will fire an event ‘onBeforeDelete’

When session expires the same event is fired.

The vent contains all session data so before session is deleted/destroyed we can make special actions.

Here my source code:


<?php


class CBeforeDeleteSessionEvent extends CEvent

{

        public $id;

        public $data;

        public $expire;

}


/**

 * CDbHttpSession class

 *

 * @author Qiang Xue <qiang.xue@gmail.com>

 * @link http://www.yiiframework.com/

 * @copyright Copyright &copy; 2008-2010 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:

 *

 * <pre>

 * CREATE TABLE YiiSession

 * (

 *     id CHAR(32) PRIMARY KEY,

 *     expire INTEGER,

 *     data TEXT

 * )

 * </pre>

 *

 * 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.

 *

 * @author Qiang Xue <qiang.xue@gmail.com>

 * @version $Id: CDbHttpSession.php 2412 2010-09-02 02:10:43Z qiang.xue $

 * @package system.web

 * @since 1.0

 */

class CDbHttpSessionEx 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 <code>protected/runtime/session-YiiVersion.db</code>.

	 */

	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:

	 * <pre>

	 * (id CHAR(32) PRIMARY KEY, expire INTEGER, data TEXT)

	 * </pre>

	 * @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;

	}


	/**

	 * Creates the session DB table.

	 * @param CDbConnection the database connection

	 * @param string the name of the table to be created

	 */

	protected function createSessionTable($db,$tableName)

	{

		$sql="

CREATE TABLE $tableName

(

	id CHAR(32) PRIMARY KEY,

	expire INTEGER,

	data TEXT

)";

		$db->createCommand($sql)->execute();

	}


	/**

	 * @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;

		else if(($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);

		}

	}


        /**

	 * Raise an event with the session data

	 * @param array representing a row of table $sessionTableName  {@link sessionTableName}

	 * @return none

	 */

        protected function onBeforeDelete($data)

        {

                if (!is_bool($data))

                {

                        $event = new CBeforeDeleteSessionEvent();

                        $event->id = $data['id'];

                        $event->data = $data['data'];

                        $event->expire = $data['expire'];


                        $this->raiseEvent('onBeforeDelete', $event);

                }

        }


        /**

	 * Delete all expired session in the table $sessionTableName  {@link sessionTableName}

	 * @return none

	 */

        protected function forceDelete()

        {

                $db=$this->getDbConnection();

		$db->setActive(true);

		$sql="DELETE FROM {$this->sessionTableName} WHERE expire<".time();

		$db->createCommand($sql)->execute();

        }


        /**

	 * Gets a list of all expired sessions. Then fires events before session are deleted

	 * @return none

	 */

        protected function safeDelete()

        {

                $db=$this->getDbConnection();

		$db->setActive(true);

		$sql="SELECT * FROM {$this->sessionTableName} WHERE expire<".time();


                $dataReader = $db->createCommand($sql)->query();


                foreach($dataReader as $row) {

                    $this->onBeforeDelete($row);

                }


                $this->forceDelete();

        }


	/**

	 * Session open handler.

	 * Do not call this method directly.

	 * @param string session save path

	 * @param string session name

	 * @return boolean whether session is opened successfully

	 */

	public function openSession($savePath,$sessionName)

	{

		$db=$this->getDbConnection();

		$db->setActive(true);


		if($this->autoCreateSessionTable)

		{

			//$sql="DELETE FROM {$this->sessionTableName} WHERE expire<".time();

			try

			{

				//$db->createCommand($sql)->execute();

                                $this->safeDelete();

			}

			catch(Exception $e)

			{

				$this->createSessionTable($db,$this->sessionTableName);

			}

		}

		return true;

	}


	/**

	 * Session read handler.

	 * Do not call this method directly.

	 * @param string session ID

	 * @return string the session data

	 */

	public function readSession($id)

	{

		$now=time();

		$sql="

SELECT data FROM {$this->sessionTableName}

WHERE expire>$now AND id=:id

";

		$data=$this->getDbConnection()->createCommand($sql)->bindValue(':id',$id)->queryScalar();

		return $data===false?'':$data;

	}


	/**

	 * Session write handler.

	 * Do not call this method directly.

	 * @param string session ID

	 * @param string 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();

			$sql="SELECT id FROM {$this->sessionTableName} WHERE id=:id";

			if($db->createCommand($sql)->bindValue(':id',$id)->queryScalar()===false)

				$sql="INSERT INTO {$this->sessionTableName} (id, data, expire) VALUES (:id, :data, $expire)";

			else

				$sql="UPDATE {$this->sessionTableName} SET expire=$expire, data=:data WHERE id=:id";

			$db->createCommand($sql)->bindValue(':id',$id)->bindValue(':data',$data)->execute();

		}

		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 session ID

	 * @return boolean whether session is destroyed successfully

	 */

	public function destroySession($id)

	{

		// First get the session row from table to fire the event

                $sql="SELECT * FROM {$this->sessionTableName} WHERE id=:id";

		$data=$this->getDbConnection()->createCommand($sql)->bindValue(':id',$id)->queryRow();

                $this->onBeforeDelete($data);


                $sql="DELETE FROM {$this->sessionTableName} WHERE id=:id";

		$this->getDbConnection()->createCommand($sql)->bindValue(':id',$id)->execute();

		return true;

	}


	/**

	 * Session GC (garbage collection) handler.

	 * Do not call this method directly.

	 * @param integer 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->safeDelete();

		return true;

	}        

}



hi there

can anyone show me how to attach this event to a handler,

right now I have something like :

Yii::app()->session->onBeforeDelete = array(new Notify(), ‘doStuff’);

thx

Hi viglu, I think you are the only one have posted about extending the Session Class. Kudos for that.

I am a newbie in Yii. I hope you can help me.

with the function below, you have overridden the destroySesssion and then you have mentioned that this was not invoked when the session time outs.

public function destroySession($id)

    {


            &#036;result = &#036;this-&gt;deleteStock(); /*  my code */


            if (&#036;result)


                &#036;result = parent::destroySession(&#036;id);


            


            return &#036;result;


    } 

with the succeeding code that you have provided that explains in invoking an event. I think it was quite extensive.

Is there anyway to invoke the event without using database? I mean I tried the below code but yet It was unsuccessful.

class SessionEx extends CHttpSession {

/**


 * Initializes this class.


 */


public function init() {





}





public function getUseCustomStorage() {


    return true;


}





public function destroySession(&#036;id) {


    &#036;this-&gt;onBeforeDelete();


    


    return parent::destroySession(&#036;id);


}





protected function onBeforeDelete(){


    &#036;event = new CEvent();


    Yii::app()-&gt;runController('controllerTest/destroySessionFunction');


    &#036;this-&gt;raiseEvent('onBeforeDelete', &#036;event);


}

}

I am not sure if it was correct.

Can you help me on this one?

I already added the class in the config file.

Hi All,

After some thorough checking of the code below is my suggested override.

You can override the openSession Function instead of destroy Session.




class SessionEx extends CDbHttpSession

{

    public function openSession($savePath, $sessionName) {

        if($this->autoCreateSessionTable)

        {

                $db=$this->getDbConnection();

                $db->setActive(true);

                try

                {

                    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 ',array(':expire'=>time()))

                                ->queryAll();

                           /* you can insert your code here and you may use the variable data, those were the session data that already expires*/

                          /*Additional Note: Signing out in Yii App, doesn't mean the session already expires.*/

                }

                catch(Exception $e)

                { /* throw exception if you want*/}

        }

        parent::openSession($savePath, $sessionName);

    }

    




}