Creating Globally Accessible User Defined Functions In ColdFusion
The set of functions built into ColdFusion is huge and impressive. But, sometimes, wouldn't it be awesome to add a function or two of your own to that set of implicit functions in such a way that your user defined functions would be accessible to all aspects of your code? I know this raises all kinds of red flags, but I figured it would be something worth exploring (especially since Railo can do it). To see what I could find, I started dumping out the GetPageContext(). After a whole bunch of trial an error, I found this:
As this is undocumented, I cannot be sure what exactly it is; but, based on its content, it appears to contain references to all the runtime scopes - "true" or otherwise (ie. CFThread, CFFile) - that are available during the ColdFusion page request. I figured this might be a good place to start sticking my fingers where they don't belong.
First, I created a ColdFusion component that would house all of my user defined functions that I wanted to use to augment the collection of implicit ColdFusion functions:
<cfcomponent output="false" hint="I define user defined functions."> <cffunction name="getMessage" access="public" returntype="string" output="false" hint="I return a test message."> <cfreturn "I am defined in the UDF component" /> </cffunction> </cfcomponent>
As you can see, it contains only a single method, getMessage(), which will return a static string for debugging. Then, I created my Application.cfc ColdFusion framework component where I would take the UDF.cfc contents and append them to the hiddenScope object:
<cfcomponent output="false" hint="I define the application settings and event handlers."> <!--- Define the application. ---> <cfset this.name = hash( getCurrentTemplatePath() ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 ) /> <!--- Get a reference to the "hidden scope" of the current page request context and add all of the methods defined in our User Defined Method component. NOTE: I do not really know what this is or what kind of impliciations there are to messing with it. It appears to have a collection of the "hidden" scopes that created and / or searched after various actions. ---> <cfset getPageContext().getFusionContext().hiddenScope.putAll( createObject( "component", "UDF" ) ) /> </cfcomponent>
The Application.cfc file gets executed at the beginning of every single page request; as such, the above code is executed for every request into our application. Within the pseudo constructor of this Application.cfc, I am grabbing a reference to the hiddenScope and appending the contents of an instance of the UDF.cfc component. This should make all the functions defined in the UDF.cfc accessible to the current page request context.
Now, the test! Since pretty much everything in ColdFusion gets its own context, I wanted to make sure that my testing was thorough. I wanted to test the access to our UDF method in the following scenarios:
- Top level page request
- Custom tag execution
- ColdFusion component (both instantiation / cached request)
Here the test code I came up with:
<!--- Call the GetMessage() method from top level page. ---> <cfoutput> #getMessage()# [top level] </cfoutput> <br /> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <br /> <!--- Call the GetMessage() method from a custom tag. ---> <cf_tag /> <br /> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <br /> <!--- Create an cache a ColdFusion component (to make sure that this works across page requests for cached objects). ---> <cfset application.cfc = createObject( "component", "Test" ) /> <!--- Call from the component (will check caching on subsequent page request - but I will not be showing that in this demo. ---> <cfoutput> #application.cfc.test()# </cfoutput> <br /> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <br /> <!--- Call the GetMessage() from a parallel thread. ---> <cfthread name="testThread" action="run"> <cfoutput> #getMessage()# [cfthread] </cfoutput> </cfthread> <!--- Join thread. ---> <cfthread action="join" /> <!--- Output thread response. ---> <cfoutput> #cfthread.testThread.output# </cfoutput>
Both the custom tag (tag.cfc) and the test component (Test.cfc) simply output the results of the unscoped getMessage() UDF call; as such, I won't bother showing you the code for the custom tag or the CFC. I also have a second page that calls the test() method on the Application-cached Test.cfc (which I am not showing). When I run the above code, I get the following output:
I am defined in the UDF component [top level]
I am defined in the UDF component [custom tag]
I am defined in the UDF component [Test.cfc]
I am defined in the UDF component [cfthread]
As you can see, our user defined function, getMessage(), is now globally accessible (without scoping) to our entire application.
I am not sure that this is a good idea to begin with; and, using an undocumented feature to make it work is certainly a "use at your own risk" sort of strategy. In my next post, however, I'll show you a much "safer" way to accomplish the same thing by leveraging automatically-searched ColdFusion scopes.
Want to use code from this post? Check out the license.
IMHO, there should be nothing red-flag about wanting to write your own global UDF. Why can't we extend the language we're writing?
Why is it acceptable for all the other languages to do so, but squick-ish when we want to? You can extend jQuery? You can extend the Java libraries? You can extend c libraries if you so wished?
POWER TO THE PEOPLE!
Word up :) Hey Todd, you're pretty good at Railo stuff - I wasn't wrong when I said that Railo let's you extend the method library, right?
@Ben Very cool find.
I'm pretty torn whether its a good idea or not though.
On one hand its very cool and useful.
On the other it could be a debugging clusterf**k since its added at the server level when really it should be at the application level. Then there is the issue that what happens when future versions of the language add a function with the same name as a user defined one.
You're correct. Railo will let you extend (and, overwrite) the native functions and tags via CFC/CFM even.
You place the CFC/CFM in a special directory, restart the railo instance (necessary for Railo to pick up the new function/tag definitions).
You could if you really wanted to, build some ugly stuff and make it globally available to your local instance (and global instance) of Railo. That being said, if I wanted to, it should be my responsibility if I want to extend the language and quite frankly, I should have the power to do so.
Railo makes it even easier for me to do without having to write java. So, yes, you could poke through the Railo source and add it appropriately and build the server, but for CFML developers that aren't java developers and need options, this works nicely and isn't java build dependent.
* (part 1) http://www.railo.ch/blog/index.cfm/2009/9/11/CFC-Custom-Tag-Example
* (part 2) http://www.railo.ch/blog/index.cfm/2009/6/4/CFCbased-Custom-Tags-by-Example--Part-2
* (part 3) http://www.railo.ch/blog/index.cfm/2009/6/12/CFCbased-Custom-Tags-by-Example--Part-3
(As an aside, I recently found out you don't need to write a CFC Custom tag to use this feature, you can also just use a regular CFM custom tag and drop it in and it will work)
Built-in-Tag example ( cfyql using Raymond Camden's Yahoo Query Language example ) :
Right now, dump.cfc is the first official Railo Built-In-Tag that anyone can use / modify. Andrea Campolonghi has re-created several ajax tags (e.g. cfajaximport ) utilizing jQuery and is working on cfmap using this method as well.
I don't think it's added at the server level since the hiddenScope has request-specific data in it. For example, when I dump it out at the end, it has the Thread info for my test in the given page. And, since threads must be name-unique for a given page *request*, I have to assume this fusion context reference is for the given page only.
... but, that is pure guesstimation.
As far as good or bad, I think it depends on how you use it. For example, I would love to add my own RegEx methods. These would be pure utility, stand-alone methods. That sort of thing would be awesome! Of course, it would tie every reference of it to your APP, but in practicality, I don't think that's too bad.
Awesome - thanks. I'll check out the links. I had seen the CFC-based tag implementations and though that was kind of badass.
Sam Farmer has a great point as well and again, I insist that this falls into the hands of the developers to pony up and be responsible and create documentation.
CFML Developers need to consider if there function sounds to much like something else and perhaps name it to something that they recognize as something that was added to the server. Example: gFunctionName() - So, gInclude() would never bump into include(), etc.
Even the guys responsible for frameworks had this issue. I believe once upon a time Mango Blog wasn't working on CF9 because it had used a include() as a function name.
Oh, and the cfwheels guys have taken to using $ in front of their functions. $include() for example.
I think what Todd said is one of the point that, in my opinion, will make Railo success.
I am absolutely convinced that any engine should allow developer to extends the language as they like.
And this is not a matter of OS or not.... is just a way to let users really USE the engine they choose.
Just to clarify Todd's point, we use a $ prefix to indicate that a method is private in Wheels.
Why in the world would we do that? We decided that we'd like for plugins to be able to access and/or override those private methods. While they aren't part of the "public API" per say, the $ functions are still accessible when needed.
Plus CF ignores an underscore prefix as if it didn't exist AFAIK.
Just as a quick follow-up - here is the same approach to this problem using a well-documented behavior of the ColdFusion engine:
So it looks like CF10 is going to ship without the ability to extend the language at the global level yet again, so I'm going to toy with using this approach.
I think I'd prefer to not pollute the form and url scopes or to have to scope-reference globals even with a shorthand, so thanks a million for this old, awesome post.
Yeah, I was just looking at this again, as well. I'd love to add more functional programming style methods to the global UDF collection (ex. arrayMap()).
I'm sure this goes without saying but please blog your experiences. I'd love to see some evidence of this functioning well in production and even get Adobe's thoughts on it. Man I really wish we could do this formally.
What's the downside of just instantiating the object in application.cf*?