Duncan’s blog

March 12, 2009

The importance of var scoping

Filed under: Coldfusion — duncan @ 9:33 pm
Tags: , , ,

As I mentioned in my Introduction to ColdFusion Components, var scoping is essential. This applies not just to CFCs, but to any functions in your CFML.

Let’s illustrate why…

Just for fun, I’m constructing an array with some ‘interesting’ entries from the menu of Heston Blumenthal’s Fat Duck restaurant*. Then I have a function that works out some random scores for each item.

<cfset food = ArrayNew(1)>

<cfset food[1] = "nitro-green tea and lime mousse">
<cfset food[2] = "snail porridge">
<cfset food[3] = "roast foie gras ""benzaldehyde""">
<cfset food[4] = "salmon poached in liquorice gel">
<cfset food[5] = "nitro-scrambled egg and bacon ice cream">

<cfloop index="i" from="1" to="#ArrayLen(food)#">
	<cfset rating = getRating(i)>
	
	<cfoutput>
		#i#. #food[i]#: #rating#<br>
	</cfoutput>
</cfloop>


<cffunction name="getRating">
	<cfargument name="rating" required="yes" type="numeric">
	
	<cfset score = 0>
	
	<cfloop index="i" from="1" to="3">
		<cfset score = score + RandRange(1,10)>
	</cfloop>
	
	<cfreturn score>
</cffunction>

What I expect to see is something like:
1. nitro-green tea and lime mousse: 17
2. snail porridge: 9
3. roast foie gras “benzaldehyde”: 22
4. salmon poached in liquorice gel: 11
5. nitro-scrambled egg and bacon ice cream: 20

Instead what I get is:
4. salmon poached in liquorice gel: 14
4. salmon poached in liquorice gel: 12
4. salmon poached in liquorice gel: 24
4. salmon poached in liquorice gel: 9
4. salmon poached in liquorice gel: 21

So, what’s going on? Well, the problem comes from the simple fact that I’ve failed to var scope the loop counter i in my function. Each time the function getRating is called, i ends up being equal to 4 at the end. You might expect it to end up being equal to 3. However, the way the loop works is that we add 1 to it until it is greater than the to attribute. Once it equals 4 we can stop looping.

So after we call getRating, when I refer to i in my code to reference a value in my array, I will always get the 4th array element.

What if I change the function to give me more random scores; let’s say I want to increase from 3 to 10:

<cffunction name="getRating">
	<cfargument name="rating" required="yes" type="numeric">
	
	<cfset score = 0>
	
	<cfloop index="i" from="1" to="10">
		<cfset score = score + RandRange(1,10)>
	</cfloop>
	
	<cfreturn score>
</cffunction>

This will throw an error due to i now being larger than the size of our array:

The element at position 11 of dimension 1, of array variable "FOOD," cannot be found. 

How to fix this? Just add in a line in the function, immediately after any tags, but before any other CFML, like so:

<cffunction name="getRating">
	<cfargument name="rating" required="yes" type="numeric">
	
	<cfset var i = 0>
	<cfset var score = 0>
	
	<cfloop index="i" from="1" to="10">
		<cfset score = score + RandRange(1,10)>
	</cfloop>
	
	<cfreturn score>
</cffunction>

Notice that I’ve also changed the initialisation of the score variable, to also be var-scoped. Even though I know that I haven’t used another variable called score elsewhere in my code, this is just as important. Ideally your CFC functions should be de-coupled, i.e. you can’t guarantee that there won’t be external variables that share the same name. So protect everything by var scoping all variables.

This can also apply even when you’re not referring to variables of the same name elsewhere. If two users simultaneously called a page that had a function that wasn’t var scoped, strange things can happen! This is often the cause of mysterious errors that you can’t replicate yourself.

In all cases, the Var Scoper tool is very useful for finding un-var scoped variables.


* The Fat Duck is reputedly the best restaurant in Britain, and one of the best in the world. It has three Michelin stars; there are only two other 3-starred British restaurants. Coincidentally, one of them is located just round the corner from the ‘Duck.

Despite being so highly-regarded, the Fat Duck has just re-opened after a mysterious bout of illness amongst its diners.

Also, for a restaurant that charges £98 for the à la carte menu and £130 for the tasting menu, they’ve got a terrible website!

March 4, 2009

Introduction to Coldfusion Components (CFCs)

Here’s something I wrote up for work, a very basic beginner’s guide to CFCs. Nothing too technical, but I thought it might be of use for some people. I’d appreciate any feedback or questions.

What is a CFC?

At its simplest, a collection of functions, with a filename that ends in .cfc. The basic structure is like:

<cfcomponent>
      <cffunction>
            ...
      </cffunction>
</cfcomponent> 

A more complete example:

<cfcomponent hint="Library of mathematical functions">
      <cffunction
            name="getAtoPowerOfB"
            output="No"
            returntype="numeric"
            description="Returns value of A^n"
            hint="Returns A to the power of n"> 

            <cfargument
                  name="A"
                  required="Yes"
                  type="numeric"
                  hint="Base">
            <cfargument
                  name="n"
                  required="Yes"
                  type="numeric"
                  hint="Exponent"> 

            <cfset var power = arguments.A^arguments.n> 

            <cfreturn power>
      </cffunction>
</cfcomponent> 

Where to store CFCs

CFC files can be stored:

  • In the same folder as the current .cfm page
  • In the components folder as defined in the CF Administrator

The preferred method is in the components folder, so it becomes accessible to other pages.

Initialising a CFC

The Init() function isn’t mandatory, but is a de facto standard.

<cfcomponent>
      <cfset this.datasource = ""> 

      <cffunction name="Init">
            <cfargument  
                  required="Yes"
                  name="DataSource"
                  type="string"> 

            <cfset this.datasource = arguments.DataSource>
      </cffunction>
</cfcomponent>

Instantiating and calling a CFC

Method 1:

Pass individual named arguments in the cfinvoke tag.

<cfinvoke
      component = "foo"
      method = "getFoo"
      returnVariable = "blah"
      x="4"
      y="2"
      name="Duncan">

Method 2:

Pass arguments as a structure in the cfinvoke tag.

<cfset args = StructNew()>
<cfset args.x = 4>
<cfset args.y = 2>
<cfset args.name = "Duncan"> 

<cfinvoke
      component = "foo"
      method = "getFoo"
      returnVariable = "blah"
      argumentCollection = "#args#">

Method 3:

Pass individual arguments with cfinvokeargument tags.

<cfinvoke
      component = "foo"
      method = "getFoo"
      returnVariable = "blah"> 
            <cfinvokeargument name="x" value="4">
            <cfinvokeargument name="y" value="2">
            <cfinvokeargument name="name" value="Duncan"> 
</cfinvoke>

Method 4:

Create an instance of the CFC, then use that to call functions. This is preferable over any of the cfinvoke methods.

<cfset ComponentFoo = CreateObject("component", "path.to.components.foo")>  

There are two ways to then call the functions.

  1. Don’t specify argument names. Arguments have to be in the same order as specified in the function.
    <cfset blah = ComponentFoo.getFoo(4, 2, "Duncan")> 
    
  2. Specify all argument names. Order of the arguments doesn’t matter. This way is also preferable because it makes it clearer in your code what the arguments are, without having to refer back to the CFC.
    <cfset blah = ComponentFoo.getFoo(x=4, y=2, name="Duncan")>
    

Scopes

CFCs have three scopes:

  • this
  • variables
  • var

The this scope is for variables that need to be accessible to the calling .cfm or .cfc.

The variables scope is for variables that are private to just this cfc. Any function within the CFC can access these.

The var scope is for variables that are private to just one function. It is essential that all local function variables are properly var scoped.

The var scope is implicit; you don’t prefix variables with the scope name, like you do most other scopes. i.e. you can’t say <cfset x = var.y>

For instance:

<cfcomponent>
      <cfset this.directory = "C:Inetpubwwwroot">
      <cfset variables.datasource = "DSN"> 

      <cffunction name="doStuff">
            <cfset var name = ""> <!--- Good --->
            <cfset age = 0>      <!--- Bad! --->
      </cffunction> 
</cfcomponent> 

<cfset ComponentFoo = CreateObject("component", "path.to.components.foo")>  
<cfset blah = ComponentFoo.directory> <!--- This will work --->
<cfset blah = ComponentFoo.datasource> <!--- This will throw an error  --->

Best practice

  • Var scoping is essential; use the varscoper tool to check your CFCs
  • Use Output=”false” on all cfcomponent and cffunction tags if possible
  • Don’t put cfoutput in a function, return information to the calling page instead
  • Use the hint attribute on <cfcomponent>, <cffunction> and <cfargument> for documentation
  • Function names should be verbs, e.g. getData, updateName, changePassword
  • Ideally, functions should be loosely-coupled, i.e. functions shouldn’t refer to shared scopes (session, application, request etc), or to variables that exist outwith the CFC. So don’t refer to request.datasource, instead when you initialise the CFC, set the datasource in the this or variables scope (passed in as an argument to the initialisation function) and refer to that instead.

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.