Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with:

Application.cfc OnRequest() Method Affects OnError() Arguments

By Ben Nadel on
Tags: ColdFusion

The other week, Thomas Messier pointed out to me that my ColdFusion Application.cfc Tutorial was a bit incomplete in the fact that it did not address the changes in the OnError() event method arguments that depend on the existence of the OnRequest() event method. To be totally honest, I did not know anything about this. I pretty much always use the OnRequest() event method, so I have never had to deal with errors that occur outside of it.

I set up a quick, little test application to see what was actually going on. In the Application.cfc ColdFusion component below, my OnRequestStart() checks for a Delete flag in the URL. If it exists, it simply deletes the OnRequest() method. Then, I throw an error in my index.cfm page and compare the two different sets of arguments.

My ColdFusion Application.cfc:

  • <cfcomponent
  • output="false"
  • hint="Handles application level evnts.">
  •  
  • <!--- Set up application. --->
  • <cfset THIS.Name = "ErrorTest" />
  • <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 0, 5 ) />
  • <cfset THIS.SessionManagement = false />
  •  
  • <!--- Set up page request. --->
  • <cfsetting
  • showdebugoutput="false"
  • />
  •  
  •  
  • <cffunction
  • name="OnRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Pre-page processing for each page request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="TargetPage"
  • type="string"
  • required="true"
  • hint="The template being requested."
  • />
  •  
  • <!---
  • Check to see if we want to keep the OnRequest()
  • method or delete it from the app component.
  • --->
  • <cfif StructKeyExists( URL, "delete" )>
  •  
  • <!--- Delete the OnRequest() method. --->
  • <cfset StructDelete( THIS, "OnRequest" ) />
  •  
  • </cfif>
  •  
  • <!--- Return out. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="OnRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="Processes the requested template.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="TargetPage"
  • type="string"
  • required="true"
  • hint="The template being requested."
  • />
  •  
  • <!--- Include the requested template. --->
  • <cfinclude template="#ARGUMENTS.TargetPage#" />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="OnError"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="Fires when an exception occures that is not caught by a try/catch block">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Exception"
  • type="any"
  • required="true"
  • />
  •  
  • <cfargument
  • name="EventName"
  • type="string"
  • required="false"
  • default=""
  • />
  •  
  •  
  • <!---
  • Dump out the ARGUMENTS scope. Here, we want to see
  • how the argument change depending on whether or not
  • we have the OnRequest() method.
  • --->
  • <cfif StructKeyExists( THIS, "OnRequest" )>
  •  
  • <!--- Use label with onrequest. --->
  • <cfdump
  • var="#ARGUMENTS#"
  • label="OnError() - WITH OnRequest()"
  • />
  •  
  • <cfelse>
  •  
  • <!--- Use label withOUT onrequest. --->
  • <cfdump
  • var="#ARGUMENTS#"
  • label="OnError() - WITHOUT OnRequest()"
  • />
  •  
  • </cfif>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

And then, my simple index.cfm:

  • <!--- Force an error. --->
  • <cfset x = y />

Here, I am simply referring to a variable, Y, that does not exist.

When I run the page without any query string flag, the Application.cfc runs with the OnRequest() method and produces this OnError() arguments CFDump (I have collapsed some of the items for better display):


 
 
 

 
ColdFusion Application.cfc OnError() Arguments With OnRequest() Presence  
 
 
 

Now, let's compare that to what happens when I call the same page, but this time, I send the "?delete" flag that will cause the OnRequestStart() method to delete the OnRequest() method from the Application.cfc. This time, my CFDump output is much smaller:


 
 
 

 
ColdFusion Application.cfc OnError() Arguments Without OnRequest() Presence  
 
 
 

The difference here, as Thomas Messier pointed out, is that the error generated without the OnRequest() method's presence does NOT have the RootCause structure. I don't know why this is, but this is certainly good to know.




Reader Comments

not sure what the difference is, but i will have to investigate a little. The application.cfc i am using does not have an OnRequest() and the ARGUMENTS.EXCEPTION.RootCause struct _is_ there, however if i extend that cfc and put my own OnError handler in the cfc that is extending the main one the ARGUMENTS.EXCEPTION.RootCause struct _is not_ available.

Very odd, this is definitely something that needs to be cleared up!

Reply to this Comment

I am using the OnError function in application.cfc and I noticed a strange occurance. If I mistype a cf tag (ie. <cfssset x = y />) the OnError does not catch it, just a generic CF error is displayed. Furthermore even if I use a cftry cfcatch around that code, with type = "any", CF still displays its own error and will not allow me to catch it.

Not that I plan on purposely adding bad code, but it would be nice if I could catch it just in case. Do you have any idea if this is a known bug?

Reply to this Comment

I forgot to mention that my mistyped tag was located in my index.cfm file.

Interestingly enough I created the same error in a cfc which I called as an object. In that way the error was caught and displayed the way I wanted to from the OnError function.

Am I missing something?

Reply to this Comment

@Jason,

It's possible that parsing errors are not handled in the same way; theoretically, a site would never go live *with* parsing errors.

It's also possible you have an error in your onError() method handler; if you do, the original error will be allowed to bubble up to the top (without the error in the onError() event handler being displayed).

Reply to this Comment

@Ben

Thank you for responding so quickly.

I thought I would post my code. I was just doing a simple POC when I noticed this.

application.cfc:
----------------------------------------
<cffunction name="OnRequest" access="public" returntype="void" output="true">

<cfargument name="TargetPage" type="string" required="true" />

<cfinclude template="#Arguments.TargetPage#" />

<cfreturn />
</cffunction>

<cffunction name="OnError" access="public" returntype="void" output="true">

<cfargument name="pass_cfcatch" required="Yes" type="any" />

<cfdump var="#pass_cfcatch#" />
</cffunction>
----------------------------------------

The file that the OnError would not catch was onerror_parsing.cfm with just <cfssset x = y /> in it.

The other file that OnError DID catch was onerror_parsing_cfc.cfm that called a cfc with <cfset Variables.onerror_template = createObject("component", "onerror_parsing").init() />. In the onerror_parsing.cfc file I had the same <cfssset x = y /> in it.

I only noticed this cause I was trying to see how the OnError would react to different types of errors. I agree with you and hope that any error like this would never reach the live server (especially since I have a Dev AND Staging server), I just thought it was worth noting.

Thanks again.

Reply to this Comment

Okay, sorry about flooding your comments, but on a whim I tried one more thing. I made no changes to the application.cfc file I posted above, but this time I created another .cfm file that simply had <cfinclude template="onerror_parsing.cfm" /> in it.

When loading it this way the OnError in the application.cfc caught and dumped the error.

Of course, I think the moral of this story is don't let these kind of errors make it to your live server, but again, I thought it was worth noting.

On a side note, I did work for a company that had a programmer (who was eventually denied access to the live server) who kept insisting on making changes directly on the live server - he would occasionally make these kind of errors. Not fun.

Reply to this Comment

@Jason,

Hmm, odd that it would catch one parsing error and not the other. There's probably something subtle and tricky going on :)

Reply to this Comment

@Jason and @Ben,
I've been doing some CF9 refactoring on our systems and noticed an odd occurrence with onError as well.

Found a way to work around my problem, but what I saw was...

Background:
Our platform is OO and creates a CFC (our API) into the session scope onSessionStart. That API is then referenced in each CFC's variables scope so it's easily accessed from any function.

If an error was generated on the same request as the one that ran onSessionStart, the onError function call had no access to the alias in the variables scope. As if it was never put there. But it IS there as that happens in the constructor of the base-class that each CFC extends.

So a call of
<cfset id = API.getUserID()> fails in the error handler, but
<cfset id = session.API.getUserID()> works.

Again, if I cause the error in a request AFTER the session is created, everything works. But if the session is instantiated in that request I get a standard CF error saying it can't find API.

I wish I could say I understand what's happening so-as to shed light on this topic, but maybe my experience could help somehow.

For clarification, the CFC being called to generate the error is not directly called, it's being processed by the API, so the API MUST be fully initialized and loaded into the variables scope to even execute the error function.

-Bry

Reply to this Comment

@Bryan,

Hmm, that is very odd. I can't think of a reason why the implicit Variables scope would not be available. If you CFDump out the Variables scope, is it there?

Reply to this Comment

@Ben,
Well, it gets clearer, and muddier, when I do that.

Good recommendation, BTW. That put me on the trail of the actual cause. Which is explained below.

I've tracked the oddity to a strange construct of my system. (process slightly simplified)
1) session.API is created by setting session.API = createObject().init() where .init() returns "this"
2) in API.init() it creates this.errorHandler and runs this.errorHandler.init()
3) in errorHandler.init() it tries to load session.API into the variables scope

So in step 3, it appears that I've got a CFC trying to reference an object that does not yet exist (session.api) due to not yet having gotten to the end of the original cfset command in step 1.

What I'm confused about, is why this would ever work? (like when not throwing an error on the session creation call)

1) Is CF late-binding to the session.API object even though it's technically not there yet?

2) How does variables.API ever get set on this object? AKA: Why is it available in non-session-creation calls?

3) Does the session.API reference work (post-session creation) because CF has created a memory space for the variable and is simply pointing variables.API to the session.API memory space?

Lesson in this? I guess it's not related to this thread. [/blush] But, to me, it's still a little enigmatic.

Thoughts?

Reply to this Comment

@Bryan,

Why it ever works is a bit of mystery :) I would think of the init() method on the original object failed in any way, the entire chain of events would fail.

That said, one way to get around this, which is probably more encapsulation-friendly, would be pass the API object into the error handler init() method directly:

2) in API.init() it creates this.errorHandler and runs this.errorHandler.init( this )

Here, when you call errorHandler.init(), you pass the THIS reference (the API object) into the init method. Then, in the errorHandler, you can store it as a property rather than referring to the SESSION scope directly.

In this way, you pass all "dependencies" into the sub-objects rather than requiring them to reach outside their known universe.

Reply to this Comment

@Ben,

Good advice. In my case, however, my init functions take a single config object {API:this} as their primary argument. These get loaded into the object's "this" scope.

In this case, for backward compatibility with our previous code, the API has to be available in the variables scope. Init takes only 1 argument, and I don't have a way to differentiate between target scopes. Adding a second init argument would cascade problems throughout my app as we have classes extending classes. Each of which overloads its parent init() but calls it with Super.init(config).

Our base-class has a function does:
variables[argName] = argVal

This allows me to inject values into a CFC's variables scope from outside the CFC. All my other CFC's were using this to get their API references into the variables scope. I'm using this to push the API reference now.

(pseudo):
this.load('errorHandler').init({config=vals}).addVar("api",this)

The downside to this is that if I need access to the API pbject in the CFC's init function, I'm hosed. :) So far that's not been necessary as none of our instanced CFC's actually overload init() themselves, just the classes do.

Any ideas on specifying target scope on my config objects? I've got thoughts, but as I mentioned above, most of them involve a lot of cascading edits to a lot of CFCs.

This has become off-topic from OP: sorry 'bout that.

Reply to this Comment

@Ben,

followup:
Decided to modify my init() in my root class as follows.

It looks at config object passed into constructor and checks for any keys named "variables"

If it finds it, I push the keys/values from that sub-struct into the variables scope, then delete that key from the config struct.

Then I push the remaining config into the object's this scope.

This seems to keep with your encapsulation recommendation; would you not agree?

In all this actually solves another problem a coworker had. Thanks again for your help.

Reply to this Comment

@Bryan,

I think I am losing you a bit in what's going on; but, I had a thought. While we often call createObject() AND the init() method in one go as a standard, there's nothing that says this has to be the case. If you break the createObject() and the init() execution into two different steps, you might get a bit over your original problem:

session.API = createObject( "...." );
session.API.init();

In this way, the "object" actually exists in session.API by the time the init() method is called.

Just a thought.

Reply to this Comment

@Ben,

You're right, that would essentially fix the issue as I've laid it out. Unfortunately, as verbose as I've been, I've been trying to distill the underlying system to its simplest form. The reality is, there are more complexities that I've left out, due to them being either redundant with the primary issue, or too detailed about our framework to post. ;)

We have a lot of built in functionality that handles things like object creation and applying objects to other objects. (Similar to Ext.apply() and Ext.applyIf())

It comes down to this I guess, in the init() I'm no longer passing an argument list or a simple struct of config params. Instead it looks like this.
<pre>
this.loadObject('class').init(
{
id=10,
name='whatever',
variables={
api=this,
parent=this
}
}
)
</pre>
In my init, I look for the variables key in the config object and apply any keys it has to the variables scope in the new CFC. Then I apply the rest to the object.

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.