invokeMethod() - How to make a dynamic method call

Coldfusion Add comments

A few months ago I caught myself building a method that would invoke another method from within a component. Basically something similar to functionality provided by the “cfinvoke” tag, but could be used through cfscript or strait against a component via the “cfset” tag. I needed this functionality because I needed to dynamically call a method and at the time was difficult to use the “cfinvoke” tag. I’ve also noticed a few entries on mailing lists lately asking on how to do this, so here it is.

Here is the method:
NOTE: The best placement for a method of this caliber would be within a super class like “object.cfc”, which would be extended from a component that would need this type of functionality.
 

<cffunction name="invokeMethod" access="public" returntype="Any" output="false">
     
<cfargument name="method" type="string" required="true" />
     
<cfargument name="argcollection" type="struct" required="false" default="#structNew()#" />         

      <cfset var returnVar = "" />             

      <!--- runs cfinvoke on self to run an internal method ---> 
     
<cfinvoke component="#this#" method="#arguments.method#" argumentcollection="#arguments.argcollection#" returnvariable="returnVar" />

      <!--- returns the variables that cfinvoke work return --->
     
<!--- will only return a variables if one is present --->
     
<cfif isDefined("returnVar")>
           
<cfreturn returnVar />
     
</cfif>
</cffunction>
  


Here is how it can be used:

<!--- this could come from anywhere (form, event, url, etc) --->
<cfset collection = "GroupById" />

<!--- here we setup the arguments that would need to be passed to the method that would be called --->
<cfset collection = structNew() />
<cfset
collection["id"] = 1 />

<!--- now we call invokeMethod, pass it the method name that needs to be called as well as the collection that would be used --->
<cfset result = object.invokeMethod(method:"get#dynamicName#", argCollection:collection) />

15 responses to “invokeMethod() - How to make a dynamic method call”

  1. Matt Williams Says:
    Hey Pay,
    I think I must be missing something. Why can't you just do a straight invoke? (assuming method to call is in same component of calling code)
    <cfinvoke method="get#dynamicName#"
    argumentcollection="#collection#"
    returnVar="result" />
  2. Matt Williams Says:
    Hey Pay,
    I think I must be missing something. Why can't you just do a straight invoke? (assuming method to call is in same component of calling code)
    <cfinvoke method="get#dynamicName#"
    argumentcollection="#collection#"
    returnVar="result" />
  3. Patrick Santora Says:
    @Matt

    Good question. A few months ago I had a case where I was doing logging through a tag and the message content needed to be dynamic. Parts of the message had deeper dynamic properties (Example: getId() or getName() depending on higher up processes).

    If I used cfinvoke it would require me to build in a new var scoped variable that would house the return cfinvoke variable (since I was logging within a method), so I build the invokeMethod method that could allow me to simply do the same, but within the main logging call itself.
  4. Patrick Santora Says:
    @Matt

    Another way to see what I was up against was by the following:

    Without using invokeMethod I would need to do this:
    <cffunction ....

    <cfset var returnVar = "" />
    <cfinvoke .... returnVariable = returnVar />
    <cfset logger...(message:"message #returnVar# />

    </cffunction ....

    With invokeMethod I can do this:
    <cffunction ....

    <cfset logger...(message:"message #object.invokeMethod("get#var#")) />

    </cffunction ....
  5. Shawn Grigson Says:
    I'm surprised and amazed so far that no one has pointed out something to you that you may very well be unaware of:

    You can call a cfc, and the methods on that cfc, without ever using CFINVOKE.

    I've been working with CF since it became an object-oriented (or at least more object-oriented) language for about 4 years now, and in that time I have used CFINVOKE exactly twice, and that was to call webservices.

    Here's how you call a cfc without cfinvoke:

    <cfscript>
    myObj = CreateObject('component','dot.separated.path.to.your.cfc').method();
    </cfscript>

    In that case, I've just set a variable called 'myObj' that is equal to the output of my cfc's 'method()' function.

    As for the rest of your use case, I'm not really sure I understand it. Why would you need to use cfinvoke to call a dynamic method? Can't you just use the Evaluate() function?

    myObj = Evaluate("CreateObject('component','#mycomponent#')." & "#myMethod#()");

    Maybe I'm missing something. I sure hope so. Because it looks like no one has pointed out the obvious fact that CFCs don't require the CFINVOKE tag.
  6. Pat Says:
    @Shawn

    If I did not know createObject I would have to shoot myself :-). Fortunately I do and use it often.

    Your example of how to use createObject is good if you want to use the component only once (like you mention), but normally I use objects/methods more than once requiring me to use createObject once. This allows me to keep memory low (not creating multiple objects) and use the same inner workings of the component as many times as I want.

    Note that your example could shed issues if you plan on passing the object throughout your components (small example: you would need each method to return "this" (object) so that you have a pointer to it which could then be passed. In a heavy OOP framework environment using services, beans, etc like Mach-II or Fusebox (to name a few) objects are passed all over the place, so this is key.

    I should point out that my example is just my 2 cents if you happen to be in similar environment that I work in (passing objects around to listeners, utils, services, factories, etc) as well as have the limitation to have your code in cfscript (hence not being able to use cfinvoke). Also a key note is that this method would reside within a possible super class that other components would extend to allowing dynamic method calls when combining variables to help figure out the variable name from outside of the component. You never know what you may run into when you hit crazy OOP applications. :-)

    My issue/example was I needed to log an entry and part of the entry needed to be information that resided within an object. This would be easy if you just did obj.getName() for example, but the log needed to be dynamic, meaning it may need have gotten "title" (getTitle) from the object instead.
    Now the code would look something like this (an logging object needing information from another object):
    <cfset logger.log(entry="Bean accessor get#arguments.accessor# was invoked and value #obj.invokeMethod('get#arguments.accessor#')# was returned") />

    I hope this helps answer your question.
  7. Pat Says:
    @Shawn

    BTW. The evaluate method is somewhat discouraged to use whenever possible as it can produce performance issues.
  8. Shawn Says:
    Okay, sorry about misunderstanding about createObject. ;) But now I think I understand the issue you were having a bit better.

    I'm not unfamiliar with many CF frameworks, in particular ColdSpring is very good at managing the issues you discuss about object instantiation. You can setup ColdSpring to designate certain beans as a singleton and persist them in the application scope, so this can obviate the problem of re-instantiating a reusable component, (usually one that contains mostly methods rather than accessors). In a ColdSpring world, your calls using CreateObject then become something more like:

    temp_logger = application.serviceFactory.getBean("logger");

    In this case, temp_logger is nothing but a pointer to the singleton 'logger' component, instantiated once during the life of your application.

    With regards to each method needing to return 'this', don't take my previous examples literally. The purpose of the init() method on a cfc, primarily, is for the <cfreturn this/> at the end, secondly to apply default values to all accessors. So long as this method is called, I don't think there would be any need for every method to return 'this'.

    I don't entirely understand your use case, and every programmer is different, but I don't think I'd handle the logger in quite the same manner. If you're passing the name of the accessor to the logger, you can just as easily have passed in the entire log message from whatever it was that is passing accessor.

    In addition, you could just as easily build a 'dump()' method on the superclass that basically returns the struct on which all of its properties are set to the logger. So long as you build your cfcs by convention to set their properties to a struct, this would work for any property on the object. The logger could then view every key in the struct and its status without the need to invoke any of the object's accessors.

    I dunno, something like...

    <cfscript>
    dumpStruct = obj.dump();
    logger.log(entry="Getter for #arguments.prop# is #StructFind(dumpStruct,arguments.prop)#");
    </cfscript>

    Oh, and I don't feel like cfscript is really limited, per se, if you have objects to handle things like database calls and the like.

    It's hard to talk about code without knowing the specifics, though. I'm sure what you had to do was exactly what the situation required, I'm just talking through a few things that came to mind.

    At least you knew about CreateObject...I was really worried at first. Glad I was offbase on that one. a:)
  9. Pat Says:
    @Shawn

    I've used coldspring (CS) plenty (see other blog posts for better examples). Autowiring is gold in my book when it comes to CS. Hehe

    You speak very true when you state "every programmer is different" hence why we have blogs :-) to better explain our thoughts on matters. I just makes us all that much better don't you agree?

    Towards the use of returning "this" I agree fully about the init() method. I always use one to set defaults as well as return "this" :-). Good to hear you follow that path :-) because I was not sure for a moment there.

    In regards to StructFind example, why would you want to pull the entire structure of variables when all you need is one? Parsing for the one you want just gives CF more to deal with? I see your reasoning, but if you are dealing with a large component with many variable this could be performance threatening (bad choice of words I know, but it's what comes to mind).
  10. Shawn Grigson Says:
    Okay, so you're familiar with ColdSpring...even without ColdSpring, however, there's no intrinsic need to reinstantiate an object over and over again using CreateObject(). You can also instantiate the object in the application scope if you are worried about it (which is essentially what ColdSpring does). But there is absolutely NO difference between using CFINVOKE on an object and calling all of its properties off of the returnVal than there is with setting a variable = CreateObject().init(). In both methods you set a variable that you can then call multiple methods from.

    As for my StructFind example...in your previous examples, you'd already referenced obj, so I assumed that 'obj' was the object passed into your logger. If you've already got the whole object, you have access to all of its methods, which includes the getAccessor() method, as well as the dump() method I proposed. getAccessor() would return obj's internal struct.accessor property. As far as I am aware, there is no real difference in performance between referencing a struct with struct.key versus StructFind(struct,"key") versus calling an accessor on the object in which getAccessor() is essentially returning struct.accessor.

    In my example I'm not really pulling the entire structure of variables. Objects in CF are passed by reference, not by value. So 'obj' that's passed into the logger method is not a copy of obj, it's a pointer to the original instantiation of 'obj'. If that's the case, then we already HAVE the entire structure of variables at our disposal, because we're pointing at the object in question.

    Whether I reference obj.getaccessor() or obj.dump().accessor or StructFind(obj.dump(),"accessor") I'm still pointing at the same referenced value, so I'm not really sure why any of these three methods would constitute more of a performance hit than another. However, referencing a struct of properties doesn't require an invoke method.

    Additionally, whether you are calling a method dynamically with CFINVOKE or whether you are calling a method dynamically with Evaluate(), you're still dynamically calling methods, right? Has there been any real study of the benchmarks between the two methods?

    In either case, calling methods dynamically is always going to be slower than calling them in a static fashion.
  11. Shawn Says:
    Oh, almost forgot. There's another method of referencing a struct value, and you can do it in a dynamic way (in case StructFind bothers you):

    myStruct["#dynamic_key#"]

    So to reprint my previous example with this method:

    <cfscript>
    dumpStruct = obj.dump();
    logger.log(entry="Getter for #arguments.prop# is #dumpStruct[arguments.prop]#");
    </cfscript>
  12. Pat Says:
    Shawn I feel like we are going around in circles here. Your approach is your personal preference and that's fine.

    If it works for you then great. I am just RECOMMENDING that it could be costly depending on the size of the struct being passed to search through. Even though searching a struct is fast, the large the struct gets the longer it could take to parse. So the idea is just to avoid it all together.

    With dynamic method calls. I recommend doing a search on Evaluate with Cold Fusion. You will find that evaluate is single threaded and when ran multiple times (depending on how you use it) could promote possible performance issue (slower processing).

    BTW. I have been working with Cold Fusion since version 3 so I am pretty familiar with the environment (not trying to sound cocky. Just trying to let you know where I stand with Cold Fusion).
  13. Shawn Grigson Says:
    I've been working with CF since 1998 (version 3.1) so it sounds like we have a similar level of experience with ColdFusion. But CF6+ has only been out for a few years (and companies are often slow to jump on the new tech bandwagon), so using OOP with coldfusion is a newer discipline, and as such I don't assume anyone understands OOP simply because they've been using CF for a long time. There are still plenty of CF developers to this day who write CF5 code and run it off of CFMX.

    First I was confused, because it seemed like you were saying that createObject could only be used with one invocation, versus cfinvoke which could be used for multiple invocations. I don't think you were saying that anymore, but I wanted to be clear on that.

    In any case, a common design principle for cfcs is that when everything in the object is set to a private scope (re: variables.dsn for the dsn) it's not uncommon for objects that represent a lot of data to set this to a struct instead. Example:

    ... Init function start
    variables.instance = StructNew();
    ... Init function end

    Then in the setter you'd set a property to:

    variables.instance.property

    and you'd also return variables.instance.property when you call getProperty() (or whatever your getter is called).

    So, again, I'm a bit confused. Why is it a big deal to output struct.property but it's not a big deal to return getProperty() which itself is just returning variables.instance.property?

    Assuming an object with 1000 properties, is there really a performance hit if you reference a property as variables.instance.property versus variables.property? And actually, isn't variables just a struct anyway? You can also do StructFind(variables,"property") or variables["#property#"] to return the contents of that variable (and yes, I know you wouldn't access the variables scope directly, you'd do this through the dump() function I mentioned before).

    So I do agree that there are many different ways to skin a cat, and that we can both say that our methods will work for the purpose you described. I don't, however, agree that referencing a value in a struct is any more intensive than accessing it from an accessor.
  14. Pat Says:
    One requires a search the other does not. That's all I pointed out. A search searches many things while the other does not. Again... All personal preference. Unless forced to do so in some way I personally would not do it. Just my 2 cents.

    Thanks for the convo as I don't think there is any other way I can simply describe it.

    BTW. Good looking kid you have. I am always interested in who I talk to. Saw you also were in some sort of protest on oil?
  15. Shawn Grigson Says:
    Yeah, I guess it comes down to personal preference. All I'm really trying to say is that either way, we're both pulling a member from a struct. Whether it's a variables struct or a different struct (and whether or not these structs are accessed by an accessor is really irrelevant at this point) it still seems like we're talking apples to apples here. I think no matter what, you're still going to be referencing a struct either way.

    As for my son, thanks, that's Rowan. :)

    The protest I was a part of was in Washington, D.C., on January 18, 2003, about two months prior to the launch of the Iraq war. I was protesting the march to war with Iraq (along with 100,000+ others). A common refrain heard during that time, and on many flyers and posters around that period was "No Blood For Oil". Wasn't exactly an oil protest, but I can see why you'd think so.

Leave a Reply





Powered by Mango Blog. Design and Icons by N.Design Studio