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: The very first time your application is used (think immediately after a reboot), the 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: Any time that a user makes a request to your application and either they haven't used it before, or it's been long enough that their session has expired -- as identified by their cookies, usually -- the 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: The two previous events have been specific to the first time something happens. 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.
- onRequest: The 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.
- onRequestEnd: Similarly to 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: You may have guessed it, and if so, you were right. There is also an 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: And of course, there's an 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: Lastly, there is an 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.
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.html // see also: http://help.adobe.com/en_US/ColdFusion/10.0/Developing/WSED380324-6CBE-47cb-9E5E-26B66ACA9E81.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 ) {}
}