Duncan's blog

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.
Advertisements

14 Comments »

  1. […] @ 9:33 pm Tags: coldfusion component, coldfusion components, var scoping 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 […]

    Pingback by The importance of var scoping « Duncan’s blog — March 12, 2009 @ 9:33 pm | Reply

  2. Duncan, some really good tips here, thanks. At the very end you said “don’t refer to request.datasource”. Why not? This variable exists for the entire request, right through all the CFCs that are invoked. Why send an extra argument to the CFC if you don’t have to? I’d be interested to learn the reason behind your thinking. Thanks.

    Comment by Gary F — August 6, 2009 @ 12:17 am | Reply

  3. That’s a harder one to explain. While technically it’ll work fine, in theory it might also be more work for you to maintain. e.g. if you ever wanted to call a function using a different datasource from your request.datasource, it’d be difficult to do.

    However if you set an optional datasource argument on your init function, with the default set to request.datasource, then it becomes easy for you to instantiate an instance of this object with any datasource you like.

    If you haven’t already try and read The Pragmatic Programmer which explains this ‘Decoupling’ much better than I can.

    Comment by duncan — August 6, 2009 @ 1:32 am | Reply

  4. Thanks for the response, Duncan. I only have 1 datasource so I feel more comfortable about using the request scope to carry application level vars through to CFCs. I thought maybe there were multi-user scope leakage issues by doing that. In fact in the onRequestStart() method I copy the session and application struct into the request scope so I don’t have to worry about cflock anywhere else in the site, and the lock is only done once per request.

    Comment by Gary F — August 7, 2009 @ 12:06 pm | Reply

  5. AFAIK, assuming you’re on CFMX and not CF 5, you don’t need to worry about cflock for session and application variables any more. See this article for more information.

    Comment by duncan — August 7, 2009 @ 7:01 pm | Reply

  6. I’ve got my cfc called Control.cfc being invoked by the current Method:

    I currently have the init function as follows:

    Now when I run a query in another function being invoked,
    Like:

    SELECT
    ID, FirstName, LastName, DisplayName, UserName, UserAccountingCode, Phone
    FROM
    #variables.db_prefix#_site_Users

    I get the variables.datasource is not defined in variables

    so I go back hardcode my datasource again, and then
    I get the db_prefix is not defined in variables

    What is it that I am doing wrong?

    Comment by James — March 14, 2010 @ 10:24 pm | Reply

  7. James, it looks like your comment has any tags stripped out of it, can you repost? Try doing a find-and-replace from < / > to &lt; / &gt;

    Comment by duncan — March 14, 2010 @ 10:29 pm | Reply

  8. Yeah Duncan Sorry::

    I’ve got my cfc called Control.cfc being invoked by the current Method:
    <cfinvoke component = “Control” method = “Init” datasrc=”#webos.datasrc#” db_prefix=”#webos.db_prefix#”>

    I currently have the init function as follows:
    <cffunction name=”Init”>
    <cfargument required=”Yes” name=”datasrc” type=”string”>
    <cfargument required=”Yes” name=”prefix” type=”string”>

    <cfset variables.datasource = arguments.datasrc>
    <cfset variables.db_prefix = arguments.db_prefix>

    </cffunction>

    Now when I run a query in another function being invoked,
    Like:

    <cfquery name=”selUsers” datasource=”#variables.datasource#” result=”res”>
    SELECT ID, FirstName, LastName, DisplayName, UserName, UserAccountingCode, Phone
    FROM
    variables.db_prefix#_site_Users
    </cfquery>

    I get the variables.datasource is not defined in variables

    so I go back hardcode my datasource again, and then
    I get the db_prefix is not defined in variables

    What is it that I am doing wrong?
    Do I need to declare the variables.datasource and variables.db_prefix in the function? ColdFusion 9 BTW.

    Comment by James — March 14, 2010 @ 10:36 pm | Reply

  9. When you use cfinvoke it creates an instance of your control.cfc. However I think you’re then using cfinvoke a second time, but essentially that is working on a new instance. i.e. it isn’t the same instance you initialised earlier!

    Change your init function, put in this line at the end.
    <cfreturn this>

    Then when you do your initial cfinvoke, add this argument:
    returnVariable=”objControl”

    Then when you do your second cfinvoke, you’ll be doing it on that object, like so (I think this should work):
    <cfinvoke component = “objControl” method = “yourSecondFunction”>

    If that doesn’t work, ditch cfinvoke (at least for the second call to your cfc), and do
    <cfset yourData = objControl.yourSecondFunction()>

    Comment by duncan — March 14, 2010 @ 10:53 pm | Reply

  10. Okay,
    Well I did what you suggested and (the second invokation cfset yourdata… worked, but I am still getting the Element DATASOURCE is undefined in VARIABLES. error…

    Hmmm.

    Comment by James — March 14, 2010 @ 11:05 pm | Reply

  11. Duncan,
    It’s working like it should, I’ve got another error in my code somewhere I have to fix.

    Comment by James — March 14, 2010 @ 11:08 pm | Reply

  12. What do you get if you get your second function to just dump out the Variables and This scopes?

    Comment by duncan — March 14, 2010 @ 11:08 pm | Reply

  13. Nevermind, we cross-posted.

    Comment by duncan — March 14, 2010 @ 11:09 pm | Reply

  14. By the way, Thanks Duncan. Sometimes when you’re in code too deep you just never see it…

    Comment by James — March 14, 2010 @ 11:21 pm | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: