Highlighted

How To call a method dynamically within a method chain

New Here ,
Mar 15, 2018

Copy link to clipboard

Copied

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??

Adobe Community Professional
Correct answer by Dave_Watts | Adobe Community Professional

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

Views

988

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more

How To call a method dynamically within a method chain

New Here ,
Mar 15, 2018

Copy link to clipboard

Copied

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??

Adobe Community Professional
Correct answer by Dave_Watts | Adobe Community Professional

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

Views

989

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Mar 15, 2018 0
Adobe Community Professional ,
Mar 15, 2018

Copy link to clipboard

Copied

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);

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Mar 15, 2018 0
LEGEND ,
Mar 16, 2018

Copy link to clipboard

Copied

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,

^ _ ^

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Mar 16, 2018 0
New Here ,
Mar 17, 2018

Copy link to clipboard

Copied

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.

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Mar 17, 2018 0
Adobe Community Professional ,
Mar 17, 2018

Copy link to clipboard

Copied

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

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Mar 17, 2018 0
Adobe Community Professional ,
Mar 17, 2018

Copy link to clipboard

Copied

<cfset myvar = 'Cat' >

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

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

Let's assume that this code is in somePage.cfm. Also assume that the code block

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

is in onRequestStart.

Then, the server-scoped variable, server.run, exists in somePage.cfm. Next consider the function variable, dynamicCatFunction. It exists within the context of the CFC, server.run("test"), but is undefined in the context of the current page, somePage.cfm. This is analogous to myStruct.myKey being defined in the current page, whereas myKey may be undefined as a separate variable.

As the context of the function variable, server.run('test').dynamicCatFunction, is the current page, so too is the context of any variable used within that function. Therefore the function variable, internalFunction, has to be defined in the current page. For example,

<cfset internalFunction = server.run('test').internalFunction >

<cfset myvar = 'Cat' >

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

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

Nevertheless, I find the following structure more stylish (with the same number of lines of code):

somePage.cfm

<cfset internalFunction = server.run('test').internalFunction >

<cfset tempCatFunction = server.run('test').dynamicCatFunction >

<cfset tempDogFunction = server.run('test').dynamicDogFunction >

<cfset myCatMessage = tempCatFunction(extraText='Snoop') >

<cfset myDogMessage = tempDogFunction(extraText='Snoop') >

/cfc/test.cfc

<cfcomponent>

     <cffunction name="internalFunction" returntype="boolean" >

          <cfreturn true>

     </cffunction>

     <cffunction name="dynamicCatFunction" returntype="string" >

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

          <cfif internalFunction() eq true >

               <cfreturn 'Hello world, #extraText# Cat!'>

          </cfif>

           <cfreturn "Default return string">

     </cffunction>

    

     <cffunction name="dynamicDogFunction" returntype="string" >

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

          <cfif internalFunction() eq true >

               <cfreturn 'Hello world, #extraText# Dog!'>

          </cfif>

           <cfreturn "Default return string">

     </cffunction>

</cfcomponent>

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Mar 17, 2018 0
New Here ,
Mar 18, 2018

Copy link to clipboard

Copied

Alright, so effectively I'm going to take this to mean that there is no other "official" way of doing this dynamically using supported built-in syntax, thus evaluate is the only real way to deal with this scenario, that's totally fine.

It's no real big deal; I'm only having to do it this way in one special-case where I am working with a pre-existing CFC file that just happens to use a half a dozen or so different functions (all of which just call SQL stored procedures) which are all identically named except for the last portion of the function names, which is then "filled in" based on a value residing on each row of a table being looped over.

In my testing, I have not noticed any performance decrease after changing this from previously using cfinvoke with the hashtags included in the method name, to now using my run function with evaluate wrapped around it.

As far as the syntax not being elegant, I couldn't disagree more; the dot-notation is built-in syntax and is elementary to follow what's going on. While yes, the "official" way to do it would be to use the "new" keyword and go from there (as I do within the function itself) but there are other nuances with that that I found made it a bit less "streamlined" than it could potentially be.

The way that the call lines up makes totally logical sense and is extremely easy to explain; even a non-programmer can read what effectively breaks down to "folder->folder->file->function->arguments" on one line, in essence literally providing a "road map" to exactly where the function itself resides starting at the root every single time, which is far better than anything else I've seen in other developers' code that I've had to work on, instead electing to do things like using cf_customtags (which MIGHT pull from a file in the same folder, OR from the root folder, OR from any other number of places specified in the cfadmin) or cfinvoke (which is itself a tag and thus can't be used the way that this method can)

Using the new keyword via. a UDF opens up possibilities of load-and-run function calling that other methods simply do not offer.

A simple example (not real-world, but syntactically correct):

<cfif server.run('security').ipwhitelist() eq true >

     <cfset haveOrDontHave = server.run('queries').getUser(123).recordCount ? 'have' : 'don't have' >

     <cfoutput>

          Welcome, we see that you #haveOrDontHave# users set up, please enter your info: <br>

          #server.run('output').contactForm('support')#

     </cfoutput>

</cfif>

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Mar 18, 2018 0
BKBK LATEST
Adobe Community Professional ,
Mar 18, 2018

Copy link to clipboard

Copied

michaelk69713248 wrote:

... so effectively I'm going to take this to mean that there is no other "official" way of doing this dynamically using supported built-in syntax, thus evaluate is the only real way to deal with this scenario...

Yes. It follows from:

... the server-scoped variable, server.run, exists in somePage.cfm. Next consider the function variable, dynamicCatFunction. It exists within the context of the CFC, server.run("test"), but is undefined in the context of the current page, somePage.cfm. This is analogous to myStruct.myKey being defined in the current page, whereas myKey may be undefined as a separate variable.

As the context of the function variable, server.run('test').dynamicCatFunction, is the current page, so too is the context of any variable used within that function.

So the only way to call the function within somePage.cfm, by means of dynamic variables, is to use dynamic evaluation. ColdFusion has just 2 functions capable of performing dynamic evaluation, Evaluate and IIf.

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Mar 18, 2018 0