You are viewing documentation for v1.0.x. Change

Filters and Verification

Stop repeating yourself with the use of filters and verifications.

If you find the need to run a piece of code before or after several controller actions, then you can use filters to accomplish this without having to explicitly call the code inside each action in question.

This is similar to using the onRequestStart/onRequestEnd functions in ColdFusion's Application.cfc file, with the difference being that filters tie in better with your Wheels controller setup.

An Example: Authenticating Users

One common thing you might find yourself doing is authenticating users before you allow them to see your content. Let's use this scenario to show how to use filters properly.

You might start out with something like this:

<cfcomponent extends="Controller">

<cffunction name="secretStuff">
<cfif NOT StructKeyExists(session, "userId")>
<cfabort>
</cfif>
</cffunction>

<cffunction name="evenMoreSecretStuff">
<cfif NOT StructKeyExists(session, "userId")>
<cfabort>
</cfif>
</cffunction>

</cfcomponent>

Sure, that works. But you're already starting to repeat yourself in the code. What if the logic of your application grows bigger? It could end up looking like this:

<cfcomponent extends="Controller">

<cffunction name="secretStuff">
<cfif cgi.remote_addr Does Not Contain "212.55">
<cfset flashInsert(msg="Sorry, we're not open in that area.")>
<cfset redirectTo(action="sorry")>
<cfelseif NOT StructKeyExists(session, "userId")>
<cfset flashInsert(msg="Please login first.")>
<cfset redirectTo(action="login")>
</cfif>
</cffunction>

<cffunction name="evenMoreSecretStuff">
<cfif cgi.remote_addr Does Not Contain "212.55">
<cfset flashInsert(msg="Sorry, we're not open in that area.")>
<cfset redirectTo(action="sorry")>
<cfelseif NOT StructKeyExists(session, "userId")>
<cfset flashInsert(msg="Please login first.")>
<cfset redirectTo(action="login")>
</cfif>
</cffunction>

</cfcomponent>

Ouch! You're now setting yourself up for a maintenance nightmare when you need to update that IP range or the messages given to the user, etc. One day, you are bound to miss updating it in one of the places.

As the smart coder that you are, you re-factor this to another method so your code ends up like this:

<cfcomponent extends="Controller">

<cffunction name="restrictAccess">
<cfif cgi.remote_addr Does Not Contain "212.55">
<cfset flashInsert(msg="Sorry, we're not open in that area.")>
<cfset redirectTo(action="sorry")>
<cfelseif NOT StructKeyExists(session, "userId")>
<cfset flashInsert(msg="Please login first!")>
<cfset redirectTo(action="login")>
</cfif>
</cffunction>

<cffunction name="secretStuff">
<cfset restrictAccess()>
</cffunction>

<cffunction name="evenMoreSecretStuff">
<cfset restrictAccess()>
</cffunction>

</cfcomponent>

Much better! But Wheels can take this process of avoiding repetition one step further. By placing a filters() call in the init() method of the controller, you can tell Wheels what function to run before any desired action(s).

<cfcomponent extends="Controller">

<cffunction name="init">
<cfset filters("restrictAccess")>
</cffunction>

<cffunction name="restrictAccess">
<cfif cgi.remote_addr Does Not Contain "212.55">
<cfset flashInsert(msg="Sorry, we're not open in that area.")>
<cfset redirectTo(action="sorry")>
<cfelseif NOT StructKeyExists(session, "userId")>
<cfset flashInsert(msg="Please login first!")>
<cfset redirectTo(action="login")>
</cfif>
</cffunction>

<cffunction name="secretStuff">
</cffunction>

<cffunction name="evenMoreSecretStuff">
</cffunction>

</cfcomponent>

Besides the advantage of not having to call restrictAccess() twice, you have also gained 2 other things:

  • The developer coding secretStuff() and evenMoreSecretStuff() can now focus on the main tasks of those 2 methods without having to worry about site-wide logic like authentication.
  • The init() method is now starting to act like an overview for the entire controller.

All of these advantages will become much more obvious as your applications grow. This was just a simple example to put filters into context.

Sharing Filters Between Controllers

So far, we've only been dealing with one controller. Unless you're building a very simple website, you'll end up with a lot more.

The question then becomes, "Where do I place the restrictAccess() function so I can call it from any one of my controllers?" The answer is that because all controllers extend Controller.cfc, you should probably put it there. The init() method itself with the call to filters() should remain inside your individual controllers though.

If you actually want to set the same filters to be run for all controllers, you can go ahead and move it to the Controller.cfc file's init() method as well. Keep in mind that if you want to run the init() method in the individual controller and in Controller.cfc, you will need to call super.init() from the init() methods of your individual controller.

2 Types of Filters

The example with authentication showed a "before filter" in action. The other type of filter you can run is an "after filter." As you can tell from the name, an after filter executes code after the action has been completed.

This can be used to make some last minute modifications to the HTML before it is sent to the browser (think translation, compression, etc.), for example.

You specify if you want to run the filter function before or after the controller action with the type argument to the filters() function. It defaults to running it before the action.

Including and Excluding Actions From Executing Filters

By default, filters apply to all actions in a controller. If that's not what you want, you can tell Wheels to only run the filter on the actions you specify with the only argument. Or you can tell it to run the filter on all actions except the ones you specify with the except argument).

Here are some examples showing how to setup filtering in your controllers. Remember, these calls go inside the init() function of your controller file.

<cfset filters(through="isLoggedIn,checkIPAddress", except="home,login")>
<cfset filters(through="translateText", only="termsOfUse", type="after")>

So What's Verification Then?

Verification, through the verifies() function, is just a special type of filter that runs before actions. Its capability is limited; the only thing you can do is verify the request method (post or get), whether it's an AJAX request, and whether specified variables exist.

Let's say that you want to make sure that all requests coming to a page that handles form submissions are post requests. While you can do this with a filter, it is more convenient to do it with the verifies() function.

All that you need to do is this:

<cfset verifies(only="handleForm", post=true)>

The code above will ensure that all requests coming to the handleForm function are from form submissions. All other requests will be aborted.

You can also specify different behavior for when the verification fails in a special handler function. You can read more about that in the documentation for the verifies() function.

^ Top
Table of Contents

Comments

Read and submit questions, clarifications, and corrections about this chapter.

[Add Comment]

  1. Ryan Cole's Gravatar Ryan Cole says:

    wow. Sharing filters between controllers is extremely coupled to a single Wheels install, right now. I'd love to see this modified into a way that decouples the two. Never should it be advised that the developer modifies the framework's actual codebase to achieve a desired effect like this.

    On another note, I love the way this works. I just am not completely in love with how it works, at the moment.

  2. Chris Peters's Gravatar Chris Peters says:

    I believe that you posted your comment to this page before approaching the Google Group. But to clarify for everyone out there, please see how Ryan's concern is addressed in this discussion:

    http://groups.google.com/group/cfwheels/browse_thread/thread/10fe0cab576b2148

Add Comment