Legitimate use for Evaluate()? Do Not Want!

I hate Evaluate(). I hate it like I hate mornings without coffee. I hate it like getting Visual Studio's Debugger to work. I hate Evaluate(), and thats why I'm sad to say that I think I've found a case where I can't use any of my standard tricks to avoid using Evaluate(), and have to give the ugly beast its moment of glory.

The Problem

I'm working on a project where I'm trying to streamline lits of little operations into single code blocks of code to make sure that I don't mess things up later. For example, I'd normally not worry about a block of code like this:

<cfparam name="Url.ID" default="" />

<cfif NOT IsNumeric(Url.ID)>
    <cfset Url.ID = 0 />
</cfif>

Its simple, straight forward, and that exact block isn't going to be used all over the place is it? Well, no, but the concept involved in those 5 lines is something I'm going to need to use a lot, and if I used IsValid() instead of IsNumeric() it means I'm going to be doing this everywhere: define a parameter, see if its value is acceptable, and reset it to the default if not. How can I break this out into something reusable?

Attempt One: Expand

If you've been paying attention to your CF documentation, you'd know that has an attribute called "type" which handles type checking for you already. Normally I use whats already available to solve something like this, but the problem is that if the param'd variable already exists, and its type doesn't match it throws an error rather that use the default value like I want it to do. In order to do what I want with the type attribute, I'd have to build it something like this:

<cftry>
    <cfparam name="Url.ID" default="" type="numeric" />
    
    <cfcatch>
        <cfset Url.ID = 0 />
    </cfcatch>
</cftry>

As you can see, thats no better; its more likes of code and introduces an error stack if the value's don't match. This probably isn't a significant performance hit, but I take it as a rule of thumb to never use try/catch blocks for anything other than catching unexpected or unmanageable errors. In this case, there has to be a better way.

Attempt Two: ParamDefault()

For my second attempt, I wanted to extract this to a UDF so I could use it all over the place. I built out my method stub with some comments to guide me, and then hit a snag.

<cffunction name="ParamDefault" access="public" output="false" returntype="void">
    <cfargument name="VariableName" type="string" required="true" />
    <cfargument name="DefaultValue" type="string" required="false" default="" />
    <cfargument name="Type" type="string" required="false" />

    <!--- Define the variable and set the default value if it doesn't exist --->
    <cfparam name="#Arguments.VariableName#" default="#Arguments.DefaultValue#" />

    <!--- See if a type is set for this default --->
    <cfif StructKeyExists(Arguments, "Type")>
        <!--- See if the value isn't of the type --->
        <cfif NOT IsValid(Arguments.Type, ?)>
            <!--- Set the value to the type --->
            <cfset "#Arguments.VariableName#" = Arguments.DefaultValue />
        </cfif>
    </cfif>

    <cfreturn />
</cffunction>

I'd solved how to set the value back using the "left side quotation" feature of ColdFusion, but I was stumped as to how to get the value of the named variable: it didn't exist in any scopes I could get at to use structure notation... and thats when I remembered Evaluate().

I couldn't bring myself to do it for about 15 minutes; I kept looking for any other way to make this UDF work withing using Evaluate().

The Solution

My final version looks like this:

<cffunction name="ParamDefault" access="public" output="false" returntype="void">
    <cfargument name="VariableName" type="string" required="true" />
    <cfargument name="DefaultValue" type="string" required="false" default="" />
    <cfargument name="Type" type="string" required="false" />

    <!--- Define the variable and set the default value if it doesn't exist --->
    <cfparam name="#Arguments.VariableName#" default="#Arguments.DefaultValue#" />

    <!--- See if a type is set for this default --->
    <cfif StructKeyExists(Arguments, "Type")>
        <!--- See if the value isn't of the type --->
        <cfif NOT IsValid(Arguments.Type, Evaluate(Arguments.VariableName))>
            <!--- Set the value to the type --->
            <cfset "#Arguments.VariableName#" = Arguments.DefaultValue />
        </cfif>
    </cfif>

    <cfreturn />
</cffunction>

I feel unclean. I post this in hopes that one of you out there has an idea of how to do this without Evaluate()... is there a scope I can check or something? Is there anything that can get rid of that Evaluate()statement?

 

Comments

Tim Leach's Gravatar You could.. make it a custom tag. Then instead of use evaluate, use caller[attributes.variableName]. If you want to avoid a using eval that bad.
Jon Hartmann's Gravatar @Tim: Thanks for the idea. You're right, I could do it as a custom tag, but that doesn't fit my general rules for custom tag usage (for managing presentation), so I shied away from it. I also think that thats a case of going too far out of the way to solve the problem. I still hate Evaluate() though, heh!
Mike Collins's Gravatar I just cleaned out 70 evaluates out of some old CF6 code from someone who did not understand CF, and that was just one tag.

Evaluates need to stop in place, and compile the code, this creates a perf issue, and if you have this function as you enter each tag it could add some drag to your requests.

I'd vote to break some of your purist thoughts and use the inumeric when needed in distinct tags. Much simpler and people reading your code will understand it better.
Tyler Clendenin's Gravatar You could add another argument for the scope of the variable.

The only caveat to that is that you would need to mass a valid scope. Think of it as StructKeyExists.
todd sharp's Gravatar I'd change a few things in your approach, but you didn't ask for that feedback, so I'll stick with answering the question.

To get rid of evaluate use structGet():

<cfif NOT IsValid(Arguments.Type, structGet("arguments.VariableName"))>
<!--- Set the value to the type --->
<cfset "#Arguments.VariableName#" = Arguments.DefaultValue />
</cfif>
Jon Hartmann's Gravatar @Mike: You have a valid point about trying to keep it simple, and I really thought about sticking with that approach, but ultimately decided against simplicity in favor of brevity. I'm coding a LOT of stuff by myself right now and I'm more interested in bang-for-my-buck than clarity at the moment. Great point though.

@Tyler: Yeah, you're right. If I passed in the scope and the variable names separately, I could use StructKeyExists() to test for its existence. Thats a good idea!

@Todd: Good call on StructGet(), thats a function I didn't even remember existed. I'm curious how it accomplishes the task of finding the variable from the string under-the-hood, as I'd imagine its doing something similar to evaluate... Its probably sand-boxed better though, so I like that idea. Thanks!
Jon Hartmann, July 2011

I'm Jon Hartmann and I'm a Javascript fanatic, UX/UI evangelist and former ColdFusion master. I blog about mysterious error messages, user interface design questions, and all things baffling and irksome about programming for the web.

Learn more about me.

Post a job. Find one. authenticjobs.com

Interested in becoming a sponsor? Contact me.