Coldfusion and unscoped variables - coldfusion

I am currently checking all my functions and components for unscoped variables. I am using a tool called varscoper4 to check all functions. Given the following code varscoper tells me that the variable "input" at line 4 in unscoped. Is this true and do i need to scope the argument variable if I alter it?
<cfoutput>#testit(1)#</cfoutput>
<cffunction name="testit">
<cfargument name="input">
<Cfset input = 3>
<cfreturn input>
</cffunction>
FYI if I do not alter the argument variable input in the function varscoper4 does not report any unscoped variables.
<cfoutput>#testit(1)#</cfoutput>
<cffunction name="testit">
<cfargument name="input">
<Cfset var output = 3 + input>
<cfreturn output>
</cffunction>

In your fist code block, input is "unscoped" but CF interprets it as arguments scope. CF will always try to find your unscoped variables by looking through a priority order. You can find more information on that here: http://www.learncfinaweek.com/week1/Scopes/
You can also view for yourself what that looks like by dumping the different scopes and seeing the output.
<cfoutput>#testit(1)#</cfoutput>
<cffunction name="testit">
<cfargument name="input">
<cfset input = 3>
<cfdump var="#variables#" label="variables">
<cfdump var="#arguments#" label="arguments">
<cfdump var="#local#" label="local">
<cfreturn input>
</cffunction>
I would strongly encourage you to take the output of your varscoper tool as a guide to where you should explicitly scope your variables. In that case, your first block of code would look like this. This is for clarity and certainty in your code.
<cfoutput>#testit(1)#</cfoutput>
<cffunction name="testit">
<cfargument name="input">
<cfset arguments.input = 3>
<cfdump var="#variables#" label="variables">
<cfdump var="#arguments#" label="arguments">
<cfdump var="#local#" label="local">
<cfreturn arguments.input>
</cffunction>
Personally, I don't like setting or changing arguments in my functions and methods. I'd rather keep them unadulterated as you have in your second block of code. But even there, I would explicitly scope the arguments so that you know where it came from -- even if not flagged by varscoper
<cfoutput>#testit(1)#</cfoutput>
<cffunction name="testit">
<cfargument name="input">
<cfset var output = 3 + arguments.input>
<cfdump var="#variables#" label="variables">
<cfdump var="#arguments#" label="arguments">
<cfdump var="#local#" label="local">
<cfreturn output>
</cffunction>
Last thing to be added here if it's not clear is that var scoping puts everything int he local scope. You could also do this and it would be functionally equivalent to the previous block of code:
<cfoutput>#testit(1)#</cfoutput>
<cffunction name="testit">
<cfargument name="input">
<cfset local.output = 3 + arguments.input>
<cfdump var="#variables#" label="variables">
<cfdump var="#arguments#" label="arguments">
<cfdump var="#local#" label="local">
<cfreturn local.output>
</cffunction>

You should use arguments.input:
<cfset arguments.input = 3>
Although even better would be to use your second example, with the arguments scope:
<cfset var output = 3 + arguments.input >
Don't modify arguments, leave them as they are when they arrive, incase you want to re-use the original value later on.
http://help.adobe.com/livedocs/coldfusion/8/htmldocs/help.html?content=buildingComponents_29.html

Related

Convert Special Characters to HTML - ColdFusion

I need to convert a lot of special characters to their html format and I am trying to do this with a function that is using ReplaceList but something is wrong with the function or the values I am passing to it.
This is the function
<cffunction name="HtmlUnEditFormat" access="public" returntype="string" output="no" displayname="HtmlUnEditFormat" hint="Undo escaped characters">
<cfargument name="str" type="string" required="Yes" />
<cfscript>
var lEntities = "&##xE7;,&##xF4;,&##xE2;,Î,Ç,È,Ó,Ê,&OElig,Â,«,»,À,É,≤,ý,χ,∑,′,ÿ,∼,β,⌈,ñ,ß,„,´,·,–,ς,®,†,⊕,õ,η,⌉,ó,­,>,φ,∠,‏,α,∩,↓,υ,ℑ,³,ρ,é,¹,<,¢,¸,π,⊃,÷,ƒ,¿,ê, ,∅,∀, ,γ,¡,ø,¬,à,ð,ℵ,º,ψ,⊗,δ,ö,°,≅,ª,‹,♣,â,ò,ï,♦,æ,∧,◊,è,¾,&,⊄,ν,“,∈,ç,ˆ,©,á,§,—,ë,κ,∉,⌊,≥,ì,↔,∗,ô,∞,¦,∫,¯,½,¤,≈,λ,⁄,‘,…,œ,£,♥,−,ã,ε,∇,∃,ä,μ,¼, ,≡,•,←,«,‾,∨,€,µ,≠,∪,å,ι,í,⊥,¶,→,»,û,ο,‚,ϑ,∋,∂,”,℘,‰,²,σ,⋅,š,¥,ξ,±,ℜ,þ,〉,ù,√,‍,∴,↑,×, ,θ,⌋,⊂,⊇,ü,’,ζ,™,î,ϖ,‌,〈,˜,ú,¨,∝,ϒ,ω,↵,τ,⊆,›,∏,",‎,♠";
var lEntitiesChars = "ç,ô,â,Î,Ç,È,Ó,Ê,Œ,Â,«,»,À,É,?,ý,?,?,?,Ÿ,?,?,?,ñ,ß,„,´,·,–,?,®,‡,?,õ,?,?,ó,­,>,?,?,?,?,?,?,?,?,³,?,é,¹,<,¢,¸,?,?,÷,ƒ,¿,ê,?,?,?,?,?,¡,ø,¬,à,ð,?,º,?,?,?,ö,°,?,ª,‹,?,â,ò,ï,?,æ,?,?,è,¾,&,?,?,“,?,ç,ˆ,©,á,§,—,ë,?,?,?,?,ì,?,?,ô,?,¦,?,¯,½,¤,?,?,?,‘,…,œ,£,?,?,ã,?,?,?,ä,?,¼, ,?,•,?,«,?,?,€,µ,?,?,å,?,í,?,¶,?,»,û,?,‚,?,?,?,”,?,‰,²,?,?,š,¥,?,±,?,þ,?,ù,?,?,?,?,×,?,?,?,?,?,ü,’,?,™,î,?,?,?,˜,ú,¨,?,?,?,?,?,?,›,?,"",?,?";
</cfscript>
<cfreturn ReplaceList(arguments.str, lEntities, lEntitiesChars) />
</cffunction>
This is how I am calling it:
<cfoutput>
<cfloop query="local.q" startrow="2">
#HtmlUnEditFormat(consultServiceType)# <br />
</cfloop>
</cfoutput>
These are the strings I am passing to it:
Security?
Security Guard®
Alarm System©
Private Investigator;
I am not getting any errors back (I had a cftry in the function before) and the strings come back the same
EDIT:
I've tried using #FindNoCase('©',consultServiceType)# and is returning 0 so I guess something is wrong with the string I am passing in?
You're using CF11, did you try EncodeForHTML() ?
The accepted answer is the better approach (don't reinvent the wheel), but your function isn't working because you have lEntities and lEntitiesChars mixed up.
<cffunction name="HtmlUnEditFormat" access="public" returntype="string" output="no" displayname="HtmlUnEditFormat" hint="Undo escaped characters">
<cfargument name="str" type="string" required="Yes" />
<cfscript>
var lEntities = "&##xE7;,&##xF4;,&##xE2;,Î,Ç,È,Ó,Ê,&OElig,Â,«,»,À,É,≤,ý,χ,∑,′,ÿ,∼,β,⌈,ñ,ß,„,´,·,–,ς,®,†,⊕,õ,η,⌉,ó,­,>,φ,∠,‏,α,∩,↓,υ,ℑ,³,ρ,é,¹,<,¢,¸,π,⊃,÷,ƒ,¿,ê, ,∅,∀, ,γ,¡,ø,¬,à,ð,ℵ,º,ψ,⊗,δ,ö,°,≅,ª,‹,♣,â,ò,ï,♦,æ,∧,◊,è,¾,&,⊄,ν,“,∈,ç,ˆ,©,á,§,—,ë,κ,∉,⌊,≥,ì,↔,∗,ô,∞,¦,∫,¯,½,¤,≈,λ,⁄,‘,…,œ,£,♥,−,ã,ε,∇,∃,ä,μ,¼, ,≡,•,←,«,‾,∨,€,µ,≠,∪,å,ι,í,⊥,¶,→,»,û,ο,‚,ϑ,∋,∂,”,℘,‰,²,σ,⋅,š,¥,ξ,±,ℜ,þ,〉,ù,√,‍,∴,↑,×, ,θ,⌋,⊂,⊇,ü,’,ζ,™,î,ϖ,‌,〈,˜,ú,¨,∝,ϒ,ω,↵,τ,⊆,›,∏,",‎,♠";
var lEntitiesChars = "ç,ô,â,Î,Ç,È,Ó,Ê,Œ,Â,«,»,À,É,?,ý,?,?,?,Ÿ,?,?,?,ñ,ß,„,´,·,–,?,®,‡,?,õ,?,?,ó,­,>,?,?,?,?,?,?,?,?,³,?,é,¹,<,¢,¸,?,?,÷,ƒ,¿,ê,?,?,?,?,?,¡,ø,¬,à,ð,?,º,?,?,?,ö,°,?,ª,‹,?,â,ò,ï,?,æ,?,?,è,¾,&,?,?,“,?,ç,ˆ,©,á,§,—,ë,?,?,?,?,ì,?,?,ô,?,¦,?,¯,½,¤,?,?,?,‘,…,œ,£,?,?,ã,?,?,?,ä,?,¼, ,?,•,?,«,?,?,€,µ,?,?,å,?,í,?,¶,?,»,û,?,‚,?,?,?,”,?,‰,²,?,?,š,¥,?,±,?,þ,?,ù,?,?,?,?,×,?,?,?,?,?,ü,’,?,™,î,?,?,?,˜,ú,¨,?,?,?,?,?,?,›,?,"",?,?";
</cfscript>
<cfreturn ReplaceList(arguments.str, lEntitiesChars, lEntities) />
</cffunction>
<cfoutput>#htmluneditformat("Company?")#</cfoutput>
Further, #ReplaceList()# in both ACF and Railo/Lucee recurse through the list, which means the order of the lists matter. With the fix I suggest, ? becomes &le;. A fix to this would be to move & and the code for it to the beginning of each list.
Consider this simple piece of code
<cfoutput>#replacelist("abc","a,b","b,c")#</cfoutput>
You would probably expect the output to be "bcc", but that's not how ReplaceList works, it works something more like this
<cfset sx = "abc">
<cfset listf = "a,b">
<cfset listr = "b,c">
<cfloop from="1" to="#listlen(listf)#" index="i">
<cfset sx = replace(sx,listgetat(listf,i),listgetat(listr,i),"ALL")>
<!--- iteration one replaces a with b to make bbc --->
<!--- iteration two replaces b with c to make ccc --->
</cfloop>
I'm not suggesting that someone use this code when CF has the built in functionality, I'm merely explaining why it doesn't work and a pitfall of ReplaceList().

Access function arguments from another function

I am trying to create a custom debug tool and I need to use a component with two separate functions in it. The first function (startTimer) has some arguments such as startCount and the other one (endTimer) has endCount. What I am trying to accomplish is something like the following code:
<cffunction name="startTimer" access="public" returntype="void">
<cfargument name="startActionTime" type="string" required="no">
</cffunction>
<cffunction name="endTimer" returntype="void" access="public">
<cfargument name="endActionTime" type="string" required="no">
<cfset finalTime = endActionTime - startTimer.startActionTime>
<!---Some SQL will go here to record the data in a db table --->
</cffunction>
And this is how I am calling the function
<cfscript>
location = CreateObject("component","timer");
loc =location.startTimer(
startActionTime = getTickCount()
);
end = location.endTimer(
endActionTime = getTickCount()
);
</cfscript>
I guess I am having scope issues because when I am trying to run the code I am getting an undefined error on startTimer.startActionTime. What is the correct way to do something like this?
You can use the variables scope like so:
<cfcomponent>
<cfset variables.startActionTime = 0>
<cffunction name="startTimer" access="public" returntype="void">
<cfargument name="startActionTime" type="numeric" required="no">
<cfset variables.startActionTime = arguments.startActionTime>
</cffunction>
<cffunction name="endTimer" returntype="string" access="public">
<cfargument name="endActionTime" type="numeric" required="no">
<cfset finalTime = endActionTime - variables.startActionTime>
<!---Some SQL will go here to record the data in a db table --->
<Cfreturn finaltime>
</cffunction>
</cfcomponent>
From the Adobe Docs: Variables scope variables created in a CFC are available only to the component and its functions, and not to the page that instantiates the component or calls its functions.

Error with undefined variables while looping over a query

I have an error while looping over a query using cfloop.
When I use a cfdump on the query (inside the loop, mind you), I can see all the data just fine. But when I try to get the value of each variable as you normally do in cfloop, I get a message that says they are undefined. I then changed each variable to reference the query specifically, and now the problem is that the variable is undefined in the query. Here's the code:
<cffunction name="writeCourses">
<cfargument name="recordset" required="yes" type="query">
<cfif recordset.RecordCount NEQ 0>
<cfset temp = "">
<cfoutput>
<cfloop query="recordset">
<!--- <cfdump var="#recordset#"> <cfabort/> --->
<cfset temp = temp & "<strong>#recordset.courseType# #recordset.courseNum# ">
<cfif isDefined("recordset.courseTHM") AND recordset.courseTHM EQ 1>
<cfset temp = temp & "(#left(recordset.courseNum,3)#4) ">
</cfif>
<cfif isDefined("recordset.courseName")>
<cfset temp = temp & "#recordset.courseName# </strong><br>">
</cfif>
<cfset temp = temp & "#recordset.courseDESC#<br>">
<cfset temp = temp & "#recordset.courseHours#<br><br>">
</cfloop>
</cfoutput>
<cfelse>
<cfset temp = "">
</cfif>
<cfreturn temp>
</cffunction>
So as you can see, each variable is enclosed in ## tags. Originally none of them were proceeded by recordset. but they were still undefined. And when I uncomment the cfdump and cfabort tags, those work fine and I can see the recordset query with all the data as it should be.
Every other time I have used cfloop with a query it works as expected. Also, I did not write this code, I am having to modify it (the original author no longer works here).
Here's an example of the recordset dump:
The error message:
Detail: [empty string]
ErrNumber: 0
Message: Element COURSETYPE is undefined in RECORDSET.
Resolvedname: RECORDSET
The error line is:
<cfset temp = temp & "<strong>#recordset.courseType# #recordset.courseNum# ">
<cfif isDefined("recordset.courseTHM") AND recordset.courseTHM EQ 1>
<cfset temp = temp & "(#left(recordset.courseNum,3)#4) ">
</cfif>
<cfif isDefined("recordset.courseName")>
<cfset temp = temp & "#recordset.courseName# </strong><br>">
</cfif>
That's all one line :/
The stored procedure/function calling the above:
<cffunction name="getCoursesByDept">
<cfargument name="deptCode" required="yes" type="string">
<CFSTOREDPROC procedure="dbo.GetCourses" datasource="WebCatalog">
<CFPROCPARAM type="IN" dbvarname="#deptCode" value="#deptCode#" cfsqltype="CF_SQL_CHAR">
<CFPROCRESULT name="result">
</CFSTOREDPROC>
<cfinvoke method="writeCourses" recordset="#result#" returnvariable="output">
<cfreturn output>
</cffunction>
Your problem appears to be failure to scope. Here are your first 4 lines:
<cffunction name="writeCourses">
<cfargument name="recordset" required="yes" type="query">
<cfif recordset.RecordCount NEQ 0>
<cfset temp = "">
Try it like this:
<cffunction name="writeCourses">
<cfargument name="recordset" required="yes" type="query">
<cfset var temp = "">
<cfif arguments.recordset.RecordCount NEQ 0>
The differences are the use of the var keyword for your local variable temp, and adding the arguments scope to the recordset variable.
(In addition to Dan's comments ...)
If [the procedure] can't find anything it returns a query containing
an error message (i.e. no course found)
Then that means the COURSETYPE column does not always exist in the resultset, which is exactly what the error message is reporting. If the procedure returns any result, regardless of the contents, the code inside the cfif block will execute. Since the first line of code uses that column, without verifying it exists, it would cause the exact error you are seeing.
Also, as I mentioned in the comments, you really need to localize the function variables result, output, temp, etectera. Lack of var scoping can create problems, even within same page, if you reuse variable names. As #Dan suggested you should fully scope all variables - in particular, the function arguments.
(As an aside, I understand you are modifying existing code, but the error message should really be handled in CF, not inside the procedure. The procedure's job is just to return data. The CF code should check the recordCount and take the appropriate action if no records are found.)

Slurp an INI file in CFML

Could anyone find a way of improving this code a bit? I want to read in an INI file in one felt swoop and create a corresponding data structure.
<cfset INIfile = expandPath(".") & "\jobs.ini">
<cfset profile = GetProfileSections(INIfile)>
<cfloop collection="#profile#" item="section">
<cfloop list="#profile[section]#" index="entry">
<cfset app.workflow[section][entry]=GetProfileString(INIfile, section, entry) >
</cfloop>
</cfloop>
I don't believe you can improve this using CFML power. Do you need to parse huge ini files? If not, why would you like to improve your code, it looks pretty straightforward for me.
Other possible (though common for CF) solution is to try pure Java. See this SO thread for pure Java examples.
P.S. BTW, in case of special performance needs you should consider using another storage for configuration. Simple SELECT query to the old good MySQL can be much faster for large datasets.
To expand on ryber's comment, you might consider using this approach instead. I'm assuming you're using CF8.01 or later, as I make use of nested implicit structure notation. This could easily be converted to CF7/6/etc syntax, but wouldn't be as clean or concise.
Again, this only applies if your ini file isn't used by any other applications or people, and doesn't need to be in ini format.
settings.cfm:
<cfset variables.settings = {
fooSection = {
fooKey = 'fooVal',
fooNumber = 2,
},
fooSection2 = {
//...
},
fooSection3 = {
//...
}
} />
Application.cfc: (only the onApplicationStart method)
<cffunction name="onApplicationStart">
<cfinclude template="settings.cfm" />
<cfset application.workflow = variables.settings />
<cfreturn true />
</cffunction>
In addition, I've use the CFEncode application to encrypt the contents of settings.cfm. It won't protect you from someone who gets a copy of the file and wants to see what its encrypted contents are (the encryption isn't that strong, and there are ways to see the contents without decrypting it), but if you just want to keep some nosy people out, it adds a little extra barrier-to-entry that might deter some people.
Update: Since you just left a comment that says you are on CF7, here's native CF7 syntax:
settings.cfm:
<cfset variables.settings = StructNew() />
<cfset variables.settings.fooSection = StructNew() />
<cfset variables.settings.fooSection.fooKey = 'fooVal' />
<cfset variables.settings.fooSection.fooNumber = 2 />
<!--- ... --->
Alternatively, you could use JSONUtil and CFSaveContent to continue to use a JSON-looking approach (similar to my original syntax), but on CF7:
<cfsavecontent variable="variables.jsonSettings">
{
fooSection = {
fooKey = 'fooVal',
fooNumber = 2,
},
fooSection2 = {
//...
},
fooSection3 = {
//...
}
};
</cfsavecontent>
<cfset variables.settings = jsonUtil.deserializeFromJSON(variables.jsonSettings) />
I created a CFC that I use in a bunch of apps. You give it an ini filepath when you init it and it creates a structure based on the ini file. It also optionally keeps the structure flat or creates sub-structures based on the [Sections] in the ini file. You can then either use its getSetting() method to get individual methods or getAllSettings() to return the entire structure. You may find it helpful.
<cfcomponent hint="converts INI file to a structure">
<cfset variables.settings=structNew() />
<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="configurationFile" type="string" required="yes" />
<cfargument name="useSections" default="false" type="boolean" />
<cfset var local=structNew() />
<cfif fileExists(arguments.configurationFile)>
<!--- Get the [sections] in the .INI file --->
<cfset local.sectionStruct=getProfileSections(arguments.configurationFile) />
<!--- Loop over each of these sections in turn --->
<cfloop collection="#local.sectionStruct#" item="local.item">
<cfset local.workingStruct=structNew() />
<cfloop list="#local.sectionStruct[local.item]#" index="local.key">
<!--- Loop over the keys in the current section and add the key/value to a temporary structure --->
<cfset local.workingStruct[local.key]=getProfileString(arguments.configurationFile,local.item,local.key) />
</cfloop>
<cfif arguments.useSections>
<!--- Copy the temporary structure to a key in the setting structure for the current section --->
<cfset variables.settings[local.item]=duplicate(local.workingStruct) />
<cfelse>
<!--- Append the temporary structure to the setting structure --->
<cfset structAppend(variables.settings,local.workingStruct,"yes") />
</cfif>
</cfloop>
<cfelse>
<cfthrow
message="Configuration file not found. Must use fully-qualified path."
extendedinfo="#arguments.configurationFile#"
/>
</cfif>
<cfreturn this>
</cffunction>
<cffunction name="getAllSettings" access="public" output="false" returntype="struct">
<cfreturn variables.settings>
</cffunction>
<cffunction name="getSetting" access="public" output="false" returntype="string">
<cfargument name="settingName" required="yes" type="string" />
<cfset var returnValue="" />
<cfif structKeyExists(variables.settings,arguments.settingName)>
<cfset returnValue=variables.settings[arguments.settingName] />
<cfelse>
<cfthrow
message="No such setting '#arguments.settingName#'."
/>
</cfif>
<cfreturn returnValue>
</cffunction>
</cfcomponent>

Can a ColdFusion cfc method determine its own name?

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.
So now 3 ways.
If you are using ColdFusion 9.0 or higher there is now 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.
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 =-
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?
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.
getFunctionCalledName() gives you the name of the active method.