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 »
67 Comments

That's truly twisted. I salute you.


Jun 17, 2010 at 8:41 AM // reply »
11,246 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 »
11,246 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 »
11,246 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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
May 23, 2013 at 4:26 PM
ColdFusion QueryAppend( qOne, qTwo )
@Heather, Glad people are still getting value out of this! ... read »
May 23, 2013 at 3:49 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, I meant the code at the bottom (not the video). I did try to experiment with an intermediary variable, like: value = users.id[ i ]; arrayContains( userIDs, value ); ... but t ... read »
May 23, 2013 at 11:06 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Are you talking about As Number: YES As String: YES As Java: YES? If so, that's with 3 different ways of referencing the constant 1, not users.id[1]. Query object references(*) are what seem ... read »
May 23, 2013 at 9:55 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dan, According to the CF Admin, I'm running Java "1.6.0_45". As far as the DB column, in the database it's an INT. I'll see if I can dig into what CF sees it as. @WebManWalking, But h ... read »
May 23, 2013 at 9:49 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, I think the problem is that we're used to loose typing in ColdFusion, like JavaScript. If a value is a number but it's needed in an expression to be a string, noooo problem. I've encountered ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools