SO, you too find yourself needing to write something that could really benefit from having its settings contained in some external resource. Let's see, what are the choices...we could possibly store our settings in the database and retrieve them at application initialization, if our architecture permits that; we could create an xml file that contains our settings in a nice, nested, readable manner; we could (as one person I mentioned this to suggested) put our settings into an INI file (ya think this person does a little too much .NET? :) ); or we COULD, as I have recently become enlightened to, put our settings into a CFC. Since I am especially partial to the idea of being able to have the choice of hard coding my settings and/or leveraging Coldfusion's dynamic personality to populate them, I went the way of using a CFC.
Allow me to preface the remainder of this post by saying thank you to those out there who have already traveled this road and have made their work open source. By dissecting several different code samples and doing a lot of experimentation, I have been able to enlighten myself as to the mechanics and theory behind it.
SCENARIOFor the sake of illustration, let's say I'm writing a plugin that will be used to provide shopping cart functionality to whatever app is implementing it. Of course, every app's needs, environment, and settings are different, so our plugin needs to be configurable. My goal then is to create a CFC that will contain a nested structure/array of settings that my shopping cart plugin CFC can utilize to control how it is implemented. Sounds simple, right? Or is it... :)
For those who want to skip right to the heart of the matter,
you may do so now. For those who, like me, enjoy the layered learning process and the excitement of examining the pieces before putting the puzzle together, let's take a quick look at essential the concepts that allow using a CFC as a configuration file to work.
THE ESSENTIAL CONCEPTSSCOPEYou already know about scopes in CF and are well versed in how and when to use them, I'm sure. You know, for instance, that if you create a variable within a template and ommit the scope, it is automatically put into the variables scope. You should also be aware, however, that any functions you create within your template also occupy a place within the variables scope. Take this template, for instance, and the accompanying dump:
<cfset thisvar = "Hello" />
<cffunction name="returnIt" access="private" returntype="any">
<cfargument name="incoming" type="any" />
<cfreturn arguments.incoming />
</cffunction>
<cfdump var="#variables#" label="MyTemplatesVariables" />

Interesting, eh? Keep this in mind.
Now, you may also already be aware of the fact that within an instantiated CFC you have scopes as well. I won't spend any time elaborating on those scopes or the differences between them (did that in another post a while back). But I WILL hit you with a short CFC and a dump of its own internal variables scope. Based on our little experiment above with our template, look at the CFC internals and formulate an idea of what you would expect to see. You may see something surprising in this one.
Component A.cfc
<cfcomponent output="false">
<cffunction name="Foo" access="public" output="false" returntype="any">
<cfargument name="nuttin" type="string" />
<cfreturn arguments.nuttin />
</cffunction>
<cffunction name="Man" access="private" output="false" returntype="any">
<cfargument name="nuttin" type="string" />
<cfreturn arguments.nuttin />
</cffunction>
<cffunction name="Chu" access="remote" output="false" returntype="any">
<cfargument name="nuttin" type="string" />
<cfreturn arguments.nuttin />
</cffunction>
<cffunction name="getMyVariablesScope" access="public" returntype="any">
<cfset var theseVars = "" />
<cfsavecontent variable="theseVars">
<cfdump var="#variables#" label="ComponentAVariables" />
</cfsavecontent>
<cfreturn theseVars />
</cffunction>
</cfcomponent>
Code to instantiate A and retrieve and output its variables scope:
<cfset objA = createobject("component","A") />
<cfoutput>#objA.getMyVariablesScope()#</cfoutput>

As you probably expected, there in the variables scope are all the functions of the CFC (regardless of what access type we gave them). Additionally, we have the often shunned THIS scope...and looky what THIS is harboring. That's right boys and girls, a reference to (not copy of) the CFC instance. THIS, as you may or may not know, IS accessible from outside the CFC instance. So if we modify slightly the code we used above to instantiate the object, and add a line to give our object another attribute, a subsequent call to getMyVariableScope will reflect the results.
Code to instantiate A and retrieve and output its variables scope:
<cfset objA = createobject("component","A") />
<cfset objA.someFunkyParam = "Doug Boude Rocks" />
<cfoutput>#objA.getMyVariablesScope()#</cfoutput>

This is perfect behavior for what we're wanting to do! Oh...what is it we are wanting to do? Let's get to the next and last essential concept and see!
THE DECORATOR PATTERNHere's a term I'm sure most of you have heard of. If you haven't heard specifically of this pattern, I'm sure you've at least heard of design patterns in general. Well, you can and should think of this one exactly as its name sounds. Let's say you figure out that the fastest way to meet chicks is to buy a dog (since you can't afford to buy a baby), so you grab a "mildly malformed" West Highland White terrier from the "we finance anyone" pet store down the street. You invest a few more bucks and take little Timmay to the groomer. When you pick him up later, he is now sporting a lovely red tartan neckerchief and smells of Eau de Tim McGraw. Timmay has been decorated! He's still a midly malformed Westie, only NOW he ALSO has been given the ability to attract hot chicks, AFTER he was born. I know, it was an elaborate analogy, but this is how I entertain myself. So then...since in our mind's eye we want our Config.CFC to be beautifully simple for the end user to populate and we don't want them to have to ignore (and try not to touch) lots of other support methods or worry about inheritance dependencies, what we ideally need to be able to do is provide them a clean, simple CFC that contains only a configure method. When they populate it (or code it to grab its values from elsewhere), we'll take it and then DECORATE it with a method that will allow us to retrieve that same configuration info! Okay, enough of concepts and ideas. On to the solution.
THE SOLUTIONThe key players in this scene are:
"ShoppingCart.cfc", a plugin designed to provide shopping cart functionality to any application;
"Config.cfc", a cfc whose sole purpose in life is to provide the developer with a single place in which to maintain relevant ShoppingCart settings;
"configTest.cfm", a cheesy template who just kinda glues this stuff together for us.
First, configTest.cfm.
<cfset objShoppingCart = createObject("component","ShoppingCart").load("Config") />
<cfdump var="#objShoppingCart.getSettings()#">
Straightforward. We're creating our shoppingcart cfc, calling the load method and passing in the class path to our configuration cfc. Then, calling shopping cart's getSettings method just to prove to ourselves that it worked.
Config.cfc.
<cfcomponent output="false">
<cffunction name="configure" access="public">
<cfscript>
settings = {
environment = {
DSN = "dbserver",
EmailServer="mail.onelove.com"
},
cartSettings = {
maxCartItems = "15",
salesTax = "true",
cartHeading = "One Love to Rule Them All",
cancelMessage = "Aw baby, why you leavin?",
thankyouMessage = "Stay Cool",
daysLeftTillXmas = ceiling(datediff("h",now(),createdate(2009,12,25))/24)
}
};
</cfscript>
</cffunction>
</cfcomponent>
This is a "hard coded" version, where all the settings are just there. You'll notice at least one of them taking advantage of a PRIME reason to do configuration this way: dynamic values!
ShoppingCart.cfc (scaled down to ONLY the relevant methods needed to illustrate the point of this blog post).
<cfcomponent output="false">
<cfset variables._settings = "" />
<cffunction name="load" access="public" returntype="any" hint="I load settings from an external CFC">
<cfargument name="configpath" type="string" required="true" />
<cfscript>
var objConfig = createobject("component",arguments.configpath);
//need to run objConfig's configure() method in order to actually create the
//configuration settings structure
objConfig.configure();
//decorate objConfig with our local _getConfig method :)
objConfig.getConfig = variables._getConfig;
//execute the method we just added to objConfig to retrieve objConfig's 'settings' struct
variables._settings = objConfig.getConfig();
return this;
</cfscript>
</cffunction>
<cffunction name="getSettings"
access="public"
returntype="any"
hint="I am the method used to get our local configuration values.">
<cfreturn variables._settings />
</cffunction>
<cffunction name="_getConfig"
access="private"
returntype="any"
hint="I am the private method that will decorate our configuration object!">
<!--- we're counting on our configuration object to have a variable called 'settings'... --->
<cfreturn variables.settings />
</cffunction>
</cfcomponent>
You'll want to study this one a bit, and let me point out a few things.
The last method, "_getConfig", serves no other purpose than to decorate our configuration object. Now, you may be thinking, "but I could just put that method inside my configuration object and tell my users to just ignore it!". Yeah, you could; but that's cheesy. So, to avoid smelling of aged Camembert, we hide this method here and add it to our configuration object within the "load" method.
Here's the results of calling configTest.cfm:
SUMMARYIn a nutshell, because an object always sports a THIS scope, AND because local functions always exist in the VARIABLES scope, it is a simple matter to attach a new method to an object. If the method you are attaching is written correctly, it can access the methods and values within the object that all of the native methods can. By leveraging this very cool relationship, you can move configuration settings to a CFC.
POST SUMMARYYou may also be thinking, "Dude, I or someone else I don't trust could wreak so much havoc in my code by decorating objects with well-written methods!". And dude, I do believe you're probably correct. I haven't researched much into the security ramifications, or how many ways one might find to defile the sacraments of "clean OO"; but, I know that with CF comes great power, and with great...oh, you know. We've chosen the more lenient world of CF, so it's up to us to respect those freedoms and work with them accordingly. If you don't feel good about that, well, there's always the more communistic "typed" languages, with their greater complexities and unbending rules. :) Hey, some people prefer domination ;) To each his own.