When upgrading Legacy Code applications to modern ColdFusion programming, one of the greatest advancement of the past decade was the ability to create CFCs. CFCs allow you to create reusable objects in code, that can have their own properties and custom methods you can define for very specific purposes. The ColdFusion Documentation gives you some great information on writing CFCs, but makes it very difficult to find out how to write scripted CFCs. The Component Tutorial on Learn CF in a Week is much easier to find and understand.
But we're still talking about scoping your code, and you might already have CFCs, or just starting to convert some things over, so we're going to discuss the various scopes you have available within a CFC and their usage.
- THIS
- - This scope is a reference to the object itself. The THIS scope is unique to a specific instance of the object. It is a public global variable scope of the object. What that means is, any data that you create in the THIS scope can be seen, and even modified from both within and without the object itself. To illustrate, we'll create this very basic component.
test.cfc
component { THIS.name = ""; /** * @access public * @output false * returntype any */ function init () { return THIS; } /** * @access public * @output false * returntype void */ function setName (required string name) { THIS.name = ARGUMENTS.name; } }
We can then create an instance of this object, set the "name", and then access the property.index.cfm
When you execute this code, you see that the "name" that you set is available as a public property of the object. But, as I said, the variables are public. This can have some unintended negative effects as well. Add these lines after that last WriteOutput statement:REQUEST.testObj = CreateObject("component", "test").init(); REQUEST.testObj.setName("Cutter"); WriteOutput(REQUEST.testObj.name); REQUEST.testObj.name = "Blades"; WriteOutput("
When you execute the code now, you can see that you were able to directly change the property of the object from outside of the object. This can be bad, as it exposes your variables and breaks encapsulation. Many developers never use the THIS scope because of this.
"& REQUEST.testObj.name); - VARIABLES
- - And here we are back to the confusing VARIABLES scope. Use of this scope, within CFCs, is one of the two documented use cases for this scope (see our last post for the other). The VARIABLES scope is a protected CFC global local scope, meaning that the variables in this scope are only available within the instance of the object, and globally available by any method of the object, or any object that inherits the object by extension.
Let's change our test.cfc trading out the THIS scope for the VARIABLES scope, and try executing our code:
component { VARIABLES.name = ""; /** * @access public * @output false * returntype any */ function init () { return THIS; } /** * @access public * @output false * returntype void */ function setName (required string name) { VARIABLES.name = ARGUMENTS.name; } }
When you attempt to execute the code, you immediately get an error, because "name" is no longer publicly available. Let's add a "getter" method to our test.cfc, to get the value of "name":/** * @access public * @output false * returntype void */ function getName () { return VARIABLES.name; }
We can then change our index.cfm to use this method for access this instance variable.
Since the variable is local to the instance of the object, and globally available to all methods, you can "set" the variable with one method, and "get" the variable with another. Let's add a bit more to our process script, to illustrate how the variables are instance specific.REQUEST.testObj = CreateObject("component", "test").init(); REQUEST.testObj.setName("Cutter"); WriteOutput(REQUEST.testObj.getName());
Try that out, and you'll see that each instance retains it's own, private, instance specific variablesREQUEST.testObj = CreateObject("component", "test").init(); REQUEST.testObj.setName("Cutter"); WriteOutput(REQUEST.testObj.getName()); WriteOutput("
"); REQUEST.testObj2 = CreateObject("component", "test").init(); REQUEST.testObj2.setName("Blades"); WriteOutput(REQUEST.testObj2.getName()); - ARGUMENTS
- - Discussed previously, this "passalong" scope is available within a specific function. In reviewing the setName() method in our test.cfc, you see that we access the arguments passed into the function via this scope. The ARGUMENTS of one function are not directly available to another, without passing them along as well.
- LOCAL
- - A cornerstone of writing threadsafe objects, the LOCAL scope is a function local scope, meaning that any variable created in this scope are only directly available to the function they are created in. To give you an idea of how this works, add the following to test.cfc:
/** * @access public * @output true * returntype void */ function localTest () { var var1 = true; local.var2 = 42; var3 = "whoops"; WriteDump(var=local, label="localTest local scope"); WriteDump(var=variables, label="instance variables scope"); }
And add this to your index.cfm:REQUEST.testObj.localTest();
When you execute the code, you'll see the basic <cfdump> output of the LOCAL scope of the function, as well as the VARIABLES scope of the object. Notice that both var1 and var2 are both in the function LOCAL scope, whereas var3 is in the object's VARIABLES scope. If you do not explicitly scope your variable, then it will be applied to the CFC global VARIABLES scope. (Just more validation for explicitly declaring each variable's scope.) While use of the var declaration is no longer necessary, as of ColdFusion 10, it is good practice to continue to use it when declaring new function local variables. User var to declare (which you can now do anywhere in your function), then prefix the variable whenever you reference it later in your function.