ColdFusion 101: Config Files A-Go-Go

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:

name=Jacob Camden
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:

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.

[default]
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 iniFile = expandPath("./test.ini")>
<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:

<cfset iniFile = expandPath("./test.ini")>
<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:

<cfif not isDefined("application.settings") or isDefined("url.reinit")>
   <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

Comment 1 by Raymond Camden posted on 8/27/2005 at 1:21 AM

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.

Comment 2 by Bryan F. Hogan posted on 8/27/2005 at 1:42 AM

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. ;-)

Comment 3 by Bill posted on 8/27/2005 at 1:55 AM

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.

Comment 4 by Chris posted on 8/27/2005 at 2:13 AM

As a CF newbie, i love seeing this type of stuff. Learn something new everyday...!

Comment 5 by Raymond Camden posted on 8/27/2005 at 2:14 AM

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.

Comment 6 by Oliver Merk posted on 8/27/2005 at 3:05 AM

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

Comment 7 by Dave Hill posted on 8/27/2005 at 3:58 AM

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!

Comment 8 by Dave Hill posted on 8/27/2005 at 3:58 AM

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!

Comment 9 by Dave Hill posted on 8/27/2005 at 3:59 AM

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!

Comment 10 by Raymond Camden posted on 8/27/2005 at 4:44 AM

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.

Comment 11 by Raymond Camden posted on 8/27/2005 at 4:46 AM

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.

Comment 12 by Jeff posted on 8/27/2005 at 4:47 AM

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.

Comment 13 by Nathan Hunsaker posted on 8/27/2005 at 7:45 AM

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.

Comment 14 by Raymond Camden posted on 8/27/2005 at 8:21 AM

Nathan - actually, I don't use Fusebox or Mach-II. I am beginning to use Model-Glue.

Comment 15 by Dave Ross posted on 8/27/2005 at 9:06 AM

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)

Comment 16 by Jeff posted on 8/27/2005 at 4:46 PM

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?

Comment 17 by Raymond Camden posted on 8/27/2005 at 5:55 PM

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. :)

Comment 18 by dick posted on 8/28/2005 at 10:04 PM

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.

Comment 19 by Bill posted on 8/29/2005 at 5:22 PM

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.

Comment 20 by Raymond Camden posted on 8/29/2005 at 6:01 PM

Bill - gotcha. I hope to have part 2 out by Wednesday.

Comment 21 by Jeff posted on 8/29/2005 at 6:08 PM

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).

Comment 22 by Nathan Hunsaker posted on 8/29/2005 at 8:05 PM

> 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.

Comment 23 by Jeff posted on 8/29/2005 at 8:25 PM

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.

Comment 24 by Todd posted on 8/30/2005 at 3:43 AM

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

Comment 25 by george posted on 3/1/2007 at 8:59 PM

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.

Comment 26 by Raymond Camden posted on 3/2/2007 at 3:54 AM

You would simply loop over the other structs:

<cfloop index="key" list="#sections.default#">

would be

<cfloop index="key" list="#sections.whatever#">

Comment 27 by Cori posted on 2/16/2011 at 6:05 PM

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

Comment 28 by Raymond Camden posted on 2/16/2011 at 7:10 PM

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?

Comment 29 by Raymond Camden posted on 2/16/2011 at 7:11 PM

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?

Comment 30 by Cori posted on 2/16/2011 at 7:18 PM

Cool,

I have been searching everywhere for a solution.
It's probably my lack of experience holding me back here.

Thank You

Comment 31 by Raymond Camden posted on 2/16/2011 at 7:21 PM

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>

Comment 32 by Cori posted on 2/16/2011 at 7:38 PM

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!

Comment 33 by Raymond Camden posted on 2/16/2011 at 7:40 PM

FYI, this line:
<cfset server_name = "#server_name#">
Doesn't do anything and should be removed.

Comment 34 by Cori posted on 2/16/2011 at 7:46 PM

Oh I see. Once I set the variable in getProfileString no need to set it again. Once again thanks a ton!

Comment 35 by Raymond Camden posted on 2/17/2011 at 4:11 AM

FYI, my code breaks if no comment exists. Just add

<cfif find(";", value)>

before.

Comment 36 by Raymond Camden posted on 2/17/2011 at 4:22 AM
Comment 37 by Alex posted on 6/28/2011 at 2:10 PM

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 ?

Comment 38 by Alex posted on 6/28/2011 at 2:12 PM

Just to add, UTF-8 does not work either

Comment 39 by Raymond Camden posted on 6/28/2011 at 3:10 PM

Just use fileread and some string parsing instead. A bit more work, but not more than an hour.

Comment 40 by Misty posted on 4/11/2012 at 9:35 PM

Hi, I never used Your BlogCFC due to its usage of getProfileString and on the most of hosts, it is disabled sorry abt that
:(

Comment 41 by Raymond Camden posted on 4/11/2012 at 10:04 PM

BlogCFC has _long_ supported initialization via CFML structs. :)