Хранение настроек в базе

Доброго времени суток!

Думаю что перед многими из Вас вставал вопрос - как хранить настройки приложения в базе, дабы менять их через панель управления.

Собственно хотелось бы узнать какие есть варианты решения. Интересует не сколько код, сколько сама идея.

Пока на ум приходит следующее:

Вариант 1 "аля решение в лоб".

Берем конфигурационный файл и через админку, при изменении каких-либо настроек, сохраняем в него данные из базы.

Вариант 2 "Кэш"

Храним данные в базе. Делаем компонент (например дополняем контроллер) и в нем вытаскиваем параметры из базы в кэш. Кэш на макс. срок и обновляем его по факту изменения настроек в админке.

Вариант 3 "Отдельный экстеншн"

Тут все просто - отдельный компонент системы, который работает с конфигом и кэшем. Аля dbParam

Вариант 4.

Сделать модель, которая сохраняет настройки файл и не парится с базой…


<?php

class Settings extends CFormModel

{

     private $_params;


     public function __construct($scenario = '')

     {

          $this->_params = include Yii:getPathOfAlias('application.config.settings') . '.php';

          parent::__construct($scenario);

     }


     public function setAttributes($data)

     {

          foreach($this->_params as $key => $value)

          {

               if(isset($data[$key]))

                    $this->_params[$key] = $value;

          } 

     }


     public function __get($name)

     {

          if(isset($this->_params[$name]))

               return $this->_params[$name];

          

          return parent::__get($name);

     }


     public function __set($name, $value)

     {

          if(isset($this->_params[$name]))

               $this->_params[$name] = $value;

          else

               parent::__set($name, $value);

     }


     public function rules()

     {

          return array(

               array('title, description, keywords', 'required'),

               array('adminEmail', 'email'),

          );

     }


     public function save()

     {

          if($this->validate())

          {

               file_put_contents(

                    Yii:getPathOfAlias('application.config.settings') . '.php',

                    '<?php return ' . var_export($this->_params, true) . ';'

               );

               

               return true;

          }


          return false;

     }

}

В config/main.php


<?php

return array(

    'params' => require dirname(__FILE__) . '/settings.php',

Сохранение


$model = new Settings;

$model->attributes = $_POST['form'];

$model->save();

В приложении


echo Yii::app()->params->adminEmail;

использую вот это решение.

очень даже устраивает, можно его немного развить, добавить группы параметров, типы данных в параметрах, etc

все зависит от вашей фантазии )

Тогда писал класс вспешке, сейчас исправил все…


<?php

class Settings extends CFormModel

{

	private $_file;

	private $_params;

	

	public function __construct($scenario = '')

	{

		$this->_file = Yii::getPathOfAlias('application.config.params') . '.php';

		$this->_params = require $this->_file;

		

		parent::__construct($scenario);

	}

	

	public function setAttributes($data)

	{

		foreach($this->_params as $key => $value)

		{

			if(isset($data[$key]))

				$this->_params[$key] = $data[$key];

		}

	}

	

	public function getAttributes()

	{

		return $this->_params;

	}

	

	public function __get($name)

	{

		if(isset($this->_params[$name]))

			return $this->_params[$name];

		

		return parent::__get($name);

	}

	

	public function __set($name, $value)

	{

		if(isset($this->_params[$name]))

			$this->_params[$name] = $value;

		else

			parent::__set($name, $value);

	}

	

	public function rules()

	{

		return array(

			array(

				implode(',', array_keys($this->_params)), 'required',

				'message' => Yii::t('base', 'Cannot be blank.')

			)

		);

	}


	public function attributeLabels()

	{

		$labels = array();

		

		foreach($this->_params as $name => $value)

			$labels[$name] = Yii::t('base', $this->generateAttributeLabel($name));

		

		return $labels;

	}

	

	public function save()

	{

		if($this->validate())

		{

			file_put_contents(

				$this->_file,

				'<?php return ' . var_export($this->_params, true) . ';'

			);


			return true;

		}

		

		return false;

	}

}


	public function actionSettings()

	{

		$model = new Settings;

		

		if(isset($_POST['Settings']))

		{

			$model->attributes = $_POST['Settings'];

			$model->save();

		}

		

		$this->render('settings', array(

			'model' => $model,

		));

	}

config/params.php


<?php return array(

  'title' => 'Заголовок сайта',

  'description' => 'Описание сайта1',

  'keywords' => 'Ключевые слова',

  'adminEmail' => 'sdevalex@gmail.com',

);

Паренек, спасибо!

А не подскажешь как быть если я хочу хранить настройки, допустим, в таком виде




'title' => 

  array (

    'label' => 'Заголовок сайта',

    'value' => 'My Yii Blog',

    'hint' => 'Укажите глобальный заголовок сайта',

  ),


'keywords' => 

  array (

    'label' => 'Ключевые слова',

    'value' => 'keyword1, keyword2',

    'hint' => 'Укажите ключевые слова для сайта',

  ),

...



и чтоб модель была одним параметром, а не всеми сразу

Так сразу не скажу… Но самое простое наверно делать поля массивы TextArea и перед сохранением парсить текст в массив.

Keywords TextArea


label: Ключевые слова

value: keyword1, keyword2

hint: Укажите ключевые слова для сайта

Парсится в


'keywords' => 

  array (

    'label' => 'Ключевые слова',

    'value' => 'keyword1, keyword2',

    'hint' => 'Укажите ключевые слова для сайта',

  ),

у меня вот такое используеться





/**

 * 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',

        ),

    ),



ну и в контроллере два екшена для обновления опции

один - обновляет опцию целиком включая форму и доступен только админу

второй - доступен простым смертным и через сгенерированную форму позволяет менять только значение