Saturday, August 4, 2012

Loading Configuration Files and Accessing Arrays Using Object Operator (->)


Sometimes is nice to be able to access a multi-dimensional or even a single-dimensional array with the object operator rather than clunky array brackets. 

$config->logs->event->filename;

vs

$config['logs']['event']['filename'];

This is easily done using the following class:

<?php

/**
 * array_to_object.php
 *
 * Class for converting arrays to objects
 *
 * @author Paul Snell
 *
 */
class ArrayToObject {

   /**
    * @var array
    */
   private $_arrayData = array();
  
   /*
    * ctor
    * @param array $arrayData
    */
   public function __construct($arrayData) {
      $this->_arrayData = $arrayData;
      foreach ($arrayData as $key => $data) {
         if (is_array($data)) {
            $this->_arrayData[$key] = new ArrayToObject($data);
         }
      }
   }
  
   /**
    * Magic __get() method that returns corresponding value
    * for a given array key
    *
    * @param string $name
    *
    * @return var
    */
   public function __get($name) {
      if (array_key_exists($name, $this->_arrayData)) {
         return $this->_arrayData[$name];
      } else {
        // throw new Exception ("No entry found for array key: " . $name);
        return null;
      }
   }
}
?>

If you are working in an environment without in-built support for configuration files (no framework), then you can use the previous class in conjunction with the following one to very elegantly manage configuration data:

<?php

require_once('array_to_object.php');

/**
 *
 * @author Paul Snell
 */
class LoadIni {
   /**
    * @var string
    */
   private static $_iniData = array();
  
   /**
    * Class constructor
    *
    */
   private function __construct() {
    
   }

   /**
    * Load up a given config file
    *
    * @param  string $iniFilename
    * @return array $config
    */
   public static function load($iniFilename, $section=null) {
      if (!array_key_exists($iniFilename, self::$_iniData)) {
         $base = $_SERVER['DOCUMENT_ROOT'] . '/../config/';
         self::$_iniData[$iniFilename] = parse_ini_file($base . $iniFilename, true);
      }
      if ($section && array_key_exists($section, self::$_iniData[$iniFilename])) {
         $newObj = new ArrayToObject(self::$_iniData[$iniFilename][$section]);
      } else {
         $newObj = new ArrayToObject(self::$_iniData[$iniFilename]);
      }
      return $newObj;
   }
}

Note that this class is a Singleton.  It is only instantiated once.  Any subsequent calls for the same configuration file will just return the existing configuration data. 

However, it will create a fresh copy of the ArrayToObject() class every time it is called.  I did that because of the option of passing in a “section” identifier.  With a little effort it could probably still be modified not to rebuild the ArrayToObject() object even then.  But in the performance has never been a significant issue, I never bothered to do it.

Using the above two classes, if you are have a configuration file that contains something like this,

; config.ini
[amazon]
queueName = event_transaction_queue
verifyHash = 1
minRetryTime = 60

[logs]
PrimaryLogger = /tmp/plog.log
PrimaryLoggerLevel = 5

then if we can find the minRetryTime quite easily via the following function calls:

$config = LoadIni::load('config.ini');
$amazonRetry = $config->amazon->minRetryTime;

 We can also do the following:

$config = LoadIni::load('config.ini');
$amazon $config->amazon;
$retryTime = $amazon->minRetryTime;
$queueName $amazon->queueName;

For a big project sans framework, this produces a more elegant and manageable solution than trying to use defines in a .php configuration file.