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 NCDevCon 2011 (Raleigh, NC) with:

Extending The Application.cfc ColdFusion Framework Component With CFInclude

By Ben Nadel on
Tags: ColdFusion

With the release of ColdFusion 8, we now have the ability to create application-specific mappings directly within our Application.cfc ColdFusion framework components. This functionality works perfectly for both createObject() method calls as well as for the CFComponent Extends attribute; unless, of course, the component that you wish to sub-class is the Application.cfc component itself. When it comes to sub-classing Application.cfc, you end up with a chicken-and-egg situation: you can't use application-specific mappings to extend the application component because the application component doesn't exist until after you've extended it. This makes creating sub-directory Application.cfc components a huge pain in the butt. However, using some very unexpected ColdFusion behavior, we can extend our root Application.cfc instance using the CFInclude tag.

The CFInclude tag is one of the ways in which ColdFusion allows us to create "Mixins". In the past, I've demonstrated how to CFInclude functions into a ColdFusion component in order to dynamically construct a ColdFusion instance. As it turns out, however, not only can you CFInclude functions, you can also CFInclude entire Components! I know ColdFusion is dynamic... but this kind of blows my mind. The language is just super powerful.

Using this CFComponent-include behavior, we can use a little magic to sub-class our Application.cfc files using the CFInclude tag. To demonstrate this, I have set up the following directory structure:

  • /Application.cfc
  • /index.cfm
  • /sub/Application.cfc
  • /sub/index.cfm

In this case, our sub-Application.cfc is going to turn on session management while our root Application.cfc is not going to have any session management at all.

NOTE: Nothing is going to be as easy or as clean as using mappings to extend your Application.cfc. This demonstration is merely a proof of concept as to how Application.cfc sub-classing can be performed in the scenario in which mappings are not easily obtained.

Let's start by taking a look at our root Application.cfc:

Application.cfc In Root Folder

  • <cfcomponent
  • output="false"
  • hint="I define the application settings and event handlers.">
  •  
  • <!--- Define the application settings. --->
  •  
  • <!---
  • When defining the name here, I typically use a hash() of
  • the current template path; however, since we are using this
  • in different directories, we need to make the name static
  • (otherwise each directory will get its own application
  • instance).
  • --->
  • <cfset this.name = "AppExtendViaIncludeTest" />
  • <cfset this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 ) />
  •  
  • <!---
  • The base app will not have any session management. Session
  • management will be turned on in the sub-application.
  • --->
  • <cfset this.sessionManagement = false />
  •  
  •  
  • <cffunction
  • name="onApplicationStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the application.">
  •  
  • <!--- Set some app variables for testing. --->
  • <cfset application.dateInitialized = now() />
  •  
  • <!--- Return true so the page can process. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the request.">
  •  
  • <!--- Set some request variables for testing. --->
  • <cfset request.dateInitialized = now() />
  •  
  • <!--- Return true so the page can process. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onRequest"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I process the user's request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="script"
  • type="string"
  • required="true"
  • hint="I am the request script."
  • />
  •  
  • --- START SCRIPT REQUEST ---<br />
  • <br />
  •  
  • <!--- Include (execute) requested script. --->
  • <cfinclude template="#arguments.script#" />
  •  
  • <br />
  • --- END SCRIPT REQUEST ---<br />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, there's nothing special going on here; we define our application settings and provide a few event handlers. Notice that our onRequest() event handler wraps the final output with some START / END text. This is important to note because our sub-Application.cfc doesn't override this particular event handler.

Now, let's take a look at our root index.cfm:

Index.cfm In Root Folder

  • <!--- NOTE: This is the ROOT index. --->
  •  
  • <cfdump
  • var="#application#"
  • label="Root Application"
  • />
  •  
  • <br />
  •  
  • <cfdump
  • var="#request#"
  • label="Root Request"
  • />
  •  
  • <br />
  •  
  • <cfdump
  • var="#session#"
  • label="Root Session"
  • />

Here, we are simply outputting the Application, Request, and Session scopes. Note that in the recent releases of ColdFusion, the session scope can be referenced even when session management is turned off - it simply appears empty. When we run our root index.cfm, we get the following page output:

 
 
 
 
 
 
Extending Application.cfc With The CFInclude Tag. 
 
 
 

So far, this is all fairly mundane. Now, let's take a look at the Application.cfc in our sub directory. This Application.cfc is going to sub-class our root Application.cfc, turning on session management and overriding the onRequestStart() event handler. And, it's going to do all of this using the CFInclude tag.

Application.cfc In Sub Folder

  • <!--- NOTE: This is the SUB Application.cfc file. --->
  •  
  • <!---
  • Include the root application.cfc. This will include the
  • CFComponent tags and their contents. Bananas! I had no idea this
  • would actually work. ColdFusion continues to amaze me.
  • --->
  • <cfinclude template="../Application.cfc" />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Override any application-level settings. In this case, we
  • are going to turn on session management.
  • --->
  • <cfset this.sessionManagement = true />
  • <cfset this.sessionTimeout = createTimeSpan( 0, 0, 5, 0 ) />
  •  
  •  
  • <cffunction
  • name="onSessionStart"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I initialize the session.">
  •  
  • <!--- Set some session variables for testing. --->
  • <cfset session.dateInitialized = now() />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <!---
  • NOTE: This method starts with an "_". Since we included the root
  • application, we can't declare methods twice. As such, we need to
  • name this one differently at first, then perform some rename
  • magic afterward (see end of file).
  • --->
  • <cffunction
  • name="_onRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I initialize the SUB request.">
  •  
  • <!--- Invoke the super onRequestStart() method. --->
  • <cfset super.onRequestStart( arguments[ 1 ] ) />
  •  
  • <!--- Set a request variable for testing. --->
  • <cfset request.isSubApplication = true />
  •  
  • <!--- Return true so the page can process. --->
  • <cfreturn true />
  • </cffunction>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Create a super scope to fake component extention. Any function
  • that we override, we have to keep a reference to it in this super
  • scope since we will have to overwrite the reference to it.
  • --->
  • <cfset super = {} />
  •  
  • <!---
  • In order to get around having dupliate function declarations, we
  • had to name any sub-functions (that override root functions) to
  • start with an "_". Now, we need to point the
  •  
  • Loop over this component and point any method that starts with
  • "_" to the target method name (who's reference should now be
  • in super).
  • --->
  • <cfloop
  • item="key"
  • collection="#variables#">
  •  
  • <!--- Chectk for a "local" method. --->
  • <cfif reFind( "^_", key )>
  •  
  • <!--- Copy oritinal reference into the fake super scope. --->
  • <cfset super[ listFirst( key, "_" ) ] = variables[ listFirst( key, "_" ) ] />
  •  
  • <!--- Now, move the "_" method in the main CFC scopes. --->
  • <cfset this[ listFirst( key, "_" ) ] = this[ key ] />
  • <cfset variables[ listFirst( key, "_" ) ] = variables[ key ] />
  •  
  • </cfif>
  •  
  • </cfloop>

As you can see, the first thing our sub-directory Application.cfc ColdFusion component is doing is CFIncluding our entire root-directory Application.cfc. Since the CFInclude tag can using relative pathing, it's no problem at all to go up one directory to get to the root Application.cfc. And, since we are including a file that has CFComponent tags, there is no need to define our own (at least, that's what appears to be the case - I'm not saying it makes total sense).

Once we've included our root Application.cfc, we can now go about overriding the application settings. As you can see above, I'm turning on sessionManagement and defining my onSessionStart() event handler. When I go to override the onRequestStart() event handler, however, things get a tiny bit tricky. I can't simply re-define the onRequestStart() method or I'll get the following ColdFusion error:

Routines cannot be declared more than once. The routine onRequestStart has been declared twice in different templates.

To get around this, I have to start the sub-classed method names with an "_" (ie. _onRequestStart()). After I've declared all of my sub-classed functions, I then need to perform a little magic to get all the method references wired up properly. At the bottom of our sub-classed Application.cfc component file, I am creating a fake "super" object and then copying any overridden method references into it. In this way, I can still use the base methods from within my sub-classed event handlers just as I have done in the onRequestStart() method.

With this sub-classed Application.cfc in place, I then defined a sub directory index.cfm file that outputs the relevant scopes:

Index.cfm In Sub Folder

  • <!--- NOTE: This is the SUB index. --->
  •  
  • <cfdump
  • var="#application#"
  • label="Sub Application"
  • />
  •  
  • <br />
  •  
  • <cfdump
  • var="#request#"
  • label="Sub Request"
  • />
  •  
  • <br />
  •  
  • <cfdump
  • var="#session#"
  • label="Sub Session"
  • />

This outputs all the same scopes as our root index.cfm; however, with session management turned on in our sub-application, we now get the following page output:

 
 
 
 
 
 
Extending Application.cfc With The CFInclude Tag. 
 
 
 

As you can see here, our sub Application.cfc has successfully turned on session management, populated the session scope, overridden the onRequestStart() event handler (adding the key isSubApplication), explicitly invoked the super onRequestStart() event handler, and still made implicit use of the root event handler, onRequest(). In short, we have now used CFInclude to sub-class our root Application.cfc without the use of any mappings.

I'll be honest with you - I'm very surprised this works. Of course, after continually being blown away by how powerful and dynamic the ColdFusion application framework is, I really shouldn't be surprised that this level of functionality is available. The ColdFusion engineers really thought this stuff through! And again, before anyone recommends that this would be much easier if I just used a mapping, this is meant to be done in cases were a mapping is not easily obtained.




Reader Comments

This is twisted, and I'll admit that I can't readily think of anything intelligent to say about it ... I just wanted that poor, sad-looking, yellow-shirted Ben to feel a little happier.

(Which is totally different than wanting to claim first post. I swear.)

@Rick,

Ha ha, I'm happy either way :) It's a strange behavior, right? I wonder how this compiles in a way that the .CFC file doesn't actually need to have a CFComponent tag directly inside of it. Very interesting.

I'm curious why you might choose this route over just extending the root Application.cfc and override the methods and app variables from there.

That's insane. It's basically cffunctions (in the sub-application) outside a cfcomponent being treated like their inside the cfcomponent (in the top application) !

No wonder writing the CFML language spec. took (is taking) so long...

@Daniel,

I am not sure I understand what you mean? Hopefully in the future versions of ColdFusion they'll add relative-pathing to the component path functionality.

@Tom,

I think when ColdFusion uses CFInclude functionality, it evaluates things in a special way. For example, you can CFInclude one function into another function (which technically can't be done statically). It looks like similar evaluation logic happens with CFComponent.

Definitely insane :)

Thanks again, Ben. More mind-boggling than you finding out about this stuff is you having the time to write about it. When was the last time you slept? :)

@Ben,

From the construction of the sessionid in cfdump "Sub Session", I see that your application name in the subfolder is still AppExtendViaIncludeTest. That makes sense, because you didn't set this.name in /sub/Application.cfc, so it would still be what it was at the time of the cfinclude.

This is starting to remind me of an aspect of the cfapplication tag that I discovered long ago. The cfapplication tag only did as much as you told it to do. So if you said setclientcookies="No", that would be fine, and you could set your own cookies manually with no expiration to assure that they would be session cookies.

But our application was spread across several directories, all using the same cfapplication name="xxx" attribute, so that they would share session variables. All it took was one bozo to miscode cfapplication setclientcookies="Yes", and suddenly CF was setting a cookie with an expiration and we were writing a cookie to the user's hard drive (= a no-no for us, a violation of policy). It took me a while to figure out that each cfapplication is processed separately according to its attributes. In other words...

... It would APPEAR that /sub/Application.cfc attached sessionManagement to the AppExtendViaIncludeTest application. While you're in /sub, you get to access the session scope, because /sub/Application.cfc said this.sessionManagement=true. But when you're in /, you go through /Application.cfc, and it says this.sessionManagement=false. If my theory is correct, sessionManagement still exists in the AppExtendViaIncludeTest application, but you just don't reestablish your connection to it, because of this.sessionManagement=false. So /index.cfm sees an empty session scope.

So HERE'S an interesting experiment:

(1) Change the applicationTimeout to an hour in /Application.cfc (to give you time to edit it later).
(2) Change the sessionTimeout to an hour in /sub/Application.cfc (ditto).
(3) Repeat the experiment.
(4) Edit /Application.cfc to set this.sessionManagement=true at the root level, but don't set any session variables.
(5) Rerun /index.cfm. See if your session.dateInitialized at the root level is the same as what you set at the sub level.

If I'm guessing right, the AppExtendViaIncludeTest application has existence independent of any Application.cfc that references it by setting this.name="AppExtendViaIncludeTest". That independent entity has session variables because /sub/Application.cfc connected to AppExtendViaIncludeTest and created session variables. And so the only reason why /index.cfm doesn't see session variables is because its /Application.cfc says this.sessionManagement=false. As soon as /Application.cfc says this.sessionManagement=true, the session variables that were created at the /sub level are made available, even at the root level. This assumes the same CFID and CFToken, of course.

In other words, an application has the union of all features applied to it by all Application.cfc files and cfapplication tags that reference its name. But the current VIEW of that application you get is restricted to the Application.cfc or cfapplication tag that your request actually executed.

This might be a way for it to "make total sense".

@Nic,

Ha ha, I get my sleep here and there :)

@Steve,

I believe what you are saying is exactly the case. What we think of as a "ColdFusion application" is really a combination of three things:

1. Memory space
2. Code
3. A request that connects memory space to code.

Each request to the server instantiates a *new* Application.cfc instance which helps bind the code used in the given request to memory spaces on the server. As such, each request can conditionally turn ON or OFF one of those relationships. As such, you are absolutely correct - the sub application does create session management for the root application; it's just that the requests into the root directory turn that association off.

Furthermore, Applications and Session exist almost independently as well. A session *is* locked down to a given application; however, an application can timeout and STILL have sessions running (although this use-case is somewhat absurd).

It's all just memory spaces and references :)

I really need to make a video recording of my SOTR presentation this week. I dig into a lot of these little caveats that make the ColdFusion framework so powerful... or flexible enough to be dangerous ;)

@Ben,

Glad we think alike on that view. :-)

An implication would be that you could have only one Application.cfc, at the root level:

(cfif Left(CGI.Script_Name,5) is "/sub/")
(cfset this.sessionManagement = true)
(cfset this.sessionTimeout = createTimeSpan(0,0,5,0))
(cfelse)
(cfset this.sessionManagement = false)
(/cfif)

But then you don't get to use super, and it's always super to use super. By definition.

G'day, Ben. I'm the same "Nic" that you originally posted this for - sorry, I know I've got a tendency to resurrect old threads on your site but I thought you might find this interesting.

I've been using your example above with great success on sites running under CF8. This morning, I moved a site to CF9 and found your code above to throw an error: "Element IMPLICITARRYSTRUCTVAR0 is undefined in a Java object of type class coldfusion.runtime.VariableScope". The line responsible is this one:

<cfset super[ listFirst( key, "_" ) ] = variables[ listFirst( key, "_" ) ] />

Now, I don't know if you'll be able to reproduce this, or if it's specific to how I'm running things, but it turns out that the line:

<cfset super = {} />

...creates an element in the variables-scope called '___IMPLICITARRYSTRUCTVAR0' (that's 3 underscores, in case you're wondering). What it points to is the struct 'super'. The funny thing is, you won't see this variable if you dump the variables-scope to screen, but you _can_ reference it directly:

<cfdump var="#variables["___IMPLICITARRYSTRUCTVAR0"]#" />

Unfortunately, the part of your code that loops through the variables-scope and does an reFind() looking for elements that begin with an underscore fails because of this bizarre variable. Specifically, listFirst( key, "_" ) returns 'IMPLICITARRYSTRUCTVAR0', which you then try to use in variables["IMPLICITARRYSTRUCTVAR0"], which throws an error because no such element exists.

Anyway, I got around it by changing:

<cfif reFind( "^_", key )>

To:

<cfif reFind( "^_(?!_)", key )>

In conclusion, I have no idea as to the nature of this mysterious variable, why it wasn't there in CF8, and why it's bothering me now in CF9. I haven't found any reports about this online, and the fact that you didn't come across it in your example above leads me to suspect that I'm doing something blatantly wrong.

But, at the risk of coming across as a complete tool, I thought I'd share my experience :)

Cheers

@Nic,

Very peculiar find! I'm on my way up to RIA Unleashed today, but will definitely look into this when I get back. I have no idea what it would be. Thanks for the post.

Adam,

Thanks for confirming and filing. It's nice to know it's nothing specific to my setup that was causing this.