You Cannot Delete Application.cfc Methods In The Pseudo Constructor

Posted June 4, 2008 at 8:24 AM by Ben Nadel

Tags: ColdFusion

I ran into something a bit strange the other day when I was playing around with a dynamic Application.cfc proxy. I tried, in the pseudo constructor of the Application.cfc (the space before the CFFunction tags), to delete the OnRequest() event method. While this appears to work if you dump out the Application.cfc object, it does not prevent the events from being triggered. Take a look at this example:

  • <cfcomponent
  • output="true"
  • hint="I define the application and listen for application level events.">
  •  
  • <!---
  • In the pseudo constructor, we are going to try and
  • delete the following methods.
  • --->
  • <cfset StructDelete( THIS, "OnRequestStart" ) />
  • <cfset StructDelete( THIS, "OnRequest" ) />
  •  
  •  
  • <!---
  • ASSERT: At this point, we should have deleted the
  • OnRequest() and OnRequestStart() event listeners. We
  • should only have the OnRequestEnd() event listener left.
  • --->
  •  
  •  
  • <!---
  • Dump out this reference to see if the methods were
  • successfully deleted from the Application.cfc.
  • --->
  • <cfdump
  • var="#THIS#"
  • label="Application.cfc THIS Scope"
  • />
  •  
  •  
  • <cffunction
  • name="OnRequestStart"
  • access="public"
  • returntype="boolean"
  • output="true"
  • hint="I run before a template is executed.">
  •  
  • <p>
  • I am the OnRequestStart() event listner.
  • </p>
  •  
  • <!--- Return out. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="OnRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I execute the page template.">
  •  
  • <p>
  • I am the OnRequest() event listner.
  • </p>
  •  
  • <!--- Include passed in page. --->
  • <cfinclude template="#ARGUMENTS[ 1 ]#" />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="OnRequestEnd"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I run after a template has been executed.">
  •  
  • <p>
  • I am the OnRequestEnd() event listner.
  • </p>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that at the top of the Application.cfc we are deleting the OnRequestStart() and the OnRequest() event listener methods. This should leave just the OnRequestEnd() method. This should also mean that the OnRequestStart() and OnRequest() methods won't fire (seeing as they have been deleted). However, when we run this page, here is the output that we get:


 
 
 

 
Application.cfc After Delete Action And Page Output  
 
 
 

Notice that in the CFDump of our Application.cfc instance, the OnRequestStart() and OnRequest() have, in fact, been deleted. Realize that this is also the very first thing that displays meaning that this execution has been completed first. Notice also, and here is the oddity, that the page output also contains the text from the OnRequestStart() and OnRequest() event methods, indicating that those two methods have been executed.

It seems that you can't alter the method configuration of the Application.cfc from the pseudo constructor. I am not saying that you should do this, I am saying you can't do this. What's really odd, however, is that you can, from the OnRequestStart() method, successfully delete the OnRequest() event method. This is how we can dynamically cope with web service calls. However, the OnRequestStart() event method executes after the pseudo constructor, so it really makes no sense as to why the delete method gesture would be more successful later on in the execution path.



Reader Comments

Jun 4, 2008 at 10:11 AM // reply »
131 Comments

I wonder whether the CF engine itself assumes the core methods when it finds an instance of Application.cfc. Probably just re-creates any of them that are lacking at runtime? In other words, doesn't the OnRequestStart() action happen even if the Application.cfc hasn't defined an override for that method?

Just thinking out loud ...


Jun 4, 2008 at 10:15 AM // reply »
11,238 Comments

@JFish,

The events definitely occur whether or not the Application.cfc has listeners. However, based on the output of the individual functions, it is clear that not only are the events being triggered, but my original functions are there to listen for it.


Jun 4, 2008 at 10:23 AM // reply »
131 Comments

@Ben,

Aha, I had missed that detail. How very very interesting indeed ...


Jun 4, 2008 at 10:25 AM // reply »
11,238 Comments

@JFish,

I didn't test this, but I wonder if this is true for every type of CFC, or if this is something specific to Application.cfc?


Jun 4, 2008 at 2:38 PM // reply »
1 Comments

@Ben

You have to delete the event handlers from both the this and variables scopes.

<cfset StructDelete( THIS, "OnRequestEnd" ) />
<cfset StructDelete( THIS, "OnRequest" ) />
<cfset StructDelete( VARIABLES, "OnRequestEnd" ) />
<cfset StructDelete( VARIABLES, "OnRequest" ) />

Your results seem to be the same as if you attempted the following in onRequestStart.

<cfset StructDelete( THIS, "OnRequestEnd" ) />
<cfset StructDelete( THIS, "OnRequest" ) />

-Jason


Jun 4, 2008 at 3:36 PM // reply »
120 Comments

Darn, Jason beat me to it! Yes, for some odd reason you must delete the methods from *both* this scope and variables scope...

I discovered this when trying to solve the onRequest() intercepting remote CFC invocations problem. I never blogged it but Ray Camden did somewhere.


Jun 4, 2008 at 7:04 PM // reply »
11,238 Comments

@Jason, @Sean,

When I have dealt with CFC calls, I never deleted from the VARIABLES scope, just the THIS scope. Pluse, the VARIABLES scope is private, the system shouldn't have access to it.

Let me test....


Jun 4, 2008 at 7:08 PM // reply »
11,238 Comments

You are right, deleting from VARIABLES scope did complete the job. This has got to be a bug. The VARIABLES scope is a private scope and its contents should not affect third-party calls.

Funky.


Jun 4, 2008 at 9:01 PM // reply »
34 Comments

Ben, I ran into a very similar situation a while back while trying to do mixins. My problem wasn't in deleting but in overriding, but the result was the same. Here's my take on it, since all the cool kids are adding links to their own blog posts these days: http://mxunit.org/blog/2008/04/adventures-in-mocking-part-1.html

the first 90% is all boring testing crap, but the last part is where i talked about my run-in with jonny law (i.e. this vs. variables).

God bless CF for letting us do this stuff!


Jun 5, 2008 at 7:29 AM // reply »
11,238 Comments

@Marc,

Very interesting. I did not know this:

....when functions call each other internally, they're calling the version of the function that lives in the variables scope. And when you, dear programmer, call a function on an object directly, you're calling it in this scope.

Good to know. Thanks!


Jun 5, 2008 at 7:41 AM // reply »
11,238 Comments

@Marc, et al,

Here's what's really strange! If you change the Access of the event listeners to private, then they do not fire. They are not available in the THIS scope any longer, only in the VARIABLES scope. This must mean that the ColdFusion server is, and this makes sense, calling the public version of the event listeners on the Application.cfc.

Therefore, it makes no sense that the VARIABLES scope should have any influence on the methods at all in our case.

I think this must definitely be a bug in the public / private implementation of methods in CFCs.


Jun 5, 2008 at 8:36 AM // reply »
34 Comments

it would indeed be interesting to hear from a cf engineer about how this stuff all works under the hood.


Jun 6, 2008 at 12:31 AM // reply »
132 Comments

@Ben

I figured out what's happening...

Your Application.cfc is really wrapped inside a coldfusion.runtime.AppEventInvoker which extends coldfusion.cfc.CFCProxy.

At first I thought it was the isMethodPresent() method on the AppEventInvoker, which only returns false if we delete from both variables and this scopes.

(you can test this by cfdumping: application.getEventInvoker().isMethodPresent('onRequest') in the onRequestStart() handler.)

Then I thought to look at the getMethod() method of the CFCProxy, and sure enough, it has the same problem.

Then I tried invoke() in the CFCProxy, and found that I could invoke methods only defined on the variables scope there too!

Then I moved on to the coldfusion.runtime.TemplateProxy's methods:

getMethods(): This returns the methods defined in the variables scope, ignores the this scope. Totally ignores public/private.
resolveMethod(): This returns the method until we delete it from both variables and this scopes. Respects public/private.

So last, I tried adding a method to the this and variables scopes manually.

<cfset this.method = test> or <cfset variables.method = test>

And you can do the below reguardless of if you assigned to the this or variables scopes:

o = createObject("component","Test.cfc");
o.test();

And it's not just invocation, it's accessing the variable itself. <cfset testMethod = o.test> works either way as well, BUT this only works when the variable "test" is a method. If we did <cfset variables.test = "foo">, then o.test would throw an undefined variable exception!

So a method assigned to the variables scope is *not* private.

Looks like the answer lies in the weird distinction between the two scopes, the way it handles methods, property look up, and how that all boils down into the CFCProxy, and by extension the Application.cfc

And wow is this part of the CF engine is super complicated, sigh.

(Apologies for the exceptionally long response)


Jun 6, 2008 at 7:40 AM // reply »
11,238 Comments

@Elliott,

Wow! I am more shocked that you were able to figure all of that than anything else. The findings are a bit strange too. I guess, I will just leave the "magic" up to the ColdFusion server :)

But seriously, how did you figure all that out?


Jun 6, 2008 at 10:07 AM // reply »
132 Comments

There's a lot of techniques for figuring out how the CF server works. From forcibly throwing errors and looking at stack traces, getMetaData(), getClass(), cfdump, google, and a lot of time.

One trick I know a lot of people don't realize is that getMetaData() on a non-CF type returns it's java.lang.Class.

So we can do getMetaData(variables).getName() and that gives us coldfusion.runtime.VariableScope, and now we have the class of that scope, and can manually create it with createObject() to play with.

If you're interested in more, I'm doing a presentation at CFUnited about more of the details of the CF engine, and how the whole thing works. :)

http://cfunited.com/go/topics/2008#topic-1758


Jun 6, 2008 at 1:33 PM // reply »
11,238 Comments

@Elliott,

Awesome. I'll definitely be there.


Jun 6, 2008 at 2:42 PM // reply »
132 Comments

As a quick correction to the above incase someone reads it and think there was a mistake:

Should be:
"""
<cfset this.test = method> or <cfset variables.test = method>
"""

I had reversed "method" and "test" in the assignments.

The issues are the same though, it was just a typo in the blog response.


Jun 12, 2008 at 10:55 AM // reply »
120 Comments

@Elliott, I'm curious as to whether you've bounced that session idea off the CF team folks - you're sailing very close to the "decompile" prohibitions in the CF license I suspect... (but it sounds like a fascinating session so I'll almost certainly attend - see you next week!).


Jun 16, 2008 at 3:00 AM // reply »
132 Comments

@Sean

I've given a very similar presentation at a user group before with no objections, and I do plan to run it over with an engineer before hand. I know at least 2 of the engineers are attending the session as well.

To be honest, I really have no interest in discussing how to decompile compiled CF code, or the CF engine, nor will I be going anywhere near that in the session. My goal is not to infringe on Adobe's IP, but rather empower CF developers with some knowledge about what really happens to make issues (like the one in this blog post), or http://www.bennadel.com/blog/1210-Dynamically-Evaluating-Image-Functions-In-ColdFusion-8.htm less mysterious and empower developers with knowledge to solve issues like the one I fielded some time ago about resetting the cfhtmlhead buffer.

It's also come in handy for other things. For instance I created CF bindings for JRuby allowing ruby code embedded in CF to call CFC methods or CF functions (while making the code look like natural ruby), and access all the scopes. I'd not really planned to talk about that specifically in the session (this is a CF conference after all, not a ruby one), but I'd be happy to share that if you're interested too.

In any case all the things I'll be discussing are easily figured out through google, stack traces, reflection, and a lot of tinkering.

See you there!


Jun 17, 2008 at 12:38 PM // reply »
120 Comments

@Elliott, thanks for the clarification. Sounds like a fascinating session. I've added it to my CFUNITED schedule!


Jun 17, 2008 at 12:39 PM // reply »
11,238 Comments

Me too :)


Jul 10, 2008 at 10:36 AM // reply »
132 Comments

As an addendum to my above conclusion, turns out I was wrong.

I've found the behavior is more complicated than I thought.

At first I thought all methods added to the variables scope were not private, but this is not the case. The result I found above was because of where I had the code that assigned variables.method = func>.

Turns out if you do that in the *body* of a <cfcomponent> then the method is public, even though you only added it to the private variables scope.

However, if you do that assignment in a *method* instead, then the method will be private!

Apologies for anyone I confused. You *can* still make methods private by assigning them only to the variables scope, but only if it happens inside a method.


Jul 10, 2008 at 10:41 AM // reply »
132 Comments

It also seems to relate to access of the function...

<cfcomponent>
<cfset variables.func1 = publicFunc>
<cfset variables.func2 = privateFunc>
...
</cfcomponent>

func1 is public, even though you only added it to the variables scope.
func2 is private, because the function you assigned to it was private.

There's a serious can of worms here.


Jul 10, 2008 at 10:42 AM // reply »
11,238 Comments

@Elliott,

Thanks for the update. This is good stuff to know.


Jul 15, 2008 at 5:14 PM // reply »
120 Comments

@Elliott, I suspect the access checks are based on the metadata of the function and have nothing to do with the (dynamic) scope in which the reference is stored...?


Aug 31, 2008 at 1:10 AM // reply »
6 Comments

@Ben,

I wait that it helps, excuse for not being in English, but who knows brief (http://www.whycf.com), but my two cents are in http://www.porquecf.com.br/blog/index.cfm/2008/8/31/Exclua-o-mtodo-durante-outro-mtodo--Applicationcfc

@Elliott,

I had already published this in 2006, these properties are really interesting.
Excuse the disorder, but I am still arranging the servant migration.
http://www.mxstudio.com.br/coldfusion/application_cfc_uma_de_suas_funcionalidades/


Oct 17, 2008 at 4:35 PM // reply »
63 Comments

Whew ....

Was it a bird? a plane? No ... it was Spren, Corfield and Nadel going Way the hell over my head ... :)


Oct 17, 2008 at 7:26 PM // reply »
11,238 Comments

Over my head a bit too ;)


Oct 27, 2008 at 4:34 PM // reply »
1 Comments

But how do you resolve bypassing the onRequestStart errors? I tried the StructDelete method, but my apps are still failing.


Feb 26, 2009 at 1:20 PM // reply »
19 Comments

Interesting discussion. I have been playing around with similar issues whan I discovered I could assign another component to a components THIS scope (this = createObject()) and found that I could actually run the methods of component B inside the component A after the assignement, while at the same time the origial methods were, well, lost. And then the instanciated component A reverted back to being itself again afterwards...

Weird.

Anyway, for the remote calls it seem to be important to set access to OnRequest as 'private'. Public or, as the Adobe docs suggest, leave out that property, does not work for me.


Feb 26, 2009 at 1:58 PM // reply »
11,238 Comments

@Stefan,

That sounds very odd. I can't remember if I've ever tried to override the THIS scope.


Feb 26, 2009 at 2:05 PM // reply »
19 Comments

Oops, setting the onRequest to 'private' is basically the same as removing it.
Any idea why that method breaks remote calls to cfc's?


Feb 26, 2009 at 2:18 PM // reply »
11,238 Comments

@Stefan,

I guess it breaks because you're not really including a CFC to execute - you're executing a method on a component; so, the include concept breaks.


Feb 26, 2009 at 2:24 PM // reply »
19 Comments

Try this:
a.cfc
<cfcomponent output="false">
<cfdump var="#this#">
<cfset this = createObject("component","b")>
<cfdump var="#this#">
<cffunction name="myA">
<cfreturn 'I am A'>
</cffunction>
<cffunction name="myAB">
<cfoutput>#this.myA()#</cfoutput>
<cfdump var="#this#">
<cfset this = createObject("component","b")>
<cfdump var="#this#">
<cfoutput>#this.myB()#</cfoutput>
<cfoutput>#this.myA()#</cfoutput>
</cffunction>
</cfcomponent>

b.cfc
<cfcomponent output="false">
<cffunction name="myB">
<cfreturn 'I am B'>
</cffunction>
</cfcomponent>

test.cfm
<cfset a = createObject("component","a")>
<cfoutput>#a.myA()#</cfoutput>
<cfoutput>#a.myAB()#</cfoutput>


Feb 28, 2009 at 1:49 PM // reply »
120 Comments

@Stefan, the docs for Application.cfc are very clear as to why onRequest() cannot be used with remote calls (a lot of people haven't read that section of the docs which is why I mention it).

onRequest() is a way for you to completely handle the request, whether it is a page request or a remote call, but for remote calls there is no way you can correctly pass back a result that the caller can use (because onRequest() is only passed the "targetPage" - the path to the CFC - and is not passed the method name or arguments, or the format of the result that needs to be returned).


Mar 2, 2009 at 7:15 PM // reply »
11,238 Comments

@Sean,

Nice articulation of why OnRequest() and CFCs don't jive. Thanks.


Mar 10, 2009 at 1:22 PM // reply »
19 Comments

Still, CF should know it can not handle CFC's and simply ignore them, let them pass without being intercepted and destroyed.


Apr 3, 2009 at 6:43 PM // reply »
19 Comments

very interesting. one thing i noticed is that we are calling the variables 'private' but i think it would be called 'protected' as pseudo-contructed variables.values are shared across the entire component/cfc, not a specific method. at least this is true when they are created inside the cfc but outside any method. or maybe they are private if created within a specific method?


Apr 6, 2009 at 10:08 PM // reply »
120 Comments

@William, you are correct. For some reason Adobe (well, Allaire or Macromedia) decided to use 'private' for what is more like 'protected' variables in other languages... but then several other languages use 'this' to refer to both public and private variables so... :)



Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 22, 2013 at 4:43 AM
How Do You Use The ColdFusion CFParam Tag?
'<cfparam>' or 'isDefined()and <cfset>' performs the same task.Is there any difference? ... read »
May 21, 2013 at 7:46 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
No luck. At least I have uncovered the cause, URLScan 3.1. Here is what I see in the IIS log when a file is over 30mb. 2013-05-21 23:29:05 10.105.45.128 GET /plupload/assets/jquery/jquery-1.8. ... read »
May 21, 2013 at 6:12 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
Ben, I did not see you after Pete Freitag's Lockdown session at cfObjective but he said that IIS sets file size limits at 30MB by default which just happened to be the threshold for file size when ... read »
May 21, 2013 at 11:51 AM
Ask Ben: Parsing Very Large XML Documents In ColdFusion
Looking at my first ever XML document that I have to parse and put into MS SQL 2000 with CF8. I get it to list the desired Field name, many times over, and have a long list of this field name displa ... read »
May 21, 2013 at 9:25 AM
Turning Off and On Identity Column in SQL Server
you are awesome..i am lucky to get this blog between such a garbage one....Thanks, Prashant ... read »
May 20, 2013 at 4:38 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, Your confusion is well founded, since this is a very confusing features. In fact, it ONLY works if you use array notation. Meaning, that this: arrayToList( query[ "columnName" ] ) ... read »
May 20, 2013 at 4:34 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I was thinking chicken and the egg, I wouldn't have expected it to work in the valuelist going in I guess. Maybe I just need a beer, long day :) ... read »
May 20, 2013 at 4:29 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, That's if you're trying to reference a specific row. In this case, we're trying to reference the entire query column as one cohesive value. So, you are correct that if you wanted to output a ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools