I'm trying to modify existing codes in my ColdFusion application left by previous programmer. I don't understand the meaning of this line of code (the one with question marks):
<cfset Application[#form.username#] = 0> ??????
<cfset Session.loggedin="Yes">
<cfset Session.username="#Trim(Form.username)#">
Maybe I haven't been working with CF long enough to see this syntax so I don't know what this mean.
When setting an application variable I usually use this syntax:
<cfset application.variableName = "some value">
Can someone explain to me what is this ?
Thank you
As well as explicitly stating variable names at "code time" with dot notation, CFML allows one to reference them dynamically at runtime via a string value.
This is done via associative array notation (using square brackets), eg:
myVariableName = "foo";
variables[myVariableName] = "moo"; // equivalent to variables.foo = "moo"
Related
I created sample code using the Microsoft SQL Server Northwind demo database. If you don't have access to this demo database here is a simple (MS-SQL) script to create the table and a row of data for this question.
CREATE TABLE [dbo].[Products](
[ProductID] [int] IDENTITY(1,1) NOT NULL,
[ProductName] [nvarchar](40) NOT NULL,
[SupplierID] [int] NULL,
[CategoryID] [int] NULL,
[QuantityPerUnit] [nvarchar](20) NULL,
[UnitPrice] [money] NULL CONSTRAINT [DF_Products_UnitPrice] DEFAULT (0),
[UnitsInStock] [smallint] NULL CONSTRAINT [DF_Products_UnitsInStock] DEFAULT (0),
[UnitsOnOrder] [smallint] NULL CONSTRAINT [DF_Products_UnitsOnOrder] DEFAULT (0),
[ReorderLevel] [smallint] NULL CONSTRAINT [DF_Products_ReorderLevel] DEFAULT (0),
[Discontinued] [bit] NOT NULL CONSTRAINT [DF_Products_Discontinued] DEFAULT (0),
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[ProductID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Products] ON
GO
INSERT [dbo].[Products] ([ProductID], [ProductName], [SupplierID], [CategoryID], [QuantityPerUnit], [UnitPrice], [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]) VALUES (1, N'Chai', 1, 1, N'10 boxes x 20 bags', 18.0000, 39, 0, 10, 0)
GO
SET IDENTITY_INSERT [dbo].[Products] OFF
GO
Here is the ColdFusion code:
<cfset variables.useTempVar = false>
<cfquery datasource="Northwind2014" name="qryNWProducts">
SELECT TOP 1 * from Products;
</cfquery>
<cfdump var="#qryNWProducts#" label="qryNWProducts">
<cfset variables['stProduct'] = {}>
<cfloop index="vcColName" list="#qryNWProducts.columnlist#">
<cfif variables.useTempVar>
<cfset variables['temp'] = qryNWProducts[vcColName]>
<cfset variables['stProduct'][vcColName] = variables.temp>
<cfelse>
<cfset variables['stProduct'][vcColName] = qryNWProducts[vcColName]>
</cfif>
</cfloop>
<cfdump var="#variables['stProduct']#" label="variables['stProduct']">
<cfloop collection="#variables['stProduct']#" item="key"><cfoutput>
variables['stProduct']['#key#'] JVM datatype = #getMetadata(variables['stProduct'][key]).getName()#<br>
</cfoutput></cfloop>
<br>
This always works:<br>
<cfset variables['aPhrase'] = "I ordered " & variables.stProduct.ProductName & " for " & DollarFormat(variables.stProduct.UnitPrice) & ".">
<cfoutput>#variables['aPhrase']#<br></cfoutput>
<br>
With "variables.useTempVar = false", the next line will throw a "Complex object types cannot be converted to simple values. " error.<br>
<cfset variables['aPhrase'] = "I ordered " & variables['stProduct']['ProductName'] & " for " & DollarFormat(variables['stProduct']['UnitPrice']) & ".">
<cfoutput>#variables['aPhrase']#<br></cfoutput>
The code above has a boolean variable named "variables.useTempVar" at the top that can be flipped to see the error that I'm getting.
It looks like the direct assignment (when variables.useTempVar = false) from the query to the structure causes the structure values to be of JVM type "coldfusion.sql.QueryColumn".
Another note: if this line of code:
<cfset variables['stProduct'][vcColName] = variables.temp>
is changed to:
<cfset variables['stProduct'][vcColName] = variables['temp']>
The JVM datatype will be "coldfusion.sql.QueryColumn".
When the dot notation temp variable is used to assign the query field (when variables.useTempVar = true); the JVM datatypes are simple types that matches up pretty well with the database column types (java.lang.Integer, java.math.BigDecimal, java.lang.String, etc.).
I've also experiemented with statements like this and that provided some odd results:
<cfset variables['stProduct'][vcColName] = qryNWProducts[vcColName].toString()>
Here's the question. Is this the best way to transfer the simple values from a query to a structure? It seems odd to be forced to use a temp variable and dot notation to make this work.
Comment: I've always thought that dot notation and associative array notation were equivalent. This code example appears to contradict that opinion.
#Leigh is correct in that you need to supply the row number when using associative array notation with a query object. So you'd reference row 1 like: qryNWProducts[vcColName][1]
As for your question
Is this the best way to transfer the simple values from a query to a structure?
Are you sure you need a struct? Your question doesn't really specify the use case, so it is entirely possible that you would be better off using the query object as-is.
If you do need it to be a struct (and since you are using ColdFusion 11) might I suggest you take a look at serializeJSON/deSerializeJSON to convert this to a struct. The serializeJSON has a new attribute that will properly serialize a query object into an "AJAX friendly" JSON array of structs. You can then deSerialize the JSON into a CF array, like so:
NWProducts = deSerializeJSON( serializeJSON( qryNWProducts, 'struct' ) )[1];
Which would return a struct representation of the first row in that query object.
Although it's not obvious from the Adobe docs for serializeJSON, the second parameter can be one of: true|false|struct|row|column which will change how the resulting data is formatted.
Here's a runnable example of using the above technique showcasing each serializeQueryAs option.
It's also a better practice to start moving that kind of code into cfscript. queryExecute is quite easy to use and makes script based queries very easy to develop. See the How To Create a Query in cfscript tutorial at trycf.com for more on how to develop script based queries.
Final note, and this is a bit off topic but it is a generally accepted best practice to not use Hungarian Notation when naming variables.
#Abram's covered the mains answer, but just to pick up one tangential point you raise.
Dot notation and associative array notation are generally equivalent in CFML. However in the case of queries, there is a slight variation. Dot notation: query.columnName is treated as shorthand for query.columnName[currentRow] (where currentRow defaults to 1).
Associative array notation with queries does not have this "syntactic sugar", so query["columnName"] refers to the entire column, as the syntax actually indicates.
There are no functions I am aware of in CFML that take a query column as an argument, however the CFML engine will convert the column to an array if it's used in an array function. This is quite handy sometimes.
ColdFusion is converting a string to number when passing to JS via a SerializeJSON and deSerializeJSON.
This is only happening when an 'E' is used between two set of numbers. like 3E6, 65E3, 56e45 etc. This is the code inside cfscript.
x = "2e9";
writedump(SerializeJSON(x));
writedump(deSerializeJSON(SerializeJSON(x)));
Output:
2.0E9 2000000000
Please suggest, if is there any other way for such issues.
It is this: https://bugbase.adobe.com/index.cfm?event=bug&id=3695627: "SerializeJSON turns strings that look like scientific notation into floats."
It's a known bug in CF9, and it's fixed in CF10.
In the meantime, you will just have to pad the string with something to force ColdFusion to not see it as a number in scientific notation.
Or upgrade to CF10 (CF9 is end of life next month, btw). Or to Railo.
I have resolved this using below solution
let's say ItemUnit = 12E45
stcReturn.firstname = "Yes";
stcReturn.lastname = "Man";
stcReturn.ItemUnit = "12E45"
error output after deSerializeJSON stcReturn.ItemUnit = 12e+46
<cfset stcReturn.ItemUnit = ItemUnit />
<cfset StructSetMetaData(stcReturn.ItemUnit, {ItemUnit :
{"type":"string","name":"ItemUnit"}})/>
deSerializeJSON(stcReturn)
Correct Output:12E45
I've just experienced a behaviour that defies any logic and could potentially lead to serious issues and was wondering if it was a bug or if the behaviour was itended and what are the best practices to circumvent the issue? If it's a bug, is there a patch?
Here's the two wierd behaviours that when put together are a threat to any system's data integrity.
int('1 2') -> 41276
isValid('numeric', '1 2') -> true
Why? Well let's see...
<cffunction name="deleteSomething" access="public" returntype="void">
<cfargument name="somethingId" type="numeric" required="yes">
<cfquery datasource="#dsn()#">
DELETE
FROM Something
WHERE id = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.somethingId#">;
</cfquery>
</cffunction>
<cfset deleteSomething('1 2')>
Here, the type="numeric" arguments validation (which perhaps is based on the same algorithm as isValid?) doesn't throw with '1 2'. Even worse, cfqueryparam cfsqltype="cf_sql_integer" seems to be using int to convert the value which will end up being 41276.
In other words, deleteSomething('1 2') will delete the entity with id 41276 instead of throwing an exception since the value 1 2 is obviously not numeric.
Now, the only fix I thought of is to perform additionnal argument validation using isValid('integer', ... or a regular expression, but that's a real pain and besides, I never understood why they haven't implemented type="integer"?
Obviously, I also always made the false assumption that cfqueryparam type="cf_sql_integer" would validate that the value passed is a valid integer.
EDIT:
It seems that even isvalid('integer', ... is also not reliable as we can see in
Why isvalid("integer","1,5") = YES?
EDIT2:
I know that I could add additionnal arguments validation for every expected integer argument in every function, however that would require to fix a huge code base in my case and it's also very error-prone. It also makes the built-in argument validation completely useless in this case.
I would rather prefer a solution where I could create and apply an unofficial patch. Is that a realistic option? If so I would like to be pointed out in the right direction.
EDIT3: It doesn't solves all the problems, but CF11 added support for a strictNumberValidation application level configuration.
"Starting from ColdFusion 11, this function evaluates on a more strict
basis. Setting this value to false makes the isValid function to
behave in the older way. This setting effects cfargument, cfparam and
cfform tags wherever integer & numeric validation is used. Based on
this setting, the validation reflects in those tags as well."
This is a variation on that theme from the other question. See this code (or run it on cflive.net):
<cfscript>
s = "1 2";
i = int(s);
v = isValid("numeric", s);
d = createOdbcDate(s);
writeDump([s,i,v,d]);
</cfscript>
s converts to 41276 when calling int(), and when using it as an input for createOdbcDate(), we get:
January, 02 2013 00:00:00 +0000
So "1 2" is being interpreted as "m d", with an implied year of the current year.
Which is utterly stupid. But there you go.
You can use regular expressions to find out if there are any non numeric characters in a given form field:
reFind( "[^\d-]", "1 2")
That will match any character that is not a number, not a -
If you want to check only positive numbers, you can use
reFind( "[^\d]", "1 2")
If this returns true, you do not have an integer.
I'm a bit stumped on this one..
I currently have a string.
Please enter your variable.firstname here
What i would like to do is find the variable.firstname in the string and convert it to be used as #variable.firstname#
Im using CF8, and ive looked at using findNoCase() but the variable.firstname portion can appear anywhere. I am also trying to use this in a Coldfusion Custom Tag as its to simply display the firstname of the user that could be dynamically populated.
I cant use any other functionality to change it IE = variable['firstname] because the variable could be the result of a dynamic variable i pass in and the query for the content will reside within the custom tag.
<cfset yourNewString = replace(yourOldString,'variable.firstname',
'##variable.firstname##', 'all')>
Note the double pound signs.
I cant use any other functionality to change it IE =
variable['firstname] because the variable could be the result of a
dynamic variable i pass in and the query for the content will reside
within the custom tag.
I'm not sure I understand exactly what you're saying here but if you're saying that variables.firstname is coming from another variable and the .firstname is the dynamic part you could still use array notation.
<cfset myName = "Travis">
<cfset yourName = "user125264">
<cfset myCustomVariable = "myName">
<cfoutput>Hi, My name is #variables[myCustomVariable]#. </cfoutput>
<cfset myCustomVariable = "yourName">
<cfoutput>Your name is #variables[MyCustomVariable]#.</cfoutput>
Output: Hi, My name is Travis. Your name is user125264.
If that isn't what you meant, I apologize.
If you're trying to replace variable.firstname with #variables.firstname# and then also get the value of that variable, you'll need to do the replace <cfset yourNewString = replace(yourOldString,'variable.firstname',
'##variables.firstname##', 'all')> and then wrap the resulting string in an evaluate() function (and an inner de() to prevent CF from evaluating everything): <cfset evaluatedString = evaluate(de(yourNewstring))>.
If there are more variables besides variable.firstname that need this kind of translation, you'll need to get into regex with reReplace() to catch them all in one statement. My regex is rusty, so you'll need to Google that bit on your own. ;o)
How would I use use a string function to select everything in a variable after the last "/"
http://domain.com/g34/abctest.html
So in this case I would like to select "abctest.html"
Running ColdFusion 8.
Any suggestions?
Um, a bit strange to give very similar answer within few days, but ListLast looks as most compact and straightforward approach:
<cfset filename = ListLast("http://domain.com/g34/abctest.html","/") />
And yes, IMO you should start with this page before asking such questions on SO.
Joe I'd use the listToArray function, passing the "/" as the delimiter, get the length of the array and get the value in the last slot. See sample below
<cfset str = "http://domain.com/g34/abctest.html"/>
<cfset arr = ListToArray(str,"/","false")/>
<cfset val = arr[ArrayLen(arr)]/>
<cfoutput>#str# : #val#</cfoutput>
produces
http://domain.com/g34/abctest.html : abctest.html