Location: PHPKode > scripts > Configuration Loader > configuration-loader/ConfigurationLoader.class.php
<?php
/*
    Copyright 2008 David Wainwright

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    Version:   0.0.0.3
    Date:      09/04/2008

    Changelog:
    Version      Date      Description
    0.0.0.2      18042008  Now parses blank elements as having null values
    0.0.0.3      18042008  Added support for array types and default paths
    0.0.0.4      30042008  Improved consistency of specifying values in xml file
                           Removed paths from error text and added ERROR_PREFIX
*/


class ConfigurationLoader
{
    //Xml element types
    const TEXT_NODE_NAME = "#text";
    const TEXT_NODE_TYPE = 3;
    const COMMENT_NODE_TYPE = 8;
    const ELEMENT_NODE_TYPE = 1;

    //Php types
    const VARIANT_TYPE = 0;
    const ARRAY_TYPE = 1;
    const CLASS_TYPE = 2;

    //ConfigurationLoader constants
    const ERROR_PREFIX = "ConfigurationLoader ERROR";

    /*Call this method to read the file at configFilePath and convert it into the php file
      specified by targetFilePath. Ensure file permissions are correct for these two operations
    */
    public static function update($_configFilePath, $_targetFilePath)
    {
        $configFilePath = $_configFilePath;
        $targetFilePath = $_targetFilePath;
        if(is_null($configFilePath))
        {
            $configFilePath = "config.xml";
        }

        if(is_null($targetFilePath))
        {
            $targetFilePath = $configFilePath . ".php";
        }

        if(file_exists($targetFilePath))
        {
            //Get last modified date of config file
            $configFileLastMod = filemtime($configFilePath);

            //Get last modified date of target file
            $targetFileLastMod = filemtime($targetFilePath);

            if($configFileLastMod > $targetFileLastMod)
            {
                self::parse($configFilePath, $targetFilePath);
            }
        }
        else
        {
            self::parse($configFilePath, $targetFilePath);
        }

    }

    //Loads the source file into a dom document and opens the target file for writing
    private static function parse($configFilePath, $targetFilePath)
    {
        $loaded = false;
        $doc = new DOMDocument();
        $loaded = $doc->load($configFilePath);

        if(!$loaded)
        {
            echo self::ERROR_PREFIX . ": Failed to load xml config file<br />";
        }

        $root = $doc->documentElement;

        //self::outputParsedXML($root, 0);

        //open a file to write to
        //The file is written to a temporary file, which is then copied over to the target filename
        $tempFilePath = $targetFilePath . ".tmp";

        $file = fopen($tempFilePath, "w");

        if($file)
        {
            fwrite($file, "<?php\n");
            fwrite($file, "/*\n");
            fwrite($file, "This file was autogenerated by ConfigurationLoader.class.php.\n");
            fwrite($file, "Please do not attempt to alter it's contents manually as any changes you make may be overwritten\n");
            fwrite($file, "*/\n");

            self::parseNode($root, $file, self::replaceSpecialChars($root->nodeName));
            fwrite($file, "?>");
            fclose($file);

            rename($tempFilePath, $targetFilePath);
        }
        else
        {
            echo self::ERROR_PREFIX . ": Please ensure that the xml config file exists and that the user running apache has read permissions for this file";
        }
    }

    //Used for debugging - outputs the xml dom tree as basic html code
    private static function outputParsedXML($node, $indent)
    {
        $indentText = str_repeat(" ", $indent);
        echo $indentText . "<" . $node->nodeName . ">\n";
        
        if($node->nodeName == "#text")
        {
            echo $node->nodeValue;
        }
        else
        {
            if($node->hasChildNodes())
            {
                for($i = 0;$i < $node->childNodes->length;$i++)
                {
                    self::outputParsedXML($node->childNodes->item($i), $indent + 4);
                }
            }
        }

        echo $indentText . "</" . $node->nodeName . ">\n";
    }

    //Recursively parses the xml dom tree. This function is too big and needs re-organising
    private static function parseNode($node, $handle, $prefix)
    {
        fwrite($handle, "class " . $prefix . "\n");
        fwrite($handle, "{\n");

        $nodeList = array();
        $count = 0;
    
        if($node->hasChildNodes())
        {
            for($i = 0;$i < $node->childNodes->length;$i++)
            {
                $childNode = $node->childNodes->item($i);
                if(self::isValueNode($childNode))
                {
                    fwrite($handle, "public $" . self::replaceSpecialChars($childNode->nodeName) . ";\n");
                }
            }

            //Write constructor
            fwrite($handle, "function __construct()\n{\n");

            for($i = 0;$i < $node->childNodes->length;$i++)
            {
                $childNode = $node->childNodes->item($i);
                $propertyName = self::replaceSpecialChars($childNode->nodeName);

                if(self::isValueNode($childNode))
                {
                    $type = null;
                    if($childNode->hasAttribute("type"))
                    {
                        $type = $childNode->getAttribute("type");
                    }

                    if($type == "array")
                    {
                        fwrite($handle, "\$this->" . $propertyName . " = array();\n");

                        //Fill the array
                        fwrite($handle, self::parseArrayItems($propertyName, $childNode) . "\n");
                    }
                    else
                    {
                        $value = self::getValue($childNode);
                        $propertyName = "this->" . $propertyName;
                        fwrite($handle, self::getPropertyCode($type, $propertyName , $value) . ";\n");
                    }
                }
                else
                {
                    if($childNode->nodeType == self::ELEMENT_NODE_TYPE)
                    {
                        $className = $prefix . "_" . $propertyName;
                        fwrite($handle, "\$this->" . $propertyName . " = new " . $className . "();\n");
                        $nodeList[$count] = $childNode;
                        $count++;
                    }
                }
            }
            fwrite($handle, "\n}\n");

            fwrite($handle, "}\n");
        }
        foreach($nodeList as $childNode)
        {
            $className = $prefix . "_" . self::replaceSpecialChars($childNode->nodeName);
            self::parseNode($childNode, $handle, $className);
        }
    }

    //Returns true if the specified node has a child node whose nodeName is #text
    private static function isValueNode($node)
    {
        $nodeType = $node->nodeType;
        //If the node is of type text or a comment - return false
        if(($nodeType == self::TEXT_NODE_TYPE) || ($nodeType == self::COMMENT_NODE_TYPE))
        {
            return false;
        }

        //If the node has no children, it is a value type with no value
        if(!$node->hasChildNodes())
        {
            return true;
        }

        //If the node has only one child which is of type TEXT, it is a value node
        $textNodes = self::getChildNodesOfType($node, self::TEXT_NODE_TYPE);
        if(count($textNodes) == 1)
        {
            return true;
        }
        else //otherwise, check if it is an array
        {
            if($nodeType == self::ELEMENT_NODE_TYPE)
            {
                if($node->getAttribute("type") == "array")
                {
                    return true;
                }
            }
        }
        return false;
    }

    //Returns an array of the specified nodes child nodes that are of the specified type
    private static function getChildNodesOfType($node, $type)
    {
        $children = array();
        $iCount = 0;
        for($i = 0;$i < $node->childNodes->length;$i++)
        {
            $child = $node->childNodes->item($i);
            if($child->nodeType == $type)
            {
                $children[$iCount] = $child;
                $iCount++;
            }
        }
        return $children;
    }

    //Returns the number of items in the specified nodeList
    private static function getNoOfItems($nodeList)
    {
        $count = 0;
        foreach($nodeList as $item)
        {
            $count++;
        }
        return $count;
    }

    //Used to generate the class names and strip out any non-php compliant characters
    private static function replaceSpecialChars($value)
    {
        //Replace any non-word characters with an underscore
        return ereg_replace("[\\W-]", "_", $value); //Convert non-word characters, hyphens and dots to underscores
    }

    //The node passed into this function must have a text node as it's only child node
    private static function getTextValue($node)
    {
        if($node->hasChildNodes())
        {
            return $node->firstChild->nodeValue;
        }
        return null;
    }

    //Just returns 'string' if the specified value contains non-digit characters, or null otherwise
    private static function getPhpType($value)
    {
        //If the value is an empty string, return string
        if(is_null($value))
        {
            return null;
        }

        if($value == "")
        {
            return "string";
        }

        //Determine whether the value is a string or not here
        if(preg_match("[\\D]", $value)) //Looks for any non-digit characters
        {
            return "string";
        }
        return null; //type not defined
    }

    //Parses a set of array items in the xml file into code
    private static function parseArrayItems($arrayName, $node)
    {
        $code = "";
        //Get all of the items;
        $items = $node->getElementsByTagName("item");
        $count = 0;
        foreach($items as $item)
        {
            $value = self::getValue($item);

            if($item->hasAttribute("index"))
            {
                $indexName = $item->getAttribute("index");
            }
            else
            {
                $indexName = $count++;
            }

            $indexType = self::getPhpType($indexName);

            if($indexType == "string")
            {
                $indexName = "\"" . $indexName . "\"";
            }

            $type = null;
            if($item->hasAttribute("type"))
            {
                $type = $item->getAttribute("type");
            }
            else
            {
                $type = self::getPhpType($value);
            }

            //DEBUG
            //echo $indexName . "=" . $value . " (" . gettype($value) . ")\n";
            $propertyName = "this->" . $arrayName . "[" . $indexName . "]";
            $code .= self::getPropertyCode($type, $propertyName, $value) . ";\n";
        }
        return $code;
    }

    //Generates the code for a variable assignment;
    private static function getPropertyCode($type, $propertyName, $value)
    {
        if(is_null($type))
        {
            //Get type using value
            $type = self::getPhpType($value);
        }

        if(!is_null($value))
        {
            if($type == "string")
            {
                $value = "\"" . $value . "\"";
            }
        }
        else
        {
            $value = "null";
        }
        
        if(!is_null($type))
        {
            $type = "(" . $type . ")";
        }
        else
        {
            $type = "";
        }

        return "\$" . $propertyName . " = " . $type . " " . $value;
    }

    private static function getValue($node)
    {
        $value = null;
        if($node->hasAttribute("value"))
        {
            $value = $node->getAttribute("value");
        }
        else
        {
            if($node->childNodes->length == 1) //If there's only one child node
            {
                $value = self::getTextValue($node);
            }
        }
        return $value;
    }
}


?>
Return current item: Configuration Loader