Skip to main content
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Justine Arreche
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Justine Arreche

Generic Invocation Wrapper For Core ColdFusion Functions

By
Published in Comments (8)

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.

Want to use code from this post? Check out the license.

Reader Comments

15,902 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.

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

15,902 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.

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()#"

15,902 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()).

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel