Dynamic XML Settings Part II

Word Count: 716

I just finished implementing the other methods in my new Settings component and I thought I would share it with everyone. First I have made some changes with the init method. I broke the path of the file up into a path and file name so now you pass the path that would hold settings file and a file name. This approach is scalable and will allow for multiple config files if need be. First I will show you my 2 new methods and give you a complete code listing at the end.

get(String parent,String key):String
<cffunction name="get" access="public" output="false" returntype="string">
      <cfargument name="parent" type="string" required="true">
      <cfargument name="key" type="string" required="true">
      <cfset var value = "">
      
      <cftry>
         <cfset value = variables.settings[arguments.parent][key]>
         <cfcatch type="any">
            <cfthrow message="Invalid parent/key.">
         </cfcatch>
      </cftry>
      <cfreturn value>
   </cffunction>

The get method allows us to extract a value from our settings. This method to me is much easier than caching the setting structure in the application scope and having to call something like (application.settings.application.name). The reason this method accepts two arguments is the parent will be my grouping. If your read part one then you will know I break my xml settings into logical groups. From the example in part 1 I have a group called database and a setting for my dsn. I could get my dsn value by simply calling the get method, this to me just makes more sense. The code below assumes you have cached the settings component to the application scope in a variable called settings.

<cfoutput>
      appname: #application.settings.get("application","name")#<br>
      dsn: #application.settings.get("database","dsn")#   
   </cfoutput>

<cffunction name="set" access="public" output="false" returntype="void"
            hint="I will set a key/pair value ">

      <cfargument name="parent" type="string" required="true">
      <cfargument name="key" type="string" required="true">
      <cfargument name="value" type="string" required="true">
      <cfset var currentValue = "">
      <cfset var newXML = "">
      
      <cftry>
         <cffile action="read" file="#variables.configPath##variables.configFile#" variable="xml"/>
         <cfcatch type="any">
            <cfthrow message="Unable to read file #variables.configPath##variables.configFile#.">
         </cfcatch>   
      </cftry>
      
      <!--- get the value of parent/key --->
      <cfset currentValue = get(arguments.parent,arguments.key)>
      
      <!--- replace current value with new value --->
      <cfset xml = replaceNoCase(xml,currentValue,arguments.value,"All")>
      
      <!--- write out the new file --->
      <cftry>
         <cffile action="write" file="#variables.configPath##variables.configFile#" output="#xml#"/>
         <cfcatch type="any">
            <cfthrow message="Unable to save changes to file #variables.configPath##variables.configFile#.">
         </cfcatch>   
      </cftry>
      
   </cffunction>
Our set method will allow us to change the value of a key in our xml file without having to touch the xml file. This is very helpful in providing a user interface to changing our application settings. The method makes great use of our new method get() as well. All we are doing here is reading in the settings file, replacing the current value of the parent/key with the new supplied value and rewriting the file back to the file system. The reason we need a parent as the argument is because in the real world two different groupings could have the same key in it. Again this example assumes you have already instantiated the component with the path and file and set it into the application scope.
<cfset application.settings.set("database","dsn","newdsn")>

So there you have it in all its glory (haha). I know this is old news to most of you but with my new grasp of OO I am trying more and more create reusable objects and components that rely less on the particular application they work with at the moment. Below is an up to date settings component. Please feel free to leave me a comment with your thoughts.

<cfcomponent name="Settings">

   <cfset variables.settings = "">
   <cfset variables.configPath = "">
   <cfset variables.configFile = "">
   
   <cffunction name="init" access="public" output="false" returntype="Settings">
      <cfargument name="path" type="string" required="true" hint="Absolute Path to config file.">
      <cfargument name="file" type="string" required="true" hint="Name of the xml file including the extenstion.">
      <cfset variables.settings = structNew()>
      <cfset variables.configpath = arguments.path>
      <cfset variables.configFile = arguments.file>
      <cfset read()>
      <cfreturn this>
   </cffunction>

   <cffunction name="read" access="public" output="true" returntype="void"
            hint="I will return a structure containing all application seetings.">

      <cfset var xml = "">
      <cfset var xmldoc = "">
      <cftry>
         <cffile action="read" file="#variables.configPath##variables.configFile#" variable="xml"/>
         <cfcatch type="any">
            <cfthrow message="Unable to read file #variables.configPath##variables.configFile#.">
         </cfcatch>
      </cftry>
      <cfset xmldoc = xmlParse(xml)>
      
      <cfloop collection="#xmldoc.settings#" item="parent">
         <!--- for each parent add a new item to our structure --->
         <cfset structInsert(variables.settings,parent,structNew())>
         <cfloop collection="#xmldoc.settings[parent]#" item="child">
            <cfset structInsert(variables.settings[parent],child,xmldoc.settings[parent][child].xmlText)>
         </cfloop>
      </cfloop>
   </cffunction>
   
   <cffunction name="set" access="public" output="false" returntype="void"
            hint="I will set a key/pair value ">

      <cfargument name="parent" type="string" required="true">
      <cfargument name="key" type="string" required="true">
      <cfargument name="value" type="string" required="true">
      <cfset var currentValue = "">
      <cfset var newXML = "">
      
      <cftry>
         <cffile action="read" file="#variables.configPath##variables.configFile#" variable="xml"/>
         <cfcatch type="any">
            <cfthrow message="Unable to read file #variables.configPath##variables.configFile#.">
         </cfcatch>   
      </cftry>
      
      <!--- get the value of parent/key --->
      <cfset currentValue = get(arguments.parent,arguments.key)>
      
      <!--- replace current value with new value --->
      <cfset xml = replaceNoCase(xml,currentValue,arguments.value,"All")>
      
      <!--- write out the new file --->
      <cftry>
         <cffile action="write" file="#variables.configPath##variables.configFile#" output="#xml#"/>
         <cfcatch type="any">
            <cfthrow message="Unable to save changes to file #variables.configPath##variables.configFile#.">
         </cfcatch>   
      </cftry>
      
   </cffunction>

   <cffunction name="get" access="public" output="false" returntype="string">
      <cfargument name="parent" type="string" required="true">
      <cfargument name="key" type="string" required="true">
      <cfset var value = "">
      
      <cftry>
         <cfset value = variables.settings[arguments.parent][key]>
         <cfcatch type="any">
            <cfthrow message="Invalid parent/key.">
         </cfcatch>
      </cftry>
      <cfreturn value>
   </cffunction>

   <cffunction name="getSettings" access="public" output="false" returntype="struct">
      <cfreturn variables.settings>
   </cffunction>

</cfcomponent>

Comments

#1 Posted By: Bruce Posted On: 1/8/07 5:50 PM
Thanks for posting this.

I tried to do a test using the settings.xml file you posted in your previous entry. Unfortunately, I got an error for a duplicate key, application.

Could you post a test xml file that should work.
#2 Posted By: Dan Posted On: 1/9/07 2:23 PM |
Author Comment
Bruce, Just wanted to update you and let you know there is a new post that contains the download.


Post Your Comment

Leave this field empty







Show Captcha

If you subscribe, any new posts to this thread will be sent to your email address.

Copyright © 2007 Dan Vega | BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.