у меня вот такое используеться
/**
* The followings are the available columns in table 'Options':
* @property string $name
* @property string $value
* @property int $loadOnStartup
* @property string $type
* @property string $description
* @property string $form // используеться совместно с form builder
*/
class Options extends CActiveRecord {
public $textForm;
private static $cachedOptions = array();
/**
* Returns the static model of the specified AR class.
* @return CActiveRecord the static model class
*/
public static function model($className=__CLASS__) {
return parent::model($className);
}
/**
* @return string the associated database table name
*/
public function tableName() {
return 'Options';
}
/**
* @return array validation rules for model attributes.
*/
public function rules() {
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('name', 'required', 'on' => 'insert'),
array('name, type', 'length', 'max' => 64),
array('textForm', 'length', 'max' => 65535, 'on' => 'insert'),
array('textForm', 'checkForm', 'allowEmpty' => true, 'on'=>'insert,update'),
array('value, loadOnStartup, description, type, textForm', 'safe'),
array('name, type', 'safe', 'on' => 'search'),
);
}
public function checkForm($attribute, $params) {
if (empty($this->textForm) && isset($params['allowEmpty']) && $params['allowEmpty'])
return true;
if (!$this->hasErrors()) {
$this->textForm = trim(str_replace(array('$this','Yii','shell_exec','popen','passthru','fopen','exec','system','::'), '', $this->textForm));
$this->textForm = trim($this->textForm, ';').';';
if(strpos($this->textForm, 'return') !== 0)
$this->textForm = 'return ' . trim($this->textForm);
try {
$this->form = eval($this->textForm);
if (is_array($this->form) && !empty($this->form))
return true;
else {
$this->addError('textForm', 'The form must be not empty array');
return false;
}
} catch (Exception $e) {
$this->addError('textForm', 'Could not parse the form');
return false;
}
}
}
/**
* @return array relational rules.
*/
public function relations() {
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
);
}
/**
* @return array customized attribute labels (name=>label)
*/
public function attributeLabels() {
return array(
'name' => 'Name',
'value' => 'Value',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
* @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
*/
public function search() {
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria = new CDbCriteria;
$criteria->compare('name', $this->name, true);
$criteria->compare('value', $this->value, true);
$criteria->compare('type', $this->type, true);
return new CActiveDataProvider('Options', array(
'criteria' => $criteria,
));
}
protected function afterFind() {
$this->form = unserialize($this->form);
if (is_array($this->form))
$this->textForm = var_export($this->form, true) . ';';
else
$this->textForm = '';
if (self::isSerialized($this->value))
$this->value = unserialize($this->value);
self::$cachedOptions[$this->name] = $this->value;
parent::afterFind();
}
protected function beforeSave() {
if (!empty($this->form))
$this->form = serialize($this->form);
self::$cachedOptions[$this->name] = $this->value;
if (is_array($this->value) || is_object($this->value)) {
$this->value = serialize($this->value);
}
return parent::beforeSave();
}
protected function afterDelete() {
if (isset(self::$cachedOptions[$this->name]))
unset(self::$cachedOptions[$this->name]);
parent::afterDelete();
}
public static function isSerialized($data) {
// if it isn't a string, it isn't serialized
if (!is_string($data))
return false;
$data = trim($data);
if ('N;' == $data)
return true;
if (!preg_match('/^([adObis]):/', $data, $badions))
return false;
switch ($badions[1]) {
case 'a' :
case 'O' :
case 's' :
if (preg_match("/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data))
return true;
break;
case 'b' :
case 'i' :
case 'd' :
if (preg_match("/^{$badions[1]}:[0-9.E-]+;\$/", $data))
return true;
break;
}
return false;
}
public static function get($name, $default = null) {
if (isset(self::$cachedOptions[$name]))
return self::$cachedOptions[$name];
else {
$option = Options::model()->findByPk($name);
if ($option) {
self::$cachedOptions[$name] = $option->value;
return self::$cachedOptions[$name];
}
else
return $default;
}
}
public static function set($name, $value = '', $loadOnStartup = null, $type=null, $decription=null) {
self::$cachedOptions[$name] = $value;
$option = Options::model()->findByPk($name);
if (!$option) {
$option = new Options();
$option->name = $name;
}
if ($loadOnStartup !== null)
$option->loadOnStartup = (bool) $loadOnStartup;
$option->type = $type;
$option->description = $decription;
$option->value = $value;
$option->save();
}
public static function remove($name) {
unset(self::$cachedOptions[$name]);
Options::model()->deleteByPk($name);
}
public static function loadOptions($type = null) {
$db = Yii::app()->db;
if (!empty($type))
$command = $db->createCommand("SELECT * FROM Options WHERE `type`='{$type}';");
else
$command = $db->createCommand("SELECT * FROM Options WHERE loadOnStartup=1;");
$dataReader = $command->query();
while (($row = $dataReader->read()) !== false) {
if (self::isSerialized($row['value']))
self::$cachedOptions[$row['name']] = unserialize($row['value']);
else
self::$cachedOptions[$row['name']] = $row['value'];
}
}
}
class StartupBehavior extends CBehavior{
/**
*
* @param CComponent $owner
*/
public function attach($owner){
$owner->attachEventHandler('onBeginRequest', array($this, 'beginRequest'));
parent::attach($owner);
}
public function beginRequest(CEvent $event){
Options::loadOptions();
Yii::app()->setLanguage(Options::get('application_language','en'));
}
}
и в конфиге
'behaviors' => array(
'onBeginRequest' => array(
'class' => 'application.components.StartupBehavior',
),
),
ну и в контроллере два екшена для обновления опции
один - обновляет опцию целиком включая форму и доступен только админу
второй - доступен простым смертным и через сгенерированную форму позволяет менять только значение