OOP Terminology : Part IV - Scopes

September 09, 2008

When you create CFCs you need to be careful that you don't expose more data than you need to. When you create a CFC you need to be aware of the different scopes available.

The THIS scope

The THIS scope creates publicly accessible variables inside the CFC. These variables can also be set from outside the CFC. Unless you have a specific need to do this then don't. One of the main problems with setting variables in the THIS scope is that they can be set without being validated. Imagine you had a Order object (an object is a specific instance of a cfc) which stores the order date in THIS.orderplaced, it would be possible to set this value to "I think it was Tuesday" when a date type is expected. With a setter you don't have this problem as the passed argument would be validated before assigning the value. Another issue is that the value may be private information which you don't want to change, for example a tax rate.

The VARIABLES scope

Values in the variables scope are private, and only available inside the CFC, these are sometimes called instance variables. In other words you can't read or write to them directly, you would need to call a public get or set method (remember these are sometimes called accessors). Any values stored in the VARIABLES scope will persist for the lifetime of the CFC. Don't confuse the variables scope in the calling template with the variables scope in your CFC. They are completely independent.

The LOCAL scope

Variables that only need to exist inside a method (also known as a function) are known as local variables. These must be declared with the var keyword at the top of your method (directly after any cfargument tags). These variables do not persist after the method has been called and are not accessible by any other methods in the cfc. If you don't use the var keyword, then any variables you create will exist in the VARIABLES scope and be available in any method inside your CFC - this will almost certainly cause unexpected behaviour if another method uses the same variable name and is really hard to debug. Mike Schierberl has created a great tool called VarScoper which will find any unscoped variables in your code.

<cffunction name="RandomNumberArray">
  <cfargument name="iterations"
    type="numeric"
    required="true"
    hint="I am the number of iterations" />
    
  <cfset var i = 0 />
  <cfset var result = ArrayNew() />
  
  <cfloop from="1" to="#arguments.iterations#" index="i">
    <cfset result[i] = RandRange( 1, 500 ) />
  </cfloop>
    
  <cfreturn result />
</cffunction>

You may prefer to declare a structure to keep all your local scope variables in. This is a personal preference and does exactly the same as the previous example.

<cffunction name="RandomNumberArray">
  <cfargument name="iterations"
    type="numeric"
    required="true"
    hint="I am the number of iterations" />
    
  <cfset var local = StructNew() />
  
  <cfset local.i = 0 />
  <cfset local.result = ArrayNew() />
  
  <cfloop from="1" to="#arguments.iterations#" index="local.i">
    <cfset local.result[local.i] = RandRange( 1, 500 ) />
  </cfloop>
    
  <cfreturn local.result />
</cffunction>

It's generally considered good practice to use getters and setters (also known as accessors) so that external code can access the instance variables (also known as private variables) that exist inside your cfc.

Example of Accessors (get and set)

<cfcomponent hint="I am a rectangle">
  <cffunction name="init"
    returntype="Rectangle"
    hint="I am the pseudo constructor">
    <cfreturn this />
  </cffunction>  

  <!--- set accessors --->
  <cffunction name="setWidth">
    <cfargument name="width"
    type="numeric"
    required="true"
    hint="I am the width" />
      <cfset variables.width = arguments.width />
  </cffunction>
  <cffunction name="setHeight">
    <cfargument name="height"
      type="numeric"
      required="true"
      hint="I am the height" />
    <cfset variables.height = arguments.height />
  </cffunction>  
  <!--- get accessors --->
  <cffunction name="getWidth">
    <cfreturn variables.width />
  </cffunction>
  <cffunction name="getHeight">
    <cfreturn variables.height />
  </cffunction>
  <cffunction name="getArea">
    <cfreturn variables.height * variables.width />
  </cffunction>  
</cfcomponent>

Your calling script would be something like this:

<cfset rectangle = CreateObject( 'component', path.to.cfc ).init( ) />
<cfset rectangle.setWidth ( 100 ) />
<cfset rectangle.setHeight ( 200 ) />
<cfoutput>#rectangle.getArea()#</cfoutput>

There is a way to get and set data dynamically in a CFC by using onMissingMethod which was introduced in ColdFusion 8.

Example of dynamic accessors with OnMissingMethod

<cfcomponent hint="I am a rectangle">
  
  <cffunction name="init"  
    returntype="Rectangle"  
    hint="I am the pseudo constructor">  
    <cfreturn this />  
  </cffunction>
  
  <!--- dynamic accessors --->  
  <cffunction name="onMissingMethod">  
    <cfargument name="method"  
	  required="true"  
      hint="I am the name of a virtual method" />  
    <cfargument name="value"  
      required="false"  
      hint="I am the optional value of a virtual method" />  
    <cfif IsDefined("arguments.value")>  
      <cfset variables[arguments.method] = arguments.value />  
    <cfelse>  
      <cfreturn variables[arguments.method] />  
    </cfif>  
  </cffunction>  
  
</cfcomponent>

There is a lot of debate about whether this is good practice or not, as with most things it depends! It saves a lot of typing (which is good) but can also cause problems. Particuarly if you don't Unit Test, I would suggest that you use individual getters and setters for three reasons:

Hopefully this has been useful, many more intelligent people than me have written about this stuff in greater detail in the past so I suggest you take the time to do some more reading.


7 comments

  1. Good post. I wanted to point out that it has been hinted that ColdFusion 9 will have an implicitly created LOCAL scope in functions. Meaning, that there will be a scope named LOCAL, but you won't have to create it using a VAR keyword.

    I'm not sure if this is definite or just one of the many "maybes", but it could be cool. I assume they will build it in such a way that is doesn't break the many many apps out there that do create an explicit LOCAL scope.

    Comment by Ben Nadel – September 09, 2008
  2. Thanks Ben, I was actually thinking about whether or not to explicitly create a variables.instance struct in a CFC when I started writing this post, so I got a bit side tracked!

    I have also heard rumour that you will be able to define the constructor in ColdFusion 9 by doing <cfcomponent init="functionName"> which will be pretty cool...

    Comment by John Whish – September 09, 2008
  3. Good stuff, John. Clearly explained.

    I'm also in the habit of setting up the "pseudo-local" scope at the beginning of each function, but I tend not to use it for looping as in your example because I feel the extra code ( local.i vs i ) is less readable in that case. Instead I'll var the index variable explicitly. Another reason is that a var'ed index variable is fractionally faster than one in the pseudo-local scope (no struct lookups to do). Normally not a concern, but in the case of loops the difference might add up.

    The other point I'd make is on generic getters/setters: I'm a big fan of them (particularly the jQuery style suggested by Hal Helms). There is a debate as you say and it does come down to personal judgement, but depending on how you implement them, all of the downsides you mention can be easily addressed:

    > When you come back at a later date (or someone else looks at your code) you can see what methods are publicly available.

    All instance variables are initialised with their defaults in the init method of my CFCs so you can see what generic methods are available by looking at that list. You can also use CFPROPERTY.

    > You aren't doing any validation on the data type passed as the value.

    You can override any generic get/set by adding a manually written method for those that need specific validation, or that you otherwise don't want to expose generically.

    > You could easily have a typo in your code that set a value for an unknown variable, it could be hard to track this type of bug down.

    Use try/catch in your base generic get/set to throw an error if the variable doesn't exist.

    I've been using generics for a couple of years now and I love the simplicity they introduce. But different strokes for different folks.

    Comment by Julian Halliwell – September 11, 2008
  4. Hi Julian, all good points :) Thanks for taking the time to post a comment.

    Generic Getters and Setters have a lot going for them, I just think that unless you understand the pros and cons and are familiar with the techniques you are probably better off doing it the long way first. I know guys like Peter Bell use them a lot, but Peter really knows what is going on.

    When I mentioned that it is easier for someone to come a long and pick up where you left off, I was thinking more about having a documented API (whether that is generated via MetaData or using the cfc explorer) which you can't have with generic technique. That way the other developer wouldn't even need to open up your cfc to know how to use it which is a goal of OO. Using cfproperty does solve this and is something I do use with generic getters and setters. As this post is mainly aimed at people fairly new to this stuff I didn't want to get into cfproperty just yet as it doesn't create a variable or assign it a value. Perhaps I should have done... :)

    With the typo issue I mentioned, if you were setting a variable, it would create it if it didn't exist. The try and catch would work for getting.

    Comment by John Whish – September 11, 2008
  5. > unless you understand the pros and cons and are familiar with the techniques you are probably better off doing it the long way first

    Yes, agree completely. Wait until you experience the "pain point" (and writing out getters/setters was definitely a PITB for me :-)

    > With the typo issue I mentioned, if you were setting a variable, it would create it if it didn't exist.

    Depends how you implement your generic get/set. Mine will throw an error for both get and set if the instance var doesn't exist.

    Anyway, thanks again for this little series, John. Really useful.

    Julian.

    Comment by Julian Halliwell – September 11, 2008
  6. @Julian, I guess you're using a StructKeyExists inside your setter with a variables.instance struct. I've used the metadata with cfproperty before so that I can check the data type as well, but I haven't settled on a preferred technique yet.

    Glad the series is useful. At least I know that you and Ben read them :)

    Comment by John Whish – September 11, 2008
  7. I've just stumbled across another issue with using dynamic getters and setters (which is pretty obvious when you think about it). If you implement an interface in your class which has get and set methods it will fail.

    Comment by John Whish – September 16, 2008

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.