Friday, 8 August 2014

CFML: <cfcatch>: my ignorance is reduced. Over a decade after it should have



I had to take this article down for a few hours as I ballsed up both the code and the analysis! Thanks to Adam Tuttle for noticing (or making me revisit it so I noticed it, anyhow).

It pleases me when I learn something I didn't know about fundamental parts of CFML. I temporarily feel daft, but I'm used to that.

Ray - amidst a fiery exchange of disagreement last night - set me straight on a feature of CFML's exception-handling that I was completely unaware of. Despite it being well documented. Since ColdFusion 4.5. Cool!

I had never noticed this from the <cfcatch> docs:

The custom_type type is a developer-defined type specified in a cfthrow tag. If you define a custom type as a series of strings concatenated by periods (for example, "MyApp.BusinessRuleException.InvalidAccount"), ColdFusion can catch the custom type by its character pattern. ColdFusion searches for a cfcatch tag in the cftryblock with a matching exception type, starting with the most specific (the entire string), and ending with the least specific.
The "funny" (at my expense) thing here is that not only did I not know that, I had actually wanted <cfcatch> to work that way, and just ass-u-me`d it didn't so never tried it! Fuckwit.

Here's an example:

// hierarchicalExceptions.cfm
param URL.type;
try {
    writeOutput(htmlEditFormat("<code>Throwing a <strong>#URL.type#</strong> exception<br>"));

}catch (com.theapplication.thepackage.TheComponent.TheException e) {
    message = "name-spaced exception";
}catch (com.theapplication.thepackage.TheComponent e) {
    message = "component";
}catch (com.theapplication.thepackage e){
    message = "package";
}catch (com.theapplication e){
    message = "application";
}catch(any e){
    message = "default";

writeOutput(htmlEditFormat("The <strong>#URL.type#</strong> exception was caught by the <strong>#message#</strong> exception handler<br></code><hr>"));

Here we can pass various different exception types to throw, and check where they "land". These are a number of tests using Railo:

Throwing a AnyOldException exception
The AnyOldException exception was caught by the default exception handler

This is correct, if not at all exciting: we don't have a pattern that catches this, so it falls through to the default.

Throwing a com.theapplication exception
The com.theapplication exception was caught by the application exception handler

This also doesn't demonstrate much: com.theapplication is an exact match of one of the catch conditions. But it works.

Throwing a com.theapplication.differentpackage exception
The com.theapplication.differentpackage exception was caught by the application exception handler

Now we get down to business, and the technique is demonstrated: the thrown exception - com.theapplication.differentpackage - has no exact match in any of the catch conditions, but it's still matched by the "namespace" of com.theapplication catch condition. Excellent!

Throwing a com.theapplication.thepackage exception
The com.theapplication.thepackage exception was caught by the package exception handler

No surprises here: it's an exact match.

Throwing a com.theapplication.thepackage.DifferentComponent exception
The com.theapplication.thepackage.DifferentComponent exception was caught by the package exception handler

Here we have a different component, but the path is otherwise the same, so it gets picked up by the package catch condition

Throwing a com.theapplication.thepackage.TheComponent exception
The com.theapplication.thepackage.TheComponent exception was caught by the component exception handler

We're in the realm of "doing this for completeness" now. Everything is still working as expected.

Throwing a com.theapplication.thepackage.TheComponent.DifferentException exception
The com.theapplication.thepackage.TheComponent.DifferentException exception was caught by the component exception handler

A different exception from the same component gets picked up by the component handler.

Throwing a com.theapplication.thepackage.TheComponent.TheException exception
The com.theapplication.thepackage.TheComponent.TheException exception was caught by the name-spaced exception exception handler

More of the same. Just one more...

Throwing a TheException exception
The TheException exception was caught by the default exception handler

This demonstrates that the "namespace" on the exception is significant. A TheException exception is different from a com.theapplication.thepackage.TheComponent.TheException

So that's quite cool.

And ColdFusion 11 works the same way (I have no reason to think any earlier version does not, that's just what I tested on).

Did you know you could do this?