1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 5.00 out of 5)
Loading ... Loading ...

The problem

Nearly every large application has some configuration parameters – site admin email, cache time for different blocks, number of latest news, number of items per search or catalogue page etc.

Standard Way

Yii comes with the built-in mechanism for this. You can create a file with an associative array of all your variables, like this:

  1. <?php
  2. return array(
  3.                 ‘brandNew’ => 7, // number of days to consider new
  4.                 ‘onSale’ => 7, // number of days to consider on sale
  5.                 ‘favourites’ => array(
  6.                                 ‘max’ => 9//max number of last favourites to show
  7.                                 ),
  8.                 ‘search’ => array(
  9.                                 ‘itemsPerPage’ => 20,
  10.                                 ‘tagsInFilter’ => 30,
  11.                                 ),
  12.                 ‘product’ => array(
  13.                                 ‘maxAlts’ => 5, //max alternatives
  14.                                 ),
  15.                 ‘cacheTime’ => array(
  16.                                 ‘assets’ => 1800,
  17.                                 ),
  18.         );
  19. ?>

Then put it somewhere, for example in /protected/config/params.php and configure your application to use it:

  1. return array(
  2.         // application components
  3.         ‘components’=>array(
  4. //components go here
  5.         ),
  6.  
  7.         // application-level parameters that can be accessed
  8.         // using Yii::app()->params['paramName']
  9.         ‘params’=>include(dirname(__FILE__).‘/params.php’),//<– here is our file
  10. );

Usage is simple, just refer to it as to array:

  1. echo Yii::app()->params[‘siteAdmin’];

This is quite convenient when you just want to save some constants. End-user will not be able to change this from admin in any way. Sure, you can create the interface for editing this config and update the file, but much more flexible way is to store the configuration in DB.

The Database Way

To make the life easier, I’ve created an extension, that can be used as an application component. Extension can be downloaded from the Yii extension page. Here I’ll give an overview of it’s work.

First thing to note, it implements IApplicationComponent, so it should implement init() method where initialization is performed. In this method we check if there are any parameters to load. If so – we try to load them. If there is an exception, it means, that no table is present, so we create it and initialize the parameters that should be preloaded with null values

  1. /**
  2.  * Initializes component, creates table if it doesn’t exist and populates preloaded attributes
  3.  * @throws CException Throws exception if table does not exist and auto creation is set to false
  4.  */
  5. public function init()
  6. {
  7.         $this->_init = true;
  8.         $db = $this->getDbConnection();
  9.         $this->preload = $this->preprocessParams($this->preload);
  10.         if (!empty($this->preload) || $this->autoLoad)
  11.         {
  12.                 $sql = ‘SELECT name, value FROM ‘.$this->paramsTableName;
  13.                 if (sizeof($this->preload))
  14.                         $sql .= ‘ WHERE name IN (\’.implode(\’,\’, $this->preload).\’)’;
  15.                 $cmd = $db->createCommand($sql);
  16.                 try
  17.                 {
  18.                         $reader = $cmd->query();
  19.                         foreach ($reader as $row)
  20.                         {
  21.                                 $this->add($row[‘name’], $row[‘value’]);
  22.                         }
  23.                 }
  24.                 catch (CException $e)
  25.                 {
  26.                         //table is not present
  27.                         $createTable = true;
  28.                         //if there is no table, then attributes are empty.
  29.                         for ($i = 0, $s = sizeof($this->preload); $i < $s; $i++)
  30.                         {
  31.                                 $this->add($this->preload[$i], null);
  32.                         }
  33.                 }
  34.         }
  35.         else
  36.         {
  37.                 //check if table exist
  38.                 if ($db->createCommand(‘SHOW TABLES LIKE \’.$this->paramsTableName.\’)->query()->rowCount <= 0)
  39.                         $createTable = true;
  40.         }
  41.         if ($createTable === true)
  42.         {
  43.                 if($this->autoCreateParamsTable)
  44.                         $this->createParamsTable($db,$this->paramsTableName);
  45.                 else
  46.                         throw new CException(Yii::t(‘xparam’,‘Params table "{tableName}" does not exist.’,
  47.                                 array(‘{tableName}’=>$this->paramsTableName)));
  48.         }
  49. }

If parameter is requested, in the __get() method we reuse the parent’s (CAttributesCollection) contains() method, which checks if we have this parameter in the collection already. If so – we simply return it. If not – we call the loadParam() method, it loads if from DB and adds to the component collection. If there is no attribute, it throws an exception. We catch it in the __get() and call the parent::__get(). Here is the code:

  1. /**
  2.  * Returns a property value or an event handler list by property or event name.
  3.  * This method overrides the parent implementation by returning
  4.  * a parameter value if the it exist in the collection or loading it from DB
  5.  * if not.
  6.  *
  7.  * @param string the property name or the event name
  8.  * @return mixed the property value or the event handler list
  9.  * @throws CException if the property/event is not defined.
  10.  */
  11. public function __get($name)
  12. {
  13.         if($this->contains($name))
  14.                 return $this->itemAt($name);
  15.         else
  16.         {
  17.                 try
  18.                 {
  19.                         return $this->loadParam($name);
  20.                 }
  21.                 catch (CException $e)
  22.                 {
  23.                         return parent::__get($name);
  24.                 }
  25.         }
  26. }
  27.  
  28. /**
  29.  * Returns parameter from DB or cache if it was requested earlier
  30.  *
  31.  * @param string $name
  32.  */
  33. protected function loadParam($name)
  34. {
  35.         if ($this->caseSensitive)
  36.         $name = strtolower($name);
  37.         $db = $this->getDbConnection();
  38.         $res = $db->createCommand(‘SELECT value FROM ‘.$this->paramsTableName.‘ WHERE name=\’.$name.\’)->query();
  39.         if ($res->rowCount == 1)
  40.         {
  41.                 $row = $res->read();
  42.                 $this->add($name,$row[‘value’]);
  43.                 return $row[‘value’];
  44.         }
  45.         else
  46.         {
  47.                 throw new CException(Yii::t(‘xparam’,‘XDbParam->{name} does not exist!’,
  48.                         array(‘{name}’=>$name)));
  49.         }
  50. }

Setter method simply updates the collection. We check if the parameter is already present in DB. If so – we update it. If not – we create it. And, if everything was OK, we add the parameter to the internal collection ($this->add($name,$value)):

  1. /**
  2.  * Sets value of a component property.
  3.  * This method overrides the parent implementation by adding a new param
  4.  * value to the collection and updating the DB.
  5.  * @param string the property name or event name
  6.  * @param mixed the property value or event handler
  7.  * @throws CException If the property is not defined or read-only.
  8.  */
  9. public function __set($name,$value)
  10. {
  11.         if ($this->caseSensitive)
  12.                 $name = strtolower($name);
  13.         $db = $this->getDbConnection();
  14.         if ($db->createCommand(‘SELECT name FROM ‘.$this->paramsTableName.‘ WHERE name=\’.$name.\’)->query()->rowCount >= 1)
  15.         {
  16.                 $sql = ‘UPDATE `’.$this->paramsTableName.‘` SET `value`=:value WHERE `name`=:name’;
  17.                 $cmd = $db->createCommand($sql);
  18.                 $cmd->bindValue(‘:value’, $value, PDO::PARAM_LOB);
  19.                 $cmd->bindValue(‘:name’, $name, PDO::PARAM_STR);
  20.         }
  21.         else
  22.         {
  23.                 $sql = ‘INSERT INTO `’.$this->paramsTableName.‘`(name, value) VALUES(:name, :value)’;
  24.                 $cmd = $db->createCommand($sql);
  25.                 $cmd->bindValue(‘:name’, $name, PDO::PARAM_STR);
  26.                 $cmd->bindValue(‘:value’, $value, PDO::PARAM_LOB);
  27.         }
  28.         $cmd->execute();
  29.         $this->add($name,$value);
  30. }

When configuring the extension or using load() or purge() method, we can specify parameters either as array or as comma-separated string. This is achieved by the preprocessing method, which also converts parameter names to lowercase and they are case insensitive (if this is required by the caseSensitive property, it must be set to true). Here is it:

  1. /**
  2.  * Sets value of a component property.
  3.  * This method overrides the parent implementation by adding a new param
  4.  * value to the collection and updating the DB.
  5.  * @param string the property name or event name
  6.  * @param mixed the property value or event handler
  7.  * @throws CException If the property is not defined or read-only.
  8.  */
  9. public function __set($name,$value)
  10. {
  11.         if ($this->caseSensitive)
  12.                 $name = strtolower($name);
  13.         $db = $this->getDbConnection();
  14.         if ($db->createCommand(‘SELECT name FROM ‘.$this->paramsTableName.‘ WHERE name=\’.$name.\’)->query()->rowCount >= 1)
  15.         {
  16.                 $sql = ‘UPDATE `’.$this->paramsTableName.‘` SET `value`=:value WHERE `name`=:name’;
  17.                 $cmd = $db->createCommand($sql);
  18.                 $cmd->bindValue(‘:value’, $value, PDO::PARAM_LOB);
  19.                 $cmd->bindValue(‘:name’, $name, PDO::PARAM_STR);
  20.         }
  21.         else
  22.         {
  23.                 $sql = ‘INSERT INTO `’.$this->paramsTableName.‘`(name, value) VALUES(:name, :value)’;
  24.                 $cmd = $db->createCommand($sql);
  25.                 $cmd->bindValue(‘:name’, $name, PDO::PARAM_STR);
  26.                 $cmd->bindValue(‘:value’, $value, PDO::PARAM_LOB);
  27.         }
  28.         $cmd->execute();
  29.         $this->add($name,$value);
  30. }

And, finally, load and purge methods:

  1.  
  2. /**
  3.  * Loads several params from DB at once. If nothing specified, all parameters
  4.  * are loaded. This saves queries if you plan to use all that params in the
  5.  * next lines.
  6.  *
  7.  * @param array|string $params
  8.  * @throws CException
  9.  */
  10. public function load($params = array())
  11. {
  12.         $params = $this->preprocessParams($params);
  13.         $db = $this->getDbConnection();
  14.         $sql = ‘SELECT name, value FROM ‘.$this->paramsTableName;
  15.         if (sizeof($params) > 0)
  16.         {
  17.                 $sql .= ‘WHERE name IN (\’.implode(\’,\’, $params).\’)’;
  18.         }
  19.         $cmd = $db->createCommand($sql);
  20.         $reader = $cmd->query();
  21.         foreach ($reader as $row)
  22.         {
  23.                 $this->add($row[‘name’], $row[‘value’]);
  24.                 $loaded++;
  25.         }
  26.         if ($loaded < sizeof($params))//this will not be thrown if loading all attributes
  27.         {
  28.                 throw new CException(Yii::t(‘xparam’,‘Some of the requested params do not exist!’));
  29.         }
  30. }
  31.  
  32. /**
  33.  * Deletes specified params (or all params if none specified) from the parameters table
  34.  *
  35.  * @param array|string $params Comma-separated list of params or array of param names
  36.  */
  37. public function purge($params = array())
  38. {
  39.         $sql = ‘DELETE FROM ‘.$this->paramsTableName;
  40.         if (sizeof($params) > 0)
  41.         {
  42.                 $params = $this->preprocessParams($params);
  43.                 $sql .= ‘ WHERE name IN (\’.implode(\’,\’, $params).\’)’;
  44.         }
  45.         $db = $this->getDbConnection();
  46.         $db->createCommand($sql)->execute();
  47.         if (sizeof($params) > 0)
  48.         {
  49.                 for ($i = 0, $s = sizeof($params); $i < $s; $i++)
  50.                 {
  51.                         $this->remove($params[$i]);
  52.                 }
  53.         }
  54.         else
  55.                 $this->clear();
  56. }

Usage Instructions

Using the extension is simple. Like any extension, you should unzip it to the /protected/extensions/ folder and then set it up as an application component. To do this, you should add it with any name to the components array in the application configuration:

  1. ‘par’=>array(
  2.     ‘class’ => ‘application.extensions.dbparam.XDbParam’,
  3.     ‘connectionID’ => ‘db’,//id of the connection component, just the same as with CDbCache
  4. //  ’preload’ => ‘test,test2′, //comma-separated string or array of params to be loaded anyway. Other params are loaded only when requested.
  5. //  ’autoLoad’ => true, //setting to true enables loading of all attributes present in DB in the beginning
  6. //  ’caseSensitive’ => true, //setting to true makes all parameters case sensitive
  7.     ),

‘par’ is the ID of the component, you will refer to it in any place of the application by it’s ID:

  1.  
  2. Yii::app()->par->test = ’1234′;//set parameter. If it is not present, it will be created
  3. echo Yii::app()->par->test;//output parameter. If it is not present, exception is thrown
  4.  
  5. //load several parameters in one query. Useful if you;re going to use them in the next lines
  6. Yii::app()->par->load(array(‘test’, ‘test2′));
  7. //OR
  8. Yii::app()->par->load(‘test,test2′);
  9. //OR
  10. Yii::app()->par->load();//load all parameters
  11.  
  12. //delete the specified parameters or all of them if none specified
  13. Yii::app()->par->purge(‘test,test2′);//delete test and test 2
  14. //OR
  15. Yii::app()->par->purge(array(‘test’, ‘test3′));//delete test and test 3
  16. //OR
  17. Yii::app()->par->purge();//delete ALL parameters!

That’s it! Enjoy!

UPDATE: Updated on March, 5 2009 according to the Jonah’s comments an this discussion. Version 3 is planned with great functionality :)

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

Share this post with a friend Share this post with a friend

14 Comments

  1. Jonah says:

    The way you designed the extension is pretty genius in most ways.

    In a lot of cases however you may not have more than 10 config values, in which case it may be better to just load all the config values at the start of the app, without having to argument every single one into load().

    Maybe if load() is called without an argument it should just load all the configurations?

    This implantation is probably optimal if you have a whole lot of configurations, but if you had less it might be more optimal to store all the configurations in a single file or table with a single row, with the configuration array serialized. Then you simply load and save them all at once very quickly.

  2. Konstantin Mirin says:

    Thanks for your review!
    Yes, that definitely makes sense. When developing this extension, I had the idea of storing all that in the file instead of DB. However, I wanted to be able to edit the settings from admin section easily, so I’d like to use model there. And it’s impossible without having all parameters in one table. However, it may be a good idea to develop this extension in order to use different storage methods, like caching is implemented in Yii. That will be the next step :)
    Your idea about loading all parameters when passing nothing to load() is excellent, I’ll implement it now. Also, I think, that autoLoad config property is a good idea, I’ll also do it.

  3. Jonah says:

    Actually, you could still use a model (and this would be very interesting to implant, & a good exercise).

    It would extend CModel however instead of CActiveRecord, and you would define your own find() methods to work with the file. It would also cache the file contents so it doesn’t have to be loaded multiple time on a single request.

    Probably you would want two models, one for working with the file and one to work with the database. Then, you would be able to configure your extension to use a certain one.

    The models would also hold load(), purge(), etc, but the extension would wrap these functions and call the one from the appropriate model that it is configured to use.

    Then 3rd parties could even easily add new storage methods.

  4. Konstantin Mirin says:

    Yeah, sounds nice :) Well, I’ll plan it to version 3 since it’s not top priority for me now.
    Thanks for your suggestion!

  5. Jonah says:

    This is pure awesomeness (I plan on testing it soon). Another idea would be to make this into a module, with administrative controllers to add/edit/delete configurations.

    I know most people are reluctant to do this (I would be), but if you open source it (put it in a SVN), I would contribute

  6. Jonah says:

    I know I have a lot of ideas, but another one:

    A method which loads all the “regular” params (defined in ‘params’) into the database. This would be nice if you had a application and you wanted to install your extension, you could just quickly call this function to move all the configurations over. (or maybe this could be done in createParamsTable() )

    It also just stuck me that in some cases you may not want to use this extension/method in replacement of the old way, but you may want to (and could) use the two methods side-by-side.

  7. Konstantin Mirin says:

    Yeah, importing is a nice idea. And, yes, you can use both of these methods.

    I was thinking about creating the project on google code for this extension, but it seemed funny to me – creating a PROJECT for 3-4 files :) I’ll think of this and maybe will do it on the weekends, together with development roadmap.
    As for contributing, feel free to contact me by email or any messenger (see my forum profile) we’ll discuss what to do and how it should be done. Coordination is really important :)

    As for me, I’ll have time for this on weekends (most probably, Sun)

  8. squiche says:

    nice! i’m gonna make my own blog

  9. squiche says:

    hh… thanks..

  10. Queerie says:

    hm.. informative ))

  11. jack parler says:

    Very nice information. Thanks for this.

  12. Antony says:

    Hi,

    Where can i download the version 3.
    Can you please give me the link?
    I am new to this YII.

    Thanks

  13. Watch Fairy Tail Online says:

    I enjoyed Handling Application Parameters in Yii – Using the Database | Programmer's Notes OMG!. my favorite posts of all time.

  14. Menyimpan Konfigurasi Yii di Database | heningsept says:

    [...] Handling Application Parameters in Yii – Using the Database [...]

Leave a Reply