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! :)