Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the New York ColdFusion User Group (Feb. 2009) with: Joakim Marner

Using Multi-Part Class Paths With CFScript-Based Argument Types

By Ben Nadel on
Tags: ColdFusion

The other day, I did what I thought was an extensive exploration of the use of ColdFusion components as data types when defining properties, return values, and argument types. As Jeremy Rottman pointed out to me, however, I did miss one very crucial use case: using a multi-part, dot-delimited path when using a ColdFusion component as an argument type. To demonstrate the issue Jeremy is referring to, let me set up a very simple example. Here is my directory structure:

  • /com/Girl.cfc
  • /com/Shoe.cfc
  • /Application.cfc
  • /index.cfm

First, let's take a quick look at the Application.cfc. In this framework file, I am creating an application-specific mapping, "com," to the "/com" directory:

Application.cfc

  • <cfcomponent
  • output="false"
  • hint="I provide application settings and event handlers.">
  •  
  • <!--- Define the application. --->
  • <cfset this.name = hash( getCurrentTemplatePath() ) />
  • <cfset this.applicationTimeout = createTimeSpan( 0, 0, 0, 20 ) />
  •  
  • <!--- Define application-specific mappings. --->
  • <cfset this.mappings[ "/com" ] = (
  • getDirectoryFromPath( getCurrentTemplatePath() ) &
  • "com/"
  • ) />
  •  
  • <!--- Define page request settings. --->
  • <cfsetting
  • showdebugoutput="false"
  • />
  •  
  • </cfcomponent>

Then, I went into my "com" directory and I created two ColdFusion components: Girl.cfc and Shoe.cfc. Shoe.cfc has no methods or properties, so I won't bother showing it at all. The only thing you need to know is that Girl.cfc will reference it as a data type.

Girl.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a girl entity.">
  •  
  •  
  • <cffunction
  • name="addToCloset"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I add the given shoe to my closet.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="shoe"
  • type="com.Shoe"
  • required="true"
  • hint="I am the shoe being added to the closet."
  • />
  •  
  • <!--- ... do something ... --->
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see above, we have one method, AddToCloset(), which takes a single argument that is of type "com.Shoe"; we are using our Shoe.cfc ColdFusion component as a data type.

Then, I went and created a test page to invoke this girlie method:

Index.cfm

  • <!--- Create a girl instance. --->
  • <cfset girl = new com.Girl() />
  •  
  • <!--- Create a new shoe instance. --->
  • <cfset shoe = new com.Shoe() />
  •  
  • <!--- Add the shoe to the closet. --->
  • <cfset girl.addToCloset( shoe ) />
  •  
  • Done!

Here, we are creating a Girl.cfc instance and a Shoe.cfc instance and passing one into the other. The Shoe.cfc instance should match the AddToCloset() "com.Shoe" data type; and, in fact, when we run the above code, we get the following output:

Done!

It worked just as expected - so where's the problem you ask? Well, there's one more caveat. As Jeremy Rottman pointed out to me, this only becomes an issue when your ColdFusion component is defined using CFScript. To test this, I went ahead an modified the Girl.cfc to use CFScript-based code:

Girl.cfc (CFScript Version)

  • component
  • output="false"
  • hint="I am a girl entity."
  • {
  •  
  •  
  • public void function addToCloset(
  • required com.Shoe shoe
  • ){
  •  
  • // ... do something ...
  • }
  •  
  • }

This is roughly equivalent to our tag-based version. This time, however, when we run our index.cfm test page, we get the following ColdFusion error:

You cannot use a variable reference with "." operators in this context

This happens whether or not the "required" keyword is used. The only work-around that I could figure out (which is what I was using in my previous post) was to import the namespace such that a multi-part, dot-delimited path was not required.

Girl.cfc (CFScript Version With IMPORT)

  • // Import namespace at app-specific mapping.
  • import "com.*";
  •  
  • component
  • output="false"
  • hint="I am a girl entity."
  • {
  •  
  •  
  • public void function addToCloset(
  • required Shoe shoe
  • ){
  •  
  • // ... do something ...
  • }
  •  
  • }

In this version of Girl.cfc, notice that I have an IMPORT statement at the top of the component which imports the namespace defined by the application-specific mapping, "com." Once this is in place, I no longer need to namespace my "Shoe" data type in the function argument. This example is a bit overly trite since this would have worked regardless (as the two components are in the same directory); but, take my word that the import statement gets the job done.

This is certainly a hacky work around at best; the use of import should not be mandatory for such things. I personally enjoy using paths for component data types as I find it to be self-documenting; but then again, when I use components as return types or arguments, I simply use the "any" data type as it just simplifies everything (a contradiction in desires, to be sure!).




Reader Comments

Ugh, definitely a bug. I imagine it's due to the CF engineering team being used to Java, where you'd typically use an import statement instead of the full, canonical class name. CF should definitely support either!

@Ben and Jeremy

Thanks for the clarification. This was the only lingering question that I had from the previous exploration. I agree, I like using the object pathing a documentation mechanism, especially in my larger applications. While I can imagine using the namespace will certainly help shorten code, does anyone know what that does to performance (especially in a highly transactional environment)? Is it going to have to do a directory scan (more unnecessary disk IO) on every instantiation (presuming you aren't working primarily with cached singletons)? Seems like that could cause problems under load.

Thanks!

@Nick,

I can't speak to the performance; but, I can tell you that the application-specific mappings seem to get cached (from what I remember). So, they are trying to implement as much optimization as possible.

import solves most of scenarios but what about this case:

/com/Girl.cfc
/com/fromouterspace/Shoe.cfc
/com/fromearth/Shoe.cfc

// Import namespace at app-specific mapping.
import "com.fromouterspace.*";
import "com.fromearth.*";

component output="false" hint="I am a girl entity."
{

public void function addToCloset(required Shoe shoe ){
// What Shoe is this?
}

public void function addToNoVoid(required Shoe shoe ){
// What Shoe is this?
}
}

@Marko,

Exactly - you make a good point. Importing namespaces can create ambiguity; at that point, we are totally stuck... unless you revert to tag-based coding.

Well lets hope this gets fixed. I keep running into this same issue over and over and over again.

Importing works for now, but I feel so dirty.

I had the same problem today and decided to just not require the component validate the type but for coding sake to put in the CFScriptDoc comment the following:
@param {MachII.framework.Event}event

I'd have preferred:
@param {MachII.framework.Event} event

But then it stores only the type which would overwrite each other if I had two items of the same type in a function. I then plan to create my own getCFCMetadata which runs the normal getMetaData but then extracts my own param format.

I'm quite disapointed with the way the CFScriptDocs have been handled in CF9 I hope they fix it up for CF9.1!

I also feel dirty and wrong having to hack in such simple and what I see as obvious requirements for script based componenets. I think just a straight copy of the JSDoc format and options with the ability to add in one or two CF specific ones would have been a much better approach.

Looks like C# to me... If you code in C# at all you then this is all to familiar. Learn to use the scripting version of CF and you'l also be learning the C# syntax. They are almost identical.

@Marcel,

I haven't had a chance to check out CF 9.0.1 yet; you have any insight as to whether or not this was fixed?

@Gerd,

Yeah, exactly. I think that was a big reason why there has been such a big push to implement all the CFScript functionality - so that this will look/feel much like other programming languages.

@Ben
dotted parameter names now work obviously, but the getMetaData function still includes whatever is first in the parameter string.
* @param MachII.framework.Event event1 description.
* @param MachII.framework.Event event2 description.

Still only returns
PARAM MACHII.FRAMEWORK.EVENT
event2 I am a Mach II Framework event object.

The component explorer still ignored all metadata added.

The below works ok now since at least the data type is in the getmetadata result separately.
@param event I am a Mach II Framework event object.

But I would still personally prefer to be able to define everything in the metadata as it isn't enforced so you can describe what you would like to happen while allowing the application to support 'any' which improves performance.

@Marcel,

I don't know too much about the actual component explorer. Pretty much the only time I ever see it is when I'm trying to hit a CFC remote method in the URL and forget to add the method attribute.

I am mixed as to how I feel about using meta data to define things (are you referring to the Java Doc style comment notation)?

Yeah I love the commenting capabilities of the Aptana JavaScript editor, you can define type in curly brackets and then element name and it bolds it so it is nice to read through.

I would have liked to see the documentation format follow the JSDoc format much closer.

I also read that there is more performance gained from not typing in CF so to have a way to comment on the expected type without actually having to type it would be the JavaScript way to do it and I think makes more sense for a dynamic language like CF.

@Marcel,

It is a bit more performant to not type your CFCs. In fact, I believe on production servers, in the CFAdmin, there is an option to disable CFC type checking (as your code typically is not causing type-related bugs at that point).

I'll be honest with you, though - I typically use type "any" for anything that's not just built into the core data types. I was never a great programmer, in the traditional sense, so this desire to use strong-type checking just never got into my psyche.

Couldn't agree more, which is why it makes more sense to do it in the comments and not in the function definition itself (where 'any' can remain) :)