Monday, July 23, 2007

Typed arrays in cfcs

This is interesting. I was trying out the new shorthand methods of creating arrays (and structures) and I came across this strange effect.

I haven't found any docs on this yet, but it seemed like a natural extension of the array shorthand. You can now do something like this:

<cfargument name="aggregate" type="dm3.com.color[]" required="false" default="#arrayNew(1)#">

The idea is to have cf throw an error if you submit anything other than an array of dm3.com.color objects. At first, this seems to work. Great! I thought. What I later found out was that the type checking appears to only occur on the first item in the array. You can put anything you want in any position other than one and the type checking will let it go through without error.

For me, the way to prevent issues (however unlikely they actually may be) is to restrict adding and removing collaborating objects with add() and remove() methods that can enforce the object type on each element. These work pretty much the same as your simple getters and setters, but instead of dealing with a simple value, they deal with one or more objects.

--- UPDATE: Sample code to illustrate the issue
The two objects are just for illustration. You should be able to use any objects you've created that use arrays to store aggregate collections. The scribble.cfm page at the bottom is used to exercise them.


Color.cfc

*============================================================================
* Template Name: Color
* ---------------------------------------------------------------------------
* Created by: CFC Blaster
* Creation date: 07/23/2007
* Description: Color Bean
*============================================================================

<cfcomponent name="Color.cfc" output="false">
<!--- Define --->
<cfproperty name="Background" required="false" default="" type="string">

<!--- Initialize --->
<cffunction name="init" access="public" output="false" returntype="dm3.com.Color">
<cfargument name="Background" required="false" default="" type="string" />

<cfscript>
// setup
variables.instance = structNew();

// assign arguments passed in to properties
setBackground( arguments.Background );

return this;
</cfscript>
</cffunction>

<!--- Accessors --->


<cffunction name="getBackground" access="public" output="false" returntype="string">
<cfreturn variables.instance.Background />

</cffunction>
<cffunction name="setBackground" access="public" output="false" returntype="void">
<cfargument name="Background" required="true" type="string" />
<cfscript>
testBackground(arguments.Background);
doBackground(arguments.Background);
</cfscript>
</cffunction>
<cffunction name="testBackground" access="public" output="false" returntype="void">
<cfargument name="Background" required="true" type="string" />
<!--- TODO: Write business rules --->
</cffunction>
<cffunction name="doBackground" access="public" output="false" returntype="void">
<cfargument name="Background" required="true" type="string" />
<cfset variables.instance.Background = arguments.Background />
</cffunction>

<!--- Print --->
<cffunction name="print" access="public" output="true" returntype="void">
<cfdump var="#variables.instance#" />

</cffunction>

<!--- Equals --->
<cffunction name="isEqual" access="public" output="false" returntype="boolean">
<cfargument name="obj" required="true" type="dm3.com.Color" />
<cfset var private = structNew() />
<cfwddx action="cfml2wddx" input="#print()#" output="private.obj" />
<cfwddx action="cfml2wddx" input="#arguments.obj.print()#" output="private.obj2" />
<cfif private.obj eq private.obj2>
<cfreturn true />
<cfelse>
<cfreturn false />
</cfif>
</cffunction>

<!--- Run --->
<cffunction name="run" access="public" output="false" returntype="dm3.com.Color" >
<cfset var obj = createObject("component","dm3.com.Color") />
<!--- TODO: fill in reasonable values --->
<cfset obj.init(
Background = ""
) />
<cfreturn obj />
</cffunction>

</cfcomponent>


temp.cfc

<cfcomponent output="false">

<cfproperty name="TestString" type="string" default="Hi There" required="false" hint="Here is where you would write your documentation.">
<cfproperty name="TestId" type="numeric" default="99" hint="z" />
<cfproperty name="AnArray" type="array" default="0">
<cfproperty name="YsNo" type="boolean" default="false">
<cfproperty name="colorArray" type="dm3.com.color[]">

<cffunction name="init" access="public" output="false" returntype="dm3.com.temp">
<cfargument name="aggregate" type="dm3.com.color[]" required="false" default="#arrayNew(1)#">
<cfset variables.instance = structNew() />
<cfreturn this />
</cffunction>

<cffunction name="setColorArray" access="public" output="false" returntype="void">
<cfargument name="val" required="true" type="dm3.com.color[]" />
<cfset variables.instance.colorArray = arguments.val />
</cffunction>
</cfcomponent>


scribble.cfm

<cfset colorArray = arrayNew(1)>

<cfloop index="i" from="1" to="3">
<cfset arrayAppend(colorArray,createObject("component","dm3.com.color").init())>
</cfloop>
<cfset arrayAppend(colorArray,"Hi There")>

<cfset temp = createObject("component", "dm3.com.temp").init(colorArray)>
<cfdump var="#colorArray#">
<cfdump var="#temp#">


Move the "Hi There" line above the loop to see the type checking fail. Move it below the loop to see it succeed.

2 comments:

  1. Mike -

    I just saw this a few weeks ago while at a training class. However, I could never get it to work here with our setup.

    Do you have an example that works each and every time? Where did you find the docs for this?

    Thanks!
    -Pat

    ReplyDelete
  2. Hi Pat,
    I didn't find anything in the docs that describes the typed arrays. The only thing I've found that led me to try this was the new array and structure shorthand that I found on adobe labs.

    I have a small example I can post up that you can see working. I'll edit this entry to put the code in it.

    ReplyDelete