Yii 1.1: settings

Another way to store configuration items to database
64 followers

This extension is an alternative to my "myconfig" extension from here: http://www.yiiframework.com/extension/myconfig/ but it uses only the database+caching.

As explained in this comment http://www.yiiframework.com/extension/myconfig/#c3727 using this extension has some advantages and some extra requirements.

Requirements

1) Yii 1.1.x
2) A Cache component activated (CFileCache will do it just fine)

Instalation

Add the component to the main.php config file:

[...]
'cache'=>array(
            'class'=>'system.caching.CFileCache',
        ),
'settings'=>array(
        'class'                 => 'CmsSettings',
        'cacheComponentId'  => 'cache',
        'cacheId'           => 'global_website_settings',
        'cacheTime'         => 84000,
        'tableName'     => '{{settings}}',
        'dbComponentId'     => 'db',
        'createTable'       => true,
        'dbEngine'      => 'InnoDB',
        ),
[...]

Usage

/*   
* Set a database item:  
* $itemName can be an associative array() in key=>value pairs  ($itemValue="" in this case) 
*/
Yii::app()->settings->set($categoryName, $itemName, $itemValue, $toDatabase=true);  
 
// Get a database item:  
Yii::app()->settings->get($categoryName, $itemName);  
 
// Get all items from a category:  
Yii::app()->settings->get($categoryName);
 
// Delete a database item:  
Yii::app()->settings->delete($categoryName, $itemName);  
 
// Delete all items from a category:  
Yii::app()->settings->delete($categoryName);  
 
//Import items from a file:  
$items=include('path/to/file.php');//returns an array() in key=>value pairs  
Yii::app()->settings->set($categoryName, $items);

The component uses something like "lazy loading" for loading items within a category, meaning that the items from a category will be loaded when you request them first time. Beside this, at the end of the request, the items from that requested category are written to cache, so next time when you request them, they will be served from cache.

Note, the component is smart enough to know itself when a new item/category has been added and refresh the cache accordingly so you don't have to keep track of the items you add to database.

Basically, in most of the cases, the database will be hit only once, then all items will be served from cache, which means you will get a nice way to manage the project configuration without performance penalties.

Notes & Changelog

Version 1.1.c
Various improvements

Version 1.1.d
-> Contains small performance improvements.
-> You can now use the get() method like

$retrieve_custom_settings=Yii::app()->settings->get('system',array('admin_email','contact_email','my_email'=>'some default value'));

In the above example, $retrieve_custom_settings becomes an array having the 'admin_email' and 'contact_email' keys. If these values are empty or they don't exists in database then they will be set to null otherwise you will retrieve their values. It is set this way so that you can safely use $retrieve_custom_settings['admin_email'] even if it doesn't exists.

Version 1.1.e
->Added setters/getters for all the public properties.
->The component supports the automatic creation of the database table (optionally, you can specify which storage engine to use in case you use MYSQL)
->Added the option to specify the name of the Cache/Database components via the $cacheComponentId and $dbComponentId properties.
->removed the __call() magic function.
->commented the class methods.

Special thanks goes to Gustavo who helped me allot with this version.

Upgrading to this version will not break the backward compatibility in any way(hope you didn't make use of the __call() method) and it is recommended to do so.
In case you use the autocreate database table option, don't forget to turn it off after the table has been created.

Version 1.2
-> added the deleteCache() public method to allow to delete cached categories
-> implemented a cache registry that will hold all the cached categories and it will be updated each time a new category is loaded/deleted Usage:
-> various changes that won't break BC.

Yii::app()->settings->deleteCache('categoryName'); //delete a single category  
Yii::app()->settings->deleteCache(array('c1', 'c2','c3')); // delete multiple categories  
Yii::app()->settings->deleteCache(); // delete all cached categories.

Please let me know in case you find any error.

Total 20 comments

#10271 report it
twisted1919 at 2012/10/16 06:17pm
Sorry, but not enough time right now :(

Hi all, i read all your comments but for now i don't really have enough time to implement everything as i am very busy at work. Hope i'll have some spare time to improve the extension in the near future(the version used in my projects already is better, so i'll post all at once).
Thanks for your pacience:)

#10268 report it
yugene at 2012/10/16 04:57am
Approach to usage with many parameters

Hi to all,

This extension is very useful and I use it in several projects. From my experience, when application has a lot of different parameters, it may become not handy to manage them.

With following approach I'm trying to solve such hassles:

1) a lot of editions when you need to add new parameters to your application

2) obtrusive (not handy) code in model, controller and view files (which I first met on a large project where this extension was used)

3) instead of writing long requests, to use small helper function to call parameters

What can be improved:

1) add proper validation method (which isn't difficult to add, I just didn't need it for now)

2) get rid of literals usage as categories and parameters names - I think it would be more handy to have kinda of constants.

Your suggestions are welcome :)

controller

class SettingsController extends Controller
{
 
    public function actionIndex()
    {
        $settings = app()->settings;
 
        $model = new SettingsForm();
 
        if (isset($_POST['SettingsForm'])) {
            $model->setAttributes($_POST['SettingsForm']);
            $settings->deleteCache();
            foreach($model->attributes as $category => $values){
                $settings->set($category, $values);
            }
            user()->setFlash('success', 'Site settings were updated.');
            $this->refresh();
        }
        foreach($model->attributes as $category => $values){
            $cat = $model->$category;
            foreach($values as $key=>$val){
                $cat[$key] = $settings->get($category, $key);
            }
            $model->$category = $cat;
        }
        $this->render('index', array('model' => $model));
    }
 
}

model

class SettingsForm extends CFormModel
{
 
    public $site = array(
        'name' => '',
        'googleAPIKey' => '',
        'numSearchResults' => '',
        'defaultLanguage' => '',
        'defaultCurrency' => '',
    );
    public $seo = array(
        'mainTitle' => '',
        'mainKwrds' => '',
        'mainDescr' => ''
    );
    public $mail = array(
        'adminEmail' => '',
        'fromReply' => '',
        'fromNoReply' => '',
        'server' => '',
        'port' => '',
        'user' => '',
        'password' => '',
        'ssl' => '',
    );
    public $filter = array(
        'priceLower'=>'',
        'priceUpper'=>'',
    );
 
    /**
     * Declares customized attribute labels.
     * If not declared here, an attribute would have a label that is
     * the same as its name with the first letter in upper case.
     */
    public function getAttributesLabels($key)
    {
        $keys = array(
            'googleAPIKey' => 'Google API Key',
            'numSearchResults' => 'Number of search results at one page',
            'mainTitle' => 'Main Page Title',
            'mainKwrds' => 'Default Keywords (Meta Tag)',
            'mainDescr' => 'Default Description (Meta Tag)',
        );
 
        if(array_key_exists($key, $keys))
            return $keys[$key];
 
        $label = trim(strtolower(str_replace(array('-', '_'), ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $key))));
        $label = preg_replace('/\s+/', ' ', $label);
 
        if (strcasecmp(substr($label, -3), ' id') === 0)
            $label = substr($label, 0, -3);
 
        return ucwords($label);
    }
 
    /**
     * Sets attribues values
     * @param array $values
     * @param boolean $safeOnly
     */
    public function setAttributes($values,$safeOnly=true) 
    { 
        if(!is_array($values)) 
            return; 
 
        foreach($values as $category=>$values) 
        { 
            if(isset($this->$category)) {
                $cat = $this->$category;
                foreach ($values as $key => $value) {
                    if(isset($cat[$key])){
                        $cat[$key] = $value;
                    }
                }
                $this->$category = $cat;
            }
        } 
    }
}

views to set params

main view shows tabs, content for which is stored in partial views

index.php

<h3>Site Settings</h3>
 
<?php echo CHtml::errorSummary($model); ?>
<?php
echo CHtml::beginForm();
?>
<ul class="nav nav-tabs" id="site-settings">
<?php
 
$tabs = array();
$i = 0;
    foreach ($model->attributes as $category => $values):?>
        <li<?php echo !$i?' class="active"':''?>><a href="#<?php echo $category?>" data-toggle="tab"><?php echo ucfirst($category)?></a></li>
    <?php 
    $i ++;
    endforeach;?>
</ul>
    <div class="tab-content">
        <?php 
        $i = 0;
        foreach ($model->attributes as $category => $values):?>
            <div class="tab-pane<?php echo !$i?' active':''?>" id="<?php echo $category?>">
                <?php
                $this->renderPartial(
                        '_'.$category, 
                        array('model' => $model, 'values' => $values, 'category' => $category)
                    );
                ?>
            </div>
        <?php 
        $i ++;
        endforeach;?>
    </div>
<?php
echo CHtml::submitButton('Submit', array('class' => 'btn'));
echo CHtml::endForm();

example partial view

<?php foreach ($values as $key => $val): ?>
    <div class="control-group">
        <?php echo CHtml::label($model->getAttributesLabels($key), $key); ?>
        <?php 
        if($key === 'ssl')
            echo CHtml::checkBox(get_class($model) . '[' . $category . '][' . $key . ']', $val); 
        else 
            echo CHtml::textField(get_class($model) . '[' . $category . '][' . $key . ']', $val, array('class'=>'input-xxlarge')); 
 
        ?>
        <?php echo CHtml::error($model, $category); ?>
    </div>
<?php endforeach; ?>

catch parameter

Helper function is stored in globals.php, which may be included to code in application bootstrap file.

/**
 * Shortcut for Yii::app()->settings->get();
 * @param string $param 
 */
function sg($category, $key){
    return Yii::app()->settings->get($category, $key);
}

usage

sg('site', 'name');

P.S. I would put this message to extension forum topic but couldn't find the link to it here.

#9974 report it
dinhtrung at 2012/09/26 04:42am
Change createTable to Yii style

Hi, after using your settings for a long time, I see some minor fix should be made:

    protected function createTable()
    {
        $schema = Yii::app()->getDb()->getSchema();
        $table = $schema->quoteTableName($this->getTableName());
        $create = $schema->createTable(
            $table,
            array(
                'category' => 'string',
                'key'   =>  'string',
                'value' => 'text',
            )
        );
        $index = $schema->createIndex('category_key', $table, 'category,key', TRUE);
        Yii::app()->getDb()->createCommand($create)->execute();
        Yii::app()->getDb()->createCommand($index)->execute();
        return TRUE;
    }

This will use Yii createTable instead of raw SQL one.

I think this is more convenient, in case switching db engine.

Also, remove the dbEngine variable, and table prefix. No need for that, right?

#9800 report it
twisted1919 at 2012/09/11 10:26am
will do

@fad
okay, this will be implemented in the next version (along with a few bug fixes) which will be available somewhere this week.

#9798 report it
fad at 2012/09/11 08:07am
Options to set table column names and default category name

Back to comment #4225 Nothing new, just new options. Question to author: Where to send the updated code, if possibly?

Or maybe I do fork on github?

protected $_dbComponentId='db';
    protected $_tableName='{{settings}}';
    protected $_idColumn='id';
    protected $_categoryColumn='category';
    protected $_keyColumn='key';
    protected $_valueColumn='value';
    protected $_createTable=false;
    protected $_dbEngine='InnoDB';
    protected $_defaultCategory='system';
.......
.......
    protected function createTable()
    {
        $connection=$this->getDbComponent();
        $tableName=$connection->tablePrefix.str_replace(array('{{','}}'), '', $this->getTableName());
        $sql='CREATE TABLE IF NOT EXISTS `'.$tableName.'` (
          `'.$this->_idColumn.'` int(11) NOT NULL auto_increment,
          `'.$this->_categoryColumn.'` varchar(64) NOT NULL default \''.$this->_defaultCategory.'\',
          `'.$this->_keyColumn.'` varchar(255) NOT NULL,
          `'.$this->_valueColumn.'` text NOT NULL,
          PRIMARY KEY  (`'.$this->_idColumn.'`),
          KEY `'.$this->_categoryColumn.'_key` (`'.$this->_categoryColumn.'`,`'.$this->_keyColumn.'`)
        ) '.($this->getDbEngine() ? 'ENGINE='.$this->getDbEngine() : '').'  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; ';
        $command=$connection->createCommand($sql);
        $command->execute();
    }
#8420 report it
seb7 at 2012/06/01 09:56am
table name format

Ok, i don't use prefix.

I suggest writing it to the extension description text, because i hav'nt read all the comments (but maybe i'm the only one not reading all comments).

Sorry.

(Feel free to delete that comment and the previous one (if you can))

#8419 report it
twisted1919 at 2012/06/01 09:44am
database table prefix

it was written somewhere here that your database settings should also include a database table prefix even if it is just an empty string .

#8418 report it
seb7 at 2012/06/01 09:39am
db design

I'm not a db expert, but it looks like the database table field 'id' is not used for insert/update/delete operation, so the primary key (id) of that table is not used. It could be better to have a composite primary key ('category','key').

#8417 report it
seb7 at 2012/06/01 09:32am
table name format

I had to remove the {{ }} around the table name param (in config file or in the default param value. To get it to work.

Otherwise it raise an exception :

CDbCommand failed to execute the SQL statement: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '{settings}} WHERE `category`='bidon' AND `key`='appname' LIMIT 1' at line 1. The SQL statement executed was: SELECT id FROM {{settings}} WHERE `category`=:cat AND `key`=:key LIMIT 1 
 
.../yii/framework/db/CDbCommand.php(528)

Then it seems to work ok (not tested a lot).


2012-06-01 17:24:39 Apache/2.2.21 (FreeBSD) mod_ssl/2.2.21 OpenSSL/0.9.8q PHP/5.3.8 with Suhosin-Patch Yii Framework/1.1.10

#6022 report it
twisted1919 at 2011/12/06 08:06am
Component updated.

The component has been updated to version 1.2 with some new goodies (see changelog) Please test it and report any bug you find. Thanks.

#6019 report it
twisted1919 at 2011/12/06 05:37am
Clear cache

This would require to have some kind of registry to store permanently all the defined categories that are cached, and it might be a good thing to have something like:

Yii::app()->settings->clearCache(); //clear all the cached content  
Yii::app()->settings->clearCache('category'); // clear only this category cache.

So i'll take it into consideration and implement it :)

#5958 report it
Pentium10 at 2011/11/30 03:12am
How to clear cache?

Can we have a method where clear the cache for all groups? (I have a migration that inserts some values into the settings table, and I would like to call a method after that to clear the cache.)

#4911 report it
twisted1919 at 2011/08/28 06:39am
Topic created

I opened a new topic here: http://www.yiiframework.com/forum/index.php?/topic/23075-extensionsettings/ , please add the file as an attachment there and i'll check it and in the end implement your changes.

#4907 report it
Gustavo at 2011/08/27 04:15pm
forum post

please create a post to discuss about the extension.

I've made a modified version of the extension that I would like to share.

Basically what I've changed is that I added a public property named 'tableName' with default value '{{settings}}' so you can easiely change the name of the table to use and this change also removes the need to add tablePrefix if you don't use it.

I've also added a connectionId property that defaults to 'db' and a getDbConnection method, replacing all Yii::app()->db to $this->getDbConnection so you can use a different db connection.

And a couple more things

Btw, great extension.

Thanks

#4353 report it
twisted1919 at 2011/06/28 08:42am
Hi there

Just now i saw the comments, don't know why but the yii website doesn't send notifications anymore for comments on my extensions.

@Wiseon3 - the values shouldn't be manipulated directly from database. And i prefer to use the serialize/unserialize functions to store all kind of data in the database.
Anyway, glad to hear this isn't another bug:)

@yiqing95 - sorry, right now i don't have too much available time, but i will take it into consideration.

#4325 report it
Wiseon3 at 2011/06/26 05:34am
Empty string bug

@twisted1919 It seems the problem I had was due to the fact that I inserted the values by hand in the db (using phpmyadmin) instead of using the functions provided by the extension, which serialize the values before saving them to the db. Sorry about that.

#4324 report it
Wiseon3 at 2011/06/26 05:25am
Empty string bug

@twisted1919 I encountered the same bug as ryurhrt After looking trough the code, I found what the problem is: In the load() function you unserialize every corresponding key value from the database. If the value is a string then the function returns false as it is unserializeable. To fix it, simply replace:

foreach($result AS $row)
                $items[$row['key']] = @unserialize($row['value']);

with

foreach($result AS $row) {
                $value = @unserialize($row['value']);
                if ($value === false)
                    $value = $row['value'];
                $items[$row['key']] = $value;
            }
#4225 report it
yiqing95 at 2011/06/16 11:46pm
need enhance some features !

@twisted1919 : I am here again :)

now my situation is this: the table your provided is different from mine. but the

table fields have same semantics , only i need is tableName and table fields mapping.

so i need something like this:

public $tableName = 'settings'; /*default name is your name for those guys who           firstly use this setting feature,and use your schema above .*/
    public $idAttribute='id';
    public $categoryAttribute='category';
    public $keyAttribute='key';
        public $valueAttribute='value';

then in your code reference above values: {$this->tableName} , {$this->xxAttibute} , thus these extension can apply to the tables existing early ,

surely i can do it myself ,but if you can add it will be better :). best regards !

#4104 report it
twisted1919 at 2011/06/06 05:02am
great.

Good to hear, but don't worry, thanks to you the extension has been improved and some bugs were fixed, so you did a great job reporting all of these.

#4094 report it
ryurhrt at 2011/06/05 09:52pm
Ops.. sorry.

Hi,

Sorry for the false bugs report, as I actually code to read the configuration with a class of my own, and i forgot the switch the reading class to read from the settings, and it read back to the yii's settings, so even i reset the value, it still reading the wrong place.

sorry for that, but now the module work correctly without any problem, thanks.

Leave a comment

Please to leave your comment.

Create extension