A couple of weeks ago, I started experimenting with ColdFusion 9's new objectSave() and objectLoad() functions for serializing and deserializing objects. In that post, I was able to leverage getFunctionCalledName() - another new ColdFusion 9 function - for creating proxy components. Creating an object that composes another object is nothing new; but, ColdFusion 9 makes it so easy that I wanted to break the concept out into its own blog post.
That secret sauce behind creating a proxy component in ColdFusion 9 is the new function, getFunctionCalledName(). This function will return the name of the parent method that is currently being invoked. Since ColdFusion component methods are variables just like anything else, they can be passed around, renamed, and reassigned; using this behavior in conjunction with getFunctionCalledName() allows us to create some powerful proxy functionality with very little code.
Before we demonstrate the proxy behavior, let's define the component that we are going to proxy:
<cfcomponent output="false" hint="I provide two functions for testing proxies."> <cffunction name="getValue" access="public" returntype="any" output="false" hint="I return a value."> <cfreturn "This is a value." /> </cffunction> <cffunction name="getVoid" access="public" returntype="void" output="false" hint="I don't return anything of value."> <cfreturn /> </cffunction> <cffunction name="getComponent" access="public" returntype="any" output="false" hint="I return a reference to the current object."> <cfreturn this /> </cffunction> </cfcomponent>
This target component demonstrates what I consider to be the three primary use cases for methods:
- They return a value.
- They return nothing (ie. void).
- They return a reference to the parent component, typically for method chaining.
The first two use cases represent nothing more than some complexity in referencing defined values. The third use case, on the other hand, presents a serious problem. If our job is to proxy a given component, we can never let our proxy methods return a reference to the target component. Doing so would break the proxy's encapsulation.
Unfortunately, ColdFusion has no sense of object reference equality; that is, you can't determine if two variables reference the same object. Using constructs like arrayFind() and arrayContains(), we can determine if two components mimic each other; but, we can't actually tell if they are the "same" object. As such, in order to deal with the third use case, we do have to alter our target component slightly, providing it with a unique identifier.
Luckily, even with these issues, creating a proxy object in ColdFusion 9 is still relatively simple. And, now that we've seen our target component, let's take a look at our proxy component. Typically, we would pass our target component into the proxy component during initialization; however, to keep this demo simple, I am creating the target component directly within the proxy component:
<cfcomponent output="false" hint="I demonstrate how to proxy a component."> <!--- To make this demo as easy as possible, we are going to create our target component directly from within our proxy component. Typically, you'd pass the taret component in during initialization. ---> <cfset variables.target = new Test() /> <!--- When proxying a given component, we need a way to identify the target component such that if a method invocation returns the target component, we can return the proxy component in its stead. ---> <cfset this.proxyUUID = createUUID() /> <!--- Copy the UUID flag into the target component. ---> <cfset variables.target.proxyUUID = this.proxyUUID /> <!--- Create our three proxy methods. Notice that both of our proxy methods just point to the one defined local method. That method will pass on the invocation message based on the name of the invoked method. ---> <cfset this.getValue = variables.proxyMethod /> <cfset this.getVoid = variables.proxyMethod /> <cfset this.getComponent = variables.proxyMethod /> <!--- ------------------------------------------------- ---> <!--- ------------------------------------------------- ---> <cffunction name="proxyMethod" access="private" returntype="any" output="false" hint="I proxy the invoked method on the target component."> <!--- Pass the invocation message onto the target component and get the result. ---> <cfinvoke returnvariable="local.proxyResult" component="#variables.target#" method="#getFunctionCalledName()#" argumentcollection="#arguments#" /> <!--- Now that we have the result, we have a few special cases to test for; we might have a void return, which will leave our result undefined. We might also have the target object returned (such as is done with method chaining). ---> <cfif isNull( local.proxyResult )> <!--- The proxy invocation returned void. Therefore, we can't return the result without error. As such, return void explicitly. ---> <cfreturn /> </cfif> <!--- Check to see if the result is the actual target component (as methods will often do to allow for chaining). ---> <cfif ( !isNull( local.proxyResult.proxyUUID ) && (this.proxyUUID eq local.proxyResult.proxyUUID) )> <!--- We don't want to allow access to the proxied target! Return the proxy instead to allow for method chaining. ---> <cfreturn this /> </cfif> <!--- If we made it this far then we have no special use-cases for return values; simply echo the value that was returned by the target. ---> <cfreturn local.proxyResult /> </cffunction> </cfcomponent>
At the top, you can see that I am creating a UUID (universally unique identifier) in order to be able to uniquely identify our target component. But, after that, setting up our proxy methods is nothing more than copying the one local method - proxyMethod() - into three different slots, one copy per target method. We can use the same method instance to proxy all of the target methods thanks to ColdFusion 9's getFunctionCalledName(). By examining the method invocation name at run-time, it allows us to create a single point of entry to any number of target methods.
To see this component proxy in action, take a look at this test code:
<!--- Create our proxy component. ---> <cfset proxy = new Proxy() /> <!--- Execute each method to make sure they don't error. ---> <cfset proxy.getValue() /> <cfset proxy.getVoid() /> <cfset proxy.getComponent() /> <!--- Output some values. ---> <cfoutput> <!--- Get a real value. ---> Value: #proxy.getValue()#<br /> <!--- Get a void. ---> Void: #isNull( proxy.getVoid() )#<br /> <!--- Get the component. ---> Component: #getMetaData( proxy.getComponent() ).name# </cfoutput>
Here, we are testing to see if the proxy methods work without error. Then, we are examining the return values. When we run the above code, we get the following output:
Value: This is a value.
As you can see, the simple values both worked. But more importantly, the third use case returned the Proxy object rather than the target object - encapsulation of the proxy object was maintained.
In this demo, I'm not doing anything more than creating a simple proxy around the target component. This demo, in and of itself, does not represent a valid use case. What you'd really want to do is add logic to your proxy component to augment the functionality of the target object creating features like logging, persistence, or even event triggering. But, the point was really just to show you how simple it is to create proxy objects with ColdFusion 9's new getFunctionCalledName() function.
Want to use code from this post? Check out the license.