Tuesday, September 29, 2009

CFCatch me if you can

One thing that really annoys me with the cfcatch struct/object (or whatever its pretending to be) is trying to use one of the keys and have it not exist. There have been a few times where I simply want to log cfcatch.detail and it's undefined, throwing a hard error. To me, if it's documented that it is going to be available, it should at least be available as an empty string. I set out this morning looking for a clean way of param'ing some of the keys in cfcatch. Since it doesn't seem to be a true struct, you can't simply do:
<cfparam name="cfcatch.detail" default="" />
That would be way too easy. I found a couple places that suggested using duplicate() to make it act like a struct. I tried that too, and you still can't do a cfparam on it. The key simply won't exist.

After much googling, I came across this post by Isaac Dealey. So using that idea, I created the following function:
<cffunction name="structifyCatch" returntype="struct">
<cfargument name="catch" type="any" required="true"/>

<cfset var err = structnew() />
<cfset structappend(err,arguments.catch,true) />

<cfreturn err />

</cffunction>
then to implement it, you call the following:
<cftry>
... some code ...
<cfcatch>
<cfset cfcatch = structifyCatch(cfcatch) />
<cfparam name="cfcatch.message" default=""/>
<cfparam name="cfcatch.detail" default=""/>
<cflog text="There was an error in somecode().
Message: #cfcatch.message# Detail: #cfcatch.Detail#"
application="yes">
</cfcatch>
</cftry>
Hopefully this will save you a little bit of angst.
There are some interesting behaviors documented here too:
cfcatch was born a struct, then it wasn’t but now it’s back

Blessings,
Terry

5 comments:

Ben Nadel said...

I wonder if StructCopy() would be work when Duplicate() didn't. Duplicate() might be *too good* at copying the underlying object, while StructCopy() might work based on keys. I haven't testet this.

Teeps (Terry Palmer) said...

I tried structcopy() first to no avail. In fact it failed because it doesn't think it's a struct. The message basically said that you can't do a struct copy on this type of object. I wish cfcatch wasn't even called a struct. It leads to all sorts of false hopes. Either that or make it a true struct.

Adam Cameron said...

Can you post some code that reproduces this symptom of keys missing from the exception struct? I've never come across this.

--
Adam

jason olmsted said...

While it does seem wrong if the detail property (or any of the others on page 72 of the CFML Reference)aren't available, why couldn't you use StructKeyExist(cfcatch,"detail") as part of a conditional statement?

Teeps (Terry Palmer) said...

@Adam .. just a quick example with the "SQL" key. This will only be available if the catch type is database. There are times where you are catching any catch type and would like to spit out sql if its available.

<cftry>
<cfset x = a />
<cfcatch>
<cfoutput>
#cfcatch.message#<br />
#cfcatch.sql#
</cfoutput>
</cfcatch>
</cftry>

@Jason .. "Why not just throw in some conditional logic?" Absolutely you could. I guess this was just out of principle, or maybe stubborness. I would much rather do:

<cffunction name="someFunction" returntype="string">
<cftry>
... do some code here ...
<cfcatch>
<cfset cfcatch = structifyCatch(cfcatch)>
<cfparam name="cfcatch.detail" default=""/>
<cfparam name="cfcatch.message" default=""/>
<cflog application="true" text="Message: #cfcatch.message# Detail: #cfcatch.detail#"/>
</cfcatch>
</cftry>
<cfreturn ''/>
</cffunction>

than I would this:

<cffunction name="someFunction" returntype="string">
<cfset var _message = ''/>
<cfset var _detail = ""/>

<cftry>
... do some code here ...
<cfcatch>
<cfif structkeyexists(cfcatch,'message')>
<cfset _message = cfcatch.message />
</cfif>
<cfif structkeyexists(cfcatch,'detail')>
<cfset _detail = cfcatch.detail />
</cfif>
<cflog application="true" text="Message: #_message# Detail: #_detail#"/>
</cfcatch>
</cftry>
<cfreturn ''/>
</cffunction>

In one particular place I was doing a lot of trapping and logging with try/catch and just got sick of having to throw in extra conditional logic in when "in theory" I should have just been able to param those keys.