Tuesday, January 13, 2009

ColdFusion mixins within singleton objects

Sometimes it's nice to be able to use ColdFusion mixins to include dynamic data at runtime inside CFCs. In the following example, I'm using a CFC to generate a database-driven form, with each form potentially containing multiple templates.


<cfcomponent output="false">

<cffunction name="displayForm" access="public" output="false" returntype="string">
<cfargument name="formID" required="true" type="numeric" />

<cfset var local = StructNew() />

<!--- get the templates associated with the form --->
<cfquery name="local.getTemplates" datasource="#application.datasource#">
select file_path
from form_templates
where form_id = <cfqueryparam value="#arguments.formID#" cfsqltype="cf_sql_integer" />
order by sort_order asc
</cfquery>

<!--- capture the generated html --->
<cfsavecontent variable="local.html">

<!--- loop over the templates and display (include) each template --->
<cfoutput query="local.getTemplates">

<cfinclude template="#local.getTemplates.file_path#" />

</cfoutput>

</cfsavecontent>

<cfreturn local.html />

</cffunction>

</cfcomponent>


Hopefully that's straightforward enough. This works just fine at first, until you store the component in the application scope as a singleton and a developer doesn't properly scope a variable inside one of the included templates, thereby creating a public variable that could bleed into other requests.

The solution is pretty simple: rather than using the cfinclude tag to include the mixin, use a helper function that mimics the functionality but traps the variables.

Doing this requires creating an include helper and an include proxy. The helper is created as a singleton and its purpose is to create instances of the proxy, which is in charge of including the original file.


<cfcomponent displayname="Include Helper" output="false">

<cffunction name="template" access="public" output="true" returntype="any">
<cfargument name="template" required="true" type="string" />

<cfreturn createObject("component","includeProxy").init(arguments.template) />

</cffunction>

</cfcomponent>



<cfcomponent displayname="Include Proxy" output="false">

<cffunction name="init" access="public" output="true" returntype="void">
<cfargument name="template" required="true" type="string" />

<cfinclude template="#arguments.template#" />

</cffunction>

</cfcomponent>


With the include components creating, I can now replace <cfinclude template="#local.getTemplates.file_path#" /> with #helpers.include.template(local.getTemplates.file_path)# and the template variables are now properly hidden from my component.

Monday, January 12, 2009

A few things about onMissingMethod()

ColdFusion 8 includes the onMissingMethod event handler, a very simple yet powerful feature that can be used in a variety of ways, from implicit getters and setters to method interceptors and more.

I've stumbled upon 2 things worth mentioning that didn't seem apparent at first.

1. onMissingMethod only works for public method calls. If you try to access a function remotely, such as http://mysite.com/components/foo.cfc?method=bar, and the bar() method doesn't exist, onMissingMethod won't catch it.

2. If you're using memcached to cache your objects, onMissingMethod won't fire at all. This seems to make sense, since ColdFusion appears to fire onMissingMethod at runtime.

Sunday, January 11, 2009

AOP, Caching, and memcached

Last week I started playing around with Aspect-Oriented Programming (AOP) in ColdSpring and was pretty amazed at how useful it could be. To be able to alter the functionality of your application without touching your core application code is an extremely powerful concept, but I wasn't sure where to begin.

The example on the ColdSpring site shows how AOP can be applied to handle logging across an application, but that doesn't seem all that useful (or sexy) to me. I wanted a better scenario.

After reviewing some code in a project I've been working on, I noticed a prime example of where I could use AOP: caching.

There was code inside multiple function calls that would perform caching based on the component, method, and arguments being passed into the method. The code was using non-deterministic caching, meaning if the data didn't exist in the cache, the system would fetch the data then put it into the cache. The caching code looks similar to this:


<cfset key = application.cache.getKey("reportDAO","getReportByID",arguments) />

<cfif application.cache.hasData(key)>

<cfset report = application.cache.getData(key) />

<cfelse>

... build the report ...

<cfset application.cache.setData(key,report) />

</cfif>


The code worked fine, but I had to add those same 4 lines of code to each method I wanted to cache. Plus, it led to a low level of cohesion; the method needed to know how to build a report as well as how to store and retrieve itself in memory.

With AOP, I could now configure ColdSpring to intercept each method call I wanted to cache without having to flood my code with caching logic.

As a nice side bonus, the non-deterministic key/value caching strategy I was using turned out to be similar to how memcached, a high-performance, distributed memory object caching system, caches its data. Knowing this, I was able to modify the caching advice component I created and plugin memcached in a matter of minutes.

I'm currently working on an XML based caching component that will handle bean/method caching in the request scope, session scope, and memcached. I'll post more details when I have something to show.

Sunday, January 4, 2009

Label Tags (or Lack Thereof)

One of my biggest pet peeves is when sites don't use label tags, especially around radio buttons. How much does this bother me? I actually emailed my bank's support email address about adding label tags to their "Make a Payment" page. So far, nothing has changed.



Why force a user to click the small circle when they could click the text instead? It's such a quick and simple thing to do and yet can make a page so much easier to use. There's really no reason to not use label tags other than developer laziness.

Tuesday, December 30, 2008

Model-Glue-esque Bean Factory, Redux

After using my latest attempt at a Model-Glue inspired bean factory a little longer, I ran into a major problem: circular dependencies.

In my application (and most others), services will need to be able to talk to each other. For example, my application has a productService and a categoryService, and each service will need an instance of the other injected into it.

In ColdSpring, you can get around circular dependencies by using properties (setter injection) rather than constructor arguments. However, I want to use the "beans" scope to inject all my dependencies rather than having to create the necessary setProductService() and setCategoryService() methods.

I solved the problem by re-working my DynamicBeanFactory. I updated the init() method to construct all the beans when the bean factory is created, then inject the "beans", "helpers" and "includes" as necessary. Doing this also allowed me to leave the getBean() method alone, which is kinda nice.

Here's the updated DynamicBeanFactory:


<cfcomponent output="false" extends="coldspring.beanutils.DynamicXMLBeanFactory">

<!------>

<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="pathToColdspringXML" required="true" type="string" />
<cfargument name="params" required="false" default="" />

<cfset var local = StructNew() />
<cfset var i = "" />

<!--- dynamic properties that get passed into the bean factory --->
<cfif not isStruct(arguments.params)>
<cfset arguments.params = StructNew() />
</cfif>

<!--- init the bean factory --->
<cfset super.init() />

<!--- load the beans --->
<cfset loadBeansFromDynamicXmlFile(arguments.pathToColdspringXML,arguments.params) />

<!--- construct each bean, which puts it in the singleton cache --->
<cfloop collection="#variables.beanDefs#" item="i">
<cfset constructBean(i) />
</cfloop>

<!--- if a "helpers" bean is defined, inject it into all the helpers and beans --->
<cfif StructKeyExists(variables.singletonCache,"helpers")>

<cfset local.helpers = variables.singletonCache.helpers.getHelpers(this) />

<cfset local.injectedHelpers = StructNew() />

<cfloop collection="#local.helpers#" item="i">

<cfset local.bean = local.helpers[i] />

<!--- inject a function to set variables into the "variables" scope --->
<cfset local.bean.__injectVariable = variables.injectVariable />

<!--- inject the "helpers" scope --->
<cfset local.bean.__injectVariable("helpers",local.injectedHelpers) />

<!--- remove the temporary inject function --->
<cfset StructDelete(local.bean,"__injectVariable") />

<cfset local.injectedHelpers[i] = local.bean />

</cfloop>

</cfif>

<cfloop collection="#variables.singletonCache#" item="i">

<!--- set the bean from the cache --->
<cfset local.bean = variables.singletonCache[i] />

<!--- inject a function to set variables into the "variables" scope --->
<cfset local.bean.__injectVariable = variables.injectVariable />

<!--- inject the "helpers" scope if it's defined --->
<cfif StructKeyExists(variables.singletonCache,"helpers")>
<cfset local.bean.__injectVariable("helpers",local.injectedHelpers) />
</cfif>

<!--- get the bean's meta data to see if there are any "beans" or "includes" defined --->
<cfset local.metaData = getMetaData(local.bean) />

<!--- create a container for all the beans that will be injected --->
<cfset local.beans = StructNew() />

<!--- check to see if the bean needs to inject any beans --->
<cfif StructKeyExists(local.metaData,"beans")>

<!--- include each bean in the order specified --->
<cfloop list="#local.metaData.beans#" index="local.injectedBeanName">

<cfset local.injectedBeanName = trim(local.injectedBeanName) />

<!--- insert each bean into the local beans struct, which will be injected after the loop --->
<cfset local.beans[local.injectedBeanName] = variables.singletonCache[local.injectedBeanName] />

</cfloop>

</cfif>

<!--- inject the "beans" scope --->
<cfset local.bean.__injectVariable("beans",local.beans) />

<!--- check for any included beans --->
<cfif StructKeyExists(local.metaData,"includes")>

<!--- include each bean in the order specified --->
<cfloop list="#local.metaData.includes#" index="local.includedBeanName">

<cfset local.includedBeanName = trim(local.includedBeanName) />

<!--- get the bean from cache --->
<cfset local.includedBean = variables.singletonCache[local.includedBeanName] />

<!--- get all the public functions in the included bean and copy them to the main bean --->
<!--- even though private functions aren't copied, references to them from within copied functions will still work --->
<cfloop collection="#local.includedBean#" item="local.includedBeanVariableName">

<!--- only copy the function if it isn't defined yet --->
<cfif not StructKeyExists(local.bean,local.includedBeanVariableName)>

<!--- put the function into the object (the "this" scope) --->
<cfset local.bean[local.includedBeanVariableName] = local.includedBean[local.includedBeanVariableName] />

<!--- set the function into the variables scope, otherwise would need to reference the function as this.fn() --->
<cfset local.bean.__injectVariable(local.includedBeanVariableName,local.includedBean[local.includedBeanVariableName]) />

</cfif>

</cfloop>

</cfloop>

</cfif>

<!--- remove the temporary inject function --->
<cfset StructDelete(local.bean,"__injectVariable") />

</cfloop>

<cfreturn this />

</cffunction>

<!------>

<cffunction name="injectVariable" access="public" output="false" returntype="void" hint="I get injected into a bean in order to set variables">
<cfargument name="key" required="true" />
<cfargument name="value" required="true" />

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

<cfreturn />

</cffunction>

<!------>

</cfcomponent>