cfproperty, onMissingMethod and data type validation
September 16, 2008
I've been playing with getting and setting variables using the onMissingMethod function of ColdFusion 8 components. I've never been a fan of this technique as it means that you don't have an API for your class so you (and any other developers in your team) need to open up the cfc to see what values can be manipulated, however I can also see the benefits of not having to type accessors for every variable. Anyway I decided that there must be a happy medium and this is what I've come up with...
myClass.cfc
<cfcomponent extends="dynamicAccessors">
<!--- define properties that can be manipulated via an accessor --->
<cfproperty name="FooNumber"
type="numeric"
hint="I can be manipulated via the getFooNumber and setFooNumber methods" />
<cfproperty name="FooString"
type="string"
hint="I can be manipulated via the getFooString and setFooString methods" />
<cfproperty name="FooUuid"
type="uuid"
hint="I can be manipulated via the getFoodUuid and setFoodUuid methods" />
<cfproperty name="FooAny"
hint="I can be manipulated via the getFooAny and setFooAny methods" />
<cfset variables.instance = {} />
<cffunction name="init">
<cfreturn this />
</cffunction>
</cfcomponent>
dynamicAccessors.cfc
<cfcomponent hint="I am an abstract class that provides dynamic getters and setters. I should not be called directly but inherited using the extends attribute of cfcomponent">
<cffunction name="onMissingMethod"
access="public"
hint="I act as a generic getter and setter for the class properties."
description="Usage getXYZ() or setXYZ(value) where XYZ is the property name"
returntype="any">
<cfargument name="missingMethodName" type="string" />
<cfargument name="missingMethodArguments" type="struct" />
<!--- the first time we get or set a property we need to build a structure of properties and types --->
<cfset var propertyName = "" />
<cfset getAccessors() />
<cfswitch expression="#Left( arguments.missingMethodName, 3 )#">
<cfcase value="get">
<cfset propertyName = ReplaceNoCase( arguments.missingMethodName, "get", "" ) />
<cfif StructKeyExists( variables.instance, propertyName )>
<cfreturn variables.instance[ propertyName ] />
<cfelse>
<cfthrow message="Unknown property '#propertyName#'. Cannot get value." />
</cfif>
</cfcase>
<cfcase value="set">
<cfset propertyName = ReplaceNoCase( arguments.missingMethodName, "set", "" ) />
<cfif StructKeyExists( variables.instance.properties, propertyName )>
<!--- check data type --->
<cfif IsValid( variables.instance.properties[ propertyName ], arguments.missingMethodArguments[1] ) >
<cfset variables.instance[ propertyName ] = arguments.missingMethodArguments[1] />
<cfelse>
<cfthrow message="Property '#propertyName#' expects the '#variables.instance.properties[ propertyName ]#' data type." />
</cfif>
<cfelse>
<cfthrow message="Unknown property '#propertyName#'. Cannot set value." />
</cfif>
</cfcase>
<cfdefaultcase>
<cfthrow message="unknown method" />
</cfdefaultcase>
</cfswitch>
</cffunction>
<cffunction name="getAccessors"
hint="I build a structure of public properties and data types."
access="public">
<cfset var properties = "" />
<cfset var index = "" />
<cfif StructKeyExists( variables.instance, "properties" )
AND IsStruct( variables.instance.properties )>
<cfreturn variables.instance.properties />
<cfelse>
<!--- properties struct has not been created yet --->
<cfset variables.instance.properties = {} />
<cfset properties = getMetaData(this).Properties />
<cfloop array="#properties#" index="index">
<cfif IsDefined("index.type")>
<cfset StructInsert( variables.instance.properties, index.name, index.type ) />
<cfelse>
<cfset StructInsert( variables.instance.properties, index.name, "any" ) />
</cfif>
</cfloop>
<cfreturn variables.instance.properties />
</cfif>
</cffunction>
</cfcomponent>
You can test out the code using this script.
<cfset Test = CreateObject( "component", "com.myClass").init( ) /> <cfdump var="#Test.getAccessors()#" label="Class Properties that can be set" /> <cfset Test.setFooString ( 'Loreum Ipsum' ) /> <cfset Test.setFooUuid ( CreateUUID() ) /> <cfset Test.setFooAny ( Test ) /> <cfoutput> getFooUUID = #Test.getFooUUID( )#<br /> getFooString = #Test.getFooString( )#<br /> </cfoutput> <!--- try to set an invalid property ---> <cfset Test.setFooBar ( 'ABC123' ) /> <!--- try to set an invalid data type ---> <cfset Test.setFooUuid ( 'ABC123' ) />
So what is going on? Well I've created an abstract class called dynamicAccessors.cfc which myClass.cfc inherits (so it has all the methods from dynamicAccessors). I use the cfproperty tag in myClass.cfc to define the instance variables that get be either set or retrieved with a get call. The onMissingMethod just checks to see if you are calling getXYZ or setXYZ. If you are trying to set the value then it will check that the instance variable exists and if it does makes sure that the correct data type is being passed.
If you are concerned about the performance hit that you might take by checking that instance variables exist and then validating the data type, then don't forget that you can always have a different dynamicAccessors.cfc on your live website which just get and sets variables without the checks. This is a similar concept to disabling the CFC type check in your CFIDE, meaning that you get the benefit of type checking in development and performance in your live environment.
I've set the access attribute of the getAccessors method to public so that you can call it for convenience. If you use the component explorer you will have a pretty good and API.
Example CFC documentation
aliaspooryorik.com.myClass
Component myClass
| hierarchy: | WEB-INF.cftags.component aliaspooryorik.com.dynamicAccessors aliaspooryorik.com.myClass |
| path: | D:\Inetpub\wwwroot\aliaspooryorik\com\myClass.cfc |
| properties: | FooAny, FooNumber, FooString, FooUuid |
| methods: | init |
| inherited methods: | getAccessors, onMissingMethod |
* - private method
| Property | Hint | Type | Req. | Implemented In | Default Value |
|---|---|---|---|---|---|
| FooAny | I can be manipulated via the getFooAny and setFooAny methods | myClass | - | ||
| FooNumber | I can be manipulated via the getFooNumber and setFooNumber methods | numeric | myClass | - | |
| FooString | I can be manipulated via the getFooString and setFooString methods | string | myClass | - | |
| FooUuid | I can be manipulated via the getFoodUuid and setFoodUuid methods | uuid | myClass | - |
| init |
|---|
| init () Output: enabled |
- Posted in:
- ColdFusion
- OOP
4 comments
Leave a comment
If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

The objects in the Members onTap plugin I wrote for the onTap framework are similarly documented (with cfproperty tags), although I don't include validation in those components. I just don't see a need. They work and they're documented. If you didn't read the documentation and you put a string in a numeric property, oh well... RTM.
Comment by ike – September 16, 2008
Comment by John Whish – September 17, 2008
Although in my own case I rarely use those docs merely because I developed all those components and for the things I use regularly I remember most of the methods. It does come in handy from time to time tho.
And I can definitely see where dumping the accessors would be visually easier to process than dumping the entire object. I may go back and tweak the framework components with something similar. :)
Comment by ike – September 17, 2008
Comment by John Whish – September 18, 2008