Implicit constructor and CF9 style implicit accessors without ColdFusion 9

One of the broadly welcomed features of ColdFusion 9 has been the addition of implicit getters and setters to CFCs.  What this means is that you no longer have to hand code the repetitive boiler plate getXXX() and setXXX() methods for each property in your model objects. However, you don’t need ColdFusion 9, (or even ColdFusion for that matter) to enjoy the benefits of the new implicit accessors with the version 9 release.  Since ColdFusion 8, developers have had access to the OnMissingMethod() method which makes tasks like this very simple to implement on your own.  The OnMissingMethod is also available in current versions of OpenBlueDragon and Railo.

For my implementation of this, I use a standard BaseBean class in a number of my projects which is extended by all of my model class objects.  By leveraging the OnMissingMethod() in this BaseBean, I create these implicit accessors, in addition to an implicit constructor init() method as well.

There are multiple examples on the internet that show how you can use OnMissingMethod(), but for the most part, the examples that I have come across do not protect the class from being having infinite previously undefined properties added to it from the outside.  For instance, I could have a class named Person.cfc with define properties of “firstName” and “lastName”, but nothing would stop someone from doing Person.setThisIsNotAProperty(true) and that value would be dynamically added to the Person instance. While some might argue that the flexibility that this approach offers is a positive thing, I prefer to lock my classes down just a bit more.  For instance I like the fact that I can open up a model class and see exactly what properties it can contain with clearly defined <cfproperty/> tags.   From a maintainability standpoint the idea of “mystery” dynamic properties strike me as just wrong.

For my implicit constructor, I take an approach where I loop through any named arguments that were passed, and if the name matches a property that I have defined with <cfproperty/>, then it will be passed to a setter method.  Any arguments that are passed that are not defined as a property are discarded.

So let’s take a look at what this looks like.  First I will paste the entire BaseBean, and below we will break it apart to discuss what is going on.

<cfcomponent output="false">


	<cffunction name="init" access="public" output="false" returntype="BaseBean">
		<cfset var i = "" />
		<cfset initializePropertyList() />
		<cfloop list="#this.propertyList#" index="i">
			<cfif StructKeyExists(arguments,i)>
				<cfset set(i,arguments[i]) />
			</cfif>
		</cfloop>
		<cfreturn this />
	</cffunction>


	<cffunction name="initializePropertyList" access="private" output="false" returntype="void">
		<cfset var i = "" />
		<cfif NOT StructKeyExists(this,"propertyList")>
			<cfset this.propertyList = "" />
			<cfif ArrayLen(GetMetadata(this).properties)>
				<cfloop from="1" to="#ArrayLen(GetMetadata(this).properties)#" index="i">
					<cfset this.propertyList = ListAppend(this.propertyList,GetMetadata(this).properties[i].name)>
				</cfloop>
			</cfif>
		</cfif>
		<cfreturn />
	</cffunction>



	<cffunction name="onMissingMethod" access="public" output="false" returntype="any">
		<cfargument name="missingMethodName" type="string" required="true" />
		<cfargument name="missingMethodArguments" type="struct" required="true" />

		<cfset var property = "" />

		<cfif ReFindNoCase("^[gs](et)",arguments.missingMethodName)>
			<cfset property = ReReplaceNoCase(arguments.missingMethodName,"^[gs](et)","") />
			<cfset initializePropertyList() />

			<cfif ListFindNoCase(this.propertyList,property)>
				<cfif StructIsEmpty(arguments.missingMethodArguments)>
					<cfreturn get(property) />
				<cfelse>
					<cfset set(property,arguments.missingMethodArguments[1]) />
					<cfreturn this />
				</cfif>
			<cfelse>
				<cfthrow message="The class #GetMetadata(this).name# does not have a property named #property# so the implicit #arguments.missingMethodName#() method is not available." />
			</cfif>

		</cfif>
		<cfreturn />
	</cffunction>

	<cffunction name="get" access="public" output="false" returntype="any">
		<cfargument name="property" type="string" required="true" />
		<cfset var value = "" />

		<cfset value = variables[arguments.property] />

		<cfreturn value />
	</cffunction>

	<cffunction name="set" access="public" output="false" returntype="void">
		<cfargument name="property" type="string" required="true" />
		<cfargument name="value" type="any" required="true" />

		<cfset variables[arguments.property] = arguments.value />

		<cfreturn />
	</cffunction>

</cfcomponent>

Before we walk through the actual flow, I would like to point out the method initializePropertyList() that you see here:

<cffunction name="initializePropertyList" access="private" output="false" returntype="void">
	<cfset var i = "" />
	<cfif NOT StructKeyExists(this,"propertyList")>
		<cfset this.propertyList = "" />
		<cfif ArrayLen(GetMetadata(this).properties)>
			<cfloop from="1" to="#ArrayLen(GetMetadata(this).properties)#" index="i">
				<cfset this.propertyList = ListAppend(this.propertyList,GetMetadata(this).properties[i].name)>
			</cfloop>
		</cfif>
	</cfif>
	<cfreturn />
</cffunction>

By using the GetMetadata() function, we are able to introspect our instance and create a list of property names which we can reference in our other methods.  This is what enables us to enforce the rules that I described above in which we make sure that a property exists before setting it in the constructor or through an implicit accessor.  You will see in both the init() and OnMissingMethod() methods that we call this method to ensure the list of properties is available before testing against it.

With that established, let’s take a look at the init() constructor method:

<cffunction name="init" access="public" output="false" returntype="BaseBean">
	<cfset var i = "" />
	<cfset initializePropertyList() />
	<cfloop list="#this.propertyList#" index="i">
		<cfif StructKeyExists(arguments,i)>
			<cfset set(i,arguments[i]) />
		</cfif>
	</cfloop>
	<cfreturn this />
</cffunction>

After making sure that our propertyList has been initialized, we loop through that list and if there is a matching named argument, we call the set() method which sets that value into the variables scope.

So with the object initialized, let’s take a look at our accessors.  For our example, let’s say that we have a Person object and we are doing to set our firstName property like this: person.setFirstName(“Dave”).  Since that setter method doesn’t exist in our Person class, the OnMissingMethod() below will be invoked.

<cffunction name="onMissingMethod" access="public" output="false" returntype="any">
	<cfargument name="missingMethodName" type="string" required="true" />
	<cfargument name="missingMethodArguments" type="struct" required="true" />

	<cfset var property = "" />

	<cfif ReFindNoCase("^[gs](et)",arguments.missingMethodName)>
		<cfset property = ReReplaceNoCase(arguments.missingMethodName,"^[gs](et)","") />
		<cfset initializePropertyList() />

		<cfif ListFindNoCase(this.propertyList,property)>
			<cfif StructIsEmpty(arguments.missingMethodArguments)>
				<cfreturn get(property) />
			<cfelse>
				<cfset set(property,arguments.missingMethodArguments[1]) />
				<cfreturn this />
			</cfif>
		<cfelse>
			<cfthrow message="The class #GetMetadata(this).name# does not have a property named #property# so the implicit #arguments.missingMethodName#() method is not available." />
		</cfif>

	</cfif>
	<cfreturn />
</cffunction>

We are then doing a regular expression test to see if the missing method names starts with either “get” or “set”.  If so, we derive the name of the target property by stripping the “get” or “set” off of the string.  After making sure that our propertyList has been defined, we then look in that list to see if the target property actually exists in the instance.  If it does, the request is then routed on to either the getter or the setter depending on whether arguments were passed to it.  Otherwise an exception will be thrown to let the developer know that he or she has attempted to access an undefined property.

One thing you might notice is that when our conditional block routes the request to the setter, we return an instance of the class itself.  This allows us to do method chaining like this:  person.setFirstName(“Dave”).setLastName(“Shuck”).  it should be noted that this is not the approach that Adobe took with ColdFusion 9.

So let’s take a look at it in action.  Here is our Person.cfc class.

<cfcomponent output="false" extends="BaseBean">

	<cfproperty name="id" type="string" />
	<cfproperty name="firstName" type="string" />
	<cfproperty name="lastName" type="string" />

</cfcomponent>

As you can see in the <cfcomponent/> tag, we are extending our BaseBean which is all that we need to have a workable domain class object.

When we put this together and access it in our code, we can do this:

<cfset person = CreateObject("component","Person").init(id=7,firstName="Dave", lastName="Shuck") />

<cfdump var="#person#" />

Once we have an instance, we can modify those properties like so:

<cfset person.setId(8).setFirstName("Johnny").setLastName("Rotten") />

I have not actually tested this on Railo, but I can confirm that it works in OpenBlueDragon (including GAE version), and ColdFusion versions 8 and 9.

UDF: pcase() – Captilize first letter of each word in a string

This is a UDF that I keep on hand for converting strings to “proper case”, such as a person’s name.  For instance, in a database we might have a name stored like DAVID B. SHUCK, but when they log into our site and we welcome them, we don’t necessarily want to shout it at them!   Saying “Hello David B. Shuck…” would hopefully be a bit less abrasive.

Enter the user-defined function pcase().  Through the use of regular expressions we are doing a search for word patterns and capitalizing the first letter in each word.  Enjoy!


<cffunction name="pcase" access="public" output="false"  returntype="string">
    <cfargument name="string"  type="string" required="true" />
    <cfreturn  REReplaceNoCase(LCase(arguments.string),"(^[a-z*]|[ *][a-z*])","U1E","all")  />
</cffunction>