ConfComponent DB based configuration Tutorial
ConfComponent allows you to store your configuration into the db, set and retrieve values organized into categories very easily.Now with caching.
changelog
=========
0.9.1:
- Added the ability to call the component in beforeFilter().
0.9:
- Added file caching to minimize db hits.
- Added default value for get()
- Changed the way you set and get values to be more cake-ish
- Introducing $getEmpty
- Introducing some options to deal with boolean values stored as 'true' and 'false'.
- Added setCat to set a category of configs at once
- Added setBatch to save a set of categories with their configs at once
changelog
=========
0.9.1:
- Added the ability to call the component in beforeFilter().
0.9:
- Added file caching to minimize db hits.
- Added default value for get()
- Changed the way you set and get values to be more cake-ish
- Introducing $getEmpty
- Introducing some options to deal with boolean values stored as 'true' and 'false'.
- Added setCat to set a category of configs at once
- Added setBatch to save a set of categories with their configs at once
Update 09-04-2007
Cake already has a configuration system, and it's really neat. You can put files in app/config, fill them with values and load them automagically. config::read(). This is however something different. This is db based, meaning your values are stored in the db.
Let's see how we can set it up and use it.
Download the component from: http://bakery.cakephp.org/articles/view/242
and save it as conf.php in your components' folder.
Alright, so the first thing we need to do is actually create the database tables
Download code
Download code
I gave the key and value columns a length of 50, but you can choose whatever you want.
Next, we create the associated models
As with any component, if we want to use it, we need to include it in the controller's $components array.
That's all we need, we can start using our component now.
insert some data in the db tables for testing, I'll assume you have created a conf category named 'app', and some configs values.
To retrieve conf values, we have two methods, get and find.
Simple, in the first one, we know that the key 'foo' belongs to the conf category 'app', so we specify the name of the category and the key.
In the second, we actually don't know which category the bar key belongs to, so we use the method find. beware though, if you have two keys with the same name, it will return the first one it finds.
In the third example we introduce a new parameter, the default value that should be returned if the key we're looking for doesn't exist.
Same thing with the forth example, but with find(). And in the fifth example we introduce a new parameter that if set to true will return true instead of 'true' and false instead of 'false', it's basically to deal with boolean values.
The last example shows the syntax for getting all values of a category.
To save values to the db, we have a method named set.
the first parameter is the key of the form category.key, the second is the value, simple. The third is ( if set ) an array of possible values, it's basically a quick validation test, $val must be one of the values in $possibleValues. You can ignore that parameter by setting it to null and indeed it's the default behavior. There are two extra parameters that default to false.
If the category passed in doesn't exist and addCat is true, the category will actually be created.
If the key passed in doesn't exist and addKey is true, the Key will actually be created.
If there is an error, set returns false.
Also, there is setCat and setBatch, the first expects a cat name and a data array where the keys are the conf names and the values are the config values something like array('i_am_a_key'=>'i_am_a_value','aww'=>'wee')
setBatch expects an array where the keys are the category names, and the values are arrays like the one passed to setCat
A new important feature has been introduced which is caching. File caching to be more specific. This was added to minimize DB hits.
A word of warning though:
Caching is really problematic in the sense, you can't know if a value has changed in the db. Maybe another user that has write access changed it and you still have the old value. There is no efficient way of detecting change. Looping through all the values in the db is just a no-no.
So, you shouldn't really change values through other interfaces than the conf component because, set() clears the cache. If you do, clear the cache manually using $this->Conf->clearCache().
The cache file is stored in app/tmp/persistent/conf.component.data.php you can change the name in the component.
Because of how components work, you can't use this component in beforeFilter. The reason is that Conf uses the startup() method to uh-huh startup. This method however is called by Cake after beforeFilter and before the current action. So as you guessed the component has not been initialized yet when beforeFilter is called and thus can't be used.
I LIE I LIE!!1 Well since the version 0.9.1 of the component, you can use the component in beforeFilter, provided you call startup() manually. I added logic so startup is not called twice by Cake.
That's it, have fun with thissimple component, and as usual comments are welcome.
Cake already has a configuration system, and it's really neat. You can put files in app/config, fill them with values and load them automagically. config::read(). This is however something different. This is db based, meaning your values are stored in the db.
Let's see how we can set it up and use it.
Download the component from: http://bakery.cakephp.org/articles/view/242
and save it as conf.php in your components' folder.
Create the DB tables
Alright, so the first thing we need to do is actually create the database tables
Download code
CREATE TABLE `configs` (
`id` int(10) unsigned NOT NULL auto_increment,
`config_category_id` int(10) unsigned NOT NULL,
`key` varchar(50) NOT NULL,
`value` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
)
Download code
CREATE TABLE `config_categories` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
)
I gave the key and value columns a length of 50, but you can choose whatever you want.
Create the associated models
Next, we create the associated models
Model Class:
Download code
<?php
class Config extends AppModel {
var $name = "Config";
var $belongsTo = array('ConfigCategory');
}
?>
Model Class:
Download code
<?php
class ConfigCategory extends AppModel {
var $name = "ConfigCategory";
var $hasMany = array('Config');
}
?>
In the controller
As with any component, if we want to use it, we need to include it in the controller's $components array.
Controller Class:
Download code
<?php
var $components = array('Conf');
?>
That's all we need, we can start using our component now.
insert some data in the db tables for testing, I'll assume you have created a conf category named 'app', and some configs values.
Usage
Retrieving values
To retrieve conf values, we have two methods, get and find.
Controller Class:
Download code
<?php
$fooVal = $this->Conf->get('app.foo');
$barVal = $this->Conf->find('bar');
$foo2Val = $this->Conf->get('app.foo2','defaultFoo');
$bar2Val = $this->Conf->find('bar2','defaultBar');
$fooAll = $this->Conf->find('foo.*');
$booleanFoo = $this->Conf->get('app.foo2',true,true);
?>
Simple, in the first one, we know that the key 'foo' belongs to the conf category 'app', so we specify the name of the category and the key.
In the second, we actually don't know which category the bar key belongs to, so we use the method find. beware though, if you have two keys with the same name, it will return the first one it finds.
In the third example we introduce a new parameter, the default value that should be returned if the key we're looking for doesn't exist.
Same thing with the forth example, but with find(). And in the fifth example we introduce a new parameter that if set to true will return true instead of 'true' and false instead of 'false', it's basically to deal with boolean values.
The last example shows the syntax for getting all values of a category.
Saving values
To save values to the db, we have a method named set.
function set($key,$val,$possibleValues = null,$addCat = false,$addKey = false)
the first parameter is the key of the form category.key, the second is the value, simple. The third is ( if set ) an array of possible values, it's basically a quick validation test, $val must be one of the values in $possibleValues. You can ignore that parameter by setting it to null and indeed it's the default behavior. There are two extra parameters that default to false.
If the category passed in doesn't exist and addCat is true, the category will actually be created.
If the key passed in doesn't exist and addKey is true, the Key will actually be created.
If there is an error, set returns false.
Controller Class:
Download code
<?php
//
$this->Conf->set('app.foo','Cake!');
// weee and bar will be created if they don't exist
$this->Conf->set('app.lang','php',array('php','python','ruby'));
$this->Conf->set('weee.bar','chocolat',null,'true','true');
?>
Also, there is setCat and setBatch, the first expects a cat name and a data array where the keys are the conf names and the values are the config values something like array('i_am_a_key'=>'i_am_a_value','aww'=>'wee')
setBatch expects an array where the keys are the category names, and the values are arrays like the one passed to setCat
Caching
A new important feature has been introduced which is caching. File caching to be more specific. This was added to minimize DB hits.
A word of warning though:
Caching is really problematic in the sense, you can't know if a value has changed in the db. Maybe another user that has write access changed it and you still have the old value. There is no efficient way of detecting change. Looping through all the values in the db is just a no-no.
So, you shouldn't really change values through other interfaces than the conf component because, set() clears the cache. If you do, clear the cache manually using $this->Conf->clearCache().
The cache file is stored in app/tmp/persistent/conf.component.data.php you can change the name in the component.
Using the component in beforeFilter
Because of how components work, you can't use this component in beforeFilter. The reason is that Conf uses the startup() method to uh-huh startup. This method however is called by Cake after beforeFilter and before the current action. So as you guessed the component has not been initialized yet when beforeFilter is called and thus can't be used.
I LIE I LIE!!1 Well since the version 0.9.1 of the component, you can use the component in beforeFilter, provided you call startup() manually. I added logic so startup is not called twice by Cake.
Controller Class:
Download code
<?php
function beforeFilter()
{
$this->Conf->startup(&$this);
// call the component's methods ..
}
?>
That's it, have fun with this
Comments
Comment
1 Looks cool
One thing I think that may benefit this component however is a delete method. For example, my component or plugin may have some default values which I can override with an option from the db, but I might want to revert back to the default option. I think the easiest way would be to have a delete method in there. Just my 2p.
Comment
2 caching
Comment
3 answers
@Ryan: Yes, minimizing db hits can help in high traffic webapps. Though session caching is problematic:
You can't know if a value has changed in the db. Maybe another user (different session ) has changed it; and you still have the old value in the session.
There is no efficient way of detecting that change.
Another solution for caching is file based, 1 db hit and x file hit is probably better than x+1 db hit.
I might look at it someday.
Regards
Question
4 Displaying Config Data inside of a View
What I am trying to do is set a variable for the siteName to be displayed in the . I have all of the code installed as per directions.
Where do I place the code:
$this->set('siteName', $this->get('display','siteName'));
I tried placing it in ConfigsController::beforeFilter() but still get,
undefined variable: siteName when I load the default page. I know I'm not doing something quite right. Any help is greatly appreciated.
Comment
5 Adendum to 4
What I am trying to do is set a variable for the siteName to be displayed in the title.
forgot to not submit html w/o the < and >
Comment
6 You can not use the component in beforeFilter
Hi Ron,
You can't use the component in beforeFilter, because it fetches values in the startup() method. The startup method in cake is called after beforeFilter() and before the current action. As much as I would like it to be possible, this is how cake works. That's why I don't use the startup method in othAuth. So anyway, a solution might be to store values in the session; however, I don't want to require session to use the component. So see, it's a dilemma.
Comment
7 Interesting and handful
I would add 'default_value' and 'modified' columns in Config model. 'modified' allows to have a cache more aggressive : we can track the last modification of a row and the table, so it's possible to regenerate the cache only when needed.
'default_value' allow to retrieve default value without setting it in the get method.
Last thing, is ConfigCategory really important? I mean if the key was 'app.foocat.foo' it would be more simple isn't it ? In addition the key could be unique...
Question
8 the same things wihout table config categories
i'm interessting by the same feature but just with the Configs database table .
What will i modify in the component to make it work ?
Kind Regards.