That’s exactly what I’d like, I don’t want any dynamically saved information at all to reside outside of my SQL database. With the session it’s simple, there already is a CDbHttpSession, but with the CStatePersister there is not?
Of course I can implement things by hand, but then I suppose I’m not the only one this applies to, so an extension would seem reasonable, but actually there should be something in the Yii core, don’t you think?
OK, thanks. Since there is nothing, I hacked it together from the original CStatePersister implementation as well as the CDbHttpSession. This is the code in case somebody is interested, one could also write an extension, but it seems not too many people actually care? (Edit: removed the caching – doesn’t make sense for a database saved entry I guess.)
class StatePersister extends CStatePersister
{
/**
* @var boolean if the database table should be automatically created if it does not exist.
* Defaults to TRUE.
*/
public $autoCreatePersisterTable = TRUE;
/**
* @var string the table name, defaults to YiiPersister
*/
public $persisterTableName = 'YiiPersister';
/**
* @var string the ID of the connection component, defaults to 'db'
*/
public $connectionID = 'db';
/**
* @var CDbConnection the DB connection instance
*/
private $_db;
protected function createPersisterTable($db,$tableName)
{
$sql="
CREATE TABLE $tableName
(
id int PRIMARY KEY,
data TEXT
)";
$db->createCommand($sql)->execute();
}
protected function getDbConnection()
{
if($this->_db!==null)
return $this->_db;
else if(($id=$this->connectionID)!==null && ($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)));
}
/**
* Initializes the component.
*/
public function init()
{
$db=$this->getDbConnection();
$db->setActive(true);
if($this->autoCreatePersisterTable) {
try {
$this->createPersisterTable($db,$this->persisterTableName);
}
catch(Exception $e) {
}
}
return true;
}
/**
* Loads state data from persistent storage.
* @return mixed state data. Null if no state data available.
*/
public function load()
{
if(($content=$this->getContents())!==false)
return unserialize($content);
else
return null;
}
/**
* Read the data from the table
*/
private function getContents()
{
$sql="
SELECT data FROM {$this->persisterTableName}
WHERE id=1
";
$data=$this->getDbConnection()->createCommand($sql)->queryScalar();
return $data===false?'':$data;
}
/**
* Saves application state in persistent storage.
* @param mixed $state state data (must be serializable).
*/
public function save($state)
{
$data = serialize($state);
try {
$db=$this->getDbConnection();
$sql="SELECT id FROM {$this->persisterTableName} WHERE id=1";
if($db->createCommand($sql)->queryScalar()===false)
$sql="INSERT INTO {$this->persisterTableName} (id, data) VALUES (1, :data)";
else
$sql="UPDATE {$this->persisterTableName} SET data=:data WHERE id=1";
$db->createCommand($sql)->bindValue(':data',$data)->execute();
}
catch(Exception $e) {
if(YII_DEBUG)
echo $e->getMessage();
return false;
}
return true;
}
}
Thanks for this! Ran into a situation where I am running Yii on a read-only filesystem and was about to implement this myself when I searched. This or something like it should be integrated into Yii.
Made some changes which may or may not be helpful for someone out there. Renamed the class to be a bit more descriptive, got rid of the try/catch block in the init() method as it was filling my logs with unnecessary errors, and I also cleaned up the functions a bit as I dislike multiple returns. Refactored code is below:
class DbStatePersister extends CStatePersister {
/**
* @var boolean if the database table should be automatically created if it does not exist.
* Defaults to TRUE.
*/
public $autoCreatePersisterTable = TRUE;
/**
* @var string the table name, defaults to YiiPersister
*/
public $persisterTableName = 'YiiPersister';
/**
* @var string the ID of the connection component, defaults to 'db'
*/
public $connectionID = 'db';
/**
* @var CDbConnection the DB connection instance
*/
private $_db;
protected function createPersisterTable($db,$tableName)
{
$sql="
CREATE TABLE $tableName
(
id int PRIMARY KEY,
data TEXT
)";
$db->createCommand($sql)->execute();
}
protected function getDbConnection()
{
$id = $this->connectionID;
if(($this->_db == null) &&
(false === (Yii::app()->getComponent($id) instanceof CDbConnection)))
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)));
$this->_db = Yii::app()->getComponent($id);
return $this->_db;
}
/**
* Initializes the component.
*/
public function init()
{
$db=$this->getDbConnection();
$db->setActive(true);
if($this->autoCreatePersisterTable &&
null === $db->schema->getTable($this->persisterTableName)
) {
$this->createPersisterTable($db,$this->persisterTableName);
}
return true;
}
/**
* Loads state data from persistent storage.
* @return mixed state data. Null if no state data available.
*/
public function load()
{
$retval = null;
if(($content=$this->getContents())!==false)
$retval = unserialize($content);
return $retval;
}
/**
* Read the data from the table
*/
private function getContents()
{
$retval = null;
$sql="
SELECT data FROM {$this->persisterTableName}
WHERE id=1
";
$data = $this->getDbConnection()->createCommand($sql)->queryScalar();
if ($data === false)
$retval = $data;
return $retval;
}
/**
* Saves application state in persistent storage.
* @param mixed $state state data (must be serializable).
*/
public function save($state)
{
$retval = true;
$data = serialize($state);
try {
$db=$this->getDbConnection();
$sql="SELECT id FROM {$this->persisterTableName} WHERE id=1";
if($db->createCommand($sql)->queryScalar()===false)
$sql="INSERT INTO {$this->persisterTableName} (id, data) VALUES (1, :data)";
else
$sql="UPDATE {$this->persisterTableName} SET data=:data WHERE id=1";
$db->createCommand($sql)->bindValue(':data',$data)->execute();
}
catch(Exception $e) {
$retval = false;
if(YII_DEBUG)
echo $e->getMessage();
}
return $retval;
}
}