One of the many new enhancements to the ColdFusion 10 language has been the ability to chain methods in your ColdFusion components.
What's chaining?
If you're a jQuery user, chances are you've seen (and probably used) method chaining before. It's a simple (but powerful) way to group your method calls together to perform actions on the same object / element:
$('#myElement') .css('backgroundColor','yellow') .attr('title', 'Chaining methods... heck yeah!');
In Action
Let's take a look at how you can use method chaining in ColdFusion 10.
We'll start off with a simple Person bean / entity object. In this object we want to store the first name, last name and email address for our individual.
<cfcomponent output="false" accessors="true"> <cfproperty name="firstname" type="string" default="" /> <cfproperty name="lastname" type="string" default="" /> <cfproperty name="email" type="string" default="" /> <cffunction name="init"> <cfreturn this /> </cffunction> </cfcomponent>
There are a number of ways that you can instantiate a component, and depending on whether you have developed the object to be 'read-only' (you can only set the properties from the init / constructor method within the object, not from any calling pages or templates) or not, you would have to set each property one at a time, like so:
<cfset objPerson = new person() /> <cfset objPerson.setFirstname('Matt') /> <cfset objPerson.setLastname('Gifford') /> <cfset objPerson.setEmail('[email protected]') />
ColdFusion 10 method chaining now means you can set any of your object's properties in one go, like so:
<cfset objPerson = new person() .setFirstname('Matt') .setLastname('Gifford') .setEmail('[email protected]') />
The Rules
There are some rules in place which define how far you can go when chaining your methods:
- The accessors attribute is set to true
- If the setter functions for the property are defined
- Until a method is found
Let's have a look at these to try and better understand them.
Accessors attribute set to true
You can see from the previous code sample showing the person entity that the opening cfcomponent tag has the accessors attribute set to true. Without this, ColdFusion will not generate the implicit accessors and mutators (or getters and setters) for each property.
Let's see what happens when you have it set to false or not defined and have chosen to write your own getter and setter methods. Here is an amended version of our person object:
<cfcomponent output="false"> <cfproperty name="firstname" type="string" default="" /> <cfproperty name="lastname" type="string" default="" /> <cfproperty name="email" type="string" default="" /> <cffunction name="init"> <cfreturn this /> </cffunction> <cffunction name="setFirstname"> <cfargument name="firstname" /> <cfset variables.firstname = arguments.firstname /> </cffunction> <cffunction name="setLastname"> <cfargument name="lastname" /> <cfset variables.lastname = arguments.lastname /> </cffunction> <cffunction name="setEmail"> <cfargument name="email" /> <cfset variables.email = arguments.email /> </cffunction> </cfcomponent>
Running this using the chaining code above will not end well:
ColdFusion will assume that the second method, setLastName(), is contained within the setFirstName() method call, in the same way a public function would be available within an instantiated component. Without the magic of implicit accessors, chaining will not work.
Setter functions are not defined
In this example, the setter attribute for the email property has been set to false. Instead of using the implicit mutator we're going to create our own setEmail() method in the CFC just to see what happens if we try to call it as part of the chaining.
Here's our revised CFC:
<cfcomponent output="false" accessors="true"> <cfproperty name="firstname" type="string" default="" /> <cfproperty name="lastname" type="string" default="" /> <cfproperty name="email" type="string" default="" setter="false" /> <cffunction name="init"> <cfreturn this /> </cffunction> <cffunction name="setEmail"> <cfargument name="email" /> <cfset variables.email = arguments.email /> </cffunction> </cfcomponent>
And here we are setting the property values once more:
<cfset objPerson = new person() .setFirstname('Matt') .setLastname('Gifford') .setEmail('[email protected]') />
If we run the above code against the revised person object, the mutators will successfully set the values for the firstname and lastname properties but will error when trying to set the email, even though we defined our own setEmail method:
We could get around this by calling our defined setEmail method after the initial chaining, like so:
<cfset objPerson = new person() .setFirstname('Matt') .setLastname('Gifford') /> <cfset objPerson.setEmail('[email protected]') />
but this then bypasses the awesomeness provided by chaining your methods and the implicit getters and setters. Why create more work for yourself if you don't have to? Of course, there may be instances when you want or need to run setter functions this way... it's always good to know you have options.
Until a method is found
Method chaining will work for any valid (for valid, see points one and two above) properties within your component and will happily set the values for as many as you wish UNTIL it reaches a method call, at which point it will stop setting values.
Here is another revised version of the person object, which now contains a getFullName() method to return a concatenated string containing the firstname and lastname property values:
<cfcomponent output="false" accessors="true"> <cfproperty name="firstname" type="string" default="" /> <cfproperty name="lastname" type="string" default="" /> <cfproperty name="email" type="string" default="" /> <cffunction name="init"> <cfreturn this /> </cffunction> <cffunction name="getFullName"> <cfreturn getfirstname() & ' ' & getLastname() /> </cffunction> </cfcomponent>
Running the following code will work as all property values will be set before returning the value from the getFullName() method:
<cfset objPerson = new person() .setFirstname('Matt') .setLastname('Gifford') .setEmail('[email protected]') .getFullName() /> <cfdump var="#objPerson#">
Running the following will NOT work as ColdFusion will not be able to find the setEmail() method within the object:
<cfset objPerson = new person() .setFirstname('Matt') .setLastname('Gifford') .getFullName() .setEmail('[email protected]') /> <cfdump var="#objPerson#">
Don't Go Nuts
Method chaining is awesome and can really help streamline your code, but if you have a multitude / butt-load of properties within the object, try not to go crazy and set all of the values in one long chain. It IS possible to do that, but it will start to make the code unreadable and potentially unmanageable.
Remember that code is a language and languages need to be read.
The End
So there you go... method chaining in ColdFusion 10 is an amazing addition to the language and one that can really help you minimise code, increase productivity and generally make your day that little bit happier.