This is a really minor post, but I just wanted to show a quick example. Several people have already demonstrated ColdFusion 9's "new" operator for use in creating ColdFusion components in lieu of the CreateObject() method. But, those demonstrations have all been done using CFScript and I wanted to kick it old-school (sometimes referred to as "Ben Style") and give a little love to ColdFusion tags.
The import statement and New operator work much like they do in compiled languages such as Java. You "import" a name space (ie. a directory) of components which you can then instantiate using the "new" operator. The import simply allows you to reference the CFC path without using the fully qualified path. In the following example, we're going to import the directory "cfcs" and then instantiate the "Girl" object:
- With the CFImport tag, we can use the new "path" attribute
- to import a CFC name space. Note that you must use the ".*"
- at the end for this to work.
- <cfimport path="cfcs.*" />
- Now that we have imported our "cfcs" library, we can use the
- new operator to create an instance of a CFC in that directory.
- <cfset objGirl = new Girl( "Tricia" ) />
- <!--- Output girl object. --->
- label="Girl Component (via New)"
While the CFImport tag does not have to be the first thing on the page, it is generally a best practice to have the CFImport at the top of the page (as moving it anywhere else has no effect). That said, when we run the above code, we get the following CFDump output:
| || || || || |
| || |
| || || |
Now, me personally, I don't know if I'm crazy about the idea of referring to a CFC without giving it a qualified path. I think this can make the code a bit confusing for someone jumping into it for the first time - you have no context to work with. Luckily, if you want to, you can skip the CFImport tag altogether and simply use the qualified path in conjunction with the new statement:
- If you don't want to use the CFImport, you can always use
- the fully qualified path in conjunction with the NEW operator.
- <cfset objGirl = new cfcs.Girl( "Joanna" ) />
- <!--- Dump out girl object. --->
- label="Girl Component (view New)"
Notice that here, rather than just instantiating the "Girl" ColdFusion component with the "new" operator, we are instantiated the "cfcs.Girl" ColdFusion component. Now, even without the CFImport tag, when we run the above code, we get the following CFDump output:
| || || || || |
| || |
| || || |
This works perfectly. Of course, if your CFC paths are long, CFImport might be the good option.
In both of the above demonstrations, you've probably noticed that the Name value that we pass to the Girl object as part of the new operator:
new Girl( "Tricia" )
... shows up in the THIS scope of the output component. This is not some sort of automatic storage; this happens because the "new" operator instantiates the target CFC and then looks for a component constructor method to execute in the following manner:
- ColdFusion looks for the InitMethod attribute on the target CFC's CFComponent tag. If the attribute exists, it executes the method named within that attribute (CAUTION: If the name is not accurate, ColdFusion throws an error).
- If the InitMethod attribute does not exist, ColdFusion looks for a function with the name "Init" (which has become the de facto way to name CFC constructors) and executes it.
- If neither the InitMethod attribute nor the Init method are found, ColdFusion does not invoke any constructor and simply returns the instantiated CFC. In this case, any arguments to pass in using the new operator are simply ignored.
If ColdFusion does call a constructor method, the New operator returns the result of the constructor and not the object instance. Therefore, if you want to return the object instance, your constructor method must return the THIS pointer.
Here is the Girl.cfc used in the above demos. I have chosen to illustrate the InitMethod attribute notation simply because it is different than what most people will use. In fact, I would go so far as to say ONLY use the Init function name, and not this example, as this has become the de facto standard in the ColdFusion world:
- hint="I define a girl object."
- I have named this method "constructor" to demonstrate
- the InitMethod attribute used on the CFComponent tag.
- NOTE: If we excluded the "InitMethod" attribute above,
- we could simply have named this method "Init" and
- ColdFusion would have executed it as the constructor.
- hint="I initialize the component.">
- <!--- Define arguments. --->
- hint="I am the name of the target girl."
- <!--- Set the instance properties. --->
- <cfset this.name = arguments.name />
- <!--- Return this object reference. --->
- <cfreturn this />
When instantiating the Girl object, ColdFusion will see the "constructor" value in the InitMethod attribute of the CFComponent tag and then invoke the "constructor" method, passing to it, all the arguments that were passed in along with the new operator. And so, in the following statement:
new Girl( "Tricia" )
... the value, "Tricia," gets passed to the constructor method to be used as the Name argument. Arguments can be passed by order, using name-value pairs, or as an ArgumentCollection.
Once you have used the CFImport tag to import a name space, the resolved path to the instantiated CFCs are cached in memory such that they do not have to be resolved again. This can lead to extremely frustrating and hard to debug problems. For example, once you run the above page the first time, you can comment out the CFImport tag and the page will continue to run fine until the ColdFusion service (NOT the application) is restarted. As such, you will probably end up with situations where things were working fine and then suddenly broke for no apparent reason. This could be a good reason to use fully qualified paths with the new operator (especially considering the next point!).
Unlike importing ColdFusion custom tag libraries, which seem to be executed at compile time, both the Path attribute of CFImport and the New operator class path can use per-application mappings defined in the Application.cfc this.mappings structure. So for example, if my Application.cfc looked like this:
- hint="I define the application settings and event handlers.">
- <!--- Define the application. --->
- <cfset this.name = hash( getCurrentTemplatePath() ) />
- <cfset this.applicationTimeout = createTimeSpan( 0, 0, 0, 20 ) />
- <!--- Create mapping. --->
- <cfset this.mappings[ "/com" ] = (
- getDirectoryFromPath( getCurrentTemplatePath() ) &
- ) />
- <!--- Define request settings. --->
- <cfsetting showdebugoutput="false" />
... where "com" was mapped to "/cfcs", then I could use either of the following statements and they would evaluate properly:
- <!--- Use mapping in CFImport Path attribute. --->
- <cfimport path="com.*" />
- <!--- Use mapping in new operator. --->
- <cfset girl = new com.Girl( "Tricia" ) />
Due to the caching nature of the resolved paths in CFImport, this is real pain in the butt to debug. As such, I thought I would throw together a small video for just this aspect of the blog post:
| || || || || |
| || |
| || || |
As you can see in the video, while the CFImport statements get cached, the new operator does not. As such, I think I would recommend never using the CFImport tag (for Path values) and always using the New operator with a mapped component path. This way, you get all of the convenience of the short paths with none of the headaches of path caching. Plus, since the CFImport tag only works on the page within which it was defined, opting for mapped paths in the New operator is just going to be more convenient across the board.
I know you've all seen import / new demos before, but I just wanted to give the Tag-based version of the functionality some play time. It's a really small feature, but as you can see, it can create a real convenience. And you know my feeling - it's the little things that are going to make the big difference over the long haul.
Looking For A New Job?
@Ben, both the 'new' operator and the 'initmethod' attribute are highly useful. The 'new' operator is useful because it gives us a more concise way to instantiate components, and the 'initmethod' attribute is useful because it gives us a convention and a way to override the convention when necessary.
Now Adobe just needs to implement fast component instantiation (you can instantiate hundreds of millions of Java or .NET objects per second, and I would bet at least millions of Ruby or Python objects per second) and perhaps object identity (so you can tell whether two structs or two components refer to the same actual object, refer to the same location in memory).
You make a good point that, when a feature such as cfimport is broken, we should avoid using it. (Of course I'm not advocating dumping the ColdFusion array, even though passing, returning, and assigning arrays by copy instead of by reference is highly broken.)
@Ben You mention having to restart the application to debug the per application mappings. I ran into this in a demo I did last night, but calling the ApplicationStop() function allowed me to "reset" my application so that the next request coming in triggered the onApplicationStart method and reinitialized the application (this scoped) variables, including mappings. I just wrapped it up so I could pass reinit=true in my url and like magic, I can reinit my app at will. A whole lot better than calling the onApplicationStart() method or actually restarting the CF instance.
Love the new Keyword and Import.
Love it, I say.
Nice post Ben. I also think that the new and import operators are good for ColdFusion generally as it will make it easier for developers from other languages to "see the light" and try out ColdFusion.
I wouldn't say that CFImport is broken - I guess it caches the resolved paths for efficiency. I'm just saying it makes things really hard to debug :)
Sorry, I may have miscommunicated; you need to restart the application service to get rid of the caching of the resolved paths from the CFImport tag. Your way of dealing with the per-app mappings should be good.
Much agreed. And I know you're one of the people who already talked about the New operator; I just wanted to give ye old Tag set some time in the spotlight. It seems everyone has gone CFScript-crazy :)
Good info Ben. Question. Is it possible to access the arguments passed to any method, in the constructor (init) of the component? e.g.
obj = Component.MethodX(param1,param2)
Is there a way to access param1 & param2 in the constructor?
In administrator>caching, there is a setting ( Component cache ) which dictates whether the resolved path is cached or not. You shouhd uncheck it at development time so that each call goes thru the cfc resolution. There is a button "clear component cache" also to clear the component cache
What you pass to the constructor becomes the arguments collection in the method. Look at how I am accessing the value "Tricia" as the "Name" argument.
Brilliant! I did not know that was there. I'll check it out. Thanks so much. That will make things much easier :)
Great post Ben,
Very creative thinking, passing in a little video in the middle of your post.
Nice article. I have a question about calling cfc methods as static methods - where there is no instance created. Is there a recommended way to do this other than using cfinvoke? Or is that the best way? I am coming out of the c# world where I can just call a static method like so:
result = myclass.mystaticmethod();
Any thoughts on this would be a great help.
A new instance of the component is always created. ColdFusion doesn't have the concept of static classes. Even if you use CFInvoke to call a method, it still instantiates the CFC, then invokes the method, then simply discards the instance.
If you don't want to use the new operator to avoid instantiating the class, there's nothing you can do. You might as well just use new and call the method on the CFC instance.
Thanks for the quick reply. So, one more question. If I want create a user.cfc that maintains user data and also has methods for logging in and out, etc, is it a lot of overhead to keep that instance around in a session variable? In this case the instance would be used for both data and methods. Does this sound like a reasonable approach?
Forgive me if this seems basic, I am coming from the asp.net side of things trying to learn the CF way.
Architecture is not my strongest suite. Still learning that myself. But, I would suggest breaking out the login/logout functionality into a completely different, single-responsibility class like a Security Manager or Security Service. Then you can instantiate one of those, cache it singleton-style, and maybe even use it to create new instances of a user (like a factory method).
But again, I am just getting my feet wet in questions like that.
Please try this example one more time - but this time, first untick the option ColdFusion Administrator - Caching - Component Cache.
While I have not tried it (I'm not at my work computer), I have been told that turning of caching will prevent the caching of the paths. But, I guess once you go to production, you're gonna want that cached for performance.
is there any possibility to get the imported paths of a component with methods like "getComponentMetadata("foo.bar")"?
I need to resolve the component names of a method argument to a fully qualified name, but it seems impossible with imports.