I'm seeing some weird behaviour when using CF's local scope in an object literal, in function arguments. But only when executed inside a loop...
Example code:
<cffunction name="f">
<cfoutput>
<cfset LOCAL.foo = 123 />
<!--- Works fine --->
#serializeJSON({blah = LOCAL.foo})#
<!--- Works fine --->
<cfloop from=1 to=1 index="i">
<cfset bar = {blah = LOCAL.foo} />
#serializeJSON(bar)#
</cfloop>
<!--- Element FOO is undefined in LOCAL --->
<cfloop from=1 to=1 index="i">
#serializeJSON({blah = LOCAL.foo})#
</cfloop>
</cfoutput>
</cffunction>
<cfset f() />
PS: serializeJSON() is just for example purposes. This is happening in any function I've tested where one of the arguments is a struct.
Works just fine in Railo.
It also doesn't make any difference if using any other container instead of local scope, also it's impossible to catch this with cftry.
If you serialize just local scope within the loop:
<cfloop from=1 to=1 index="i">
#serializeJSON(local)#
</cfloop>
Result is:
{"ARGUMENTS":{},"___IMPLICITARRYSTRUCTVAR1":{"BLAH":123},"___IMPLICITARRYSTRUCTVAR0":{"BLAH":123},"FOO":123}
Looks like a bug. Mind filing?
LOCAL is a scope used only within functions. If you try to create a LOCAL scope variable outside of a function, it will fail.
I will write up a test and prove it to you in a minute....
UPDATE Actually, I have CF 8 at work and can't test it.
In CF8 and below, you can set LOCAL.Foo, but it's not really a CF scope.
In CF9 and above, LOCAL can be set only within a function.
<cffunction>
<cfset LOCAL.foo = 1>
<cfreturn LOCAL.foo>
</cffunction>
Related
I've not used cfthread before but i'm using the following code and it's not working.
<cfloop from="1" to="5" index="local.pageNo">
<cfthread name="thr#local.pageNo#" threadIndex="#local.pageNo#" action="run">
<cfif local.pageNo GT 1>
<cfhttp url="#local.apiURL#&page=#local.pageNo#" method="get" result="local.myResults" >
</cfhttp>
<cfset local.myResponse = deserializejson(local.myResults.filecontent)>
</cfif>
<cfloop from="1" to="#arrayLen(local.myResponse.result)#" index="i">
<cfset local.apartmentList = listAppend(local.apartmentList,local.myResponse.result[i].id & '-0')>
</cfloop>
</cfthread>
</cfloop>
<cfthread action="join" name="thr1,thr2,thr3,thr4,thr5"/>
I'm expecting local.apartmentList to be a big list of ID's but it returns empty. It's almost as if the code inside the thread is just being skipped. Can anybody spot what i'm doing wrong?
When you're using <cfthread>, the code within those tags is not run within the same context as the code around it. So you need to pass any variables you intend to use into it (as attributes of the <cfthread> tag), or "share" them via the request scope.
So your <cfthread> block won't know what variables like local.pageNo are.
Any error occurring in <cfthread> processing is logged, so you need to look at your logs to see what errors are cropping up.
I recently upgraded a system from CF8 to CF10 and have one bug that I'm having problems tracking down. It has to do with a remote API call that gets a JSON string back and that string then gets converted to a query object. That's where I'm coming across the error:
Element [n] is undefined in a Java object of type class coldfusion.runtime.Array. The problem is in the function that converts the string to a query.
<cffunction name="CFjsonToQuery" access="public" returntype="query" output="no">
<cfargument name="cfData" required="yes" type="struct"/>
<cfset var LOCAL = {}/>
<cfset LOCAL.tmpQry = QueryNew( ArrayToList(ARGUMENTS.cfData.Data.COLUMNS) ) />
<cfloop index = "i" from = "1" to = "#ArrayLen(ARGUMENTS.cfData.Data.DATA)#">
<cfset LOCAL.Row = QueryAddRow(LOCAL.tmpQry) />
<cfloop index="k" from="1" to="#ArrayLen(ARGUMENTS.cfData.Data.DATA[i])#">
<cfset LOCAL.colName = ARGUMENTS.cfData.Data.COLUMNS[K]/>
<cfset QuerySetCell(LOCAL.tmpQry,LOCAL.colName,ARGUMENTS.cfData.Data.DATA[i][k],LOCAL.Row)/>
</cfloop>
</cfloop>
<cfreturn LOCAL.tmpQry/>
</cffunction>
Anywhere the JSON returns 'null' (i.e. "...","19107-3609",null,null,null,"...") the error is thrown. I've tried using isNull to check if it's null in the cfloop:
<cfif isNull(ARGUMENTS.cfData.Data.DATA[i][k])>
<cfset ARGUMENTS.cfData.Data.DATA[i][k] = 'I AM NULL'/>
</cfif>
EDIT - here's a simplified example - the issue is the way the newer deserializeJson() works I believe:
<cfset jstr = '{"SUCCESS":true,"ERRORS":[],"DATA":{"COLUMNS":["ID","FNAME","LNAME"],"DATA":[[390132,"steve",null]]}}'/>
<cfset cfData = deserializeJson(jstr) />
<cfloop index = "i" from = "1" to = "#ArrayLen(cfData.Data.DATA)#">
<cfset Row = QueryAddRow(tmpQry) />
<cfloop index="k" from="1" to="#ArrayLen(cfData.Data.DATA[i])#">
<cfset colName = cfData.Data.COLUMNS[K]/>
<cfset QuerySetCell(tmpQry,colName,cfData.Data.DATA[i][k],Row)/>
</cfloop>
</cfloop>
I've tried all sorts of tests for empty string, isNull etc. and I'm still not sure how to get the query object built if deserializejson returns:
[undefined array element] Element 3 is undefined in a Java object of type class coldfusion.runtime.Array.
This does seem to work:
<cfset cfData = deserializeJson(returnData,'FALSE') />
<cfset qryData = cfData.data />
This lets me then use qryData as if it were a normal cfquery.
You can do a check if the element is undefined using the CF Function ArrayIsDefined(array, elementIndex)
What I've done for now is add 'FALSE' to the deserializeJSON strictMapping flag and that seems to automatically create a query object? I'll admit though this is getting into the underpinnings of CF10 and I could be wrong on that. I'll update my code above for visual clarity.
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.)
I am trying to convert an application I support from application.cfm to application.cfc. I followed Ben Nadel's ColdFusion Application.cfc Tutorial And Application.cfc Reference, but my pages cannot access any of the variables in the APPLICATION scope, without adding APPLICATION. to the variable. As a side note, this application uses 0 cfc's currently.
here is my application.cfc.
<cfcomponent displayname="Application" hint="Handle the application" output="true">
<cfset THIS.Name = "AAS" />
<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 60, 0 , 0) />
<cfset THIS.SessionManagement = true />
<cfset THIS.setClientCookies = false />
<cfset THIS.versionNum = '1'>
<cfset THIS.genericUserID = 'o005265'>
<cfset THIS.genericPassword = 'zo005265'>
<cffunction
name="OnApplicationStart"
hint="Fires when the application is first created."
access="public"
output="false"
returntype="boolean">
<cfset APPLICATION.aasURL = 'http://127.0.0.1:8500/aaas'>
<cfset APPLICATION.dsn = 've0_aaas'>
<cfset APPLICATION.aas_system = 'development (studio)'>
<cfreturn true />
</cffunction>
</cfcomponent>
Basically I just copied what was in the application.cfm page, and figured it would work the same. I am guessing that I have to invoke this somewhere? That is the only thing that I can think of. Any help would be great.
--- EDIT ---
I have added the OnRequest and OnApplicationStart methods after #EvikJames answer
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false" hint="Fires when the application is first created.">
<!--- Return out. --->
<cfset APPLICATION.aasURL = 'http://127.0.0.1:8500/aaas'>
<cfset APPLICATION.datasource = 've0_aaas'>
<cfset APPLICATION.aas_system = 'development (studio)'>
<cfreturn true />
</cffunction>
<cffunction name="OnRequest" access="public" returntype="void" output="true" hint="Fires after pre page processing is complete.">
<!--- Define arguments. --->
<cfargument name="TargetPage" type="string" required="true">
<!--- Include the requested page. --->
<cfinclude template="#ARGUMENTS.TargetPage#" />
<cfset VARIABLES.dsn = APPLICATION.dsn>
<cfset VARIABLES.aasURL = APPLICATION.aasURL>
<cfset VARIABLES.aas_system = APPLICATION.aas_system>
<!--- Return out. --->
<cfreturn />
</cffunction>
You aren't really trying to use "application" variables (which always need to be scoped). I suspect your old application.cfm page had something like.
<cfapplicatin name="blah"...>
<cfset dsn = 'mydsn'/>
And then you were able to do:
<cfquery datasource="#dsn#">
This approach does not utilze the application scope ... it is merely taking advantage of the fact that your application.cfm always runs no matter what. What it is actually doing is putting variables in the variables scope. Because CF always checks the "variables" scope first you soemthing like #dsn# works - but that is not the same as the application scope.
To mimic this behavior in Application.cfc (as has been suggested) you will need to put your variable in the "onRequest()" function instead of the "onApplicationstart()" function - like so:
<cffunction name="onRequest">
<cfset dsn = 'mydsn'/>
</cffunction>
That is expected. To reference application variables you need to prefix them with application.
In your onApplication start method, do this:
<cfset APPLICATION.datasource = 'MyDSN'>
In your onRequest method, do this:
<cfset VARIABLES.DSN = APPLICATION.datasource>
Then, this query will work:
<cfquery datasource="#dsn#">
// sql
</cfquery>
I should add that when you are fully upgraded, you can remove all of the code above just set the default datasource:
<cfset THIS.datasource = 'MyDSN'>
If the variable is in the application scope, you will always need to identify it that way in your .cfm pages. If you use a variable name without the scope prefix, the application scope is not looked at.
if you want to declare variables in the application.cfc that can be accessed without the application scope in your other pages, simply declare the variables outside of any functions.
<component>
<cfset this.name = "applicationName">
<cfset otherVar = 'something'>
<cfset otherVar2 = 'something else'>
<cffunction name="onApplicationStart>.....</cffunction>
</component>
otherVar and otherVar2 can be called without scope prefix on all .cfm pages.
It sounds like you were not originally using application scoped variables. If the variables were not originally scope with "application." then they were simply in "variables scope" (confusing wording I know) which is accessible by the cfm page hit and others included. That is one big change when moving between application.cfm and application.cfc. The general idea there follows the principle that included CFM files share variables scope and CFC files do not.
If the only change you have to make is changing #dsn# to #appplication.dsn# then just do it and get it over with. There are tools such as WinGrep or even Notepad++ which have find and replace across multiple files.
Thanks for replying!! But I am still not able to do it. Error that I am getting is
"Element objGet1 is undefined in a Java object of type class coldfusion.runtime.VariableScope."
Below is my full code. I just want to dump the value of each thread containing cfhttp information.
http://www.google.com/search?" & "q=Vin+Diesel" & "&num=10" & "&start=") />
<cfset intStartTime = GetTickCount() />
<cfloop index="intGet" from="1" to="10" step="1">
<!--- Start a new thread for this CFHttp call. --->
<cfthread action="run" name="objGet#intGet#">
<cfhttp method="GET" url="#strBaseURL##((intGet - 1) * 10)#" useragent="#CGI.http_user_agent#" result="THREAD.Get#intGet#" />
</cfthread>
</cfloop>
<cfloop index="intGet" from="1" to="10" step="1">
<cfthread action="join" name="objGet#intGet#" />
<cfdump var="#Variables['objGet'&intGet]#"><br />
</cfloop>
and when I use after thread joining inside the loop. I get the desired results
Thanks!!
Two problems happening here.
As pointed out by Zugwalt, you need to explicitly pass in variables that you want to reference within the scope of your thread. He missed the CGI variable, that scope doesn't exist within your thread. So we pass in just what we need to use in the thread, userAgent, strBaseURL, and intGet.
Second problem, once joined, your threads are not in variable scope, they are in the cfthread scope, so we have to read them from there.
Corrected code:
<cfloop index="intGet" from="1" to="2" step="1">
<!--- Start a new thread for this CFHttp call. Pass in user Agent, strBaseURL, and intGet --->
<cfthread action="run" name="objGet#intGet#" userAgent="#cgi.http_user_agent#" intGet="#intGet#" strBaseURL="#strBaseURL#">
<!--- Store the http request into the thread scope, so it will be visible after joining--->
<cfhttp method="GET" url="#strBaseURL & ((intGet - 1) * 10)#" userAgent="#userAgent#" result="thread.get#intGet#" />
</cfthread>
</cfloop>
<cfloop index="intGet" from="1" to="2" step="1">
<!--- Join each thread --->
<cfthread action="join" name="objGet#intGet#" />
<!--- Dump each named thread from the cfthread scope --->
<cfdump var="#cfthread['objGet#intGet#']#" />
</cfloop>
Generally, unscoped variables get put into the Variables scope, so you can use struct bracket notation to refer to them:
Variables['objGet#intGet#']
or
Variables['objGet'&intGet]
These are both basically doing the same thing - just different syntaxes.
Code run inside a cfthread tag has its own scope. Try passing the variable you want it to access as an attribute. I like to name it something different just to help me keep track.
<!--- Start a new thread for this CFHttp call. --->
<cfthread action="run" name="objGet#intGet#" intGetForThread="#intGet#">
<cfhttp method="GET" url="#strBaseURL##((intGetForThread- 1) * 10)#" useragent="#CGI.http_user_agent#" result="THREAD.Get#intGetForThread#" />
</cfthread>