Overriding Built-In ColdFusion Methods With Custom ColdFusion Component Methods
ColdFusion has a TON of amazing functions built right into. The problem (in a weird way) with some of these functions is that they have great names. This is a good thing most of the time, but sometimes, a function name is so good that you want to use it for another purpose. Image a situation where you have a custom data type. You might want to slap a ToString() method on it, DataType::ToString(). Or what about a bean that has some sort of validation issues. You might want to slap an IsValid() method on it, Bean::IsValid().
If you try to define these methods within the ColdFusion component you are creating as such:
<cfcomponent displayname="Bean" output="false"> <cffunction name="IsValid" access="public" returntype="boolean" output="false" hint="Determines if bean data is valid."> <!--- For testing just return true. ---> <cfreturn true /> </cffunction> </cfcomponent>
... ColdFusion throws the compile-time error:
The names of user defined functions cannot be the same as built-in ColdFusion functions. The name IsValid used for a user defined function is the name of a built-in ColdFusion function.
This is a shame since IsValid() would be such a sweet member method of the ColdFusion component right? You have two options at this point: One, choose a different name or Two, declare the method indirectly using method pointers.
Option one is lame. Why change the name of the method you want just because ColdFusion doesn't like it. The reason you chose the name (and the reason ColdFusion has a method with the same name) is because that name rocks! Let's not run and hide.
Option two is the one I would suggest. It is slightly kludgy but it accomplishes exactly what you want it to do with minimal work. Remember, the errors we get her are compile time errors, not run time errors. Also remember that pretty much everything in ColdFusion is an object, including variables and user defined functions. We can use this to our advantage.
To get around the compile time issue, we simply cannot name the function exactly how we want it to be named. Sorry. But, we can name a variable with that name. Then, at run time, we can set that variable to point towards the method we want. This, for all intents and purposes, is the same thing as defining a method with the chosen name. Here is the above example with our method:
<cfcomponent displayname="Bean" output="false"> <!--- Set method access pointers. ---> <cfset THIS.IsValid = $IsValid /> <cffunction name="$IsValid" access="private" returntype="boolean" output="false" hint="Determines if bean data is valid."> <!--- For testing just return true. ---> <cfreturn true /> </cffunction> <cffunction name="IsNotValid" access="public" returntype="boolean" output="false" hint="Determines if bean data is not valid."> <cfreturn NOT THIS.IsValid() /> </cffunction> </cfcomponent>
Notice that in the ColdFusion component's pseudo constructor (the code outside of method definitions), I am creating a public, THIS-scoped variable of the same name (as the member function I want to create). I am setting this variable to be a pointer to the first class object which is our private, VARIABLES-scoped member method. When I declare the method I prepend the name with the "$" to make sure the code compiles. Since the method gets validated at compile time, this ColdFusion component does not have any parsing errors. However, since the pseudo constructor runs a run time, we end up with a public, THIS-scoped method with our desired name.
<!--- Create the bean. ---> <cfset objBean = CreateObject( "component", "Bean" ) /> <!--- Call test methods. ---> #objBean.IsValid()# #objBean.IsNotValid()#
... gives us:
Now, when calling this method from within the ColdFusion component (say from the IsNotValid() method), you MUST scope the method call. If you do not, ColdFusion first looks in its list of methods before checking the scopes of the ColdFusion component itself. Not scoping will result in the error:
Parameter validation error for function IsValid. The function allows 3 parameters, but found 0.
Personally, I think scoping all of your method calls is a good idea any way. To me, this ups readability of the code. The down side of this is that since we calling a scoped variable (IsValid) then we have to use the appropriate scope during the method invocation. This however, should not be a big deal, but something to note.
If anyone knows of a way to do this WITHOUT having the intermediary step, I am all ears.
Want to use code from this post? Check out the license.
I'd say its better to pick a different name and to bite the bullet rather than using method pointers because doing so breaks inheritance.
If you extend your object and redefine the $IsValid method and do not explicitly redefine the this.IsValid variable then method calls with call the old method. Subclasses shouldn't need to maintain function pointers in super classes either.
How about BeanIsValid() instead?
You make a good point. Due to my limited OOP experience, I do not always think about a solution's implications within an OOP environment (such as one that would use "extend"tion).
As far as just biting the bullet, I suppose you are correct. However, there are going to be times when I am certain a class will never be extended, and when they do, I can handle it then. I am not quite ready to bite the bullet as of yet... I just don't like the idea of redundancy in method and variable names. If I am in a bean, there really is no reason that any of the methods (99%) of the time should have "bean" or the class's name should be in any variable or method name... if you are in a bean all those methods and variables relate to that bean.
But again, I have limited to experience with such matters, so I might have to come down off my pedestal and do as you suggest. And, to be honest, that is mostly what I have been doing as thw whole pointer thing just occurred to me a few weeks ago and I have been using CFCs since MX6.
Just what I needed to override the toString() method in my component.
This is really neat, way to think out of the box Ben!
I don't imagine there is any way to do this with ColdFusion tags? I would like to override the cfinclude tag to display the name of the file its including (would be great for tracking down display code within a framework).
You can always create a custom tag to do things like this. And, with CF8 and the usage of AttributeCollection, it becomes much easier:
<cf_location url="" addtoken="" />
... and then your tag internals (which I think work for the CFLocation tag):
<cflocation attributecollection="#attributes#" />
Then, of course, you do what ever you want before the CFLocation tag is actually executed.
True, but that would require me to go through and change all my code. And the framework's core code.
But if you could override it, you could have each included file displayed with a border around it, with the file name displayed in it as well. Would be awesome for tracking down which display files you need to edit when you want to change something you are looking at.
Yeah, it would be kind of cool. I think Railo might let you do that stuff.
I have found to my surprise that CF9 seems to allow for overriding system functions without this trick. I haven't found this documented anywhere yet but it doesn't complain about my trace() dump() toString() or any other such function.
Really! That is very interesting. I'll have to take a look at the on my local machine. That could lead to some confusing / interesting approaches.
David is totally right. In CF9 we can create function with same name that available in coldfusion, only issue with this is we need to scope with this to call same function within CFC.
I am looking for override now() function with additional argument timezone where it will automatically convert current server time to my timezone. I thought same way it may work in Application.cfc as well and it is but not able to call from my cfm/cfc due to no scope available to call Application.cfc function.
The downside I've found to this is that if within public function a() you call private function b(), where b() is a function name ALSO used by ColdFusion, you can't do this.b() -- Coldfusion reports that it can't find the function b().
At this point, you are compelled to rename b() to something that doesn't collide with the ColdFusion built-in function.