-
Notifications
You must be signed in to change notification settings - Fork 24
CFC Primer Part 3: Variable Scopes in CFCs
By Matt Woodward (matt at mach-ii.com)
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 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.
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.
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.