Skip to main content
Participant
March 15, 2018
Answered

How To call a method dynamically within a method chain

  • March 15, 2018
  • 2 replies
  • 2898 views

I have been struggling with this issue for the majority of the day now, I am basically trying to figure out how to use a variable to specify a function name dynamically while using chaining in order to load and run a function from within a CFC.

Some background:

I designed a simple function which takes 2 arguments and has 1 hard-coded variable:

<cfargument name="cfcname" required="yes">

<cfargument name="args">

<cfset local.dir = "cfc">

if(isdefined("arguments.args")){

     if(isstruct(arguments.args)){

          REQUEST[cfcname] = new '#local.dir#.#cfcname#'(argumentcollection=arguments.args);

     } else {

          REQUEST[cfcname] = new '#local.dir#.#cfcname#'(arguments.args);

     }

} else {

     REQUEST[cfcname] = new '#local.dir#.#cfcname#'();

}

return REQUEST[cfcname];

I call the function "run" and then put it into the server scope after the function is defined, so that I can reference it anywhere on the server with the following syntax:

server.run('somefolder.somecfcfile').methodnamehere(argument1=thisvalue,argument2=thatvalue);

The end result of which is that it will:

look in the local.dir folder on the root of the server.

Drill down into the "somefolder" folder

open the "somecfcfile.cfc" file

run the "methodnamehere" function

and pass argument1 and argument 2 to the function... all in one line of code.

It works elegantly and has helped me to drastically minimize the code footprint for various bits of functionality needed in dozens or hundreds of files on a template-driven site for a client of mine.

HOWEVER, I have run into something of a brick wall with it: I CAN NOT seem to specify the function name using a variable; it was MY HOPE that something like this would work:

myfunctionname = "methodnamehere";

server.run('somefolder.somecfcfile').#myfunctionname#(argument1=thisvalue,argument2=thatvalue);

however, that does not work.

The CLOSEST I have gotten to getting this to work is something like this:

evaluate("server.run('somefolder.somecfcfile').#myfunctionname#(argument1=thisvalue,argument2=thatvalue)");

which actually works and does what I want it to do... but is there no form of "supported" syntax that can accomplish this without having to resort to evaluate??

    This topic has been closed for replies.
    Correct answer Dave Watts

    First, I'm going to mildly disagree with WolfShade's blanket statement that using Evaluate is bad. If you can avoid using it, you probably should, but if you want to execute an arbitrary string of text as if it were code, that's what it's for. There's a lot of feeling that Evaluate is bad because it was often overused in the past, but it's there for a reason.

    Second, though, I'm not sure that what you're doing counts as "elegant". Fewer lines of code is not how elegance is defined. Code that will be difficult to debug or understand later is not elegant. Everyone knows how objects, methods, etc work, and you are trying to circumvent that in order to have fewer lines of code. So, I think that you should probably write a few more lines of code when you want to invoke an object, instead of trying to make some generic syntax that can run object methods that you don't know until runtime.

    Third, if you insist on doing it this way anyway, you will either have to use Evaluate or rewrite your objects so that they all have some generic method that can act as an entry point for whatever you want to do. For example, you could have a method in each object called "dothis" which does nothing except use conditional logic to figure out what method you actually want to invoke, and invokes it on your behalf.

    Dave Watts, Fig Leaf Software

    2 replies

    WolfShade
    Legend
    March 16, 2018

    One should never, ever use evaluate().  Ever.  Dangerous.  Bad mojo.

    As BKBK​ has pointed out, the hashtags may be getting in the way.  Hashtags should only be used in strings, or for output.  I don't have any evidence with me, now, but I have heard about using hashtags when unnecessary can slow things down.

    There was something else I was going to add to this, and now I've completely blanked on it.  If I think of it, again, I'll come back and report.

    V/r,

    ^ _ ^

    Participant
    March 17, 2018

    Well, the first suggestion would not really be valid; I think perhaps I wasn't clear on what the purpose of the dynamic portion of the call is; it's not a reference to an existing function, it's SUPPOSED to be evaluated as a string, let me break it down:

    (IMPORTANT: the "server.run()" function is NOT being run from INSIDE the cfc file; it is being run from ANYWHERE on the server, any CFM file in any directory anywhere, the purpose of the run function is to LOAD AND RUN functions contained in cfc files, by passing the directory path, filename, function name and arguments to it using dot-notation)

    Since this site is, for some reason, not allowing me to edit my original post, let me provide an updated and more "bare-bones" version of the function and how it gets defined into the server scope, also eliminating 2 out of 3 ways that it can reference the cfc (leaving only the way it's being used in my example):

    <cfif not isdefined("server.run")>

         <cffunction name="run" output="false">

              <cfargument name="cfcname" required="yes">

              <cfreturn new 'cfc.#cfcname#'() >

         </cffunction>

         <cfset server.run = variables.run >

    </cfif>

    The above single-line function included in any cfm file, or ideally in the onRequest portion of an application.cfc file, should be sufficient to replicate what I am talking about and activate a basic "server.run" function on any CF server anyone wants to test this out on.

    As long as you make a root-level folder named "cfc" and then inside of there place a cfc file containing two functions (one which references the other inside of itself) then you'd have a valid test scenario.

    example CFC file located in /cfc/test.cfc

    <cfcomponent>

         <cffunction name="internalFunction">

              <cfreturn true>

         </cffunction>

         <cffunction name="dynamicCatFunction">

              <cfargument name="extraText" required="yes">

              <cfif internalFunction() eq true >

                   <cfreturn 'hello world #extraText#!'>

              </cfif>

         </cffunction>

    </cfcomponent>

    The objective: in some other random CFM file, call the "dynamicCatFunction" using a variable to reference the word "Cat" in a way that works, here's an example which DOES WORK, but has to use evaluate to do it:

    <cfset myvar = 'Cat' >

    <cfset myMessage = evaluate("server.run('test').dynamic#myvar#Function(extraText='bloop')") >

    <cfoutput>#myMessage#</cfoutput>

    Note that the inclusion of the "internalFunction" call that the other function references is essential for this test, because I am not just trying to instantiate that one function, I need to do so in a way where the one function can still reference the other function contained within the same CFC file without breaking.

    Here's the only other clue I have:

    <cfset myvar = 'Cat' >

    <cfset tempFunction = server.run('test')['dynamic#myvar#Function'] >

    WILL DEFINE THE FUNCTION, BUT attempting to actually CALL it:

    <cfset myMessage = tempFunction(extraText='bloop') >

    would ultimately give you an error because "internalFunction is undefined" since the bracket-notation call only instantiates the function in a stand-alone way as an object, rather than being inclusive of the entire CFC which is necessary for it to be able to reference other functions contained within the CFC file.

    Dave WattsCommunity ExpertCorrect answer
    Community Expert
    March 18, 2018

    First, I'm going to mildly disagree with WolfShade's blanket statement that using Evaluate is bad. If you can avoid using it, you probably should, but if you want to execute an arbitrary string of text as if it were code, that's what it's for. There's a lot of feeling that Evaluate is bad because it was often overused in the past, but it's there for a reason.

    Second, though, I'm not sure that what you're doing counts as "elegant". Fewer lines of code is not how elegance is defined. Code that will be difficult to debug or understand later is not elegant. Everyone knows how objects, methods, etc work, and you are trying to circumvent that in order to have fewer lines of code. So, I think that you should probably write a few more lines of code when you want to invoke an object, instead of trying to make some generic syntax that can run object methods that you don't know until runtime.

    Third, if you insist on doing it this way anyway, you will either have to use Evaluate or rewrite your objects so that they all have some generic method that can act as an entry point for whatever you want to do. For example, you could have a method in each object called "dothis" which does nothing except use conditional logic to figure out what method you actually want to invoke, and invokes it on your behalf.

    Dave Watts, Fig Leaf Software

    Dave Watts, Eidolon LLC
    BKBK
    Community Expert
    Community Expert
    March 16, 2018

    myfunctionname = "methodnamehere";

    server.run('somefolder.somecfcfile').#myfunctionname#(argument1=thisvalue,argument2=thatva lue);

    And this?

    myfunctionname = methodnamehere;

    server.run('somefolder.somecfcfile').myfunctionname(argument1=thisvalue,argument2=thatvalue);