Ask a Jedi: A onSessionEnd Question/Example

This post is more than 2 years old.

Pat asks:

I'm writing a 'shop' which is selling unique items. To stop confusion I'm loading a table in the database with items that are in peoples shopping baskets. If an item is in the database table, it won't get displayed on the website, preventing two people buying the same unique item. The issue arises when a customer adds an item and then fails to buy it. The shopping basket uses session variables so, using a session timeout, their basket is easily emptied. To empty the database table however, I've tried to use OnSessionEnd in the Application.cfc. This will send a simple 'session ended' email, however, when I try to run a DB query, it just doesn't work.

Here's the code:

<cffunction name="onSessionEnd" returnType="void">
<cfargument name="SessionScope" required="True" />
<cfargument name="ApplicationScope" required="False" />

<cfquery name="emptyBasket" datasource="#Application.DSN#">
DELETE FROM basket
WHERE session = '#Arguments.SessionScope.sessionid#'
</cfquery>

</cffunction>

Is there something fundamentally wrong here ? Or is there a better way of doing this ?

I'm thinking along the lines of an hourly scheduled task that scans the DB for timed out basket contents (they contain a timestamp).

This is a very simple problem. Notice how you have two arguments for onSessionEnd, the session and application scope? I assume you copied this from somewhere, like maybe my reference, but you may not understand exactly why.

When ColdFusion runs the onSessionEnd method, it does not give you direct access to the Application and Session scopes. That's why they are passed in as arguments. If you want to use Application.DSN as you have here, you need to switch to the Argument copy:

<cfquery name="emptyBasket" datasource="#arguments.AppScope.DSN#">

I'll also use this space to make a few more recommendations. First off - your query isn't using cfqueryparam. You should update to use this tag for the dynamic portion of the query.

Lastly, I think your idea of scanning the table every hour is a good one. Why bother when you fix your code? Well, even though you and I know the onSessionEnd code will work, it makes me feel uneasy that if it does fail, your table isn't updated. Personally I'd use both. In the best of situations, as soon as a session times out, the unique item is back for sale. In the worst of situations, the item is back for sale at most an hour later.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by todd sharp posted on 1/16/2008 at 6:36 PM

How could one safely assume that a session is indeed timed out based on only a timestamp on an hourly task? Unless of course the 'lastHit' is recorded in onRequestStart that is...

Comment 2 by ted parton posted on 1/16/2008 at 8:38 PM

@todd.sharp -- While each online shop is different, I think by and large most consumers will complete a purchase in 1 hour or less. That said, you could certainly adjust as needed for your online shop.

As for the date/time-stamp I would place this on each item as it was put in the basket. But wait, you say, I want all of the items to expire at the same time for that basket. OK, well you could handle that a couple of different ways. For instance, you could update all related items with the most recent time-stamp; or you could split the basket and the items into two tables and control the expiration at the basket level. The choice is yours. :)

As Ray suggests, I would have a scheduled task that runs every 15 or 30 minutes to clean up items > 60 minutes old. This clean up routine would also lend itself to be a stored procedure and ran as a scheduled event on your database server, if your db server supports such.

Comment 3 by Jason Martin posted on 1/17/2008 at 11:17 PM

If you store the basket information in the users session, you can use the sessiontracker to get all active sessions, loop over the collection, and match any product id's that are already in someones cart. This way you don't have to mess with databases or scheduled tasks.

<cfset sessionTracker= createObject("java","coldfusion.runtime.SessionTracker") />
<cfset activeSessions = tracker.getSessinoCollection(application.applicationName) />
<cfdump var="#activeSessions#" />

Comment 4 by Jason Martin posted on 1/18/2008 at 8:04 AM

Doh, I just re-read my code. I was in a hurry earlier and didn't proof read. This should work better:

<cfset sessionTracker= createObject("java","coldfusion.runtime.SessionTracker") />
<cfset activeSessions = sessionTracker.getSessionCollection(application.applicationName) />
<cfdump var="#activeSessions#" />

Comment 5 by Dave Phipps posted on 1/18/2008 at 3:14 PM

Is there a neat way to use OnSessionEnd to provide a message (possibly in cfwindow) to let a user know there session is about to timeout and give them an opportunity to login again and therefore not lose any changes they are in the process of making? Or is there a better place to put this and if so how could it work - can you get the session timeout info?

Comment 6 by Raymond Camden posted on 1/18/2008 at 7:10 PM

No. Think about it - when onSessionEnd runs - no one is there, right?

You can do something like so:

http://www.coldfusionjedi.c...