4

If I have

<cfset arr_arguments = ["a","b","c"]>
<cfunction name="someFunction">
 <cfargument name="someArgumentOne">
 <cfargument name="someArgumentTwo">
 <cfargument name="someArgumentThree">

</cffunction>

Is there any way to invoke someFunction with the arguments arr_arguments, similar to someFunction("a","b","c")? I of course know I can use argumentCollection to pass a (keyed) structure to a function, but I am specifically asking about passing in an (keyless) array. In JS this could be easily done with someFunction.apply(this,arr_arguments), but in coldfusion I just can't find any way of doing this.

David Mulder
  • 26,123
  • 9
  • 51
  • 114
  • I'm probably misunderstanding your question, or the reason for the requirement but could you use `someFunction.apply(arrayToList(arr_arguments))` I've never tried it. – genericHCU Apr 22 '13 at 12:20
  • `someFunction.apply()` is javascript syntax, not CF. And the reason for the requirement is that the arguments are generated from an external keyless source and can go to various different functions based on information in that same source. – David Mulder Apr 22 '13 at 12:22
  • I don't see any reason you can't pass an array to a cffunction using `` – genericHCU Apr 22 '13 at 12:24
  • if you're still trying to pass the values of the array as individual parameters, you can still try `someFunction(arrayToList(arr_arguments))` – genericHCU Apr 22 '13 at 12:27
  • Because I don't want to pass an array, but 3 different separate arguments. As I say in my question `someFunction("a","b","c")` and **not** `someFunction(["a","b","c"])`. – David Mulder Apr 22 '13 at 12:27
  • And `someFunction(arrayToList(arr_arguments))` is again going to call `someFunction("a,b,c")` which is **entirely** different from `someFunction("a","b","c")`... like not even similar. – David Mulder Apr 22 '13 at 12:28
  • Nope, Like I said, I hadn't tried it before. CFFunction isn't as easy to manipulate as sql. I also tried `#someFunction(x)#` but that didn't work either. – genericHCU Apr 22 '13 at 12:42
  • Of course it doesn't, because in the end you're just generating a string which you pass as the first argument to the function. If string level manipulation like that would work it would mean no end of trouble from a security and development perspective. – David Mulder Apr 22 '13 at 12:43
  • You're right, stupid idea. is arr_arguments guaranteed to have the correct number of elements? – genericHCU Apr 22 '13 at 13:24

5 Answers5

5

Unnamed arguments are passed into a function as a structure with numeric keys matching the positions of the arguments defined in the function arguments. So instead of passing the named arguments, you can convert your array to a struct with numeric keys, and then pass the struct in with argumentCollection:

<cfset arr_arguments = {"1"="a","2"="b","3"="c"}>
<cfset someFunction(argumentCollection=arr_arguments)>

You can easily convert an array to a struct with numeric keys like this:

<cfset args = {}>
<cfloop from="1" to="#arrayLen(arr_arguments)#" index="i">
    <cfset args[i] = arr_arguments[i]>
</cfloop>
imthepitts
  • 1,647
  • 10
  • 9
2

In Coldfusion 10, you can use the invoke function to do this. It appears to be an undocumented way to pass an array of arguments in order. Example in cfscript:

invoke('', 'someFunction', ['a','b','c']);

The first argument of invoke is the component name (or an empty string for UDFs). The second is the function name, and third is an argument array. Note that both component and function names must be passed as strings.

I tested this both with and without defined argument names and the order of arguments was preserved.

Russ
  • 1,931
  • 1
  • 14
  • 15
1

Ok if you are looking to call a function with an arbitary array of arguments the best option I can suggest is to make a wrapper function. See below example code

<cfscript>
    function testMe(
        required string arg1,
        required string arg2
    ) {
        writedump(arguments);
    }

    function invokeFunction(
        required inFunction,
        required array functionArguments
    ) {
        var stcArguments = {};
        // Introspect to get the method parameters
        var arrFunctionArguments = GetMetaData(arguments.inFunction).parameters;
        // Now figure out what we are iterating to
        var numArgumentsToParse = Min(ArrayLen(arguments.functionArguments),ArrayLen(arrFunctionArguments));
        // Populate the arguments structure
        for (var i=1;i<=numArgumentsToParse;i++) {
            stcArguments[arrFunctionArguments[i].name] = arguments.functionArguments[i];
        }
        // And try to call it
        return arguments.inFunction(
            argumentCollection = stcArguments
        );
    }

    invokeFunction(testMe,[1,2]);           // Works fine
    invokeFunction(testMe,[1,2,3,4]);       // Just skips 3 and 4
//  invokeFunction(testMe,[1]);             // Errors due to not enough arguments
//  invokeFunction(fakeFunctionName,[1]);   // Errors due to undefined function
</cfscript>

This will of course error if either of the following happen

  • One or more arguments are not of correct type
  • The function doesn't actually exist
  • Not all required arguments are passed in

But you can handle that outside. This will use introspection to figure out the argument names, populate the structure accordingly, and call the requested function.

  • if I understand his question now, he has an array that contains values that are used by multiple functions. I'm guessing the functions use different argument names that prevent him from using a single structure and argumentCollection and he's looking for a super shorthand way to use the array as multiple parameters for multiple functions. – genericHCU Apr 22 '13 at 13:53
  • Hmm, I may have got the wrong end of the stick here then. That sounds dangerously like a case for Evaluate, as much as I hate using that :\ BUT I have an idea, just need a few mins – Simon at The Access Group Apr 22 '13 at 13:55
  • Well, the reason for the entire thing in the first place is as the basis of a framework, sort of like codeigniter (but different). Still the part of passing in the url 'arguments' into functions of various controller components is similar. So the source (e.g. "controller/page/argument1/argument2") doesn't provide information about which parameter is what or anything along those lines, but as a developer just writing ` do stuff ` to define \shop\item\3103 is really natural. – David Mulder Apr 22 '13 at 14:02
  • Put simply: I never write but this is generated from the url scope dynamically. So it doesn't need to be short or anything like that either. I am now considering a new setup which fakes an argument scope, going to try that in a sec. – David Mulder Apr 22 '13 at 14:05
  • If you check my updated option, this will take in the function and an array of arguments, and use introspection to handle them. You can easily work from that to add in any more advanced checks / handling you wish in the wrapper function. I've used similar to get around differences in Railo and Coldfusion error handling – Simon at The Access Group Apr 22 '13 at 14:08
  • Very cool! Figured out a crazy solution myself :P going to write it out as an answer below – David Mulder Apr 22 '13 at 14:15
  • I've just posted a near-identical answer. Great minds and all that. – barnyr Apr 22 '13 at 14:19
1

Here's a UDF which will do that for you:

<cfscript>
function test_function(a,b,c){      
    writeOutput("arguments.a"&arguments.a&"<br>");
    writeOutput("arguments.b"&arguments.b&"<br>");
    writeOutput("arguments.c"&arguments.c&"<br>");

    return arguments.a & "::" & arguments.b & "::" & arguments.c;
}

function invoke_positional(func,arr){
    var metaData = GetMetaData(func);
    var args={};
    for(var pos=1;pos<=ArrayLen(arr);pos++){
        args[metaData.parameters[pos].name]=arr[pos];
    }
    return arguments.func(argumentCollection=args);
}

data = ["StringOne","StringTwo",22];

result = invoke_positional(test_function,data);

</cfscript>

<cfoutput>
<p>
Returned: #result#
</p>
</cfoutput>

invoke_positional works by using the UDF's metadata to build up a named set of parameters and then uses them with argumentCollection=

barnyr
  • 5,678
  • 21
  • 28
  • Apart from the fact I posted this quarter na hour ago, this could error if you have more arguments in invoke_positional's arr argument than there are arguments within function func hence the check I included to prevent that occurring. – Simon at The Access Group Apr 22 '13 at 14:23
  • Agreed, yours was there first and has a fuller write up. When I started writing mine up, yours wasn't there and I didn't see it until posting mine. Yours ought to be accepted in my opinion. I'll take mine down – barnyr Apr 22 '13 at 15:08
0

So, one option I have found out to work is to create an arguments object separately from the actual real arguments scope and simply add the values to it using a numeric index.

<cfset args_input = createObject("java","com.naryx.tagfusion.cfm.engine.cfArgStructData").init()>
<cfloop from="1" to="#arraylen(arr_arguments)#" index="i">
    <cfset args_input[i] = arr_arguments[i]>
</cfloop>
<cfset someFunction(argumentCollection=args_input)>

If you encounter this situation using adobe coldfusion I would advice you to try

<cfset correctClass = arguments.getClass().getName()>

To get the string to pass to the createObject. Or if that doesn't work just have a dedicated function to return an empty arguments object

<cfset args_input = this.generateArgumentObject()>

and

<cffunction name="generateArgumentObject">
    <cfreturn arguments>
</cffunction>

Oh well, all I know for sure is that it will work for sure in OpenBD and based on a post I saw in the past on Ben Nadel's blog I am pretty sure this should work in Adobe ColdFusion as well.

David Mulder
  • 26,123
  • 9
  • 51
  • 114
  • While I appreciate that you know what you are looking for, not entirely sure I see how this correlates to your own question? Also in coldfusion the struct definition should be `{test = "test2}` instead of using a `:` – Simon at The Access Group Apr 22 '13 at 14:27
  • The `args_input` can (unlike the array) be passed straight to the `argumentCollection`, without the need to inspect the relevant function. Does that make it clearer? – David Mulder Apr 22 '13 at 14:29
  • Updated the answer to make it a bit clearer. Of course all 3 answers would definitely work, right now I am tempted to use this solution because it seems kinda neater to not do any function inspection, although it's probably not as expensive as it sounds. Might run some benchmarks when I have some time comparing the two options against eachother. – David Mulder Apr 22 '13 at 14:31