ColdFusion Muse

CFCs, the Variables Scope, and the Application Scope

Mark Kruger June 16, 2008 6:21 PM Coldfusion Tips and Techniques, Coldfusion 8 Comments (9)

It's pretty common to use the application scope to cache components. If your component is a collection of methods or data access functions it's often faster to put them into the application scope than it is to create an instance with each request. Now you probably know that you should quality all of the variables in a function with the "var" key word. This insures that the variable exists inside the "scope" of the function call. This allows multiple function calls to be made to the same instance without one set of variables over writing the other.

One of the areas where this can be difficult to manage is when using a ColdFusion tag that creates its own scope. Take CFHTTP as an example.

The Problem

Consider this "test.cfc" component.

<cfcomponent>
    <cffunction name="test">
        <cfhttp url="http://www.yahoo.com"/>
    </cffunction>

    <cffunction name="test2">
        <cfhttp url="http://www.cfwebtools.com"/>
    </cffunction>
    
    <cffunction name="dumpthis">
        <h4>This scope</h4>
        <cfdump var="#this#"/>
        <h4>Variables Scope</h4>
        <cfdump var="#variables#"/>
    </cffunction>

</cfcomponent>
It doesn't do much. It simply makes a CFHTTP call to 2 different web sites within 2 different functions - test() and test2(). As you probably already know when you create a CHTPP call Coldfusion automatically creates a structured variable called "cfhttp" for you to work with. The results "page" or "content" of the HTTP call is stored in "filecontent" - so "cfhttp.filecontent" contains the results you are after. So far so good.

The function "dumpthis( )" dumps out 2 scopes that exist within the scope boundaries of the component - "this" and "variables".

Now consider this test framework.

<cfif NOT isDefined('application.test')
    OR isDefined('url.refresh')>

<!--- caching our test component --->
<cfset application.test = createobject("component", "test")/>
<!--- running one of the functions --->
<cfset application.test.test()/>

</cfif>

<h1>Dump the this and variables scopes</h1>
<cfset application.test.dumpthis()/>

<!--- run the 2nd call --->
<cfset application.test.test2()/>

<h1>Dump the new content</h1>
<cfset application.test.dumpthis()/><br>
<!--- local variables empty --->
<cfdump var="#variables#"/>

This code instantiates the "test" CFC in the application scope if it does not already exist and then runs a call to "test" (the function retrieving the yahoo page). It dumps out the “this” scope and “variables” scope that belong to the component. If you run the test you will notice that a cfhttp variable containing the Yahoo content is a part of the "variables" scope inside the CFC.

Finally, the code runs the function "test2()", which retrieves the cfwebtools page. The dumpthis() functions shows that now the variables scope contains a cfhttp variable that contains the cfwebtools content.

The implications

Maybe you are saying, “So what ... that's what I would expect”. But the implications reach a bit farther than that. What is actually going on here is that the variables scope that is local to the CFC is persisting in the application scope. In fact, once you have run the code above put the following code on a separate script within the same application.

<cfset application.test.dumpthis()/>

You will notice that, even though you have not called either test() or test2(), the variables scope still contains a cfhttp variables and the "filecontent" key still contains the content of the last cfhttp call made. How would this a problem? Let's say that 2 requests arrive simultaneously - one designed to return the results of "test()" (the yahoo page) and the other designed to return the results of test2() (the CF Webtools page). One request could overwrite the other and they could both get the same content even though they both fired different functions from entirely different request threads. Consider the implications for a web service, stock quotes, tracking information etc. You could cause yourself some real data headaches.

The fix

How to fix it? Luckily there is a useful attribute called "result" that you can use for your CFHTTP call. The fix is to var the result variable and then use it as an attribute to your CFHTTP tag:

<cffunction name="test">
        <cfset var rs = ''/>
        <cfhttp url="http://www.yahoo.com" result="rs"/>
    </cffunction>

    <cffunction name="test2">
        <cfset var rs = ''/>
        <cfhttp url="http://www.cfwebtools.com" result="rs"/>
    </cffunction>
This keeps the CFHTTP results encapsulated inside the function scope. Rerun the test script and you'll find that there is no longer a CFHTTP variable in the CFC's variable scope.

Of course in addition to CFHTTP there are other Coldfusion tags that create special scopes (cffile with the "upload" action for example). As a rule of thumb when you are caching CFCs in the application scope, take an extra look at any calls to external resources made from within a function.

And now, dear readers, I'm sure that some of you will be tempted to comment on whether you should or should not be using the application scope in this way. Please understand that the muse believes there are folks on both sides of the issue that have a legitimate point of view - and keep the comments charitable.

  • Share:

Related Blog Entries

9 Comments

  • Brian Kotek's Gravatar
    Posted By
    Brian Kotek | 6/16/08 5:11 PM
    This is all true. The need to var-scope variables within CFCs that are assigned to a shared scope is already quite widely known. Though it is such a critical thing to understand that it does need to be reiterated on occasion.

    The same thing hold true for *any* variable that is used within a method: query result sets, loop index variables, etc. Note that avoiding the issue in this case can also be achieved by var scoping the cfhttp structure within these methods: <cfset var cfhttp="" /> will do the same thing.

    To comment on your final thought, there is nothing at all wrong with instantiating CFCs into the application scope; it is done all the time. Every framework, from Model-Glue to ColdSpring, does this. It's ColdFusion's implementation of the Singleton pattern. Using it does mean, as you point out, that diligent care must be taken to var-scope properly. Basically, if you explicitly want to put something into the variables scope of a CFC, specify it using "variables.myVar". Otherwise, var scope absolutely everything else.

    Regards,

    Brian
  • Steve 'Cutter' Blades's Gravatar
    Posted By
    Steve 'Cutter' Blades | 6/16/08 10:00 PM
    @Brian - But isn't the cfhttp returned variable actually a structure? Wouldn't this cause one of those "attempted to access a scalar variable", or something similar? So maybe better to

    <cfset cfhttp = {} />

    I could be wrong, I didn't test any of this. Maybe someone could write a quick test?
  • Brian Kotek's Gravatar
    Posted By
    Brian Kotek | 6/16/08 10:06 PM
    Hey Cutter. No it doesn't. Being dynamic, CF will just assign the structure to the variable, it doesn't matter what type it was initially.
  • Brian Kotek's Gravatar
    Posted By
    Brian Kotek | 6/16/08 10:08 PM
    Just reading that I thought maybe it sounded terse and that wasn't my intent at all. :-)

    So just to be sure, what I meant was: Hey Cutter how are ya. No, it doesn't trigger an error or anything. CF will just assing the structure to the variable, regardless of what type it was initially.
  • Brian Kotek's Gravatar
    Posted By
    Brian Kotek | 6/16/08 10:09 PM
    Um...that's ASSIGN! :-o
  • Steve 'Cutter' Blades's Gravatar
    @Brian - Hey Brian, How ya doin'? ;)

    Yeah, I couldn't remember. I think I was going a little too quick there, because now that I think about it it's typically the other way around. If I declare a var as a struct, then attempt to assign a string instead, then it throws the scalar variable error.

    Thanks for keeping things straight. I will now go write 1000 times "I must not code in sleep, I must not code in sleep..."
  • Mike Henke's Gravatar
    Posted By
    Mike Henke | 6/19/08 11:49 AM
    Mark - check out this project VarScoper - http://varscoper.riaforge.org/
  • james beuthling's Gravatar
    Posted By
    james beuthling | 3/6/09 2:55 PM
    what is faster to use the cfhttp or the cffile tag ?
  • Declan's Gravatar
    Posted By
    Declan | 3/17/09 4:38 AM
    Hey i have same problem with results coming back from cfstoredproc, whats the best way to solve this , thanks