StructKeyExists vs. IsDefined when checking 2+ scopes
There has been a movement away from using the IsDefined() function to using the StructKeyExists() function. The reasoning is that when IsDefined() is used to find an unscoped variable, it follows a specific order of scope evaluation that goes through over half a dozen scopes (9 actually) in order until it finds the variable or fails. StructKeyExists() on the other hand requires a scope and limits the search to that scope.
Note: There is a thought in the community that StructKeyExists() is more efficient than searching for a scoped variable using IsDefined(). I can't comment on this as the difference is probably tiny.
Note: I say over half a dozen rather than 9 as some of the scopes 'exist' but are empty unless specifically used (cffile, url, form, client) or only exist in certain places like the arguments or the 'unnamed local scope' in a UDF.
Now all of this is fine and good, but what happens when a variable may exist in one of two scopes, such as Url or Form? In such a case, most people move back to an unscoped IsDefined() search. I suggest instead to simply use two StructKeyExists() functions and trust in ColdFusion's short circuited boolean evaluation to keep things efficient. All you have to do is make sure you check the scope where you expect the variable to occur most often first.
For example, this CFIF is expecting an email passed to it from an URL or Form. The URL is the most common way in which the email will be sent so I check the URL scope first.
If the email variable is passed on the url, then the second StructKeyExists() will never be processed and the end result is a single function use. In the worst case all you are doing is running 2 functions against 2 (probably small) variable scopes rather than running through half a dozen, at least one of which (CGI) is rather large.
Of course the use of 2 functions may be less cost effective and I await the word of someone like Sean who knows the inside of ColdFusion better than I.
Those who a simple explanation of short circuited boolean evaluation can read this Fusion Authority article from 2000 which details it rather simply: ColdFusion with Style


Your link to the 9 scopes does not work.
http://www.houseoffusion.com/documentation/htmldoc...
dw
1. If both the url and form have the same variable, which takes precedence?
2. As a fast test will show, the new 'scope' is a full copy of both the url and form, which means more memory overhead (which does not matter to many but can have an effect).
3. It obfuscates where the variable is from just as much or more so than multiple StructKeyExists functions.
Bottom line is that it was a hack to solve a problem that could have been solved with a little more code and less 'games'. But that's just my perspective. :)
I know it uses the hated "eval()" function, but hey...
<!--- Fake some vars --->
<cfparam name="url.email" default="you@somewhere.com">
<cfparam name="form.email" default="you@somewhere.com">
<cfset s = structNew()>
<cfparam name="s.nestedStruct.username" default="joeboo">
<cffunction name="structKeyInList" access="public" hint="i look for a struct key in a list of scopes and return true if any of them exist" output="true" returntype="boolean">
<cfargument name="keyList" hint="a comma seperated list of structures to search (for example: url,form)" required="true" type="string">
<cfargument name="value" hint="i am the key to look for" required="true" type="string">
<cfset var exists = false>
<cfset var i = 1>
<cfset var curStruct = "">
<cfloop condition="exists neq true and i lte listLen(arguments.keyList)">
<cfset curStruct = listGetAt(arguments.keyList, i, ",")>
<cfif structKeyExists(evaluate(curStruct), arguments.value)>
<cfset exists = true>
<cfelse>
<cfset i = i+1>
</cfif>
</cfloop>
<cfreturn exists />
</cffunction>
<cfoutput>
structKeyInList("url,form", "email"): #structKeyInList("url,form", "email")#<br/>
structKeyInList("url,form,s.nestedStruct", "username"): #structKeyInList("url,form,s.nestedStruct", "username")#<br/>
</cfoutput>
1) why, whichever you configure, of course.
2) the base CF memory footprint is measured in hundreds of MB. Page parameters totalling even a few KB are atypical. If that's significant to your infrastructure's hardware, I'd say CF is probably the wrong technology.
3) no it removes an irrelevant distinction, it doesn't obfuscate. With structKeyExists, you have to check whether it exists, and then still know where it came from in order to use it. Combining the scopes simplifies both pieces, and is only a problem if you actually care whether the variable came from the URL or a form post (which is almost always an irrelevant distinction). The Servlet spec doesn't differentiate between the two, and ASP.NET provides combined and separate views (last I checked), so I think there's a strong precedence for not wanting to care.
For me, one of the most compelling arguments FOR an attributes scope is that it doesn't matter where your incoming variables are coming from.
This means a task triggered manually by posting a form (form scope) can also be triggered automatically using a scheduled task (URL scope), and embedded within another process as a custom tag style call (attributes scope). In my current programming, seeing "Form" or "Url" scoped variables in my code is a hint to me that I haven't finalized the controller component of my application.
The same principle exists in object oriented programming -- a called function doesn't care where it's arguments come from, just that they reflect the correct types (if specified). A fuse in Fusebox is expected to behave the same way, generally.
In any case, for those wishing to not create a pseudo-scope in their apps, your solution seems to be a good one.
Thanks,
Toby
3) no it removes an irrelevant distinction, it doesn't obfuscate. With structKeyExists, you have to check whether it exists, and then still know where it came from in order to use it. Combining the scopes simplifies both pieces, and is only a problem if you actually care whether the variable came from the URL or a form post (which is almost always an irrelevant distinction). The Servlet spec doesn't differentiate between the two, and ASP.NET provides combined and separate views (last I checked), so I think there's a strong precedence for not wanting to care.
Fusebox, Mach II and Model Glue all have a setting for which scope takes priority. I suppose there could be some rare use case where you wanted per request control over this, making the issue more complicated. I have yet to see such a conflict though as why would you have url parameters when submitting a form (other than the fuseaction or event parameters for the framework itself)?
My two cents.
Short answer: structKeyExists is SLOWER and has no advantage other than forcing coder to use scoping, which s/he should do anyway...
Long Answer:
whereas for the examples below url.pageid is a real url variable
STRUCTKEYEXISTS:
structKeyExists is pretty much the same speed no matter what you throw at it, keys can be real or not (url, 'pageid' and url, 'pageadfasdf' are about the same, no big diff one way or the other)
and if you throw a scope at it that does not exist, it throws, so it forces you to be careful about scoping.
ISDEFINED:
isDefined is super fast when the scope exists and you pass in a scope (url.pageid),
however, it is extremely extremely mega super slow when you pass in a non-existant scope (urlss.pageid).
it is also super super mega slow if you pass in a non-scoped non-existant variable (pagelksjdljsdf)
it is quite fast, on average 10 to 20% faster than structKeyExists, when you pass in a SCOPED variable, existant or not (url.pageid or url.aoiausldkjf)
So what does that mean in real life?
Well, as long as you are asking for a SCOPED variable that is extant, and you don't mispell the scope (url for instance), isDefined will be faster on average....
If you are looking for a NON-SCOPED variable, you have no choice but to live with isDefined.... hum....which can be very very slow when the variable does not exist.
If you want to make sure you scope all your vars, use structKeyExists and live with a small speed hit, but be assured it is scoped to a real scope (or throw an error).
So for me, in real life use, that means isDefined is probably the better choice as it is overall going to be faster as long as i can spell my scope correctly.
It also is the only choice to use when looking for multiple scope variables such as FORM.ID vs URL.ID and your page/function can take either and want to simply ask isDefined('ID')
However if you want to prioritize one over the other (which you should) then you can again use either define/struct where you have a little bit of script that checks each (form.id and then url.id) and assigns a variables.myID (or whatever your inbound scope/variable is) which is then passed in to page/function to use....(this is what we do so that we DO NOT fall prey to fusebox style mixed scope variable overrides and can determine within each module what SHOULD have priority, and yes, usually FORM wins over URL, but not ALWAYS - in either case, you should KNOW where your variable came from, sometimes it could come from both and you should know how to respond to that as well if necessary, NOT trust some background mixer)
Again, this would indicate isDefined would be preferred as it would be faster for that call too where you check specific scopes, as long as it is scoped and the scope exists.
So I can't find any reason to use structKeyExists other than it might seem like better code because it forces scoping....
How sad... it seemed like it should be faster as it specifies the scope....
here is sample code for you to run and see for yourself.... i'd love to know if you find anything different than what I did by platform or other (I am using CFMX702 on a WINDOWS platform). Is linux version any faster? Anyone know what actually happens at the compiled java level?)
<CFSCRIPT>
ITERATIONS = 100000;
writeoutput('ITERATIONS : ' & ITERATIONS & '<BR>' );
i=0;
j=0;
k=0;
ttt1 =0;
ttt2 =0;
t1c =0;
t2c=0;
for(k; k lte ITERATIONS; k=k+1){
T1 = getTickCount();
for(i; i lte ITERATIONS; i=i+1){
if(isDefined('url.asdfpageid')){
//do nothing
}
}
T2 = getTickCount();
TT1 = T2-T1;
ttt1=ttt1+TT1;
T3 = getTickCount();
for(j; j lte ITERATIONS;j=j+1){
if(structKeyExists(url, 'pageeid')){
//do nothing
}
}
T4 = getTickCount();
TT2 = T4-T3;
ttt2=ttt2+TT2;
if(TT1 gt 0){t1c = t1c+1;}
if(TT1 gt 0){t2c = t2c+1;}
if(TT1 gt 0 or TT2 gt 0){writeoutput('Iteration K: ' & k & ' isDefined: ' & TT1 & 'ms'); writeoutput(' structKeyExists: ' & TT2 & 'ms<br>');}
}
writeoutput('<BR>Times isDefined used time: ' & t1c);
writeoutput('<BR>Times structKeyExists used time: ' & t2c);
writeoutput('<BR>FINAL isDefined: ' & TTT1 & 'ms');
writeoutput('<BR>FINAL structKeyExists: ' & TTT2 & 'ms');
</CFSCRIPT>
<br>
<!--- The cfchart --->
<cfchart format="flash" xaxistitle="function" yaxistitle="Loading Time">
<cfchartseries type="bar" serieslabel="isDefined">
<cfchartdata item="isDefined" value="#variables.TTT1#">
</cfchartseries>
<cfchartseries type="bar" serieslabel="structKeyExists">
<cfchartdata item="structKeyExists" value="#variables.TTT2#">
</cfchartseries>
</cfchart>
I changed the code so that one iteration of each function was done at a time and the timings totalled up. So:
BEGIN LOOP
Test 1 iteration of IsDefined()
Increment total time
Test 1 iteration of StructKeyExists()
Increment total time
END
The results were now much more consistent. I then went on to test out 3 different scopes: form, url and my own user defined struct. A summary of the results, using 10million iterations, is as follows (running CF7 on Linux):
Form & URL very much alike. When the variable existed, StructKeyExists() was approximately 15% slower, when it didn't exist, both methods were pretty much neck and neck.
With the user defined struct, and when the variable existed, StructKeyExists() was approximately 10-15% FASTER, and when the variable didn't exist it was consistently 120-150% faster!
My conclusion is that if you want to check against a single scope (i.e. form, url, etc), use IsDefined(), otherwise use StructKeyExists().
My tuppence worth ,
Dominic
The scenario is when checking for the existence of an argument to a function. The code I used is as follows:
<cffunction name="tester">
<cfargument name="foo" required="false">
<cfscript>
var start = 0;
var times = StructNew();
times.ID = 0;
times.SKE = 0;
start = getTickCount();
if(StructKeyExists(arguments, 'foo')){
//do nothing
}
times.SKE = GetTickCount() - start;
start = getTickCount();
if(isDefined('arguments.foo')){
//do nothing
}
times.ID = GetTickCount() - start;
return times;
</cfscript>
</cffunction>
<cfscript>
iterations = 1000000;
i = 0;
ID_total = 0;
SKE_total = 0;
for(i; i lte iterations; i=i+1){
times = Tester(5); // remove value to test non-existence
ID_total = ID_total + times.ID;
SKE_total = SKE_total + times.SKE;
}
</cfscript>
<cfoutput>
<p>Iterations: #iterations#</p>
<p>IsDefined: #ID_total#ms</p>
<p>StructKeyExists: #SKE_total#ms</p>
</cfoutput>
<br/>
<!--- The cfchart --->
<cfchart format="flash" xaxistitle="function" yaxistitle="Loading Time">
<cfchartseries type="bar" serieslabel="isDefined">
<cfchartdata item="isDefined" value="#ID_total#">
</cfchartseries>
<cfchartseries type="bar" serieslabel="structKeyExists">
<cfchartdata item="structKeyExists" value="#SKE_total#">
</cfchartseries>
</cfchart>
Perhaps people are aware of this, but to combine the url scope with the form scope, you can do this:
<cfscript>
form.putAll(url);
// or
url.putAll(form);
// or
formURL = StructNew();
formURL.putAll(url);
formURL.putAll(form);
</cfscript>
This should be much faster than looping the keys and adding them one by one.
Dom
1. I totally agree with not breaking O-O in cfcs and functions by referencing outside variables. I have just been working on a project that did this left, right and middle and my head still hurts.
2. The most common version of CF I write for is CF6.1, servicing mainly UK government websites. Such things are still very relevant to me, even with the advent of CF8 (I can't wait for them to upgrade lol)!
3. The final test I did clearly shows that StructKeyExists() out performs IsDefined() in that particular situation and by a large margin. I had to look at this as I was writing a component that needed every bit of tweaking possible and I see no reason to use IsDefined() in this instance.
But agreed, in the case of checking url, form, etc., IsDefined() is faster.