Creating Globally Accessible User Defined Functions In ColdFusion (Safer Version)
This morning, I did a quick post about how to use undocumented features of ColdFusion in order to extend the library of built-in ColdFusion functions. The technique I presented worked well, but sometimes using undocumented features leaves you feeling... less than fresh. As such, I wanted to write a brief follow-up post on how to accomplish the same thing using built-in, well documented ColdFusion functionality.
This approach will leverage the fact that when you reference an unscoped-variable, ColdFusion will automatically search several, predefined scopes looking for said variable. Some of those scopes are template-specific like Variables, This, Arguments, and Attributes; but, some of the scopes are request-specific like Url, Form, CGI, and Cookie. Working with this well documented behavior, we are going to append our user-defined functions to the URL scope such that they are available for the entire breadth of a given request.
As a quick refresher from this morning, here is my user-defined function library:
UDF.cfc
<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>
Now, in the Application.cfc, just as we did before, we are going to instantiate our UDF.cfc and append it to our globally-available URL scope:
Application.cfc
<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 ) />
<!---
Add all of our "global" methods to the URL scope. Since
ColdFusion will automatically seach the URL scope for
non-scoped variables, it will find our non-scoped method
names.
--->
<cfset structAppend(
url,
createObject( "component", "UDF" )
) />
</cfcomponent>
Here, just as we did with the undocumented "hiddenScope", we are instantiating our UDF.cfc and appending its properties to the URL scope. This will copy all of the UDFs to the URL scope. And, since the URL scope is automatically searched for unscoped variable references, we can now use these methods throughout our system. To make sure this still holds true, I re-ran my test page which makes a UDF method call in the following scenarios:
- Top level page request
- Custom tag execution
- ColdFusion component (both instantiation / cached request)
- CFThread
Here is the test code:
<!--- 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>
When we run the above code (internals of the CFC and custom tag not codified), we 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. While this is somewhat of a misuse of the intent of the URL scope, it is a much safer approach as it does not reach beyond the documented world of the ColdFusion engine. You might be worried that this puts a lot of extraneous data in the URL scope; but, as long as you're not taking shortcuts with the URL data, I cannot foresee any real concerns.
Want to use code from this post? Check out the license.
Reader Comments
Wouldn't the request scope be safer to shove this into?
@Todd,
The request scope is not automatically searched by ColdFusion. But, if it were, yes, definitely.
Understood. If I shoved a include() UDF into the url scope, all it takes is for the user (or an unaware developer) to type &include=whatever and your website is borked.
In the end, I don't think trying to get around having to type {scope.}functionName() is necessarily safe(r). :) I guess I'd rather just shove into the request scope per-request and then in the CFC, pull it back out of the request scope and perhaps into a private function.
@Todd,
The UDFs would actually overwrite the conflicting URL variables since it assembled after the request is constructed; but, regardless of the order, if something is conflicting, you're gonna get *bad* results.
Which bring up the question of "intent". Right, we could throw the stuff in the Request scope and then simply use "request.getMessage()" in our code. But, if we do that inside of a ColdFusion component, then really, we're breaking proper encapsulation. At that point, we should either be extending a Base component that has these methods, or passing in some sort of UDF library such that Component doesn't have to break its own scope.
Of course, using the URL scope doesn't really mean we're not doing that anyway... both ways would be breaking encapsulation. The only difference is that in using the URL scope, I can kinda-sorta pretend like they methods are really built-in and I can feel better about myself for not braking encapsulation (if only to live in a dense jungle of denial :P).
So really, what is the intent? Not to create a UDF library that I could reference anywhere. If I wanted to do that, I'd put in the Application-scope as a singleton. The ultimate goal is seamless extension of the library.
... but, as you point out, using the URL scope to do that can lead to fast problems in the right (erm, wrong) situation.
All to say, we really need a native way to do this (ala Railo).
I concur. Wouldn't surprise me to see it somewhere else in the future.
What I like about Railo's method is that it is completely sandboxed. Host A's tags & functions libraries never cross/conflict with Host B's libraries unless they're put into the global server folders. It's the same way for the virtual file system too and pretty much everything else (datasources, etc).
@Todd,
Perhaps this is where the $-prefix approach (or similar) would be good? As long as the custom UDFs are $-prefix (ie. $getMessage()), then they probably won't conflict with true URL-scoped variables.
@Todd,
Yeah, the sandboxing is truly a great concept.
Using the $functionName() would definitely be safe(r) than not using it, but it is definitely security by obscurity / obfuscation.
@Todd,
Obfuscation FTW! :)
I usually use the application scope for UDFs and load them onApplicationStart. The call is a bit longer (application.udf.getMessage() with my setup), but it seems to me a more logical choice than the url or request scope (UDFs are not request specific).
Jean
@Jean,
If you take a look at the comments I shared with Todd above, it may offer some more insight into what I was trying to go for. Creating a UDF library was not quite the point (that can be done as you state); the underlying intent was to "extend" the ColdFusion function collection in a seamless way.
Thanks for thinking outside the box. I like this.
@Henry,
Just learning to rock right along side you :)
All right, all right, point taken :-)
@Jean,
I didn't intend to be snippy (in case I came off that way).
"All to say, we really need a native way to do this (ala Railo)"
FWIW, at one time Adobe listed a feature of CF10 (codename Link) as "Pluggable Architecture".
http://www.codersrevolution.com/index.cfm/2008/9/17/A-Look-Into-ColdFusions-Future-Centaur-Sully-Link
Sadly though, the latest version of that piece of marking no longer includes the "Pluggable Architecture" part.
http://www.codersrevolution.com/index.cfm/2009/10/24/A-Look-Into-ColdFusions-Future-Again-Sully-Link-Storm
Ben, awesome posts.
I lobbied my ass off to get this capability into CF9, even suggesting solutions as cheap as letting you add application and request to the unscoped variable wiring queue on a per-application ( application.cfc setting ) or per-instance basis ( administrator option ).
But, mostly because of some oldschooler CFers tantrumming "how will people ever figure out where those functions are! It'll be a debugging nightmare!!!! People will get confused and overdose on narcotics!!", not enough people got behind it.
So sadly, I end up injecting all of my languagey functions( which in my mind are just extensions and enhancements of CFML ) into the application scope onAppStart and calling them with a global shorthand like this: app.getSentences( string, 2 ) or app.stripHTML( string )
It's the closest you can get to the native CF euphoria you're going for here without Railo, or at least I thought it was before I saw your post leading up to this ( I wonder if there's a getApplicationContext or anything similar to get to one of the persistent scopes! ).
The one thing that will probably stop me from using that approach in production though is the performance overhead of doing all of that work on every request. But it was an effing sweet experiment. Thanks for the knowledge.
Just set up your utility methods/functions in an application-scoped singleton as David suggests, then loop over the collection of functions at the top of your onRequest, assigning each to a same-named alias in the variables (or unnamed) scope. You can even programmatically disambiguate the function names (or add a unique prefix like an underscore) if you're worried about collisions.
So now app.util.kick(type='crescent') can be called on the page as the ersatz-native function kick(type='crescent') or _kick(). Calls will be by reference to the original functions in the application-scoped utility cfc.
@David, @Ken,
I think to get rid of the performance issues, you could get a sort of best of both worlds as I think @Ken is eluding to. Create and cache the UDF cfc in the Application scope on application start. Perhaps start all the UDF function names with "$" for name collisions. Then, at the request start just structAppend() the cached component.... oh wait. If this happens in the pseudo constructor, then I am not sure the Application scope exists at that point.
... Ok, so do it in the onRequestStart() - just structAppend() the cached UDF to the URL scope. This way, you don't have to re-create the CFC each request, you just have to copy the keys, which I have to imagine is rather performant. After all, I'm not thinking about hundreds of methods here - just a handful of great utility methods.
Good point -- structappend() is even better, assuming you don't need to inspect the function names individually. And I suppose it's unlikely you would, unless you're in some highly dynamic or unpredictable context (e.g., you're writing a framework or plug-in that could be used across multiple CFML engines with differing native function lists).
I'm still not sure about the url scope versus variables/unnamed. If we're working in onrequest(start), either is an option. I think variables is checked earlier in the scope walking sequence, for what that's worth. The use of either could still result in variables being stepped on at page execution...
@Ken,
One other method, that might make people feel safer, would be to create a base Component that all of the other CFC's extend. You could define your UDFs as methods on the base CFC and then utilize them from the concrete classes.
The only down side would that this would only work in CFC; for custom tags and any other non-CFC-based execution, you'd have to find another way.
"create a base Component that all of the other CFC's extend"
ColdFusion already has a base component that all components extend. Its called located here: WEB-INF/cftags/component.cfc :)
@Brad,
True. My only reservation about using that is that it is globally shared across all applications on that instance. While I do want to extend the CF engine, I still only want to do it on an app-by-app basis.
So, how about having any page/component/udf declare that it intends to use global udfs go get the global udfs into the unnamed scope?
Here is some code that I use for what Ken talked about ("Just set up your utility methods/functions in an application-scoped singleton...")
1) In application.cfc cfinclude the function library (which isn't a component, just a file containing cffunction tags). Could rework to be a component if you insist.
2) Put this function in application.cfc (or in the cfincluded file in #1:
<cffunction name="registerglobalmethods" output="no">
<cfscript>
keys = structkeylist(application.globalmethods);
for (i=1; i LTE listlen(keys); i=i+1) {
key = listgetat(keys,i);
evaluate(key & " = structfind(application.globalmethods, """ & key & """)");
}
</cfscript>
</cffunction>
3) Put this onApplicationStart:
application.globalmethods = structnew();
keys = structkeylist(this);
for (i=1; i LTE listlen(keys); i=i+1) {
key = listgetat(keys,i);
if (iscustomfunction(this[key]) and left(lcase(key),2) NEQ "on") // hide application methods
structinsert(application.globalmethods, key, this[key]);
}
application.registerglobalmethods = this.registerglobalmethods;
4) Whenever a page/cfc/udf wants to use global functions, first do: application.registerglobalmethods();
@Sean,
That's an interesting idea. My only concern with that would that it would alter the API of the component in which it is called. It's definitely a cool idea.
@Ben Nadel,
It's really just a (manual, late/runtime) pseudo-inheritance of a base class! Har har har....
It is a bummer that those functions become public functions of the component. Can't think of any way to make them private.
Very strange thing happened as I was tinkering:
I changed registerglobalmethods() to take take a parameter and to use implicit dynamic evaluation instead of the call to evaluate() (and I forgot to use the var statements). Here is out it looks now:
<cffunction name="registerglobalmethods" output="no">
<cfargument name="scope" required="false" default="variables"/>
<cfscript>
var key = ""; var i=1;
var keys = structkeylist(application.globalmethods);
for (i=1; i LTE listlen(keys); i=i+1) {
key = listgetat(keys,i);
"#scope#.#key#" = structfind(application.globalmethods, "#key#");
}
</cfscript>
</cffunction>
Now I cand call this from my page component with this:
application.registerglobalmethods("this");
I can get the functions into whatever scope I want. If I call it with "this", things work as I expect. The strange part is, if I call it with argument as "variables", the function pointers are declared in the unnamed scope, but they are still public! They don't show in cfdump of the cfc, but the page that instantiated the cfc can call just fine one of my global functions: sanitize() as mycfc.sanitize("test") !!
Can you shed some light as to why it is that in this case, the unnamed or variables scoped variables are available to the caller of the cfc that called this register function?
From the CF docs:
"The Variables scope
The Variables scope in a CFC is private to the CFC. ... You set a Variables scope variable by assigning a value to a name that has the Variables prefix or no prefix."
Shouldn't that mean the variables registerglobalmethods creates would be private? I don't understand why the global sanitize function, registered inside mycfc with my registerglobalmethods("variables") is callable by my page component that instantiated mycfc as mycfc.sanitize("test").
Sean
@Sean,
All pages in ColdFusion should have a variables scope. In a standard template, the Variables scope is the implicit scope. In a CFC, the variables scope is the private scope. In a custom tag, the variables scope is the implicit scope.
As far as why it's not working, it might be the way you are creating the dynamic names. You might want to try the array-notation when storing into the scope:
variables[ key ] = application.globalmethods[ key ]
That might help the references evaluate properly.
@Ben,
All right. Your idea worked splendidly! Now the API of the cfc is not changed using this scheme. Also, I stopped the loop-on-structkeys silliness and just used structappend.
Here is the whole package:
[Put in application.cfc:]
<cffunction name="onApplicationStart">
. . . snip . . .
<cfscript>
application.globalmethods = structnew();
keys = structkeylist(this);
for (i=1; i LTE listlen(keys); i=i+1) {
key = listgetat(keys,i);
if (iscustomfunction(this[key]) and left(lcase(key),2) NEQ "on")
structinsert(application.globalmethods, key, this[key]);
}
application.registerglobalmethods = this.registerglobalmethods;
</cfscript>
. . . snip . . .
</cffunction>
<cfinclude template="GlobalFunctions.cfm">
<cffunction name="registerglobalmethods" output="no">
<cfset StructAppend(variables, application.globalmethods, true)>
</cffunction>
Finally, in any cfc that uses a global function, call the register function:
<cfset application.registerglobalmethods();>
Now, you could skip the register function and just use:
<cfset StructAppend(variables, application.globalmethods, true)>
when ever a cfc needed to use global functions. But I think this is more readable.
Sean
@Sean,
I'm a huge fan of StructAppend(). I always felt that it was an un-used ColdFusion method.
Sean Corfield over at Mere Mortals offered a cleaner method for the code:
http://stannard.net.au/blog/index.cfm/2008/5/9/Using-Constants-in-ColdFusion-Components#c7E7025A1-3048-2D64-FCB1288CFBE390B4
application.globalmethods = structnew();
for (key in this) {
if (iscustomfunction(this[key]) and left(lcase(key),2) NEQ "on")
structinsert(application.globalmethods, key, this[key]);
}
@Sean,
I can't remember why the methods start with "on"? Looping is only cleaner if not all of the methods are meant to be copied over (or if the CFC has additional properties (not methods)). Generally, though, if all you have is a collection of methods, structAppend() would be the way to go, as far as I can see.
The code skips the registration of any application scope methods (methods defined in application.cfc) that are the CF application control methods:
onApplicationStart()
onRequestStart()
onSessionStart()
onError()
onApplicationEnd()
I just did that to cut down clutter. It's doubtful any page would want to call these methods (I'm assuming you have a url.reinit like block in onRequestStart to restart the app).
If you just had to have a custom global method that began with "on", like maybe onFormPost(), then you'd want to cut out of the code the left check:
left(lcase(key),2) NEQ "on"
Sean
@Sean,
Ohh, I see what you're doing - you're copying the methods out of Application.cfc into the application.globalMethods; I was confused a bit about where the methods were coming from.
I was wondering why you did not use the CGI scope instead of URL, now I know.
@Nelle,
Yeah, the CGI scope is read-only (as far as I can remember). While you can request anything from it (even keys that don't exist), I am pretty sure you can't alter it in any way.
@Ben,
you are right, unfortunately altering CGI scope is impossible. i learn something new every day :)
i ended up using the url scope as you suggested, but i fear as the UDF library grows, that name collision will create errors which are hard to debug (i already had my share of debuging errors coming from coldfusion trying to validate form variables based on their name, i think the field was called location_required).
@Nelle,
Yeah, I know what you mean. I think this is a fun experiment; but until (if ever) ColdFusion allows for this at a more "native" level, I am not sure if it's ever something I actually take advantage of.
Ben,
I'm not clear with the code: application.cfc.test()
In here I think test() refers to a function name? but on the other hand, this code indicates that test is a component name not a function's name:
<cfset application.cfc = createObject( "component", "Test" ) />
If I go back to your explanation codes, where the name of global UDF is UDF.cfc and in Application.cfc you are setting it as:
<cfset structAppend(url, createObject( "component", "UDF" ) ) />
Than how can I call getMessage function and passing parameters?
Is it:
<CFSET application.cfc.UDF.getMessage(parameter1,parameter2)? ?