16
ColdFusion arguments.callee
12 Comments · Posted by Elliott in Adobe, ColdFusion, Programming
Last night Ben Nadel sent me an email asking if there was any way to get the currently executing function so you could get the metadata from it.
<cffunction name="test" myAttribute="1"> <--- How can we get the myAttribute value? ---> </cffunction>
The first obvious attempt at this is to use getMetaData(test).myAttribute
, and that works fine until you pass the function as a pointer and then it’s not called test anymore. Instead we need a different way to get the current function.
I had looked into this for implementing closures some time ago, and even talked about this at BFusion 2008. My original workaround was to create an API around the function pointer. For example executeFunction(func) that passes the function pointer in as an argument. Unfotunately, this also means you can’t just pass this function around transparently to anything that expects a function pointer.
Last night, however, I had a eureka moment and figured this one out. To get a reference to the current function we’re going to harness exceptions and the information we can get from the stack.
<cffunction name="getStackFunction" access="public" returntype="any" output="false"> <cfargument name="name" type="string" required="true"> <cfargument name="depth" type="numeric" required="false" default="1"> <cfset var TemplateClassLoader = createObject("java","coldfusion.runtime.TemplateClassLoader")> <cfset var servletContext = getPageContext().getServletContext()> <cfset var templatePath = ""> <cfset var TemplateClass = ""> <cfset var field = ""> <cftry> <cfthrow type="Exception"> <cfcatch type="any"> <cfset templatePath = cfcatch.tagContext[depth+1].template> </cfcatch> </cftry> <cfset TemplateClass = TemplateClassLoader.findClass(servletContext,templatePath)> <cfset field = TemplateClass.getDeclaredField(arguments.name)> <cfset field.setAccessible(true)> <cfreturn field.get(TemplateClass.newInstance())> </cffunction>
We can then use this code with the (case sensitive) name of the function in the current stack to get a pointer to that function.
<cffunction name="test" myAttribute="1" output="false"> <cfargument name="x" type="numeric"> <cfset arguments.callee = getStackFunction("test")> <cfreturn arguments.x + getMetaData(arguments.callee).myAttribute> </cffunction>
You can use the depth parameter to get functions farther down the stack as well.
I’ll go over how to build proper closure constructs using this technique, and some other novel ones that don’t even require runtime magic in an upcoming post.
Oh, and hats off to Ben for sparking my interest in this issue again! :)
No tags
Steve Bryant · July 16, 2009 at 11:33 pm
I have been thinking about this problem recently as well so it is nice to see a solution. Somehow, though, using exception handling just seems… wrong. How is the performance on that?
I was thinking of implementing a method that uses the named arguments of the method that calls it (long story). Basically, I want to be able to programmatically get a list of the arguments for a method. I could pass in This and the method name, but I wanted something with a cleaner API.
Anyway, this is really great to see and I am eager to see what else you do with this.
Adam Cameron · July 17, 2009 at 4:38 am
Hi Elliott
This seems rather similar (although more complicated) than a UDF I mentioned to you in “another forum” a few months back. Good to see a slightly different technique though.
It’s probably also worth while pointing people to the issue you raised with Adobe on this one, now that the bugbase is public:
http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=73010
It’d be good to get more votes.
—
Adam
Author comment by Elliott · July 17, 2009 at 6:38 am
@Adam
I don’t remember you posting anything remotely like this. My suggested function for getting the called name also doesn’t address the use case for the function in this blog post.
arguments.udfs.func1 = myFunction;
arguments.udfs.func1(1);
The called name here is func1, but knowing it doesn’t let us get a reference to ourself from inside that function since there’s no way to know the UDF is inside another struct without scanning every scope.
I’m curious to know what function you wrote that you think solves this as there’s no other way to get a function pointer to yourself regardless of the calling context.
Especially in a case like this which models passing a function to another function like map or fold.
// template A.cfm
function a() {}
request.a = a;
// template B.cfm
b = request.a;
request.a = 1;
b();
What “technique” do you think is more simple that solves this?
Author comment by Elliott · July 17, 2009 at 7:32 am
@Adam
Okay I just found your function in the “other forum”. Your getCurrentFunctionName() does *not* do what this blog post is about, or what the entire thread is about that you posted that function on. It doesn’t address the ER either.
Your post does *not* return the called function name. It returns the declared function name.
I’d suggest you reread the ER and this blog post again. You seem to be confused. :)
Adam Cameron · July 17, 2009 at 7:57 am
Yep, as per other discussion, you’re dead right. Sorry for the confusion (albeit it’s mostly on my part, it seems ;-)
Cheers.
—
Adam
Author comment by Elliott · July 17, 2009 at 8:02 am
Hah, it’s all good. Btw, do you have a blog? You’re kind of a ghost around these parts.
Ben Nadel · July 17, 2009 at 8:03 am
Elliott,
Very interesting stuff! I failed after 2 hours and I’m satisfied in saying that I would NEVER have come up with this at all :D Nice work!
Adam Cameron · July 17, 2009 at 10:51 am
Nope: no blog, me. Well I do, but not a technical one. I keep thinking about it, but I so seldom have anything worthwhile saying, I don’t see the point.
Most of the things I think of to say stem from what I read on other people’s blogs, so I just stick my oar in as-and-when. Not that today’s effort was a shining example: I’m usually better than that!
—
Adam
aMeen · July 18, 2009 at 3:32 am
Got Lost … I gave up
:(
Ben Nadel · July 21, 2009 at 9:54 am
I can’t seem to get this to work if the function pointer is in another scope such as:
At this point, it looks like “test” is not a declared field in the template taken from the exception tag context.
Thoughts??
Author comment by Elliott · July 22, 2009 at 5:58 am
@Ben
Seems my blog ate your code sample. Doh! I need to dump wordpress or upgrade and get code comments and blog post code working. It was a real pain to make this post too.
Can you post your code on http://gist.github.com/ perhaps?
I don’t know what you mean by a different scope. I have a bunch of test cases here and they all work.
Sean · February 9, 2010 at 4:06 pm
This got me part the way:
thisCFCPage = GetPageContext().getFusionContext().getPagePath();
Here’s what’s sad: There is this gem:
GetPageContext().getFusionContext().methodCalledName
but it’s a private property and there is no getter function. ARRRGGGHHHHH!!!!!!