Consider the following code:
<cfset local.quiz = getQuiz(param1,param2) />
<!--- returns a struct that has a key called unlock_at --->
<cfset quiz.unlock_at = (quiz.unlock_at EQ '') ? '' : DateConvert('utc2Local',createODBCDateTime(ISOToDateTime(quiz.unlock_at))) />
This is how I origionally wrote my code. When I called the page that ran this code multiple times, the value of quiz.unlock_at was changing in 6 hour increments (the amount of adjustment in the function). When I say increments I mean first page load the time was say 12:00. The next page load 6:00, the next page load 00:00 and so on. The physical time was changing each time. I changed the code to this:
<cfset local.unlock_at = (quiz.unlock_at EQ '') ? '' : DateConvert('utc2Local',createODBCDateTime(ISOToDateTime('#quiz.unlock_at#'))) />
The first thing is I'm storing the value in a local variable and no longer updating the existing struct. The second thing is I am passing in the date to the function as a string and not as the variable. I wasn't sure how it was getting altered. In either case:
How the heck was this changing between requests. I didn't think these variables lives between page requests. All of this code is inside a function that lives inside a CFC. What of any of that data is persistent. I called the page a few times and watched the dates change then opened a NEW browser and the data was altered too. How is that possible?
Is this a scoping issue, a data persistence issue with components? Threw me for a loop and I still and not sure what happened and until I do I'm afraid I may have other issues.
I figured it out. Grrr...
The function getQuiz created a struct and then cached it. The reason being is that it's an API call and I don't want to have to run an API call every time for data that doesn't change that often. So what I was doing was:
<cffunction name="getQuiz">
<cfset local.variable = {} />
<!--- Load Sturct Data --->
<!--- CachePut the variable --->
<cfreturn variable />
</cffunction>
<cfset quiz = getQuiz() />
<cfset quiz.unlock_at = 1 />
<!--- At this point I have actually edited a direct reference to the cached variable quiz even though I tried locally scoping it etc.
So, apparently returning a struct is the same as passing a struct into a function, i.e. it passes the struct by reference not by value. For some reason I thought returning a struct didn't return it as a reference, but it makes sense because that is the way CF passes values between functions.
Related
I wrote the following function:
<cffunction name="check_session_valid" returntype="boolean">
<cfif NOT StructKeyExists(session,"username") OR (len(session.username) EQ 0)>
<script>location.href = 'logout.cfm'</script>
<cfabort>
</cfif>
<cfset session.myApp_start = now()>
<cfreturn true>
</cffunction>
In my .cfm page, I can call that function using
<cfset session_valid = application.lib.check_session_valid()>
OR
#application.lib.check_session_valid()#
What's the difference? Best practice?
Since you asked about best practice, which is a matter of opinion, I think you can improve your function by having it returning either true or false depending on whether or not session.username exists and has a length greater than 0. Then you can use it like this:
<cfif application.lib.check_session_valid()>
code for this condition
<cfelse>
<cflocation href = "logout.cfm">
<!--- note that cfabort is not necessary --->
<cfif>
Regarding your specific question, I think the extra variable, session_valid, is a waste of typing. However, that is simply my opinion.
Not related to your question, I found it curious that you would direct users to a page called logout.cfm. Often users are directed to a page that allows them to log in.
To be honest, both are valid and both would be considered best practice depending on what you are trying to do.
My rule of thumb is if I will need to use the result of a function call more than once, I will set it to a variable
myResult = application.lib.check_session_valid();
If I will only need to use the variable once I would do what Dan mentioned
if( application.lib.check_session_valid() ){
// Do stuff
}
The difference between the examples you showed are
<cfset session_valid = application.lib.check_session_valid()>
This will set the variable named session_valid to whatever is returned from the call to check_session_valid().
#application.lib.check_session_valid()#
This will, in .cfm pages, simply render the value returned from the call to check_session_valid() assuming it is inside of a <cfoutput> tag. There are other places this would also render the value, such as inside a <cfsavecontent>.
I've been seeing intermittent errors in a couple of systems I've been working on, when using the same methodology (not the same code) leading me to believe the problem may be linked to creating and using structs in the same request. I'm wondering if it's possible there's a race condition?
The scenario is this:
We're on an e-commerce system, looking at a product, or in some cases a list of products. The code in question is designed to return the images associated with each product, in a struct that we can use for display of said images.
At the beginning of the request, the code looks for database records associated with the item in question. These records represent images for the product(s).
These records are returned in a single CFQuery call (or more accurately, a call to a function which returns the results of a CFQuery call, shaped into a struct containing various info).
The code then loops through the supplied image struct, and adds various information to a Local struct. Later in the request we use the data in the struct to display the images in our <img> tags. We also populate the <img> tag with data- attributes for use with JavaScript.
In the case that any particular image was not correctly returned by the query - usually because the physical file is missing - we use a generic placeholder image. This is done by putting the struct creation inside a try/catch block.
Importantly: this works.
What's happening however, is that very intermittently, when referring to a node in the struct we've created, we find that it does not exist and CF throws an error - this happens maybe 1% of the time and reloading the same page, everything will work perfectly.
I've had this same problem on multiple systems, on multiple servers, on different versions of ColdFusion (8 & 10 to be specific) and using completely different code to achieve similar results.
The first system I saw this issue on, actually used FileExists to check that the image file was available and thus I thought that the problem was probably caused by the bottleneck of the filesystem - I tried many ways around this and eventually eliminated it altogether in the new system - but the problem persists.
The only thing I can think of, is that when creating a struct and then using that struct later in the same request, there's a possibility that a race condition occurs; whereby I refer to a node in the struct before it's finished being created. I'm not using threading here though, so I can't really see how that's possible... I'm out of other ideas.
Some code is below to show what I'm doing, but given that the same issue arises on completely different systems, I think it's the methodology rather than the code that has a problem.
<!--- Get product images --->
<cfset Local.stProductImages = Application.cfcParts.getPartImages(
l_iItemID = Arguments.pid
) />
<!--- Loop through images --->
<cfloop list="#ListSort(structKeyList(Local.stProductImages['item_' & Arguments.pid]), 'text')#" index="i">
<cftry>
<cfset Local['ImageURL_' & i & '_Large'] = Local.stProductImages['item_' & Local.arguments.pid][i].large_watermarked.URL />
<cfcatch>
<cfset Local['ImageURL_' & i & '_Large'] = Application.com.Images.getMissingImages().large />
</cfcatch>
</cftry>
<cftry>
<cfset Local['ImageURL_' & i & '_Med'] = Local.stProductImages['item_' & Local.arguments.pid][i].med.URL />
<cfcatch>
<cfset Local['ImageURL_' & i & '_Med'] = Application.com.Images.getMissingImages().med />
</cfcatch>
</cftry>
<cftry>
<cfset Local['ImageURL_' & i & '_Small'] = Local.stProductImages['item_' & Local.arguments.pid][i].small.URL />
<cfcatch>
<cfset Local['ImageURL_' & i & '_Small'] = Application.com.Images.getMissingImages().small />
</cfcatch>
</cftry>
<img class = "altProdImg<cfif i EQ 'image_03'> endImage</cfif>"
src = "#Local['ImageURL_' & i & '_Small']#"
image = "#i#"
alt = ""
data-imgsmall = "#Local['ImageURL_' & i & '_Small']#"
data-imgmed = "#Local['ImageURL_' & i & '_Med']#"
data-imglarge = "#Local['ImageURL_' & i & '_Large']#"
data-imgnum = "#i#"
data-pid = "#Arguments.pid#"
/>
</cfloop>
The error occurs in the <img> tag, when referring to a node created in the preceding code - Something like:
Element ImageURL_image_02_Large is undefined in a Java object of type class coldfusion.runtime.LocalScope.
But only very occasionally... I'll reload and it'll work perfectly every time.
So... sorry for the epic length of question, but can anybody see how this could occur?
Answer from comments...
The behaviour you describe is symptomatic of not var scoping, so it might be as simple to fix as using index="local.i" in the cfloop tag (you only need the scoping when writing the variable).
Side note: A relatively easy way to check if you're in a function, without going through code, is by throwing an error (i.e. <cfthrow message="where am i?" />) then check the stack trace - if you see stuff like coldfusion.runtime.UDFMethod or $funcSTUFF.runFunction(filename:line) you know you're inside a function (even when the template you're in shows no sign of it).
If there is a better way to go about this (which is quite likely), please let me know how to go about it.
I'm working on some code that is supposed to dynamically set the form variables as regular variables so that we can be lazy and not have to refer to the variable with form.somevariable name.
That part works perfectly. Until I start testing for URL conflicts in which a URL variable has the same name. For instance. . .
I have a form that passes two variables; FirstName and LastName. If I hit the page, the form shows up, I input a first and last name and click submit. The code works perfectly.
However, if I have URL variables with the same names, the code reports the url variable values instead of the form values.
Some sample values;
url.FirstName = Joe
url.LastName = Black
form.FirstName = Steve
form.LastName = White
My code that exposes the form variable will correctly find the form field names, but then when I 'evaluate' the value of the given form field, it will return the value of the URL variable of the same name rather than the form variable.
What I am really wanting (as I described briefly up above) is to have code that automatically converts client, URL and Form variables into 'regular variables' so that you don't have to write lots of extra code grabbing them later on. Frameworks like CFWHEELS and ColdBox do this by default, but at the company I work out, we aren't using any of them. I need it to expose the URL variables, but give presidence to form variables if they have the same name, because they are likely to be intended to do an update or such.
The code follows Feel free to ignore the code for the URL and client variables if you wish as they don't directly affect how the form code works, I have tested with them commented out and I get the same result. I provided all of it to give a more complete idea of what I have been toying with so far. Please note that I don't normally use 'evaluate'. There is probably a better way to go, but I don't know what it is.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++
First Name
Last Name
URL variables:
<cfloop index="i" list="#paramsurl#">
<cfset myDynVar = Evaluate(i)>
<!--- Let's output the dynamically created variable as a test --->
#i# = #myDynVar#<br />
</cfloop>
<cfoutput>
<b>Field Names:</b> #Form.FieldNames#
<p>
<b>Field Values:</b><br>
<cfloop INDEX="TheField" list="#Form.FieldNames#">
#TheField# = #Evaluate(TheField)#<br>
<cfset TheField = Evaluate(TheField)>
</cfloop>
</p>
Lets try and output the two form fields without using the "form." notation<br>
FirstName : #FirstName# <br />
LastName : #LastName#
</cfoutput>
The client variables currently available are:
<cfset nVarCounter = 1>
<cfloop list="#GetClientVariablesList()#" index="whichClientVar">
#whichClientVar# : #client[whichClientVar]#<br />
<cfset whichClientVar = Evaluate(whichClientVar)>
</cfloop>
You should always scope your variables. When you use evaluate it runs through the scope order and it pulls the values out of the url scope before it gets to the form scope
You can use associative array notation to pull the data (as seen below).
<cfoutput>
<b>Field Names:</b> #Form.FieldNames#
<p>
<b>Field Values:</b><br>
<cfloop INDEX="TheField" list="#Form.FieldNames#">
#TheField# = #form[TheField]#<br><!--- specify form scope --->
<cfset myField = structKeyExists(url,TheField) ? url.TheField : form.TheField>
</cfloop>
</p>
</cfoutput>
You can 'copy' the values from form scope and url scope into the variables scope by using structAppend().
structAppend( variables, form, true );
structAppend( variables, url, false );
In the first line, any element of the form scope is copied to the variables scope and if a variable already exists with the same name in variables scope, it will overwrite that value with the value from the form scope.
In the second line, elements form URL scope are copied to variables scope but if a variable already exists in the variables scope, it is NOT overwritten.
You can do this for ANY scope and any other ColdFusion structure. You can also reorder them so that one scope has precedence over the others.
In CF 10 or Railo 4, you could use the defaults() function of the Underscore.cfc library to succinctly accomplish what you're trying to do. Example:
// instantiate Underscore library
_ = new Underscore();
// copy values from form and url scopes into the variables scope
_.defaults(variables, form, url);
This function "fills in" any undefined values in the first struct to the values in the subsequent structs. It works from left to right, so in this example it gives precedence to values in form over the values in url.
Disclaimer: I wrote the Underscore.cfc library.
Please refer to the following Adobe documentation for order of precedence:
http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WSc3ff6d0ea77859461172e0811cbec09af4-7fdf.html
If you want to reverse this precedence for some reason you should be able to just set all of your FORM fields into the variables scope...
Maybe something like ...
<cfloop collection=#form# item="varName">
<cfset SetVariable("variables.#varName#", evaluate("FORM." & varName))>
</cfloop>
Thanks for all of the great ideas.
Following is what I ended up going with.
<cfset scopes = "url,client,form">
<cfloop list="#scopes#" index="i">
<cfloop list="#structKeyList( evaluate( i ) )#" index="j">
<cfset structInsert( VARIABLES, j, evaluate( i & '["' & j & '"]' ), true ) />
</cfloop>
</cfloop>
<cfoutput>
Lets try and output the two form fields without using the "form." notation and make sure that the URL variables are NOT over writing the Form ones<br>
FirstName : #FirstName# <br />
LastName : #LastName#<br />
</cfoutput>
<cfdump var="#VARIABLES#" abort="1" label="Combined Variables Scope stuff" />
Key points before reading further
All variables are properly var'ed (you'll have to trust me)
Scopes are not being reset while these long-running processes are happening
When dumping the metadata for the supposedly missing/invalid method, I get the right information
There are only two places where the name of this method are referenced in the application. Once where it's defined, and once were the method is called in the code below.
I have a very strange intermittent error that I can't seem to track down. Here's the background (these are severely trimmed down to simplify for posting).
FeedService.cfc:
<cfcomponent output="false" extends="FeedDAO">
<cffunction name="processXmlFile" access="public" output="false" returntype="struct">
<cfset Var local = StructNew() />
/***************************************
THE VARIABLES ARE ALL VAR'D - PROMISE!!!
Lots of other stuff goes on in here to get the ultimate set of XML nodes to loop through
*****************************************/
<cfloop from="1" to="#ArrayLen(local.arrChannels)#" index="local.currentChannelItem">
... Lots of XML parsing and stuff and things going on here ...
<cfset LOCAL.invCheck = checkCustomerListing(
Acct_ID = local.invStruct.AcctID
, CustomerListingID = local.invStruct.CustomerListingID
) />
... Lots more stuff going on here ...
</cfloop>
</cffunction>
</cfcomponent>
FeedDAO:
<cfcomponent output="false">
<cffunction name="checkCustomerListing" access="public" output="false" returntype="numeric" hint="Returns the numeric inventory ID for an existing inventory listing, or 0 if the listing doesn't exist.">
<cfargument name="Acct_ID" type="numeric" required="true" hint="" />
<cfargument name="CustomerListingID" type="string" required="true" hint="" />
<cfset var rs = "">
<cfquery name="rs" datasource="#Variables.DSNs.Primary#">
SELECT ID FROM TheTable
WHERE
Acct_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#Arguments.Acct_ID#" />
AND Customer_Listing_ID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#Arguments.CustomerListingID#" />
</cfquery>
<cfif rs.RecordCount>
<cfreturn rs.Inv_ID />
<cfelse>
<cfreturn 0 />
</cfif>
</cffunction>
</cfcomponent>
I'm calling the initial function like so:
<cfset processStruct = Server.FeedService.processXmlFile(filePath) />
So, when a feed gets submitted to the processXMLFile function, it looks through all of the items in the file. A feed file may have 10, 100, or even 1000 entries. I get occasional error messages like this while a file is getting processed:
[struct]
Detail: The symbol you provided checkCustomerListing is not the name of a function.
Message: Entity has incorrect type for being called as a function.
StackTrace: coldfusion.runtime.CfJspPage$UninvocableEntityException: Entity has incorrect type for being called as a function.
at coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:2441)
at coldfusion.runtime.SuperScope.invoke(SuperScope.java:18)
at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2222)
More stack dump information
Type: Application
symbolName: checkCustomerListing
[object of coldfusion.runtime.CfJspPage$UninvocableEntityException]
Class Name: coldfusion.runtime.CfJspPage$UninvocableEntityException
Fields:
java.lang.String symbolName: checkCustomerListing
Parent Class: [object of coldfusion.runtime.ApplicationException]
Class Name: coldfusion.runtime.ApplicationException
Parent Class: [object of coldfusion.runtime.NeoException]
Class Name: coldfusion.runtime.NeoException
Methods:
findAdvancedCFTarget(coldfusion.runtime.AdvancedCFException, java.lang.String[]) returns int
findCustomTarget(coldfusion.runtime.CustomException, java.lang.String[]) returns int
findThrowableTarget(java.lang.Throwable, java.lang.String[]) returns int
getDetail() returns java.lang.String
getLocalizedMessage() returns java.lang.String
getMessage() returns java.lang.String
getRootCause() returns java.lang.Throwable
getString(java.lang.Throwable, java.lang.String, java.util.Locale) returns java.lang.String
getType() returns java.lang.String
setLocale(java.util.Locale) returns void
unwrap(java.lang.Throwable) returns java.lang.Throwable
Parent Class: [object of java.lang.RuntimeException]
Class Name: java.lang.RuntimeException
Parent Class: [object of java.lang.Exception]
Class Name: java.lang.Exception
Parent Class: [object of java.lang.Throwable]
Class Name: java.lang.Throwable
Methods:
fillInStackTrace() returns java.lang.Throwable
getCause() returns java.lang.Throwable
getLocalizedMessage() returns java.lang.String
getMessage() returns java.lang.String
getStackTrace() returns java.lang.StackTraceElement[]
initCause(java.lang.Throwable) returns java.lang.Throwable
printStackTrace(java.io.PrintWriter) returns void
printStackTrace(java.io.PrintStream) returns void
printStackTrace() returns void
setStackTrace(java.lang.StackTraceElement[]) returns void
toString() returns java.lang.String
I may get one error in 1000 entries, or I may get a small batch of errors at one time, and the rest of the feed processes just fine (due to some try/catch logic to prevent the entire thing from crapping out). At one point, the checkCustomerListing was in a completely different Server scoped object, and I never had a problem. I moved it into the FeedDAO and started calling it via the Super scope, and that's when these intermittent errors began.
UPDATE: I have everything properly var'ed, I just chopped it all out for the sake of brevity.
UPDATE AGAIN: Changed code sample comments to make it clear that there is a lot of stuff going on before the first loop begins, including setting all LOCAL variables that will be used in the loop.
More Code Information:
I should note that there are only two places in our entire application (thousands upon thousands of lines of code) where the string 'checkCustomerListing' exists. One is where the function is called, and two is where the function is declared. There are no other instances of the string checkCustomerListing anywhere.
Update: 6 September, 2011
I added some additional error checking to see if I could find out what the app thought checkCustomerListing was (thanks Adam and Ryan). Here's my new try/catch statement:
<cfcatch type="any">
<cfset local.tmpError.cfcatch = cfcatch>
<cfif isDefined("checkCustomerListing")>
<cfset local.tmpError.customerListing = checkCustomerListing />
<cfset local.tmpError.customerListingMeta = getMetaData(checkCustomerListing) />
<cfelse>
<cfset local.tmpError.customerListing = "Checkcustomerlisting is not defined" />
</cfif>
<cfset Server.Utilities.Errors.emailCaughtError(local.tmpError)>
</cfcatch>
So I got an error this morning, and in the email I received, there is no customerListing node in the dump, but there is a meta node:
CUSTOMERLISTINGMETA:
[struct]
ACCESS: public
HINT: Returns the numeric inventory ID for an existing inventory listing, or 0 if the listing doesn't exist.
NAME: checkCustomerListing
OUTPUT: false
PARAMETERS:
[array]
1) [struct]
HINT: [empty string]
NAME: Acct_ID
REQUIRED: true
TYPE: numeric
2) [struct]
HINT: [empty string]
NAME: CustomerListingID
REQUIRED: true
TYPE: string
RETURNTYPE: numeric
All of that meta information is exactly correct... so if it can find the metadata for the function, why can't it find the function itself?
This usually crops up as a result of lack of VARing as others have alluded to. Quite often people have a private function called "getStuff" and within that they have a query called "getStuff". If the variable isn't VARed, then the getStuff query result goes into the CFC's variables scope, which will overwrite the function getStuff because that also resides in the variables scope.
So check your usage of checkCustomerListing for any same-named variables that aren't VARed (or in the local scope).
(and note that the checkCustomerListing variables don't need to be within the same method for this to happen... it could be anywhere in the CFC or any extending or super CFCs...)
Why are you doing super.checkCustomerListing? You would only do that if you overrode the function in your service and wanted to run the 'parent'. Just call checkCustomerListing().
It may be in your abridged code but are you validating that local.invStruct.AcctID and local.invStruct.CustomerListingID both exist and are of the proper types? Sometimes in Java "function doesn't exist" means "method signature doesn't exist". I am not sure when CF validates datatypes but if you are looping over the same two variables if might be shortcutting and not validating the types in later loops.
Might also toss a val() on the return value just in case.
i've seen this before when you have an object cached in a scope that is taking a while to run and you either overwrite or remove the object in the cache. my guess is this is what is happening, another application is doing something to the object in the cache and it's casuing the other applications to trip over each other.
a good way to test this is to duplicate the object cached in the server scope to a local variable and then use that local variable to run your process:
<cfset _feedservice = duplicate(Server.FeedService)>
<cfset processStruct = _feedservice.processXmlFile(filePath) />
now even if the object in the server scope gets overwritten or removed, you still have a copy of the original object that your process will use. it also won't be a bad idea to use locks around the above code:
<cflock scope="server" timeout="5" type="readonly">
<cfset _feedservice = duplicate(Server.FeedService)>
<cfset processStruct = _feedservice.processXmlFile(filePath) />
</cflock>
now personally an even better practice (in my opinion) would be to add something to your onApplicationOnStart() event that will copy all the objects that you are caching to the server scope into the application scope. the only reason i can see anyone ever using the server scope is because you want all the applications on the server sharing the same objects. doing this will still allow your applications to share the same code, but will prevent other applications from accidentally tripping over each other
I have to use a Variable(Query Resultset) in ColdFusion, which will get the results from Other Application DB, and stores in Coldfusion Application.
The main idea is that I need to call the other Application DB only at Server startup time and cache the results in local. And I need to read the variable in other pages in my Application. I won't overwrite that variable in any page.
On googling I found that 'onApplicationStart' is useful to assign the variables at Application Startup time.
Is using the onApplicationStart fine or is there any other way? We can assign a variable at startup time(one time).
If onApplicationStart is fine: how to use? Maybe any link where it is explained clearly is helpful.
Well, it depends. How often will this query data be updated? If it really is unchanging, then onApplicationStart() is a fine place to put it. However, if it will change every so often, you can just tell Coldfusion to cache the query for a certain period of time, then you don't need to mess with onApplicationStart(), but rather when you call the query it will return the cached result automatically (within your specified time period).
Regardless, I would write a custom function to retrieve the data. Then it will be trivial to call it from onApplicationStart() or elsewhere.
Startup.cfc: (Named whatever you like)
<!--- Replace the datasource name with your db name --->
<cffunction name="getStartupQuery" hint="Returns a query recordset for startup">
<cfargument name="datasource" required="no" type="string" default="OtherAppDB">
<!--- Init the query variable --->
<cfset var result = queryNew("id")>
<!-- Get the query dataset --->
<cfquery name="result" datasource="#arguments.datasource#">
YOUR QUERY HERE
</cfquery>
<cfreturn result>
</cffunction>
Application.cfc: (Just the important parts)
<cffunction name="onApplicationStart">
<!--- init the startup.cfc, then retrieve the data
and save it to the application scope. Remember the component name must match
your component above --->
<cfset var startup = createObject("component", "startup")>
<cfset application.varFromOtherDB = startup.getStartupQuery()>
<cfreturn true>
</cffunction>
Now, you should be able to access this variable from any CFM or CFC in your application using:
<cfset myNewVar = application.varFromOtherDB>
or
#application.varFromOtherDB#
IF you use the onApplicationStart() method, I highly recommend implementing a method to reinit the application. For an example, see this other discussion.