settings

Another way to store configuration items to database
44 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

#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
gusnips 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.

#4074 report it
twisted1919 at 2011/06/03 06:22am
i see no issue, please share code.

Well, i cannot reproduce what you are saying, at least here is the test i made:

$settings=Yii::app()->settings;  
echo $settings->get('test','itemAlreadySet').'<br />';  
$settings->set('test','aNewItem','aNewValue');  
echo $settings->get('test','aNewItem').'<br />';  
$settings->set('test','aNewItem','aNewValueOverwritten');  
echo $settings->get('test','aNewItem').'<br />';  
$settings->set('test','itemAlreadySet','rewrite this value');  
echo $settings->get('test','itemAlreadySet').'<br />';

And the output of the above code:

valueAlreadySet  
aNewValue  
aNewValueOverwritten  
rewrite this value

Which seems correct to me.
Next step was just to retrieve the values i just set above:

$settings=Yii::app()->settings;
echo $settings->get('test','itemAlreadySet').'<br />';
echo $settings->get('test','aNewItem').'<br />';

Which outputs:

rewrite this value  
aNewValueOverwritten

So, once again, it is correct.

As you see, the code works exaclty as expected, that's the reason why i can't find any error.
I will gladly look into your issue, but please post some code so that i can run it in my backend and see if it fails or not.

#4066 report it
ryurhrt at 2011/06/02 09:52pm
Another Problem Happen

Hi,

after tested the new code, i found that i can't set the settings, which means at the same page, i do set the value to another, but the get back the old value.

any idea?

#4062 report it
twisted1919 at 2011/06/02 06:23am
Try this

Please ignore my above PHP code (not the cache advices) and try this version, just overwrite the component class with this one :
Paste Bin Code

#4061 report it
twisted1919 at 2011/06/02 05:06am
let's try something else

@ryurhrt - thanks for your tests, i also appreciate your time .
Let's test a few things and see where we go next:
1) Change the get method like:

// line 49 
public function get($category='system', $key='', $default='')  
    {  
        if(!isset($this->_items[$category]))  
        {  
            $this->load($category);      
        }  
 
        if(!isset($this->_loaded[$category]))  
        {  
            $this->load($category);  
            $this->_items[$category]=array_merge($this->_loaded[$category],$this->_items[$category]);  
        }  
 
        if(empty($key)&&empty($default)&&!empty($category))  
            return isset($this->_items[$category])?$this->_items[$category]:null;  
 
        if(isset($this->_items[$category][$key]))  
            return $this->_items[$category][$key];  
        return !empty($default)?$default:null;  
    }

After doing the change, test it and see if the bug still persists, if it does, then let's do a more direct approach to the get function:

// line 49 
public function get($category='system', $key='', $default='')  
    {  
        if(!isset($this->_loaded[$category]))  
        {  
            $this->load($category);  
            $this->_items[$category]=array_merge($this->_loaded[$category],$this->_items[$category]);  
        }  
 
        if(empty($key)&&empty($default)&&!empty($category))  
            return isset($this->_items[$category])?$this->_items[$category]:null;  
 
        if(isset($this->_items[$category][$key]))  
            return $this->_items[$category][$key];  
        return !empty($default)?$default:null;  
    }

Also, what i would ask you to do in your tests, is to clear the cache folder before you do each test, because when you move the project to a new site the old cache might be invalidated and the tests will not be accurate.
So, clear the cache, run the test, see the restult, and repeat the steps each time.
Please let me know of your findings so that i can update the component if indeed what you just explained is a bug :)
Thank you for your time on this .

#4058 report it
ryurhrt at 2011/06/01 11:32pm
is it a bugs?

Hi, i finally repeated the bugs which return "false" value.

the bugs is occur in another testing server where the server do not call any "set" function yet, as all the settings had been called and saved using my development, so no matter how many times we called "get" function, the value return is "false", but after run at least once the "set" function, then all the "get" function works.

i would guess it happen because of caching not yet initialized? any idea to resolve this issue beside calling set function every time server reboot or implement to new machine?

Thanks.

#4056 report it
ryurhrt at 2011/06/01 09:44pm
Testing result

@twisted1919

I had tested, I guess the bug is now fixed.

However, I am not so sure about it, because the 1st time when I run the test, it return a Boolean "false" for me, but once after I refresh it, it works without any problem, and I could not repeat the fault.

anyway, well done. Thanks. :D

#4043 report it
twisted1919 at 2011/05/31 10:47am
i understood

I understood your requirement since first time, don't worry.
The thing is that, integrating this will make the component heavier, thing i don't want right now, i want to keep this as light as possible.
And yes, i agree with you that doing this in one of the way i shown you can be a bit hard with large sets of data, but it is doable, so for now, you'd have to work with it as it is.
In my projects, where the items need description, i use one of the two methods i shown, and i have no problems.
Of course, there is always the second option, modify the component to fit your needs, nobody says you cannot do this :)

#4042 report it
pentium10 at 2011/05/31 10:34am
cool that I can save complex objects too

It is nice that I can save complex data. But the descriptions I am refering would be set/retrieved rarely when the administrator edits in the CMS admin. The descriptions are used only for the admins and would describe what each value means, not used by the read/write mechanism. It would be too complex to store each value as an associative array, and have a value and a description field each time. It would make things harder when you need to display a value from the associative array. Is it clear the description requirement?

#4041 report it
twisted1919 at 2011/05/31 10:31am
Another way.

Another way, would be like:

$data=array(  
'phone'   =>   array(  
   'value'      =>'0123456789',  
   'description'=>'this is the phone number of the administrator',
),  
'email'   =>   array(  
   'value'   =>'admin@domain.com',  
   'description'=>'this is the email address of the administrator', 
)    
);  
Yii::app()->settings->set('admin',$data);  
//  
$phone=Yii::app()->settings->get('admin','phone');   
echo $phone['value'].' - '.$phone['description'];

As you see, we have some alternatives :)

Leave a comment

Please to leave your comment.

Create extension