How can I call a variable name as part of a function name in ColdFusion?
I'm writing a function that loops through some info on a registration page. Within the loop I'm trying to call functions based on an array. What I'm having problems with is actually calling the functions properly, 开发者_如何转开发because I'm trying to incorporate a variable as part of the function name.
Here's my code:
<cfscript>
fields = arraynew(1);
fields[1] = 'r_email';
fields[2] = 'r_uname';
fields[3] = 'r_pass';
for(i = 1; i lte arraylen(fields); i = i + 1)
{
func = fields[i].split('r_');
func = 'validate_#func[2]#(#fields[i]#)';
}
</cfscript>
So, I have three functions: validate_email, validate_uname, validate_pass. If I throw in a writeoutput(), and attempt to output the results of the function, they do not work.
Here's that code:
<cfscript>
fields = arraynew(1);
fields[1] = 'r_email';
fields[2] = 'r_uname';
fields[3] = 'r_pass';
for(i = 1; i lte arraylen(fields); i = i + 1)
{
func = fields[i].split('r_');
func = 'validate_#func[2]#(#fields[i]#)';
writeoutput('#func#');
}
</cfscript>
Now, I understand that when you're using writeoutput(), and you're calling a function, you need the hash symbol on either end. So, let's say I write it like this:
writeoutput('#validate_#func[2]#(#fields[i]#)#');
It won't work, because the second hash symbol cancels out the function call. This is how the function should ultimately look (email example):
writeoutput('#validate_email('email@site.com')#');
How can I replace 'email' (#validate_email...) with the proper variable name, and still have the function work? I hope I've made this understandable!
Functions are variables too, so in the same way you can use bracket notation for arrays, structs, and scopes, you can use this to access dynamic variable names (and thus dynamic function names)
For example:
<cfloop index="Field" list="email,uname,pass">
<cfset Result = Variables['validate_'&Field]( Variables['r_'&Field] ) />
...
</cfloop>
Well... not quite. Due to a bug in Adobe ColdFusion, that doesn't work like that (though it does in other CFML engines, like Railo), and you have to split it into two lines, like this:
<cfloop index="Field" list="email,uname,pass">
<cfset TmpFunc = Variables['validate_'&Field] />
<cfset Result = TmpFunc( Variables['r_'&Field] ) />
...
</cfloop>
(This assumes both function and fields are in the variables
scope, if they're not you need to refer to whichever scope they're in.)
This method does have an issue if the function was in an object with state, it loses reference to those variables.
On CF10, there is the invoke
function. Earlier versions of CF need to use the cfinvoke
tag.
(As a side note, CF10 did add the inverse ability of referencing function results with bracket notation, i.e. doSomething()[key]
which comes in useful at times.)
Peter's answer is spot on (except the "bug" reference that I commented on). One other option if one is not in the middle of a CFScript block is that <cfinvoke> takes a string as its METHOD attribute value, and that can - obviously - take any dynamic value one likes.
This ain't so uch help for your specific situation, but it's handy to bear in mind.
I would NOT use the evaluate() approach.
I will add that before ColdFusion 6.1 and the move to Java, evaluate() was always considered anathema to performance, but since 6.1 all it does is evaluate the expression and then create an inline PageContext with the included string as its code.
Very slick, very performant.
So, the simplest and most straightforward way to achieve your aims is:
<cfset result = evaluate("validate_#Field#(variables.r_#Field#)") />
So assuming you have a field named "username", this will be the equivalent of following method call:
<cfset result = validate_username(variables.r_username) />
and whatever it returns will be assigned to variables.result.
In tests it actually outperforms the bracket-notation technique of reassigning a method to a new name. I don't have stats here at the moment but it's faster by a substantial margin because an include is faster (and happens in a different phase of parsing/compilation) than a variable assignment.
One other approach to this issue is to use a UDF such as the following:
<cffunction name="callMethod">
<cfargument name="methodName" type="string" required="true" />
<cfargument name="methodArgs" type="struct" default="#{}#" />
<cfset var rslt = 0 />
<cfinvoke method="#arguments.methodName#" argumentcollection="#arguments.methodArgs#" returnvariable="rslt" />
<!--- account for the possibility that the method call wiped out the rslt variable --->
<cfif structKeyExists(local,'rslt')>
<cfreturn rslt />
</cfif>
</cffunction>
If you need to you could this:
<cfinclude template="invokerudf.cfm" />
<cfscript>...</cfscript>
or wrap it up in tags and toss it into a CFC:
<cfset retval = createObject("methodinvoker").callMethod(methodName,methodArgs) />
There are many, MANY ways to make this happen... just tossing various ideas out there now.
精彩评论