Generic Invocation Wrapper For Core ColdFusion Functions

Posted June 16, 2010 at 9:24 PM by Ben Nadel

Tags: ColdFusion

I happen to love ColdFusion's CFInvoke and CFInvokeArgument tags. They allow user defined functions (UDF) and ColdFusion component functions to be dynamically invoked using variable values. They don't need to be used all that often; but, when the time comes, these two tags can really make life easy. When it comes to core ColdFusion functions, however, these two tags can't help us. Right now, if you want to execute core ColdFusion functions dynamically, you have to fall back on the Evaluate() method. Now, there's nothing wrong with Evaluate(); but, it is neither as easy to use nor is it as flexible as CFInvoke. As such, I wanted to create a component-based proxy to allow CFInvoke-based execution of core ColdFusion functions.

 
 
 
 
 
 
 
 
 
 

The idea is straightforward: it's still powered by the traditional Evaluate()-based approach; but, it is made generic through the use of ColdFusion's onMissingMethod() function. I have a single component, FunctionProxy.cfc, that has a single method, onMissingMethod(). The onMissingMethod() method contains the code that invokes the requested method using Evaluate(). And since onMissingMethod() will accept any method name thrown at it, it allows all core ColdFusion functions to be invoked by-proxy.

FunctionProxy.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a generic ColdFusion function wrapper for use with reflection-type invokation.">
  •  
  •  
  • <cffunction
  • name="onMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I provide ordered-argument-based invocation of core ColdFusion method.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="missingMethodName"
  • type="string"
  • required="true"
  • hint="I am the name of the core ColdFusion method."
  • />
  •  
  • <cfargument
  • name="missingMethodArguments"
  • type="any"
  • required="true"
  • hint="I am the arguments collection used to invoke the missing method."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Create an array into which we can store the collection
  • of index-based argument references.
  • --->
  • <cfset local.orderedArguments = [] />
  •  
  • <!--- Build up the argument references. --->
  • <cfloop
  • index="local.argumentIndex"
  • from="1"
  • to="#arrayLen( arguments )#"
  • step="1">
  •  
  • <cfset local.orderedArguments[ local.argumentIndex ] = "arguments.missingMethodArguments[ #local.argumentIndex# ]" />
  •  
  • </cfloop>
  •  
  • <!---
  • Evaluate and return the method execution. We are
  • getting the page-based function reference since it
  • appears that not all core ColdFusion methods can be
  • invoked directly with Evaluate().
  • --->
  • <cfreturn Evaluate(
  • "GetPageContext().GetPage().#arguments.missingMethodName#" &
  • "( "
  • & arrayToList( local.orderedArguments, ", " ) &
  • " )"
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, the onMissingMethod() method is programmatically building a snippet of code in the form of:

Evaluate( "function( x, y, z, .... )" )

You'll notice, however, that I am not simply using the name of the target function; rather, I am using the function reference made available within the current page context. I am only doing this because I found out a while back that Image-based functions don't play well with Evaluate(). That might be an old bug, since fixed; but for now, until I re-test, I figured I'd rely on the more universally proven approach.

With this FunctionProxy.cfc ColdFusion component in place, we can now use CFInvoke and CFInvokeArgument to dynamically execute core ColdFusion functions. In the following demo, I am using three different proxy-approaches:

  • <!--- Create an instance of the ColdFusion proxy. --->
  • <cfset proxy = createObject( "component", "FunctionProxy" ) />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Invoke the RandRange() function with CFInvoke. --->
  • <cfinvoke
  • returnvariable="value"
  • component="#proxy#"
  • method="randRange">
  •  
  • <cfinvokeargument name="1" value="10" />
  • <cfinvokeargument name="2" value="20" />
  • </cfinvoke>
  •  
  • <!--- Output the random value. --->
  • <cfoutput>
  •  
  • Random Value: #value#
  •  
  • </cfoutput>
  •  
  •  
  • <br>
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br>
  •  
  •  
  • <!--- Create a struct for use with argumentCollection. --->
  • <cfset args = {} />
  • <cfset args[ "1" ] = "10" />
  • <cfset args[ "2" ] = "20" />
  •  
  • <!---
  • Invoke the RandRange() function with CFInvoke. Notice that
  • this time, we are using argumentCollection instead of
  • multiple CFInvokeArgument.
  • --->
  • <cfinvoke
  • returnvariable="value"
  • component="#proxy#"
  • method="randRange"
  • argumentCollection="#args#"
  • />
  •  
  • <!--- Output the random value. --->
  • <cfoutput>
  •  
  • Random Value: #value#
  •  
  • </cfoutput>
  •  
  •  
  • <br>
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br>
  •  
  •  
  • <!---
  • This time, I'm just going to invoke it without andy fancy
  • reflection (as I might a standard method).
  • --->
  • <cfset value = proxy.randRange( 10, 20 ) />
  •  
  • <!--- Output the random value. --->
  • <cfoutput>
  •  
  • Random Value: #value#
  •  
  • </cfoutput>

As you can see, FunctionProxy.cfc allows us to dynamically invoke core functions using both individually defined arguments (CFInvokeArgument) and a collection of arguments (ArgumentCollection). Because the core ColdFusion functions all rely on ordered arguments, you have to name the arguments using their invocation index. When we run the above code, we get the following output:

Random Value: 16

Random Value: 18

Random Value: 10

If you think the use cases for CFInvoke are few and far between, the use cases for dynamically executing core ColdFusion functions are even more rare. But, if you do need to use a more reflection-based approach to core function invocation, the ability to leverage CFInvoke on top of the core functions can be super useful. Ideally, what I'd like to see down the road is CFInvoke's functionality extended to include core ColdFusion functions.




Reader Comments

Jun 17, 2010 at 12:16 AM // reply »
66 Comments

That's truly twisted. I salute you.


Jun 17, 2010 at 8:41 AM // reply »
10,640 Comments

@Rick,

Now I just have to come up with a reason to use it :) I know I've wanted it in the past, but it's always so hard to remember for what. Yesterday, I only wanted it for some R&D in CF9... then I remembered that my CF9 is bugging on out on me.


Jun 17, 2010 at 8:59 AM // reply »
5 Comments

Great!


Jun 17, 2010 at 10:39 AM // reply »
7 Comments

Hey Ben

I'm kind of a fan of that last way of getting the result: ie.
<cfset value = proxy.randRange( 10, 20 ) />
instead of using cfinvoke. It seems cleaner and simpler to me.

Is there any down side of using that.

Cheers
Marty


Jun 17, 2010 at 10:41 AM // reply »
10,640 Comments

@Martin,

The point wasn't just to simply invoke the method. After all, if you just want to call the method using it's pre-defined name, then you could simply call it directly:

randRange( 10, 20 )

The point of the proxy is to *allow* you to be able to invoke the method using CFInvoke if you wanted to.


Jun 17, 2010 at 10:52 AM // reply »
19 Comments

Jeebus ColdFusion is cool. I was going to do this when you tweeted about it @Ben but I was too busy working ;)

@Martin - the problem with proxy.randrange() style is that you can't do proxy.#whateverCFfunctionIwant#() for syntax reasons. Say for instance at runtime you want to use either ArrayMax or ArrayMin depending on criteria X - with cfinvoke you can skip a cfif block or even encapsulate the logic with a function, like cfinvoke method="#MinOrMax()#"


Jun 17, 2010 at 10:57 AM // reply »
10,640 Comments

@Darren,

Yeah, CF is super awesome :)

I like your ArrayMax/ArrayMin example. Thinking back, I think one of the places I really wanted this was allowing someone to toggle between the "Case" and "NoCase" functions (ex. reReplace() / reReplaceNoCase()).


Jun 17, 2010 at 11:02 AM // reply »
7 Comments

@Ben, @Darren
Thanks - that's just what I needed to know.
Cheers
Marty


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »