Last Time
At the end of the last discussion on Zend_Config, I mentioned how it would be interesting to leverage the code already existing in Zend_Confing, instead of reinventing the wheel. Our main issue was how to use defined PHP constants in an ini file (or in an xml file).
View a better formatted version of the code in this post here
A Solution
We will extend Zend_Config_Ini with a custom method to find references to defined constants in a configuration object and resolve them. The resulting class should function exactly as Zend_Config_Ini would for “normal” ini files. First we need to know what are the constants already defined at the time Zend_Config_Ini parses our ini file. PHP does this easily
with the get_defined_constants() function (available in PHP 4 >= 4.1.0, PHP 5). Next we need to tell our extending class when it can find and replace defined PHP constants in a configuration object.
Extending Zend_Config_Ini
We will call our new class ConfigPhp (I’m sure you could find a better name):
class ConfigPhp extends Zend_Config_Ini{ //...}
Its constructor shall refer to Zend_Config_Ini constructor.
Tell ConfigPhp When To Parse Defined Constants
We need to tell ConfigPHP when our ini file contains PHP defined constants. The ConfigPhp constructor can be used:
public function __construct($filename, $section, $config = false)
{
if (is_array($config))
{
$this->php_defs = $config["php_defs"];
$this->allow_mods = $config["allow_mods"];
} //...
}
Where $config["php_defs"] is a boolean telling whether or not the ini file contains defined PHP constants.
Using Defined PHP Constants
When our ini file contains defined PHP constants, and we have told ConfigPhp by setting $config["php_defs"] to true, the constants are found in the configuration object and replaced with their real values.
if($this->php_defs)
{
$this->data = $this->toArray();
$this->parsePHPDefs();
}
The parsePHPDefs() method
The parsePHPDefs() is the heart of this extension. It will retrieve the data contained in the Zend_Config_Ini object and for all ocurences of defined PHP constants replaces them with their real value:
protected function parsePHPDefs()
{
$report_error = null;
$this->defined_constants = get_defined_constants(true);
if (is_array($this->defined_constants["user"]))
{
foreach($this->defined_constants["user"] as $key => $value)
{
foreach($this->data as $datakey => $dataval)
{
if($datakey == 'includes')
{
$report_error = ini_get('error_reporting');
error_reporting(0);
foreach($dataval as $inckey => $incval)
{
$pattern = '/(\.)?'.$key.'(\.)?/';
$incval = preg_replace($pattern , $value, $incval);
error_reporting($report_error);
$this->data["includes"][$inckey] = $incval;
$this->__set($inckey,$incval);
}
}
}
}
}
}
The get_defined_constants(true) method will return an array of arrays. Each key in that array refers to the origin of the defined constants in the array it has as value. Thus
$this->defined_constants = get_defined_constants(true); will refer to the constants we have defined (at a point or another before this code is reached). For each defined constant we search each value in our Zend_Config_Ini object and for each occurence of it replace it with the actual value of the constant. You would notice the line
$this->defined_constants["user"]if($datakey == 'includes'). It just that the values I have in my ini file all start by the string “includes.”. It would be easy to parameterized this, and be able to replace arbitrary values in the ini file. In fact I use the Zend_Config_Ini object to set my include_path. I created a method to do so:
function makeIncludePath()
{
foreach($this->data as $datakey => $dataval)
{
if($datakey == 'includes')
{
foreach($dataval as $inckey => $incval)
{
$this->include_path.=$incval. DS.PATH_SEPARATOR;
}
}
}//....}
Advantages
Let’s say you are integrating a third party library to Zend and would like to make some constants defined in the library available to your Zend Application, but you want to use Zend_Config_Ini and … an ini file. This approach would allow you to access the third party library (or application) defined constants within your ini file.
Rationale
The approach itself might seem a bit of an overkill. I mean why use predefined constants in an ini? The point here is that it is possible, and someone out there might need such a feature. I do for once. I would use Zend_Config to help me in setting paths (Include_path) during the bootstrap process. To bootstrap, I need to define a minimal set of PHP constants. For example:
//Forced to have these declarations here as a minimum requirement for bootstrap to happen
if(!defined('DS'))
{
define('DS',DIRECTORY_SEPARATOR);
}
define ('MY_LIBS', dirname(dirname(dirname(__FILE__))).DS.'libs');
define ('MY_APP',dirname(dirname(dirname(__FILE__))).DS.'apps'.DS.'my_app'.DS.'application');
define('MY_ZEND_CORE',dirname(dirname(dirname(__FILE__))).DS.'libs'.DS.'zend'.DS.'library');
if (function_exists('ini_set')) {
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . MY_ZEND_CORE .DS . PATH_SEPARATOR . MY_APP . DS);}
//end bootrap declarations
I keep my applications folders and library folders out of the server’s webroot. So these constants are needed so I don’t have to write long paths every time I refer to a file in my app or in the libraries. The settings in the ini file might need to “knnow” about these constants.
Improvement
I am aware that there is room for improvement. The Zend_Config class itself could be extended instead of the Zend_Config_Ini class, so that Zend_Config_XML benefits from the parsePHPDefs method. I will probably take more time to see what can be improved. Don’t hesitate to give your point of view on the matter. You might not even like this approach, please let me know why.

