By now you've learned the basics of ColdFusion, script vs. tag syntax, scopes, how to deal with data, and even some code-reuse techniques. You're now able to write something useful, so it's time we introduce you to the Request Lifecycle.
You see, when someone requests a ColdFusion page, CF doesn't just start executing your code. There are several events that first take place, which you can be waiting for, and to which you can react. This is not strictly necessary, but you'll find that any complex application will eventually want to make use of some or all of these features, so it's best that you know about them. In order to react to these events, you need to have an Application.cfc file. ColdFusion has designated Application.cfc as a special component that it will automatically look for, and in which we can put our event listeners for request lifecycle events.
A Note on Terminology
I'm going to use some technical terminology here for the sake of correctness, so let's explain it quickly. When an event "happens" we call that "broadcasting" the event. When you react to an event, that's called "listening" for it. A function that "listens" for or "handles" an event is often referred to as either an "event listener" or "event handler".
Lifecycle events add a lot of power and flexibility to your ColdFusion applications, so it's important to understand what events are available and why you might want to use each.
onApplicationStart
event is
broadcast. This gives you an opportunity to define some
application-specific configuration, prime caches, and do a lot more.
The important part to remember is that, as a rule of thumb, it only
happens once, until your application times out (hasn't been used in
a while), the process is restarted, or the computer is restarted.onSessionStart
event is broadcast. This
allows you to set session defaults, like a flag indicating that the
user is not currently logged in; or to redirect to the login page.onRequestStart
is broadcast before
every request, giving you an opportunity to set variables that
should be accessible on every page in the site, or to validate that
the user is allowed access to the requested page, for instance.onRequestStart
event has this weird cousin,
onRequest
, which for now we'll just say does almost the same thing,
but differently. You can effectively use them both (onRequestStart
is broadcast before onRequest
), but they operate a little
differently. Don't worry, we'll cover this in more depth shortly.onRequestStart
, onRequestEnd
is
broadcast after your template returns control to CF, giving you the
opportunity to add code to every request after the requested
template has executed. You might use this to add logging, for
example.onSessionEnd
event, which you can use to do
things like empty a shopping cart that was never paid for (thus
returning that reserved stock back to availability for other
customers to buy).onApplicationEnd
event, too. Lovely symmetry, isn't it? This event is broadcast when
your application times out (hasn't been used in a while), or if
ColdFusion is shutting down.onError
event broadcast in the
event of an un-caught exception, including any you might manually
throw. This gives you a last line of defense to deal with any errors
that might have slipped through the cracks in your application.If you're reading this course sequentially, you've already been exposed to ColdFusion Components, or CFC's. If not, please go back and read the previous chapter.
Note: For the sake of keeping the mid-chapter code samples brief and readable, I'll only show one function at a time; but any combination of them, all of them, or none of them could be in your component, and the order they are in has no effect.
component {
function onApplicationStart(){
application.datasource = "my_database";
return true;
}
}
This method listens for the onApplicationStart
event and sets the value
"my_database" into the Application Scoped variable named datasource
.
This function, onApplicationStart
, will only be called on the relatively
rare occasion that your Application has timed out or is otherwise not
already running. The benefit of this should be clear: You can put a lot
of code in here, and it will only be executed once in awhile, instead of
for every request.
This is the logical place to set most Application-scoped variables;
since the variables live as long as the application, and when the
application is just starting up, this method will re-initialize them for
you. For that reason, it makes perfect sense to set your application
configuration as application-scoped variables in the onApplicationStart
method.
Of course, every single one of the events broadcast for Application.cfc are optional. You don't have to include listeners for all of them; most people don't.
Do you remember when I said that onRequest was a little bit different?
Here's how. The requested template, e.g. index.cfm, will be passed to
onRequest
as an argument, and it's up to you to include it.
This allows you more control over what happens before and after template execution, and allows you to wrap the include; for example, in a transaction, or in a try/catch block. Here's how it might look:
component {
function onRequest( string targetPage ) {
try {
include arguments.targetPage;
} catch (any e) {
//handle the exception
}
}
}
onSessionEnd
is passed two arguments, SessionScope, and
ApplicationScope. Neither is direct access to the actual Session or
Application scopes, and instead, are copies of what those scopes were as
the session was ending. onApplicationEnd
is similarly passed a copy of
the former Application scope.
They are called when the session expires and the application stops, respectively, whether or not a request is made. For that reason, there may not be a "page" to render any output to. Instead, use alternative output options like logging, writing to files, and email, if you need any output from either of them.
This method is passed two arguments: Exception and EventName. Exception
is functionally equivalent to a cfcatch result, and EventName is the
name of the event handler in which the error occurred. If the error
occurred in a requested template (e.g. not in Application.cfc), and you
have not implemented onRequest
, it will be an empty string.
A common practice is to use this method to catch application exceptions and send a notification to the developer(s) via email. While this is a perfectly functional approach, it tends not to scale well as your application grows and errors become more frequent. If you start to notice yourself spending too much time dealing with error emails, look into community projects like BugLogHQ and Hoth.
These methods are very similar to onApplicationStart
in their usage,
with the one exception that onRequestStart
is passed the targetPage as
an argument. None of them need to return anything or are required to
operate in any specific way. The above samples pretty well summarize how
you would write each of the Request Lifecycle Event methods, so we won't
bore you by writing the same code sample over and over. You'll find a
complete empty template for an Application.cfc at the end of this
chapter. Once you grasp the basics, it should be pretty self explanatory
as a reference.
As if that wasn't enough, Application.cfc has more parts!
You are free to define as many other functions in Application.cfc as you would like, if they would be of use. These functions will be available for use anywhere in Application.cfc, and anywhere in a template included from onRequest. However, if a template from onRequest is not included, the functions will not be available to every template in your application.
It also accepts setting certain configuration settings in the implicit constructor, discussed in the Components section of the Code Reuse chapter. To review, anything inside a component (between the <cfcomponent></cfcomponent> tags or the component {} braces) but not inside a function, is part of the implicit constructor, and as such will be executed during the instantiation of the component.
ColdFusion defines more than 20 settings that can be set and updated from the implicit constructor in Application.cfc, however, a full discussion of each is outside the scope of this course. We'll cover the basics, and for the rest, you should read the official documentation. Inexplicably, the primary documentation for Application.cfc settings makes no mention of it, but there are also numerous ORM-related settings that can be configured here, which is documented in ORM Settings.
Every application needs a name. It must be more or less alphanumeric and as a best practice, should not include spaces or any symbols or punctuation. This name is used to create different contexts for each application running on the server, so that they don't share any memory. Without a unique name, your application's code could potentially overwrite memory values for another application.
this.name = "my_application3";
While a default application timeout setting is set server-wide in the CF Administrator, you can override this setting, up to a CF Administrator defined limit, with this.applicationTimeout.
this.applicationTimeout = CreateTimeSpan(10, 0, 0, 0); //10 days
Custom tags, covered in the Code Reuse chapter, can be sourced from numerous directories. You can add to the global list in the CF Administrator, and your Application.cfc can also define some of its own additions to the list; making your code more portable.
this.customTagPaths = [ expandPath('/myAppCustomTags') ];
Similarly, you can set per-application mappings that supercede any mappings of the same name set in the CF Administrator for requests in your application.
this.mappings = {
"/foo" = expandPath('/com/myCompany/foo')
};
Each application is allowed one app-global default data source, which will be used if none is defined for a query or stored procedure.
this.datasource = "my_datasource";
Session management -- whether or not sessions are enabled and tracked for your application -- can be enabled or disabled via the sessionManagement setting. You can also specify the session timeout.
this.sessionManagement = true;
this.sessionTimeout = CreateTimeSpan(0, 0, 30, 0); //30 minutes
Just a reminder: Do yourself a favor and check out the primary list of other available settings, as well as the ORM settings.
ColdFusion applications differ from traditional desktop applications in that they are not always "running" when enabled. CF Applications only ever do anything when an http request is made; with the exception of special cases like onApplicationStop and onSessionStop.
When a request is made during the execution of the Application.cfc
implicit constructor code, the applicable application name is
determined, and the applicable memory scopes for that application are
associated with the request. Then, any necessary events are broadcast
and responded to, in serial (onSessionStart
will not fire before
onApplicationStart
returns; onRequestStart
waits for onSessionStart
, and
so on...). Control is then passed to the requested template. After the
template completes, control shifts back to Application.cfc where
onRequestEnd
, if defined, will be executed.
Technically speaking, and assuming the default settings from a vanilla ColdFusion install, you can have an Application.cfc anywhere within your application structure, and even a few places outside of it. Doing so implies that everything in that folder and all of its subfolders (and theirs, and theirs...) are part of that application.
If a request is made for you.com/foo/bar/widgets/index.cfm, CF looks inside that widgets folder for an Application.cfc. If found, then it uses the application name set therein. If not found, CF then checks the parent folder, bar. This process is repeated over and over until it reaches the system root. This is the default setting as of ColdFusion 10.
You can change this setting in the CF Administrator, under Server Settings > Settings, near the bottom of the page, under the heading "Application.cfc/Application.cfm lookup order". Other options are "until webroot", where the process described above stops at the web root instead of the system root, and "in webroot", which specifies that CF should only ever look for you.com/Application.cfc.
The obvious next question is, if we can have Application.cfc files anywhere, can we nest one application inside another? The answer is Yes.
Consider the following directory structure:
Here we have two applications: One for the main website, and one for the admin section of the site. Since there is no Application.cfc in the widgets folder, any requests to widgets/sprockets.cfm will use the Application.cfc from the parent folder.
The two applications can have completely different settings and different code for their event handlers, as long as they have distinct names.
A relic of a bygone time! Back in the dark ages, before ColdFusion had components, Application.cfm was a similar approach to solve some of the same problems. The primary difference is that everything in Application.cfm is executed at the start of every request, more or less like the onRequestStart method does now. The other major difference is that instead of the component implicit constructor to set configuration settings, you set them as arguments to the cfapplication tag.
You can "fake" similar functionality to onApplicationStart in Application.cfm by using a flag in the application scope:
<cfif not structKeyExists(application, "initialized")>
<cfset application.datasource = "my_datasource">
<cfset application.initialized = now()>
</cfif>
Depending on what you are doing inside that block, you may want to make use of locking to prevent multiple simultaneous requests from all executing the application initialization code at the same time.
Fortunately, we have evolved. These days, Application.cfm is mostly just a sign of old code.
For your reference, here is an empty Application.cfc, with all of the bells and whistles waiting to be filled in.
component {
this.name = "myApplication";
this.applicationTimeout = CreateTimeSpan(10, 0, 0, 0); //10 days
this.datasource = "my_datasource";
this.sessionManagement = true;
this.sessionTimeout = CreateTimeSpan(0, 0, 30, 0); //30 minutes
this.customTagPaths = [ expandPath('/myAppCustomTags') ];
this.mappings = {
"/foo" = expandPath('/com/myCompany/foo')
};
// see also: http://help.adobe.com/en_US/ColdFusion/10.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-750b.htmlhttps://helpx.adobe.com/coldfusion/cfml-reference/application-cfc-reference/application-variables.html
// see also: https://helpx.adobe.com/coldfusion/developing-applications/developing-cfml-applications/designing-and-optimizing-a-coldfusion-application.html
function onApplicationStart() {
return true;
}
function onSessionStart() {}
// the target page is passed in for reference,
// but you are not required to include it
function onRequestStart( string targetPage ) {}
function onRequest( string targetPage ) {
include arguments.targetPage;
}
function onRequestEnd() {}
function onSessionEnd( struct SessionScope, struct ApplicationScope ) {}
function onApplicationEnd( struct ApplicationScope ) {}
function onError( any Exception, string EventName ) {}
}