Extending The Application.cfc ColdFusion Framework Component With A Relative Path
The ColdFusion application framework is a truly beautiful thing. It's high level of flexibility affords an unbelievable amount of power and control to the developer in regard to all aspects of the request processing. Its Achilles heel, however, has always been the fact that extending the Application.cfc component is a nightmare. As such, people are less likely to leverage the ability to sub-class parts of an application.
I like to think that I have a pretty strong grasp on how the ColdFusion application framework operates. But even after all these years, I am still finding some new features. A couple of months ago, I discovered that you could CFInclude one ColdFusion Application component (CFC) into another. I tried to use this behavior in order to extend Application.cfc but, the implementation was fairly junky.
Then, yesterday, as I was talking to someone about this problem (of relative paths), I had a eureka moment! What if I could use a ColdFusion custom tag to define my ColdFusion Application component? A custom tag, executed within a ColdFusion component, would have the unique ability to maintain its own memory space and, at the same time, have implicit access to the parent component's local scopes.
As it turns out, by combining the relative-path abilities of CFInclude with the encapsulation of a ColdFusion custom tag, we can define and thereby extend our ColdFusion application framework components using relative paths.
To see what I'm talking about, let's take a look at a simple application with the following directory structure:
./
./Application.cfc
./index.cfm
./sub/Application.cfc
./sub/Application.cfm
./sub/index.cfm
As you can see, we have two Application.cfc components and two index.cfm files (for each level of the application). We also have an Application.CFM file, which we will be using as a custom tag - but, we'll get to that in a little bit.
Let's look at the root Application.cfc ColdFusion application framework definition:
Application.cfc (In The Root)
<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 = "AppExtendViaCustomTag" />
<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, the root Application.cfc defines a few event handlers and explicitly turns off session management. Take special note of the onRequest() event handler as this defines output that will be used by both the root and sub-directory aspects of our ColdFusion application.
Now, let's take a look at the index.cfm file in the root:
Index.cfm (In The Root)
<!--- 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"
/>
When we make a request to the root index.cfm file, we get the following page output:
As you can see, the page processed exactly as we expected it to.
Ok, now let's get into the fun stuff. Let's take a look at the Application.cfc within our SUB directory:
Application.cfc (In The Sub Directory)
<!--- NOTE: This is the SUB Application.cfc file. --->
<!---
Define the Application.cfc. Notice that we are using the
Application.CFM as a custom tag to allow us to extend the root
Application.cfc files with a relative path.... that's right - a
relative path!
--->
<cf_application
extends="../Application.cfc"
hint="I define the application settings and event handlers.">
<!---
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, 1, 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>
<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>
</cf_application>
As first glance, this might appear to be completely mundane - we have our ColdFusion component, it extends our root Application.cfc, it defines some application settings, and it defines some event handlers.
But wait, we're using a relative path to define our parent Application.cfc. And hold on professor - where's our CFComponent tag? What the heck is going on around here?!?
Now things are starting to get sexy; rather than using a traditional CFComponent tag to define our component body, we're using the lower-precedence Application.cfm file as a ColdFusion custom tag in order to define our Application.cfc ColdFusion application component.
Before we look at the underlying magic, let's first make sure that everything else is in order. If you look at this sub-Application.cfc, you'll see that it overrides some of the application settings, turning on session management. You'll also see that it is overriding the root onRequestStart() event handler by both calling the "super" event handler and then subsequently adding its own request property (isSubApplication). Of particular interest, however, notice that we are extending the sub-Application.cfc with a relative path to the base Application.cfc instance.
The sub directory of our application also has an index.cfm file which is pretty much a mirror of the root index.cfm.
Index.cfm (In The Sub Directory)
<!--- 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"
/>
As you can see, this is simply CFDump'ing the same scopes as the other version. When make a request to this sub-directory index.cfm file, we get the following output (which I have marked up for convenience):
As you can see, a request to the sub directory of our application was able to bring in aspects of the root Application.cfc as well as the overriding aspects of our sub-application. When I saw this work for the first time, I was once again overcome with a sense of awe and amazement regarding the flexibility and power of the ColdFusion application framework.
Now, going back to the novel way in which we are defining our ColdFusion component, let's take a look at what that ColdFusion custom tag is actually doing. Before we look at the code, though, I just wanted to mention that I used the Application.CFM file as a convenience; there's nothing that requires that particular file for this to work. I just thought it was nice to keep all the "application" stuff in a related set of files. And, since the Application.cfc file always takes precedence over the Application.cfm file, I knew there would never be any unexpected invocation.
Application.cfm (Our "CFComponent" Custom Tag)
<!--- Check to see which mode we are executing. --->
<cfif (thisTag.executionMode eq "start")>
<!--- Param the tag attributes. --->
<!---
I am the relative path of the Application.cfc that we are
going to extend. Since Application.CFC and Application.CFM
exist at the same level, we can use a relative path.
--->
<cfparam
name="attributes.extends"
type="string"
/>
<!--- Include the given root Application.cfc. --->
<cfinclude template="#attributes.extends#" />
<!---
Build up a copy of the root Application.cfc. Now that we've
included the root Application.cfc, we now have all of the
THIS-based variables in the custom tag Variables scope.
--->
<cfset thisTag.super = {} />
<!---
Loop over the variables scope to copy all the root-level
variables to the super collection.
--->
<cfloop
item="key"
collection="#variables#">
<!---
Make sure that this is not an ordinary custom tag variable.
--->
<cfif !listFindNoCase( "attributes,caller,thisTag", key )>
<!--- Copy the framework property. --->
<cfset thisTag.super[ key ] = variables[ key ] />
</cfif>
</cfloop>
<!--- Copy the super object to the caller scope. --->
<cfset caller.super = thisTag.super />
<!---
Copy over the THIS scope base properties into the caller
scope immediately. These can be easily overriden within the
body of the custom tag.
NOTE: We can't simply assigne the THIS scope. That will
corrupt it; rather, we have to leave the caller THIS in tact
and just add to it.
--->
<cfset structAppend( caller.this, thisTag.super.this ) />
<!---
Now that we have copied the THIS scope, delete it from
super. This way, we can copy append the Variable scope
without messing up the super values.
--->
<cfset structDelete( thisTag.super, "this" ) />
<!---
Now that we have santized our super collection (to remove
THIS scope), we can now append the local variables
collection to the caller variables collection.
NOTE: This will not cause existing function name errors
if we override the the root methods with the sub-methods.
--->
<cfset structAppend( caller.variables, thisTag.super ) />
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<cfelse>
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!--- Nothing to do here that I can think of. --->
</cfif>
The fact that this even works sort of blows my mind! Like I said, I am once again in awe of the ColdFusion application framework. Inside of this custom tag, we are including the root Application.cfc. And, since we are using CFInclude to do this, using a relative path to define the root Application.cfc is no longer a problem.
Once included, all of the root Application.cfc's variables come to exist in the encapsulated memory space of the custom tag. It then becomes a simple matter of copying them from the custom tag into the CALLER scope - our sub-Application.cfc instance. And, since this include-n-copy takes place in the "start" mode of our custom tag, it allows the sub-Application.cfc to easily override any of the root-Application.cfc settings (including event handlers).
At first, I was concerned that using a ColdFusion custom tag to define the component would create CFFlush-issues since you can't flush the output buffer within the execution of a custom tag. But then I remembered, we're not actually executing our event handlers in the context of the custom tag - we're simply defining them. Once our sub-Application.cfc gets instantiated, the custom tag has finished executed and the event handlers can be invoked without limitation.
Of course, none of this would be an issue if we could just use relative paths to define ColdFusion component locations; but, until that is possible, I think this creates a fairly clean solution. And, it provides a very easy upgrade path - should we suddenly be able to use relative pathing, all you would have to do is replace the cf_application tag with a CFComponent tag and all is good.
NOTE: In full disclosure, I should mention that this doesn't quite work if you have doubly-nested applications. That is if you have one Application.cfc which extends an Application.cfc which extends a root Application.cfc. If you try that, you get a bizarre custom-tag-path caching problem. I'm going to try and figure out how to fix that bug; but for now, this only works with a single level of extension.
Want to use code from this post? Check out the license.
Reader Comments
Classic Nadel! What use case are you envisioning for this?
@Tim,
The use-case is simply creating a specialized version of a sub-section of your application. I don't do this very often, so I don't know what the juicy use-cases are. I've had people tell me that they will create a sub-section that has session management turned on (ie. user logs into a sub directory) while the root app (public users) have no session management.
Why not just use an Application Proxy?
Sub Application Extends "ApplicationProxy.cfc" and
ApplicationProxy.cfc extends root "Application.cfc"
@Giancarlo,
Because you still run into the same problem - how do you define the path to the root application that is used in the ApplicationProxy.cfc?
If you look at something like Sean Corfield's ApplicationProxy approach:
http://corfield.org/entry/Extending_Your_Root_Applicationcfc
... it only works if the application is directly in the web root. The beauty of a relative-path is that your application can be located anywhere in your file system.
Hi Ben,
I was an early adopter of Object Oriented Programming. For a long time, traditional programming languages didn't support it. So I developed techniques to code in the OOP way with no help from the language.
Suppose you have a shared Application.cfc for the military. I choose that example because there are well-known inclusion hierarchies, such as Marines being part of the Navy. You could then have the following hierarchy of ColdFusion structures to hold session variables:
So you tell your developers, this is what you have to work with. If you have a session variable for your subsystem, store it in the structure that belongs to you, period, end of discussion.
This is a ColdFusion structure rendering of a mechanism for simulating property inheritance. Suppose you're in the Navy subsystem. You would do the following (cfscript):
See how that works? You use the forgotten function :-) to walk the inclusion hierarchy and move all of your session variables into the Variables scope, partly to avoid implicit shared-scope locking, but mostly to provide subsystem overrides.
So if the user doesn't work at the Pentagon, Session.mil.AdminUser is "No". But if the same user instead works for the Navy, and Session.mil.navy.AdminUser could be "Yes". In that scenario, the user has Variables.AdminUser set to "No" when they aren't in the Navy subsystem, but they have it set to "Yes" when they are in the Navy subsystem. Capisce?
You can even automate the code to walk the inclusion hierarchy with StructAppend in Application.cfc file, based upon the directory names you see in CGI.Script_Name.
Method inheritance is a little harder, due to CF's tendency to crash when you try to define a UDF that's already defined.
Now all you have to do is yell at your developers for saving something off in Session.variablename, as if the Session scope belonged to them.
Hey Ben,
Guess you are right, was not thinking about the subject of relative path. The only thing I can think of is having the ApplicationProxy.cfc extend the application file you want using dot notation like so ... folder.otherfolder.application. But again this is from root down, not what you are trying to do.
Wouldn't a (CF) mapping pointing to the application root solve this issue?
I realize you still can't extend using relative pathing, but would you need to if you always knew how to start from the root?
Jalpino, thing is, you don't really always know the path. You can try to guess it using CF functions and converting it to use dot (.) notation, but then you may end with a corrupt path.
I can imagine using something like that, with relative paths, to apps that need to be "installed" in many servers, with different file structures, without the burden of manually configuring some paths.
But, again, it is probably be possible to guess the path to the root using directory and path functions...
Regards,
@WebManWalking,
I see where you're going with that. Definitely, as you said, a rogue developer could step in and just mess it up (shakes fist!!). StructAppend() is awesome, though. I use it all the time.
The first attempt I made at this (including one App.cfc into another), I was getting the duplicate-method errors; however, since this approach pulls each Application.cfc into a custom tag first, you're just copying method references - you don't ever actually define the same method in the same variable scope.
... however, now that I am thinking out loud a bit, I am surprised the CFFunction tags weren't hoisted. That is, a UDF is typically accessible in the page no matter where it was defined (above OR below the calling code). As such, I am perhaps a bit confused as to why my structAppend() didn't override the CFFunction. Hmm, need to think that through a bit.
@Giancarlo,
Right, exactly. It's the dot-notation that really causes the problem.
@JAlpino,
You could use a mapping, but it can't be application specific. Otherwise, you get a chicken-and-egg situation. Your Applicaiton.cfc needs a local mapping to define itself... so that it can then define the local mappings.
You'd have to default to a mapping within the ColdFusion Administrator; and, for easy of maintenance and portability, I'd like to stay out of the CF Admin as much as possible.
@Fernando,
... exactly - using a hard-coded value on a multiple serves would be irksome.
@WebManWalking,
Hmm, there IS function hoisting taking place; however, due to the fact that the Application.cfc has two scopes (This and Variables), it's not quite noticed. Within the Sub Application.cfc:
this.onRequestStart()
variables.onRequestStart()
.. are different functions. This shouldn't be. These should be the same - the hoisting IS messing it up.
Rather than using structAppend(), I need to check to see if the variable exists before appending it. Grrrrr.
To all, rather than simply structAppend()'ing the Variables scope, this is a better approach - it creates a "truer" Application.cfc experience:
This copies the Event handlers into both the THIS and VARIABLES scope (which is what happens in a real application.cfc). It also gets around function hoisting by not pulling in the "super" event handlers unless they are not already defined.
Hi Ben,
I like this solution:
./Application.cfc
./sub/Application.cfc
./sub/AbstractApplication.cfc
./sub/Application.cfc
./sub/AbstractApplication.cfc
./Application.cfc
@Walter,
Very interesting! That might just be brilliant! I want to test that a bit locally. That looks to be a best of both worlds kind of situation.
@Walter,
I love this! This is just what I was looking for in an application I'm writing where the user could locate it anywhere on a site in any directory structure. I want the ./sub/ directory to contain the admin portion that just overrides the onRequestStart method to check for an admin session variable and if not to show login screen.
To save time, there are problems too (of course). I use it to create an instance of a class outside of the webroot because I couldn't use application mappings, because they dont't exist in the constructor area of the Application.cfc, where I needed some processing. I didn't use aliases or ColdFusion Administrator mappings, because I wanted to keep the code droppable.
Example:
/framework/load.cfm
/framework/BootLoader.cfc
/framework/AbstractApplication.cfc
/project/site1/www
/project/site2/www
/project/site3/www
/project/Application.cfc
/project/AbstractApplication.cfc
The wwws are the webroots.
With the code of my previous comment, I can subclass the Framework-Application.cfc.
The Framework-Application has some odd behaviour then
/framework/AbstractApplication.cfc
So the Framework-AbstractApplication.cfc "lives" in the project directory and you cannot create classes in the framework directory. But there is an easy workaround.
/framework/AbstractApplication.cfc
/framework/load.cfm
The access=package methods of the BootLoader can be accessed from the load.cfm, but not from the AbstractApplication.
Hope I could help anyone.
...and finally attributes don't work on the /framework/AbstractApplication.cfc
:(
@Walter,
Minor problems aside, this is still quite excellent. Thanks for the dynamite tip.
@Walter,
Thanks to you, we're traveling rather down the rabbit hole which is, the ColdFusion component:
www.bennadel.com/blog/2115-Extending-The-Application-cfc-ColdFusion-Framework-Component-With-A-Relative-Path-Proxy.htm
I'm starting to have trouble keeping this all in my head!