Dynamic XML Settings Part II
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<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.
appname: #application.settings.get("application","name")#<br>
dsn: #application.settings.get("database","dsn")#
</cfoutput>
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>
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.
<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>
