Ask a Jedi: Structure as an Argument
Joel asks:
This is just a quick "best practices" question about CFC's, specifically regarding arguments.Let's say that I am creating a book entry in my database. My form has 3 simple fields--title, author, number of pages. Easy enough.
My question is what a "best practice" for dealing with this scenario would be. Although I am still pretty new to CFC's, I have learned that bundling information as structs to be used in the CFC can be extremely useful, especially when the contents of said struct are dynamic.
But what of known values? Obviously, when invoking the component I could bundle title, author and number of pages into a struct and look for that in the CFC. However, in doing this, it would seem that the various specifications for individual arguments (i.e., 'required', 'type', etc.) would be lost as these could only be applied to the expected struct. Also, it seems that the whole idea of encapsulation is compromised to an extent as the specificity of the arguments being expected is reduced and made more generic (e.g., the struct).
On the other hand, when dealing with ridiculously long forms full of known values, loading them all into a struct would potentially save some coding on the CFC side.
So just in case folks don't quite get what Joel is saying - he is talking about two options to pass data to a CFC. You can either use a set of arguments or have one argument that is a struct. Consider the following two examples:
<cffunction name="test" access="public" returntype="string" output="false">
<cfargument name="name" required="true" type="string" hint="The name of the user.">
<cfargument name="age" required="true" type="numeric" hint="The age of the user.">
<cfreturn "Hello, #arguments.name#, I'm happy you are #arguments.age# years old.">
</cffunction>
<cfset res = test("Ray",34)>
<cfoutput>#res#</cfoutput>
This is how most people set up their methods and here is the alternate way (struct as arguments) that Joel mentioned:
<cffunction name="test2" access="public" returntype="string" output="false">
<cfargument name="data" type="struct" required="true" hint="Struct of arguments.">
<cfreturn "Hello, #arguments.data.name#, I'm happy you are #arguments.data.age# years old.">
</cffunction>
<cfset s = {name="Ray", age=34}>
<cfset res = test2(s)>
<cfoutput>#res#</cfoutput>
So as you can see - Joel is right. In the second example, you no longer have any real clues as to what the UDF is using, outside of one structure. You also lose the validation that helpfully double checked name and age. So in my simple example, I definitely think Joel is right - using "real" arguments does make sense. But as he points out - what about a method that takes many more attributes? It would be simpler, for example, to be able to pass the FORM scope as a structure and not use a lot of...
form.name,form.age,form.foo,form.goo,form.rustillreading
Luckily there are a few things we can to make this easier.
First off - don't forget that you can use CFINVOKE and CFINVOKEARGUMENT syntax. While this results in more typing (potentially), it does make things readable. And you can use these tags without typing until the cows come home. Consider this example:
<cfinvoke component="#application.foo#" method="doIt" returnVariable="result">
<cfloop item="key" collection="#form#">
<cfinvokeargument name="#key#" value="#form[key]#">
</cfloop>
</cfinvoke>
All I've done here is treat the form scope as a structure where each name reflects an argument in my CFC method. You can, if you want, add conditional logic to hide certain form fields, like submit button names, but if your CFC isn't using them, they will simply be ignored. And don't forget the use of attributeCollection:
<cinvoke component="#application.foo#" method="doIt" returnVariable="result" attributeCollection="#form#">
This example is equivalent to the previous form - just more inline and less typing, but again if you did want to skip certain form fields you would not be able to.
So all in all - I agree with Joel here and hopefully the examples above will show ways to mitigate cases where you have to deal with a large number of attributes.
Comments
So in your second example you could have declared your arguments in the UDF and passed:
<cfset res = test2(argumentCollection=s)>
<cfargument name="theStruct" type="struct" />
<cfparam name="arguments.theStruct.numericValue" type="numeric" />
then if you pass in a struct with a key called numericValue that holds a string, it will fail validation as well.
Not ideal but could be used with older code already taking a struct to ensure the validation.
And it's definitely convenient when your function can expect a lot of optional parameters.
But I've found that the benefit of cfargument acting as a vehicle to communicate to other programmers what you're expecting, and as an automatic way to enforce things (otherwise you have to do a bunch of validation yourself), outweighs the convenience factor when you have a bunch of developers on the project.
It's a few extra keystrokes (wizards abound), but when you properly model, you get a strongly typed API that can handle changes to what a user is (what if a third property gets added?) without regards to implementation. A book called "The OO Thought Process" has a good passage that says something like "once you go beyond a single simple parameter, see if the parameters represent a type of object: if so, model that object and pass it."


Thanks for response on this. I have been debating with myself over which method to use. However, given the greater flexibility and validation possible with the more explicit argument methodology, I will continue to use that.
Thanks again!