1: <?php
2: /**
3: * CDbHttpSession class
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CDbHttpSession extends {@link CHttpSession} by using database as session data storage.
13: *
14: * CDbHttpSession stores session data in a DB table named 'YiiSession'. The table name
15: * can be changed by setting {@link sessionTableName}. If the table does not exist,
16: * it will be automatically created if {@link autoCreateSessionTable} is set true.
17: *
18: * The following is the table structure:
19: *
20: * <pre>
21: * CREATE TABLE YiiSession
22: * (
23: * id CHAR(32) PRIMARY KEY,
24: * expire INTEGER,
25: * data BLOB
26: * )
27: * </pre>
28: * Where 'BLOB' refers to the BLOB-type of your preffered database.
29: *
30: * Note that if your session IDs are more than 32 characters (can be changed via
31: * session.hash_bits_per_character or session.hash_function) you should modify
32: * SQL schema accordingly.
33: *
34: * CDbHttpSession relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to access database.
35: *
36: * By default, it will use an SQLite3 database named 'session-YiiVersion.db' under the application runtime directory.
37: * You can also specify {@link connectionID} so that it makes use of a DB application component to access database.
38: *
39: * When using CDbHttpSession in a production server, we recommend you pre-create the session DB table
40: * and set {@link autoCreateSessionTable} to be false. This will greatly improve the performance.
41: * You may also create a DB index for the 'expire' column in the session table to further improve the performance.
42: *
43: * @property boolean $useCustomStorage Whether to use custom storage.
44: *
45: * @author Qiang Xue <qiang.xue@gmail.com>
46: * @package system.web
47: * @since 1.0
48: */
49: class CDbHttpSession extends CHttpSession
50: {
51: /**
52: * @var string the ID of a {@link CDbConnection} application component. If not set, a SQLite database
53: * will be automatically created and used. The SQLite database file is
54: * is <code>protected/runtime/session-YiiVersion.db</code>.
55: */
56: public $connectionID;
57: /**
58: * @var string the name of the DB table to store session content.
59: * Note, if {@link autoCreateSessionTable} is false and you want to create the DB table manually by yourself,
60: * you need to make sure the DB table is of the following structure:
61: * <pre>
62: * (id CHAR(32) PRIMARY KEY, expire INTEGER, data BLOB)
63: * </pre>
64: * @see autoCreateSessionTable
65: */
66: public $sessionTableName='YiiSession';
67: /**
68: * @var boolean whether the session DB table should be automatically created if not exists. Defaults to true.
69: * @see sessionTableName
70: */
71: public $autoCreateSessionTable=true;
72: /**
73: * @var CDbConnection the DB connection instance
74: */
75: private $_db;
76:
77:
78: /**
79: * Returns a value indicating whether to use custom session storage.
80: * This method overrides the parent implementation and always returns true.
81: * @return boolean whether to use custom storage.
82: */
83: public function getUseCustomStorage()
84: {
85: return true;
86: }
87:
88: /**
89: * Updates the current session id with a newly generated one.
90: * Please refer to {@link http://php.net/session_regenerate_id} for more details.
91: * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
92: * @since 1.1.8
93: */
94: public function regenerateID($deleteOldSession=false)
95: {
96: $oldID=session_id();
97:
98: // if no session is started, there is nothing to regenerate
99: if(empty($oldID))
100: return;
101:
102: parent::regenerateID(false);
103: $newID=session_id();
104: $db=$this->getDbConnection();
105:
106: $row=$db->createCommand()
107: ->select()
108: ->from($this->sessionTableName)
109: ->where('id=:id',array(':id'=>$oldID))
110: ->queryRow();
111: if($row!==false)
112: {
113: if($deleteOldSession)
114: $db->createCommand()->update($this->sessionTableName,array(
115: 'id'=>$newID
116: ),'id=:oldID',array(':oldID'=>$oldID));
117: else
118: {
119: $row['id']=$newID;
120: $db->createCommand()->insert($this->sessionTableName, $row);
121: }
122: }
123: else
124: {
125: // shouldn't reach here normally
126: $db->createCommand()->insert($this->sessionTableName, array(
127: 'id'=>$newID,
128: 'expire'=>time()+$this->getTimeout(),
129: 'data'=>'',
130: ));
131: }
132: }
133:
134: /**
135: * Creates the session DB table.
136: * @param CDbConnection $db the database connection
137: * @param string $tableName the name of the table to be created
138: */
139: protected function createSessionTable($db,$tableName)
140: {
141: switch($db->getDriverName())
142: {
143: case 'mysql':
144: $blob='LONGBLOB';
145: break;
146: case 'pgsql':
147: $blob='BYTEA';
148: break;
149: case 'sqlsrv':
150: case 'mssql':
151: case 'dblib':
152: $blob='VARBINARY(MAX)';
153: break;
154: default:
155: $blob='BLOB';
156: break;
157: }
158: $db->createCommand()->createTable($tableName,array(
159: 'id'=>'CHAR(32) PRIMARY KEY',
160: 'expire'=>'integer',
161: 'data'=>$blob,
162: ));
163: }
164:
165: /**
166: * @return CDbConnection the DB connection instance
167: * @throws CException if {@link connectionID} does not point to a valid application component.
168: */
169: protected function getDbConnection()
170: {
171: if($this->_db!==null)
172: return $this->_db;
173: elseif(($id=$this->connectionID)!==null)
174: {
175: if(($this->_db=Yii::app()->getComponent($id)) instanceof CDbConnection)
176: return $this->_db;
177: else
178: throw new CException(Yii::t('yii','CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.',
179: array('{id}'=>$id)));
180: }
181: else
182: {
183: $dbFile=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'session-'.Yii::getVersion().'.db';
184: return $this->_db=new CDbConnection('sqlite:'.$dbFile);
185: }
186: }
187:
188: /**
189: * Session open handler.
190: * Do not call this method directly.
191: * @param string $savePath session save path
192: * @param string $sessionName session name
193: * @return boolean whether session is opened successfully
194: */
195: public function openSession($savePath,$sessionName)
196: {
197: if($this->autoCreateSessionTable)
198: {
199: $db=$this->getDbConnection();
200: $db->setActive(true);
201: try
202: {
203: $db->createCommand()->delete($this->sessionTableName,'expire<:expire',array(':expire'=>time()));
204: }
205: catch(Exception $e)
206: {
207: $this->createSessionTable($db,$this->sessionTableName);
208: }
209: }
210: return true;
211: }
212:
213: /**
214: * Session read handler.
215: * Do not call this method directly.
216: * @param string $id session ID
217: * @return string the session data
218: */
219: public function readSession($id)
220: {
221: $db=$this->getDbConnection();
222: if($db->getDriverName()=='sqlsrv' || $db->getDriverName()=='mssql' || $db->getDriverName()=='dblib')
223: $select='CONVERT(VARCHAR(MAX), data)';
224: else
225: $select='data';
226: $data=$db->createCommand()
227: ->select($select)
228: ->from($this->sessionTableName)
229: ->where('expire>:expire AND id=:id',array(':expire'=>time(),':id'=>$id))
230: ->queryScalar();
231: return $data===false?'':$data;
232: }
233:
234: /**
235: * Session write handler.
236: * Do not call this method directly.
237: * @param string $id session ID
238: * @param string $data session data
239: * @return boolean whether session write is successful
240: */
241: public function writeSession($id,$data)
242: {
243: // exception must be caught in session write handler
244: // http://us.php.net/manual/en/function.session-set-save-handler.php
245: try
246: {
247: $expire=time()+$this->getTimeout();
248: $db=$this->getDbConnection();
249: if($db->getDriverName()=='sqlsrv' || $db->getDriverName()=='mssql' || $db->getDriverName()=='dblib')
250: $data=new CDbExpression('CONVERT(VARBINARY(MAX), '.$db->quoteValue($data).')');
251: if($db->createCommand()->select('id')->from($this->sessionTableName)->where('id=:id',array(':id'=>$id))->queryScalar()===false)
252: $db->createCommand()->insert($this->sessionTableName,array(
253: 'id'=>$id,
254: 'data'=>$data,
255: 'expire'=>$expire,
256: ));
257: else
258: $db->createCommand()->update($this->sessionTableName,array(
259: 'data'=>$data,
260: 'expire'=>$expire
261: ),'id=:id',array(':id'=>$id));
262: }
263: catch(Exception $e)
264: {
265: if(YII_DEBUG)
266: echo $e->getMessage();
267: // it is too late to log an error message here
268: return false;
269: }
270: return true;
271: }
272:
273: /**
274: * Session destroy handler.
275: * Do not call this method directly.
276: * @param string $id session ID
277: * @return boolean whether session is destroyed successfully
278: */
279: public function destroySession($id)
280: {
281: $this->getDbConnection()->createCommand()
282: ->delete($this->sessionTableName,'id=:id',array(':id'=>$id));
283: return true;
284: }
285:
286: /**
287: * Session GC (garbage collection) handler.
288: * Do not call this method directly.
289: * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
290: * @return boolean whether session is GCed successfully
291: */
292: public function gcSession($maxLifetime)
293: {
294: $this->getDbConnection()->createCommand()
295: ->delete($this->sessionTableName,'expire<:expire',array(':expire'=>time()));
296: return true;
297: }
298: }
299: