So I totally dropped the ball during my CFC presentation at CFUNITED and forgot to mention one of the features of ColdFusion components. I didn't mention it as I rarely have a use for it.
If you look into your {cfinstall folder}\wwwroot\WEB-INF\cftags folder, you will find an empty tag called component.cfc. Whenever you create a CFC, ColdFusion will automatically make it extend this CFC.
So if you wanted all your CFCs to have a sayHello() method, you could just write it there. Hal Helms has a much better explanation here.
So as I said, I almost never use this file. For one - I typically don't want to modify code in the cfusion folder. (Um, of course, that's not exactly true. I've written mods to the exception handling and debugging templates, but that's the Mirror Universe Ray for those Trek nerds out there.)
Last night though I had a need to quickly examine a bean CFC. My bean CFCs typically have a bunch of set/get methods, and I thought it would be nice if I could quickly call all the get() methods. I rewrote my base Component.cfc like so:
<cfcomponent>
<cffunction name="dump" output="true" access="public" returnType="void">
<cfset var md = getMetaData(this)>
<cfset var x = "">
<cfset var result = structNew()>
<cfset var value = "">
<cfloop index="x" from="1" to="#arrayLen(md.functions)#">
<cfif left(md.functions[x].name,3) is "get">
<cfinvoke method="#md.functions[x].name#" returnvariable="value">
<cfset result[md.functions[x].name] = value>
</cfif>
</cfloop>
<cfdump var="#result#">
</cffunction>
</cfcomponent>
So what does this code do? It uses the meta data feature of CFCs to return the functions of the CFC itself. I loop over them, and if the method is getSomething, I simply invoke the method. Pay special attention to the cfinvoke tag:
<cfinvoke method="#md.functions[x].name#" returnvariable="value">
Notice how I didn't use a component attribute? When you don't do that, cfinvoke simply looks for a method locally. So, I call all the get methods, store the result in a struct, and then dump the struct. So now my beans have a dump method.
If I wanted I could have gotten more complex. Since I have the metadata, I could check the return type, and if it isn't complex, call the method, no matter what the name. One day I need to package up all my little dangerous mods to the CF core files.
Archived Comments
I know this is an example about using the base coldfusion component in interesting ways but I couldn't help but comment on the way I do this so that I don't have to do all that introspection.
[[off topic]]
I put all of my bean's instance data into a struct named variables.instance; I then have a function called getSnapshot() that returns the variables.instance struct and presto I have a struct with all my instance data. This is a lot easier, and a better practice than modifying the primary cfc file.
[[/off topic]]
Also just because you can modify the top level component doesn't mean you should. I think that this is generally considered a bad practice. You should probably just have a class that your bean extends so that you can easily include this functionality, or perhaps have a separate component that you can pass your bean into that will return all the instance data. In the end while this technique is interesting I don't feel that it is something developers should do unless they have to, and I have yet to find a situation where they have to.
Then again you were just showing that you could do this not advocating that people should do this.
I agree with Paul, I found that you could do this a while back, and there a lot of reasons not to, such as the fact that shared host users have no way of doing this, any upgrade to cf will probably overwrite this component, and any code that relies on code inside of that component will be very very hard to follow unless it is commented very well.
Anyway, thinking about this again I started to wonder... could you make the base component extend other components? It would be a dangerous thing, I know, but just curious...
Also good point about cfinvoke. I see a lot of developers that think that cfinvoke is only for invoking stateless cfc's and webservices, but I use them for objects all the time because it makes the code a little easier to read IMHO.
Both Paul and you dinged me on recommending this - but I don't think I did. I thought I made it clear I normally wouldn't do this, but wanted to share the technique anyway.
No, I didn't think you were recomending it, I just wanted to give some more reasons why you might not want to. I think its very good information to know... Definately something to keep in the back of your mind if you are ever debugging an application someone else built that may have done something like this...
Cool. Just being sure I didn't come off wrong. :)
No, not at all, I was just trying to add to the discussion ;)
It did seem to me at least like you were ambivalent toward weather or not someone should use this technique. This is why I piped up, but you'll notice I did say that you weren't advocating the use of this technique.
I just wanted it to be very clear where some of the community stood on this issue since a lot of coldfusion beginners use your site as a reference I'd hate for them to start down a dangerous path.
You'll also notice that on Hal's site, the page you reference, he doesn't modify the root cf component he creates his own basecomponent.cfc that all his components extend.
Anyway like you said, like I said, no one is advocating the use of this technique but it is an interesting one that should be shared.
Cheers!
Good points Paul. This is why I love blogging. It isn't just me pontificating from up high. :)
Ryan, no you can't make component extend something else... because that something else would *automatically extend component*...
Ah, of course. Glad I didnt try it! Hmm, wonder what that error message would be though...
RE: Paul's OT comment: your getSnapShot() function is simpler but it seems to me it's doing something slightly different and might be better named getInstanceData(). Ray's is perhaps more appropriate as getSnapShot() because it would also return any available calculations, boolean tests of instance data that might not have been set as instance vars, for example a boolean getIsInTheFuture() where that depends on a date instance var. Basically your method returns the private data, Ray's the public state. Both could be useful in different situations.
I agree with you all though that neither should go in the base component.cfc
To be clear, Julian, my code is _definitely_ just for Beans too.
You know, I know I said I agreed about not using the base class. However - if my goal is simply for debugging, I don't think it makes sense to use some BaseBean type class. If it won't be used in production, maybe it makes sense to use the base Component.
Thoughts?
I guess for dev/debugging anything goes if it's useful... but I would be uncomfortable about putting anything I regard as part of my application code base into the CFMX install directory.
I have my own base Bean cfc which I store in a server-wide library folder, and I must say, Ray, your dump method is a really useful addition to it. Thanks.
In this case, the code -wasnt- part of the app. It was a utility to dump a bean, and since I (typically) do beans all the same way, it would be useful box-wide.
Exactly, Ray. Which is why I find it useful to have a Base Bean (DRY principle).... but as a separapte cfc in a library location, not component.cfc