As a design pattern, developers like to create and cache Singleton objects in persistent scopes within an application. To do this, you basically create a ColdFusion component instance and store it in something like the APPLICATION scope (or within another object stored within the APPLICATION scope). This is, effectively, caching that instance for later use. One of my readers emailed me today and told me that they decided not to go with a mixin-based hack because, while the parent ColdFusion component was caching properly, the code within the included file was not.
I don't want to argue whether or not this mixin was an appropriate solution, I want to talk about caching behavior. If you stop to think about the nature of the CFInclude tag, the caching he talked about makes a lot of sense. CFInclude tags are dynamic, in that they can change at run time:
<cfinclude template="#some_calculated_runtime_path#" />
Therefore, files included into a ColdFusion component cannot be cached just because the parent CFC is cached. If that were the case, then CFIncludes would have to be valid at compile time and I am sure that would make us all very unhappy.
Regardless, I thought you guys might like to see this in action, to explore caching behavior, just so no one gets confused about it. To demonstrate this, I have set up a small application that does nothing but creates and caches a single ColdFusion component:
<cfcomponent output="false"> <!--- Define application settings. ---> <cfset THIS.Name = "App Cache Test" /> <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) /> <!--- Define page settings. ---> <cfsetting showdebugoutput="false" requesttimeout="10" /> <cffunction name="OnRequestStart" access="public" returntype="boolean" output="false" hint="Fires when the current page begins to process."> <!--- See if we need to re-init. ---> <cfif ( StructKeyExists( URL, "reset" ) OR (NOT StructKeyExists( APPLICATION, "Cache" )) )> <!--- Create a Cache instance and store it in the APPLICATION scope. ---> <cfset APPLICATION.Cache = CreateObject( "component", "Cache" ) /> </cfif> <!--- Return out. ---> <cfreturn true /> </cffunction> </cfcomponent>
The Cache.cfc ColdFusion component then turns around and includes a function, mixin-style:
<cfcomponent output="false"> <!--- Include the Function mixin. ---> <cfinclude template="function.txt" /> </cfcomponent>
The function.txt file then outputs some text and in turn, includes another file:
<cffunction name="Go" access="public" returntype="void" output="true" hint="Outputs two values."> <p> From function.txt: [[ DEFAULT ]] </p> <!--- Include sub-function. ---> <cfinclude template="sub_function.txt" /> <!--- Return out. ---> <cfreturn /> </cffunction>
This is the sub_function.txt being included into the function.txt code above:
<p> From sub_function.txt: [[ DEFAULT ]] </p>
Let's take a moment to review whats going on here. The Cache.cfc ColdFusion component includes another function via CFInclude. That function then includes a chunk of text from another file. These are both includes, and therefore there paths are evaluated at run time, but be careful. Since the Cache.cfc is only being compiled once, the function.txt is only being included once (at instantiation of the Cache.cfc). The sub_function.txt, on the other hand, is being evaluated every time the Go() function is being invoked.
Ok, so now, let's create an index file that tests the caching:
<p> First Run </p> <!--- Run original code. ---> <cfset APPLICATION.Cache.Go() /> <!--- Loop over the two files and replace the [[ DEFAULT ]] text with [[ BLAM ]] text to see where caching is effective. ---> <cfloop index="strFileName" list="function.txt:sub_function.txt" delimiters=":"> <!--- Read in file name. ---> <cffile action="read" file="#ExpandPath( './#strFileName#' )#" variable="strFileContent" /> <!--- Replace DEFAULT with BLAM. ---> <cfset strFileContent = Replace( strFileContent, "[[ DEFAULT ]]", "[[ BLAM ]]", "ONE" ) /> <!--- Write the file back to disk. ---> <cffile action="write" file="#ExpandPath( './#strFileName#' )#" output="#strFileContent#" /> </cfloop> <p> Second Run </p> <!--- Run again with modified code that has not been re-cached. ---> <cfset APPLICATION.Cache.Go() />
Here, we are calling the Go() method once. Then, we are overwriting the data files with new code. Then, we are invoking the Go() method for a second time in the same page request. Running the above code, we get the following output:
From function.txt: [[ DEFAULT ]]
From sub_function.txt: [[ DEFAULT ]]
From function.txt: [[ DEFAULT ]]
From sub_function.txt: [[ BLAM ]]
Notice that text output in the function.txt file stays the same ([[ DEFAULT ]]) even though its corresponding code file was updated. Notice also that the text output in the sub_function.txt file changed to read "[[ BLAM ]]" in the second run. Again, this is because the function.txt file was included once at CFC instantiation time and the sub_function.txt file is dynamically evaluated once per method execution.
This is just a subtle point, but it makes a whole lot of sense when you stop to think about how CFInclude works. Also, I am unclear as to the proper use of the phrase "compile time" and "run time", but I hope you get the jist of what I am saying.
Want to use code from this post? Check out the license.