This question came in this morning. Looking at the code below, see if you can determine the issue. (Of course, the title of the blog entry is a bit of a give away.)
<cffunction name="onRequestStart" returntype="boolean" access="public">
<cfargument name="url" required="true" type="string">
<cfif StructKeyExists(url, "init")>
<cfset onSessionStart()>
</cfif>
<cfreturn true>
</cffunction>
This is a fairly simple onRequestStart - and one based on a pattern I use quite a bit - specifically the init URL hook to restart something (normally the Application, but in this case the Session). When run, this gives the following error:
You have attempted to dereference a scalar variable of type class java.lang.String as a structure with members.
So what's the issue? By accident, the user named the argument to onRequestStart the same as one of ColdFusion's built-in scopes. By telling ColdFusion to pass the requested page as "url", it blew away the URL scope. Instead of a structure, URL was now a simple string.
Easy enough mistake to make - and I swear I looked at it for five minutes before I noticed it.
Archived Comments
I know people are going to say reserved words here, but truly this is an error with ColdFusion itself.
In this case URL should be in the arguments scope, so how does the URL scope get trashed here?
Now if I was to type
<cfset url = 'Something' />
Then I could understand a problem, but it's not and ColdFusion should be adhering to scopes internally.
I have blogged about another scenario, that is a bug, and is a similar problem. But this got broken somewhere between ColdFusion 8 and ColdFusion 9, and was also reported to Adobe as a bug as well.
Reason it is a bug, because it works in ColdFusion 8. And I am sure that this example would also work in ColdFusion 8 as well.
For reference to a similar bug head on over too.
http://www.andyscott.id.au/...
Well the first issue is the onRequestStart doesn't get passed a url (string or scope) it gets passed the path to the requested template, so url is a misleading name for the argument.
It looks like CF is treating "url" as an unscoped variable and is looking in the arguments scope in preference to the url scope.
fwiw. Railo doesn't suffer from this and the following does exactly what you would expect
function onRequestStart(url) {
writeDump(url); // url scope
writeDump(arguments.url); // arguments.url
}
@andrew,
I would disagree that this is a bug. The url scope would be left unchanged by this code. By naming an argument url you are effectively eliminating any way you have of referencing the url scope, because CF will look in the arguments scope for the variable, and it will find it there.
@Michael... How?
@Chris, and I am almost 90% positive that ColdFusion 8 doesn't either.
It's a feature, not a bug!
I actually use this technique myself, except with the form scope. After playing around with argumentcollection="#form#", I thought "screw this - if I pass the form itself, it's passed by reference anyway, so why not just pass the form as a structure?"
So then I had to ask myself "What name should I give the cfargument? How about 'form'? Would that work?" And it does!
So now in my components, I reference form.LastName, and that means arguments.form.LastName, which came from form.LastName.
@Andrew, because when CF is evaluating an unscoped variable it firsts starts in the Local scope (function-local, UDFs and CFCs) then moves to the arguments scope and so on. URL is treated as variable so when CF is looking for it, it finds arguments.url and says this must be what you were looking for. So because CF views the scopes as just another variable this is the behavior I would expect. I don't see this as being any different from if I define url.username = "my name" and form.username = "someone else"
then i writeDump(username) I would expect the value that is output to be "my name", because I know the url scope is evaluated before the form scope. Does that make sense?
@Michael.... I am very well aware of the search order for searching variables in scopes.
<cfargument>
Is saying or it should be saying that I wish to define a variable with the name of name="" into the argument scope. Nothing more nothing less.
By the sounds of it, this is not the case.
For example look at my blog post.
<cfset var local = {} />
<cfset local.session = 'test' />
This is specifically saying that I want the variable name session to be defined inside the local scope, however this is another example that where a scope is trashed.
Now how does the search order work when setting variables? I don't believe even you can argue that the search order plays apart when setting a variable, surely!!
@Andrew, Okay I read through your blog post. Consider this code.
In application.cfc
<cfset session.config.name = "something">
<cfset application.pointerToSession = session>
In some CFC function
<cfset local.session = "here">
<cfset local.temp = session.config.name> <!--- this will error CF sees this as local.session.config.name --->
<cfset local.temp = application.pointerToSessio...> <!--- this will set local.temp to "something" --->
<cfdump var=#application.pointerToSession#> <!--- this woul d dump the session scope --->
local.session does not do anything to the session scope, but in a function CF will local.session and session as the same variable.
@phillip that will still work, it just trashes url, or will error if you don't pass url as an argument.
@Michael that is correct, so it's still a bug, because the session scope has been trashed.
If you also read on further, you will notice that the same thing happens when setting a variable into a scope that also use a method/function name.
By the way I should point out that the code in that blog post, also works in ColdFusion 8, however it became buggy in ColdFusion 9.
@Everyone else I tested this current post in ColdFusion 8, and it still trashes the url scope. Now like I said people might say do not use reserved words, but in these examples you are clearly defining a name to a scope, and it is trashing that scope. That is a bug because it is unexpected behaviour.
@Andrew, try this code.
Application.cfc
<cfcomponent>
<cffunction name="onApplicationStart" returntype="boolean" access="public">
<cfset application.url = url>
<cfreturn true>
</cffunction>
<cffunction name="onRequestStart" returntype="boolean" access="public">
<cfargument name="url" required="true" type="string">
<cfset onApplicationStart()> <!--- save current url scope into application.url so we can still access the url scope --->
<cfset request.urlHasInit = false>
<cfset request.onRequestStartURL = url>
<cfif StructKeyExists(application.url, "init")>
<cfset request.urlHasInit = true>
</cfif>
<cfreturn true>
</cffunction>
</cfcomponent>
index.cfm
<cfdump var="#url#" label="index.cfm - url">
<cfdump var="#request#" label="request">
<cfdump var="#application.url#" label="application.url">
@michael and that proves what exactly?
@Andrew, It proves the the url scope is not trashed by onRequestStart. Inside onRequestStart I call onApplicationStart which access the url scope. As I am sure you know, CF copies structs by reference when doing an assignment, so application.url and the URL scope both point to the same block of memory when onApplicationStart returns. If onRequestStart was trashing the URL scope then when the assignment was made in onApplicationStart the value of application.url would be the path to the requested page and not a reference to the URL scope.
Interesting, because on ColdFusion 9 and 10 it is trashed for me.
Try modifying you application.cfc to this.
<cfcomponent>
<cffunction name="onApplicationStart" returntype="boolean" access="public">
<cfset application.url = 'test me out'>
<cfreturn true>
</cffunction>
<cffunction name="onRequestStart" returntype="boolean" access="public">
<cfargument name="url" required="true" type="string">
<cfset onApplicationStart()> <!--- save current url scope into application.url so we can still access the url scope --->
<cfset request.urlHasInit = false>
<cfset request.onRequestStartURL = url>
<cfif StructKeyExists(application.url, "init")>
<cfset request.urlHasInit = true>
</cfif>
<cfreturn true>
</cffunction>
</cfcomponent>
You will see that the same problem occurs.
And based on that code I would expect to get the same error, because in onApplicationStart you are no longer saving a pointer to the URL scope, you are setting application.url to a string. If I change onApplicationStart to:
<cffunction name="onApplicationStart" returntype="boolean" access="public">
<cfset application.url = arrayNew(1)>
<cfreturn true>
</cffunction>
This will fail as well, except now CF throws this error:
You have attempted to dereference a scalar variable of type class coldfusion.runtime.Array as a structure with members.
Really there is nothing special about any of the scopes, as far as CF is concerned they are just another variable.
So michael what you are saying is that it is ok to use url as an argument name, as long as it is stored in the Application scope.
Because that is what is happening.
Sorry but your example is mute, because you have just identified another example of the problem at hand.
And btw I raised this as a bug with Adobe a long time ago, my view is very simple on the matter.
If you look at my blog and take my example, when ColdFusion uses the name session as in local scope. Then ColdFusion should be able to know that
writeDump(session); is a scope and do no lookup in the variable lookup, and should dump the session scope.
writeDump(local.session) should do as it is told and dump the variable session that is stored in the local scope.
Sorry but I consider it a bug, especially my example on my blog because it was a pain in the ass to change so much code to have an application migrated from ColdFusion 8 production code, to ColdFusion 9.
@Andrew
The local.session issue you describe is only Coldfusion 9 breaking backwards compatibility with Coldfusion 8 by introducing the local scope as a built-in scope.
Before CF9 the local scope convention you used existed in what was known as the unnamed scope. But Adobe (in CF9) officially canonized the local scope to the scope stack.
This is why your CFCs broke. The session scope still existed, but in the context of your CFC the local.session variable was popped off the stack first.
In the future, I would advise not using built-in keywords/scopes for your variable names to minimize the impact of future updates. This of course is never bullet proof, since conventions becoming standards can always have side effects. In this case, Adobe made the judgment call that adding the local scope would help, but these edge cases are always a possibility.
@Andrew
I should also state that, yes, it is ok to use a variable named "url" as an argument, but in the context of the function it is expected behavior to not have access to the url scope in that case.
Actually it is not expected behaviour, for example how many of you use the line debugger?
This is also a problem there, because when you are looking at all the snapshots of scopes and the scope goes missing, a developer can be left bewildered as to why.
Don't get me wrong here, I understand the problem, or lack of in most of your eyes. But Adobe has agreed that this is actually wrong behaviour, and it under review.
That doesn't mean it will be fixed, but it means enough merit is given that it is a problem.
I agree with Andrew, this is 100% a bug in the way CF is declaring "arguments.url". If it was not allowed it should throw an error, but considering it works in Railo I think it should be fixed!
I'm going with the reserved words argument.
I'll go a step further and say that for sanity purposes, argument names should be named by their order, not their semantics.
cfargument name="arg1"
cfargument name="arg2"
...
To:Sean
OMG no - never. :) Arguments should be named something sensible - so you know what they represent!
I'm using coldfusion 11 and I still get the same error
You have Attempted to dereference scalar variable of type class java.lang.String as a structure with members.
How can I fix it?
Um... don't do it. Maybe I'm misreading you, but isn't it obvious to just not use the built in scope as an argument name?