views:

884

answers:

4

I am creating an API, and within each method I make a call to a logging method for auditing and troubleshooting. Something like:

<cffunction name="isUsernameAvailable">
    <cfset logAccess(request.userid,"isUsernameAvailable")>
    ......
</cffunction>

I'd like to avoid manually repeating the method name. Is there a way to programatically determine it?

I've looked at GetMetaData() but it only returns info about the component (including all the methods) but nothing about which method is currently being called.

+1  A: 

Well you might try this:

 <cffunction name="getFunctionName" returntype="any">
     <cfset meta =getMetaData(this)> 
     <cfreturn meta.functions[numberOfFunction].name>
    </cffunction>

I've tried various things, and this is not accurate as the functions seem to be added to the array of functions in reverse alphabetical order. This seems limiting (and not solving the problem). I would imagine some native java code could be invoked, but i'm going to need to look into that.

This and This look like interesting reading on related internal functions.

Re: The other answer on coldspring. I found this in depth article on function metadata with coldspring.

Related question : How to get the name of the component that’s extending mine in ColdFusion?

ethyreal
+6  A: 

So now 3 ways.

If you are using ColdFusion 9.0 or higher there is not a function named GetFunctionCalledName(). It will return what you are looking for. http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WS7cc222be8a31a47d-6e8b7083122cebfc8f2-8000.html

OR

Use ColdSpring and Aspect Oriented Programming (http://www.coldspringframework.org/coldspring/examples/quickstart/index.cfm?page=aop) to handle this for you.

OR

Use a cfthrow to generate a stack trace that has the information for you:

<cffunction name="determineFunction" output="FALSE" access="public"  returntype="string" hint="" >
<cfset var functionName ="" />
<cfset var i = 0 />
<cfset var stackTraceArray = "" />
<cftry>
<cfthrow />
<cfcatch type="any">
    <cfset stacktraceArray = ListToArray(Replace(cfcatch.stacktrace, "at ", " | ", "All"), "|") />

    <!---Rip the right rows out of the stacktrace --->
    <cfloop index ="i" to="1" from="#ArrayLen(stackTraceArray)#" step="-1">
        <cfif not findNoCase("runFunction", stackTraceArray[i]) or FindNoCase("determineFunction", stackTraceArray[i])>
            <cfset arrayDeleteAt(stackTraceArray, i) />
        </cfif>
    </cfloop>

    <!---Whittle down the string to the func name --->
    <cfset functionName =GetToken(stacktraceArray[1], 1, ".") />
    <cfset functionName =GetToken(functionName, 2, "$")/>
    <cfset functionName =ReplaceNoCase(functionName, "func", "", "once")/>

    <cfreturn functionName />
</cfcatch>
</cftry></cffunction>

My recommendation would be use getFunctionCalledName, or if not on CF 9 ColdSpring, as it will probably buy you some other things.

Terry Ryan
good call on the coldspring, but you would think it would not be this complicated.
ethyreal
+3  A: 

I agree w/ tpryan. ColdSpring makes this very easy. However, here is another alternative. Instead of parsing the stack trace, you can parse the CFC file itself.

<cffunction name="foo" displayname="foo" hint="this is just a test function" access="public" returntype="string">
 <cfset var test = getFunctionName(getMetaData().path, getPageContext().getCurrentLineNo()) />
 <cfreturn test />
</cffunction>

<cffunction name="getFunctionName" hint="returns the function name based on the line number" access="public" returntype="string">
 <cfargument name="filepath" type="string" required="true" />
 <cfargument name="linenum" type="any" required="true" />
 <cfset var line = "" />
 <cfset var functionName = "" />
 <cfset var i = 1 />
 <!---- loop over CFC by line ---->
 <cfloop file="#ARGUMENTS.filepath#" index="line">
     <cfif findNoCase('cffunction', line, 1)>
   <cfset functionName = line />
  </cfif>
  <cfif i EQ ARGUMENTS.linenum><cfbreak /></cfif>
  <cfset i++ />
 </cfloop>
 <!---- parse function name ---->
 <cfset functionName = REMatchNoCase("(\bname=[""|'])+[a-z]*[""|']", functionName) />
 <cfset functionName = REMatchNoCase("[""']+[a-z]*[""']", functionName[1]) />
 <cfset functionName = ReReplaceNoCase(functionName[1], "[""']", "", "all") />
 <!---- return success ---->
 <cfreturn functionName />
</cffunction>

The above is written for ColdFusion 8. CFLOOP added support for looping over files line by line (and doesn't read the entire file into memory). I did a few tests comparing the stack trace method vs. file parsing. Both performed equally well on a small CFC being called directly from a single CFM template. Obviously if you have very large CFCs the parsing method might be a bit slower. On the other hand, if you have a large stack trace (like if you are using any of the popular frameworks) then file parsing may be faster.

-= Viva ColdFusion =-

Adrocknaphobia
+1  A: 

I thought of another way that could work.

Setup an OnMissingMethod something like this:

<cffunction name="onMissingMethod">
    <cfargument name="missingMethodName" type="string"> 
    <cfargument name="missingMethodNameArguments" type="struct">

    <cfset var tmpReturn = "">
    <cfset var functionToCallName = "Hidden" & Arguments.missingMethodName>
    <cfset arguments.missingMethodArguments.calledMethodName = Arguments.missingMethodName>
    <cfinvoke method="#functionToCallName#" argumentcollection="#Arguments.missingMethodArguments#" returnvariable="tmpReturn" />
    <cfreturn tmpReturn>
</cffunction>

Then name each of the regular methods with a prefix ("Hidden" in this example), and mark them as private. So my initial example would become:

<cffunction name="HiddenisUsernameAvailable" access="private">
    <cfset logAccess(request.userid,Arguments.calledMethodName)>
    ......
</cffunction>

Now all the calls will be intercepted by onMissingMethod, which will add the method name to the arguments that get passed to the real method.

The downsides I see to this are that introspection no longer works properly, and you must be using named arguments to call all your functions. If you are not using named arguments, the args will randomly change order in the missingMethodNameArguments structure.

thats creative! i never thought about a function wrapper.
ethyreal