Wednesday, June 18, 2008

Getters/Setters Redux

There has been a lot of news about some of the forthcoming additions to CF9 and they all sound great! All the SysCon media and OpenBlueDragon chatter has been really tiresome. I'm glad that we're getting to hear about "neat" and "cool" stuff from Adobe again.

One of the features that I've heard is planned for cf9 is implicit getters and setters. I imagine those will be along similar lines to the ones in as3, but I don't know any more than you at this point. That sounds like it will save a lot of keystrokes for lots of us and I'm looking forward to it. What I'm not looking forward to is knowing that it's not going to be out until sometime in 2009. So, to help ease that pain a little, here's a little code I have running that sort of does that for basic value object like components. Just extend it to provide get/set/val/do functions for each property you declare with cfproperty. All of the methods can be overridden when needed, and in some cases you should override them.


<cfcomponent hint="Extension point for Value Objects" output="false">

<cffunction name="onMissingMethod" output="false">

<!--- define arguments --->
<cfargument name="missingMethodName" type="string" />
<cfargument name="missingMethodArguments" type="struct" />

<cfset var name = "" />
<cfset var propList = ""/>
<cfset var private = structNew() />

<!---
Build a list of properties from the cfproperty tags in the
extending object. We will use this list to determine if
the properties belong to the object
--->
<cfset md = getMetaData( this )>
<cfset foundMethod = structFindValue( md , arguments.missingMethodName , "all")>
<cfset propArray = md.properties/>

<cfloop from="1" to="#arrayLen(propArray)#" index="item">
<cfset propList = listAppend(propList , propArray[item].name , ",")>
</cfloop>

<!---
Getters/Accessors - examine the missing method that was called to see
if it began with "get". If it does, then we are going to return the
value of the property that matches the rest of the method name
--->
<cfif left(missingMethodName,3) is "get">
<cfset name = right( missingMethodName , len(missingMethodName) - 3 ) />
<cfif listFindNoCase( propList , name )>
<cfreturn variables.instance[name] />
<cfelse>
<cfthrow type="ValueObject" message="Property #name# is not defined in component #md.name#."/>
</cfif>

<!--- Setters --->
<cfelseif left(missingMethodName,3) is "set">
<cfset name = right( missingMethodName , len(missingMethodName) - 3 ) />
<cfif structIsEmpty(missingMethodArguments)>
<cfthrow type="ValueObject" message="The #uCase(name)# parameter to the get#name#
function is required but was not passed in."/>
<cfelseif NOT listfindNoCase( propList,name )>
<cfthrow type="ValueObject" message="The set#name#() method of the component
#md.name# is not defined."/>
<cfelse>
<cfset evaluate( "this.val#name#('" & missingMethodArguments[1] & "')" )>
<cfset evaluate( "this.do#name#('" & missingMethodArguments[1] & "')" )>
</cfif>

<!--- Doers --->
<cfelseif left( missingMethodName , 2) is "do">
<cfset name = right( missingMethodName , len(missingMethodName) - 2 ) />
<cfset variables.instance[name] = missingMethodArguments[1] />
<!---
Business Rule place holder
if this method is not overriden in your component for each property,
no business rules or format enforcement is taking place
--->
<cfelseif left(missingMethodName,3) is "val">
<!--- This should be overriden in your component --->
<cfset name = right( missingMethodName , len(missingMethodName) - 3 ) />

<!--- Print --->
<cfelseif missingMethodName is "Print">

<cfreturn variables.instance />

<!---
The isEqual method here is not detecting if you have two handles
on the same object. It's actually checking to see if the
properties are the same on the two objects.
--->
<cfelseif missingMethodName is "isEqual">
<!--- serialize the attributes of the first object --->
<cfwddx
action = "cfml2wddx"
input = "#this.print()#"
output = "private.obj"
/>

<!--- serialize the attributes of the second object --->
<cfwddx
action = "cfml2wddx"
input = "#missingMethodArguments.obj.print()#"
output = "private.obj2"
/>

<!--- Test for equality --->
<cfif private.obj eq private.obj2>
<cfreturn true />
<cfelse>
<cfreturn false />
</cfif>
<cfelse>
<cfthrow
type = "ValueObject"
message = "The method #missingMethodName# was not found in component
#expandPath('/' & replace(md.name,'.','/','all'))#"
detail = "Make sure that you have defined the property using cfproperty."
/>
</cfif>
</cffunction>
</cfcomponent>

Friday, June 13, 2008

coldfusion.runtime.AbortException

Most of the time when I'm working, I like to have CF hit me with as much debugging information as possible.  I'm familiar with it enough now that I know what to ignore generally. 

Back in the days of cf7 ;) there was a little problem with CF throwing an error on every cflocation tag it hit if you had implemented the OnError method in your Application.cfc file.  It has since been corrected, but the underlying technology that deals with cflocation must still trigger that error somewhere in the bowels of cfs java code.

If you are working in cfeclipse and using the line debugger, there is an option under preferences | ColdFusion | debug settings that lets you break on a CFML runtime error.  Turning this on means that instead of getting the big CF error page with all of its debugging goodness, your code will halt execution and you can see where exactly in your code the error was first determined.  Occasionally this can be helpful, especially if something is confusing cf about the location of your error. 

Unfortunately, with this on, CF still thinks of cflocation as an error.  For all I know, it technically may be because of how cflocation has been implmented.  Hey, if we're allowed to trap an error sometimes, so should the people that wrote our language.

If you want to try a workaround in your code, Ray Camden has a post on his blog with some code.  For what I'm doing, it's probably best just to leave the thing off and deal with runtime errors in the resulting page in the browser instead of trapping them by default.