Skip to content

CFC Primer Part 3: Variable Scopes in CFCs

thofrey edited this page Apr 1, 2014 · 10 revisions

By Matt Woodward (matt at mach-ii.com)

Table of Contents

  1. Variable Scopes in CFCs
  2. The this Scope
  3. The variables Scope
  4. The var Scope
  5. Next parts of this primer

Variable Scopes in CFCs

Since CFCs are modular software components that are accessed by other components within an application, it is very important to consider the scope within which data are placed when developing CFCs. The main scopes used (or not used in the case of a particular scope!) within a CFC are the this scope, the variables scope, and var-scoped variables.

The this Scope

The this scope is a global scope within the CFC. Any this-scoped variables are accessible directly both inside and outside the CFC. For example, if the firstName attribute in the Person CFC was placed in the this scope, it could be accessed directly by using dot notation.

Let's create an instance of a Person CFC called bob and use the this scope to set the firstName attribute to "Bob":

    <cfset bob = createObject("component", "Person") />
    <cfset bob.firstName = "Bob" />

Note that by setting the value of firstName directly in this way, the firstName attribute is placed in the this scope. Within the CFC, the word this is literally used to designate the this scope, so if firstName was being set within the CFC itself, the set statement would be as follows:

    <cfset this.firstName = "Bob" />

With the firstName attribute in the this scope it is accessed using simple dot notation. The following would return a value of "Bob":

    <cfset bobsFirstName = bob.firstName />

Although the above code may be convenient, it provides no data protection whatsoever. If the getter and setter functions are eliminated, there is nothing to stop someone from doing this:

    <cfset bob.firstName = "Bill" />
    <cfset bob.birthdate = "A long time ago!" />

These <cfset> statements would execute without throwing an error. Without using a setter function to protect the data, Bob's first name can easily be changed to "Bill", and Bob's birth date can be set to a string value that does not represent a valid date. This type of code makes debugging difficult because, in the case of the birthdate issue, the error will be unknown until the value "A long time ago!" is accessed and attempted to be used as a date.

The this scope is common in all OO languages. The this scope in CFML, however, functions quite differently than it does in other OO languages such as Java. It is important to remember that the this scope in a CFC is completely public, and as such functions very similarly to using a CFML struct. Therefore, it is strongly recommended that the this scope never be used. Many large-scale object-oriented CFML applications have been built since the addition of CFCs to CFML without ever using the this scope.

The variables Scope

Unlike the this scope, the variables scope is private to the CFC. For example, <cfset bob.firstName = "Bill" /> cannot be called to set the firstName attribute on the "bob" instance of the Person CFC. Instead, the getter and setter methods that are publicly available need to be used. Using the variables scope and providing a simple API allowing access the Person CFC's data encapsulates functionality that the Person CFC does not want or need to expose directly.

Consider the birthdate attribute again. If a developer uses a setBirthdate() method that explicitly specifies it must receive a date value as an argument, calling <cfset bob.setBirthdate("A long time ago!") /> would throw an error. This kind of error is helpful because it tells developers that functions are not getting what they expected, which makes debugging much easier.

Note that variables in the variables scope are available throughout the CFC. In other words, any function within the CFC has access to data in the variables scope, but data in the variables scope is not accessible directly to any code outside the CFC itself. Outside the CFC data in the variables scope must be accessed using methods that are exposed publicly and that allow getting or setting data in the variables scope.

The var Scope

Var-scoped variables are declared within <cffunction> tags, and they must be declared immediately following the <cfargument> tags. Unlike variables in the variables scope, var-scoped variables are local to the <cffunction> within which they are declared and are not available throughout the CFC. This is important because two functions within a CFC, both of which contain a <cfloop> with an index variable of i, may exist within a CFC. Keeping the variable i local to the function is crucial; otherwise, the value of i would be available to the two functions simultaneously, which would cause unexpected behavior.

Use of the var scope is also critical to making CFCs thread-safe. Variables that are not var scoped and are not explicitly placed into another scope are put into the variables scope by default, which means they are global to the CFC. As shown by the loop counter example above, this can cause unexpected behavior that is very difficult to debug. Moreover, errors or unexpected behavior may begin to appear only when the application is under heavy load.

It is extremely important for ALL variables in CFCs that are local to functions to be var scoped.

This may seem a simple thing to do at first, but keep in mind that this rule applies to all variables that are explicitly created as well as variables that are created as a result of calls to functions or the execution of CFML tags. Some of these cases are obvious; others are not. The loop counter example is a not-so-obvious case because developers usually do not think about the loop index variable until the <cfloop> tag is used. Other tags that must be var scoped include <cfquery>, <cfhttp>, calls to custom tags that return data, and UDFs that return data. Be sure to remember that any variables that are explicitly created, or any variables that are generated by calls to CFML tags, custom tags, or UDFs, must be var scoped.

The following is an example of a <cffunction> that contains a <cfquery> and a <cfloop> and returns a simple string:

    <cffunction name="varScopeFunc" access="public" output="false" returntype="string"
        hint="I am a var scope example">
        <!--- cfargument tags come first --->
        <cfargument name="foo" type="string" required="false" default="" />

        <!--- var scoped variables for the entire function must be declared
                immediately following the cfargument tags!!! --->
        <cfset var i = 0 />
        <cfset var myQuery = 0 />
        <cfset var returnString = "" />

        <!--- now with the var scoped variables declared, we can safely do our work --->
        <cfquery name="myQuery" datasource="#myDSN#">
            SELECT firstName, lastName
            FROM person
            WHERE id = <cfqueryparam value="1" cfsqltype="cf_sql_integer" />
        </cfquery>

        <cfloop index="i" from="1" to="10" step="1">
            <cfset returnString = returnString & "Loop iteration #i#<br/>" />
        </cfloop>

        <cfreturn returnString />
    </cffunction>

Note that the loop counter i, the myQuery variable, and the returnString variable must be var scoped and set to a default value before completing the rest of the function. It may seem odd to set the default value of the query to 0, but since the initial value of the variable does not matter, this is a simple way to set default values for complex objects like queries. Since var-scoped variables cannot be declared anywhere in the <cffunction> other than immediately following the <cfargument> tags (trying to do so will throw a compiler error), developers need to do some planning before adding new variables to functions.

Next parts of this primer

Clone this wiki locally