One of the first things a beginning ColdFusion developer realizes, at least after their first few applications, is that configuration is something you want to make as easy as possible. What do we mean by configuration? Well imagine your web site has various forms. Forms for product reviews. Forms for job applications. Forms for budding Jedi Masters. You can build all of these forms so that on submission, they email to some email address, let's say junkbin@microsoft.com. This is all fine and good until the client says, "Hey, can we have all forms go to bjork@scandi.com instead?"
So, if you are using a decent editor, this isn't a big deal. But obviously a multi-file search and replace isn't the answer.
What you need is a config file, son! (Funny, and off-topic story on where that quote came from later on.)
What do we mean by a config file? A config file is a human-readable file that contains configuration information about the application. A perfect example is the scenario we described above. If the "Send All Forms Here" value had been in a config file, it would have been even easier to update the value based on customer demand. It may even have been something the client could do! (Yes, I know, scary, but it's ok. Clients are perfectly ok working with files as long as take deep breaths and type very slowly.)
There are a couple different ways we can create a config file. For this first post, we are going to start with the venerable "ini" file format. (Don't worry, the XML example will be in the next post.) What is an ini file? Ini files have been a part of Windows for many years. Scientists have reported discovering early versions of ini files buried deeply under prehistory middens. Basically they are text files with name/value pairs. Here is a simple example:
age=5
rank=Padawan
Simple enough, right? Along with name/value pairs, an ini file will be separated into sections, using bracket notation. Every ini file will have at least one section. Here is a simple example:
email=admin@bigclient.com
[staging]
email=ray@camdenfamily.com,admin@bigclient.com
[dev]
email=ray@camdenfamily.com
In the ini file above, there are three sections: live, staging, and dev. (I'll talk more about these sections in my third post.) Each section has an email entry with a different value. My application can select the email key from any section it wants to, or load them all up.
So how can ColdFusion use ini files? ColdFusion comes with a set of built in functions to read and write ini files: GetProfileSections, GetProfileString, and SetProfileString. Let's start by defining a simple ini file. Take the code below and save it as test.ini.
email=ray@camdenfamily.com
Now let's look at how we can read the file. We will begin with a simple example of getProfileString:
<cfset email = getProfileString(iniFile, "default", "email")>
<cfoutput>Email=#email#</cfoutput>
The first line simply creates a full path to our ini file. The first attribute that getProfileString expects is the full path to the file. (Am I the only who wishes that more ColdFusion functions would allow for relative paths?) The second argument we pass is the name of the section. In this case, we know it is default. Lastly we tell the function what key to retrieve. If you run this example in your browser, you will see my email address. (Spam away, folks.)
What happens if you pass a section or a key that doesn't exist? No error is thrown. Instead you get a simple empty string. You could example the ini file before hand using getProfileSections:
<cfdump var="#getProfileSections(iniFile)#">
The function getProfileSections returns both the sections of the ini file and all the keys within that ini file. Since our ini file had one section, our returned structure will have one key, "default". Because that section had one key, "email", the value of the struct's "default" key will be "email":

So we can now read ini file settings. Obviously you would want to store and cache these settings:
<cfset iniFile = expandPath("./test.ini")>
<cfset sections = getProfileSections(iniFile)>
<cfset data = structNew()>
<cfif structKeyExists(sections, "default")>
<cfloop index="key" list="#sections.default#">
<cfset data[key] = getProfileString(iniFile, "default", key)>
</cfloop>
<cfset application.settings = data>
<cfelse>
<cfthrow message="Ini file has a missing default section!">
</cfif>
</cfif>
<cfdump var="#application#">
What's going on here? First we check to see if an application variable called settings is defined. If it isn't, or if we pass in a special URL variable to refresh the cache, we know we need to load our settings. We use getProfileSections to first load up the sections from the ini file. After checking to see the there is a default section, we loop over the keys from the section and copy over the values. The data structure is basically a copy of all the name/value pairs from the ini file. Once done with the loop, we copy them over to the application scope. Notice we also do a bit of error checking. If for some reason the default section doesn't exist, we immediately throw an error. This makes sense if you think about it - if the application isn't properly configured, we should stop everything.
So, the last function we need to talk about is setProfileString. As you can probably guess, this allows you to update an ini file. It takes four arguments: The name of the ini file, the section, the key, and lastly the value. I'm not really going to bother with an example of this since I don't find myself updating ini files from code very often. In fact, I've never done it. But it is there if you need to.
In my next entry (most likely not till Monday) I will discuss using XML files for application configuration.
So, this was my first 101 posting. Too much? Too little? Be critical!
Archived Comments
I forgot to include the "funny OT story." Back in college I was in film class, watching a movie. I believe it was "The Man Who Shot Liberty Vallance", with John Wayne and Jimmy Stewart. At one point in the film, if I remember right, John Wayne's character was eating steak, drinking liquor, and smoking. He then tells Stewart's character something along the lines of "What you need is a hand gun!"
I bust out laughing. Something about _all_ those vices at once just made me loose my mind.
Ray it is right on target for noobs, but my suggestion would be to take it out of the blog and place it in a new section.
p.s. Congratulations on whatever you got that lit your fire. ;-)
You may want to expand this article to deal a little more with the live, dev, staging sections. For instance, since all of your settings are in the .ini file - how does a beginner identify which section of settings they want.. do they set a variable in the application.[cfm|cfc] file? Do they just hard code it at the line they are reading the settings at? Do they set up some kind of dynamic evaluation thing based on server ip to know which section to grab? I'm sure there are a bunch of different options as well. Which do you use and why?
While there might not be a "best practice" for this adding that little nugget of information would probably be very beneficial considering the target audience.
As a CF newbie, i love seeing this type of stuff. Learn something new everyday...!
Bill, notice this line in the post:
In the ini file above, there are three sections: live, staging, and dev. (I'll talk more about these sections in my third post.)
My plans are to talk about using an ini/xml file to handle different config values for dev, staging, and live servers. It will be my 3rd post.
Ray,
One thing I notice people often miss with this approach is that, potentially, someone could access the contents of your .ini file via their browser if they guessed the right url. To avoid this security risk, I call the file something like "config.cfm" with the same contents as a typical .ini file; the difference is that the first line of the file is CFABORT ;)
As to whether to use an xml file for config values, I usually find the .ini file approach faster, but I'd love to see a follow-up article that would convince me other wise!
My $0.04
Cheers,
Oliver
Not very much related to the entry, but as a British reader of this blog and watcher of American TV and internet I have to ask what is this 101 thing? It may be naive of me but i never get it!
Not very much related to the entry, but as a British reader of this blog and watcher of American TV and internet I have to ask what is this 101 thing? It may be naive of me but i never get it!
Not very much related to the entry, but as a British reader of this blog and watcher of American TV and internet I have to ask what is this 101 thing? It may be naive of me but i never get it!
Dave: "101" refers to the normal number given to an introductory college class. So your first English call is English 101, first Math is Math 101, etc.
Oliver - good point. I never keep config files in web root. I will bring this up, probably in part 3, or as a 'p.s.' post (part 1.5 ;)
In apps where I _cant_ go out of web root , like some of my DRK projects, I do something close to what you suggested. I use an xml file with a cfm exstension where all the data is wrapped in CFML comments.
Ideally, I'd prefer to put this type of stuff in an "admin" section and allow the users to edit it at their will via a web-based interface. In such a case, I'd probably store it in a DB instead of an actual file.
Jeff,
From a best practices standpoint I think storing it in the file is the better route. Live, Stage and Dev should different databases. Also, not every application makes use of a database. It might be sense in your application, but I think Ray's example was great for CF beginners.
Ray,
Maybe in a future part you should show Fusebox and Mach-II config files for references. It would also be useful to newbies to explain what to store in a config and why.
Nathan - actually, I don't use Fusebox or Mach-II. I am beginning to use Model-Glue.
I use .properties files and load them using the appropriate java classes. You end up with a nice struct to use.
Also, a fellow-coder added the habit of using a classpath resource to load in the .properties file. It allows you to make your app more cross-platform friendly (because the .properties file can live anywhere in cf's classpath)
Nathan,
I find it hard to imagine a web application w/o a database behind it. I agree that live, staging, and dev should be different databases. In many cases, the relevant settings we are talking about would also be different on each one. ( Although in an ideal situation, staging and live would be mirrors of each other ).
In Ray's example, he stored, live staging and dev settings all in a single file. If I were storing the settings in a db, I would only store a single set of data for each tier, and change those settings in each database.
Why do you think storing it in a file is best practice over a database?
Jeff, I never said it was -best- per se, just one way. Now, I can think of some good reasons. First off - the actual DSN is normally stored in the config file. Obviously you can't talk to a db until you have the dsn info. I'll also put the mapping info in a config file as well. There is no reason why not to use both either. For example, our Element product, a CMS, uses a mix of both. The config file is normally stuff only we mess with, and the Settings stuff is stuff that the client changes. CF provides many ways of solving the problem. :)
Don't forget that GetProfileString() could be disabled in a shared hosting situation. I found this out trying to implement Ray's fine Soundings app.
Ray,
I did notice the line; I just thought that including all three now might cause some confusion (since you're targetting beginners) and that they might want just a hint toward the right direction along within the context of this article.
No worries though it was still a well written article with a worthwhile topic that should help some folks.
Bill - gotcha. I hope to have part 2 out by Wednesday.
Ray,
One clarification. Nathan said that a file was a best practice, and my question was intended to be directed at him.
And yes, you need the dsn before you can access the database. Just like you need the location of the config file before you can access it. It is the same "catch-22".
I've had problems with the location of config-files before and one application accessing the config of another. ( This was, of course, due to the inconsistency of the environment and should probably not be a reflection on config-files in general).
> Why do you think storing it in a file is best practice over a database?
Jeff, I guess there are a number of reasons but I don't want to push what works for me down your throat. For my applications, I keep what I consider "settings" in the database. Settings are user configurable, easily accessible, and change frequently. When I deploy an application, or setup a new environment, I edit the configuration file. I store "fail safe" parameters in that file. This lets the application know who to email in the event an exception is thrown, if I want tracing, or turn caching on or off, etc. Other developers can easily view, understand, and modify those core settings. Frameworks set a pretty good standard, and both Mach-II and Fusebox use configuration files. Neither require you to setup a database to store the configuration.
Nathan,
There are benefits and cons to each method.
I understood your first post to be advocating one over the other. "From a best practices standpoint I think storing it in the file is the better route."
Your second post makes more sense to me, where you appear to keep some things in files and other things int he db, depending on the type of setting.
Ray:
Just my opinion, but I love the idea of CF101. Some (if not many) coders like me (and I believe you) are completely self taught with no formal programming education. These kind of tutorials are just what I look for to learn. Keep up the good work.
Thanks!
Todd
Hi Ray, thanks for that very helpful bit of code with the ini file setup. How do I code in the application.cfm file for multiple sections in the ini file. Second, how do I access specific sections of the ini file? I am okay doing it if I have 1 section not with multiple sections in the ini file.
You would simply loop over the other structs:
<cfloop index="key" list="#sections.default#">
would be
<cfloop index="key" list="#sections.whatever#">
I know this post is way old but I have a question on it. I am a total noob to CF so bare with me.
Ok so I followed you step by step here(I used my own ini file).
example:
[SERVER]
server_name=Cori ;Comment is here
When I output "server_name" it also outputs the comment to the right. How can I stop this?
Thanks
Cori
Interesting. Well, a simple regex would solve that. Before I help you with that though - is using a comment like that standard in INI files? How would you escape a semicolon?
Well well well- according to Wikipedia you are right:
http://en.wikipedia.org/wik...
Semicolons indicate a comment. They don't mention how to escape it so I guess you can't. I suppose CF is doing the right thing by giving you the entire line - but I can see how you would need code to handle INI files like this. Give me a few hours and I'll whip up a blog entry. Cool?
Cool,
I have been searching everywhere for a solution.
It's probably my lack of experience holding me back here.
Thank You
I didn't want to leave you hanging. Here is real quick code to do it. Since you can't escape ; and the 'Guide' seems to imply only one is allowed, I just treated it like a list:
<cfset s = "Foo ;comment">
<cfset s = trim(listDeleteAt(s, listLen(s, ";"), ";"))>
<cfoutput>#s#</cfoutput>
AWESOME! Thanks for your help here is the code I am going to use
<cfscript>
server_name = getProfileString("server.ini", "SERVER", "server_name");
</cfscript>
<cfoutput>
<cfset server_name = "#server_name#">
<cfset server_name = trim(listDeleteAt(server_name, listLen(server_name, ";"), ";"))>
#server_name#
</cfoutput>
Results in
"Cori"
Thanks again!
FYI, this line:
<cfset server_name = "#server_name#">
Doesn't do anything and should be removed.
Oh I see. Once I set the variable in getProfileString no need to set it again. Once again thanks a ton!
FYI, my code breaks if no comment exists. Just add
<cfif find(";", value)>
before.
My update: http://www.coldfusionjedi.c...
Ray,
If you save the .ini file in 'Unicode' format, then GetProfileSections and GetProfileString return nothing.
Some of the configuration variables in my app have their values in Chinese and I am having to save the .ini in anything other than ANSI so that the characters don't get garbaged.
Any ideas or workarounds ?
Just to add, UTF-8 does not work either
Just use fileread and some string parsing instead. A bit more work, but not more than an hour.
Hi, I never used Your BlogCFC due to its usage of getProfileString and on the most of hosts, it is disabled sorry abt that
:(
BlogCFC has _long_ supported initialization via CFML structs. :)