Last week, I came up against a really strange issue in ColdFusion 9. I was trying to build some ColdFusion components on the fly and I was using CFDump to test my algorithm. Unfortunately, it seemed that something was going very wrong; no matter what I did, I couldn't get the assembled methods to work. Or, at least they didn't appear to be working according to CFDump. After banging my head against a wall for a while, I finally tried to execute one of the methods out of desperation and to my surprise, it worked fine! Apparently, in ColdFusion 9, methods appended to a ColdFusion component after it has been instantiated don't actually show up in CFDump.
To demonstrate this, I created the following Test ColdFusion Component:
- hint="I am a test component.">
- hint="I return an initialized component.">
- <!--- Copy the stub file to another key. --->
- <cfset this.innerCopy = this.stub />
- <!--- Return this object reference. --->
- <cfreturn this />
- hint="I return a string.">
- <cfreturn "Hello there." />
As you can see above, the init() method, which executes after instantiation, copies the "stub" method reference into the key, "innerCopy." Then, I created a test script which instantiated this component and even tried to append another method reference:
- <!--- Create an instance of our test component. --->
- <cfset test = createObject( "component", "Test" ).init() />
- <!--- Copy the stub method to another key. --->
- <cfset test.outerCopy = test.stub />
- At this point, we have an inner copy and and outer copy of
- the stub file in the test component. Let's CFDump out the
- component to see if those keys show up.
- label="Test Component"
- <br />
- <!--- Let's also dump out the key list. --->
- <cfdump var="#structKeyList( test )#" />
When I run the above code in ColdFusion 9, I get the following CFDump:
| || || || || |
| || |
| || || |
As you can see, only the compile-time methods, init() and stub(), actually show up in the CFDump. However, when I output the structKeyList(), I get all of the following:
This behavior is a change from ColdFusion 8. In CF8, the new method references would show up in both the CFDump and the key list. I don't know if this is a bug - it feels like one. Perhaps it has been fixed in the CF9.0.1 Updater (I have not yet upgraded). All to say, be careful in ColdFusion 9 if you are using CFDump to help debug your ColdFusion components - it might not show you what is truly going on.
Looking For A New Job?
ORM components (as I just tweeted you) seem to show a similar problem...
For example, changing the property "price" to "money" would let me invoke "getMoney", but it would still dump as "getPrice".
Only a full CF restart would solve this, as ormRestart was uneffective.
I was running 9.0.1, so probabily your bug is still there, too..
That's very interesting that it would let you access the correct method, but still CFDump the wrong one. That's kind of crazy. I guess with all the ORM stuff, they start optimizing how the properties were introspected or something. They must have erred on the side of "static" of performance reasons.
I wonder if this is related to the issue I pointed out with injected methods not showing up in getMetaData() in CF.
Yeah, definitely sounds like they are very related. I always feel a bit foolish when I blog something, then see *my* comments in a related post :)
I also discovered this when I was writing a nifty service object for ORM, and then using a factory that injects the methods into a CFC upon creation.
Except I didn't notice that it used to work in CF8. I thought it was just old news.
I only noticed the CF8/CF9 discrepency because I was specifically writing something to work in both languages (with some CF9 *features* when available). That's what was so confusing - the CF8 CFDump worked and CF9 was not working. I was convinced I was doing something wrong.
I don't think it's injected methods, per se, because if you inject a NEW function into the CFC, then it shows up in the dump just fine, eg:
<cfset o = createObject("component", "Cf9InjectedMethods")>
<cffunction name="g" access="public" returntype="boolean" output="false" hint="g()'s hint.">
<cfargument name="b" type="boolean" required="true" hint="A boolean arg">
<cfreturn not arguments.b>
<cfset o.g = g>
And this works the same if one grabs a method from an instance of a different CFC (but not another instance of the same CFC).
I guess it's only one instance of each class (ie: the classes CF creates under the hood... each method gets its own class, for some reason) that gets displayed in <cfdump>
I reckon this is a bug. Have you raised it as one? I'll vote for it, if so.
Good insight. I wonder what kind of logic is being performed under the hood in the CFDump. Especially if the key does show up in structKeyList(), you'd think the CFDump logic would pick it up.... unless a Collection Loop wouldn't see the keys either.
The cfdump documentation does not provide any informations on hiding the CFC method references. On the other hand, looping over the "test" collection doesn't miss any of its keys, so this would mean that the references are partially exposed which looks kinda buggy.
Instead, the getMetaData function, which is supposed to reveal "substantially more data about the CFC than the cfdump tag shows" did not offer any information regarding the method references at any time during the existence of ColdFusion 7-9. So, maybe this should be the main behaviour of the cfdump as well, who knows? Anyway, this should be documented as the default behaviour has been changed starting with CF9.
But, if you want to completely expose those references, you could do something like this
<cfset this.references.innerCopy = this.stub />
<cfset test.references.outerCopy = test.stub />
though this is definitely not the same thing as expected in the first place.
It definitely feels like a bug to me. And, as @Adam pointed out above, this only happens when *existing* function references are duplicated within a component. My guess is, they changed something and simply didn't come across the problem in testing ... especially since this kind of approach would not have had any use at all before the introduction of getFunctionCalledName().
I've experienced something similar with cfdump, where complex types specified as properties in a CFC are displaying their methods with the CFC methods after instantiation. I'm not extending the CFC but just declaring a property and generating the accessors and mutators dynamically by setting the accessor attribute to true. The methods don't actually exist in the cfc and can't be access, which is correct but the ColdFusion displays them as native methods within the hierarchical dump. Weird!
The good thing is that it doesn't affect run-time functionality; it's just confusing when you are trying to debug your code.
This is driving me crazy. I can't figure out a way to pull in a cffunction from a file where it's the only code in the file and inject it as a function or any other type of variable into a cfc instance outside of the pseudo constructor. Has anyone else ever figured this out?
The only workaround I can think of is having a skeleton cfc that reacts to some global scope variable ( request for example ) and cfincludes the files in its pseudo constructor which I could instantiate and then copy from, but that seems double dog hacky. Ideas?
You can CFInclude a function from within another function. The caveat is that is makes the function private. However, after you include it, I think you can then copy it to the public scope:
- <cfinclude template="myUDF.cfm" />
- <cfset this.myUDF = variables.myUDF />
AHA! Ignore that it was just a scoping issue. For some reason compile time cffunctions are accessible through both this and variables while runtime injected ( through cfinclude at least ) cffunctions from anywhere outside of the pseudo constructor go straight into the variables scope only. HOORAYYYYYYY.
Oops, sorry Ben. Thanks! Had this page open and wrote my 2nd comment before I got the email about your response.
I dare you to make your blog posts comety and push comments to them as authored.
I like the idea of pushing comments :) Hmmm. I'd love to carve out a few weekends actually and totally re-build this blog architecture from the the ground up.
This may be a stupid question. I want to get just the properties of a cfc and not the methods. How do I get that?
<cfparam name="THIS.xx" default="HI"/>
<cfset a = createobject(..)/>
When I do this I get the properties but also the methods. How do I just get the properties(cfparams,THIS)?
There's no single-step way to do that that I know of since the component is basically just a container of variables, some of which are methods, some of which are properties.
If you are just using the CFDump tag, later versions of ColdFusion have an attribute where you can actually hide the UDFs:
<cfdump var="#a#" showudfs="no" />
However, I suspect that you want more than just CFDump. In that case, you would have to CFLoop over the keys in the component and check them with the native function:
isCustomFunction( component[ key ] )
That will return true for all methods. You can just ignore those in your case.
Ah thank you.
By the way I was looking around and saw a few items about components. Is there a best practice when it comes to properties?
<cfparam name=THIS.xxx" default="0"/> - someone said this was bad practice.
<cfset THIS.xxx = 0/>
It depends on what your intent is. If you're in a situation where you want to set a default value for something like a new CFC, then I would just set it explicitly:
<cfset this.property = defaultValue />
However, if you are dealing with a situation where you are not sure if a value will exist (say for example, in a form post), that is where I use CFParam all the time:
<cfparam name="form.property" default="" />
Of course, that's typically outside of a CFC (or, at the least, refers to non-CFC properties). If you are trying to initialize a CFC, I would go with the former approach - an explicit set.
The CFProperty tag is only meaningful if you are using synthesize getters/setters in CF9+. Before that, the only use for CFProperty was web service information and what ever kind of "meta programming" you might be doing. I don't think I have ever used them outside of CF9+, though.