Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Validating And Documenting Complex Object Structures With CFParam In Lucee CFML 5.3.7.47

By Ben Nadel on
Tags: ColdFusion

At InVision, we generate our transactional emails by including a CFML template into a <CFSaveContent> buffer; and then, using that buffer as the body attribute of a CFMail tag. And, since the definition of that template feels somewhat "far away" from the context in which it is being consumed, I've gotten into the habit of parameterizing the template variables using CFParam tags. This way, it's intensely obvious which variables are being used in the template; and, if I accidentally forget to define a variable during refactoring, the template will blow-up when I go to test it. Part of what makes this easy to do is the fact that the CFParam tag can validate complex object structures. I don't often use it this way, so I thought it would be interesting to share in Lucee CFML 5.3.7.47.

At a high-level, the CFParam tag looks at a variable reference and can ensure that it exists and that it has a given type. So, for example:

<cfparam name="favoriteColor" type="string" default="ff3366" />

This looks at the favoriteColor variable, checks to make sure that it is of type String; and, if it doesn't exist, it defines and defaults the variable to be ff3366. If the variable didn't exist and I omitted the default attribute, ColdFusion would raise a runtime exception.

Now, in the vast majority of cases, the name attribute in the CFParam tag contains a simple variable reference. However, it can contain just about any kind of reference, including deep object and array references. Which means, we can get it to validate object properties and array indices.

To see what I mean, let's look at a ColdFusion custom tag that accepts a project attribute where project is a complex Struct that contains other Structs and Arrays. We can use the CFParam tag to validate and document much of that complexity:

<cfscript>

	// For safety and DOCUMENTATION (regarding which values are needed within this
	// template), validate the general structure of the attributes.
	param name="attributes.project" type="struct";
	param name="attributes.project.id" type="numeric";
	param name="attributes.project.name" type="string";
	param name="attributes.project.createdAt" type="date";
	param name="attributes.project.owner" type="struct";
	param name="attributes.project.owner.id" type="numeric";
	param name="attributes.project.owner.name" type="string";
	param name="attributes.project.screens" type="array";

	// We can't validate screen structures unless we have at least one screen.
	if ( attributes.project.screens.len() ) {

		param name="attributes.project.screens[ 1 ].id" type="numeric";
		param name="attributes.project.screens[ 1 ].name" type="string";
		param name="attributes.project.screens[ 1 ].clientFilename" type="string";

	}

	// .... consume attribute values ....
	dump( attributes.project );

</cfscript>

As you can see, we're using the CFParam tag here to validate both Structs and Arrays, including their various properties and indices.

Now, when I go to consume this CFML template as a module with attributes, the inputs are validated at runtime:

<cfscript>

	project = {
		id: 1,
		name: "Public Site Redesign",
		createdAt: createDate( 2021, 1, 15 ),
		owner: {
			id: 4,
			name: "Ben Nadel"
		},
		screens: [
			{
				id: 101,
				name: "Home Page",
				clientFilename: "home@2x.png"
			}
		]
	};

	module
		template = "./MyTag.cfm"
		project = project
	;

</cfscript>

Right now, everything works as expected. But, imagine that I was going to refactor this workflow, and I accidentally left-out the clientFilename property in the screens collection. If I were to run that code against the same CFML template, I'd get this runtime error:

As you can see, the CFParam tag correctly validated this deep-object/array reference:

attributes.project.screens[ 1 ].clientFilename

Of course, it only validated the first index. But, it's probably a good bet that all the array indices are defined in the same manner. And, at the end of the day, this is more about documentation and safety than it is about an exhaustive test.

I Don't Do This All The Time

To be clear, I don't do this all the time - using the CFParam tag to validate template variables. But, sometimes, it gives me a sense of comfort that there's a place that acts as the gate-keeper to the template body. In fact, I kind of think of the CFParam tag-block much like the Arguments collection in a Function: it defines and validates the "call signature".

As I mentioned above, I do find this particularly helpful in my HTML Email templates. Since these templates don't change often - and they're not right next to the code that consumes them - having the breadth of values parameterized at the top really helps when trying to remember what data is needed for the email.

Anyway, if nothing else, this may just demonstrate that the CFParam tag is perhaps more flexible than maybe you realized it was.



Reader Comments

I love cfparam and use them often. I think it's so helpful to know which variables are expected in the page below, especially since my team tends to write long, imperative code segments.

I was super excited to read the title. I was hoping you'd show me a syntax for CFParam like this...

param name="{ project: 
	{
		id: { type="numeric" },
		name: { type="string", default="Joe" }
	}";

But maybe that's not anymore readable :-/

Reply to this Comment

@Chris,

That actually reminds a bit of how I do things on the TypeScript side. One of the most awesome things about TypeScript is that it has structural types. So, the "type" concept isn't hard-coded to a given reference; instead, it's tied to the shape of an object.

So, in my Angular files, I'll often have something like this:

interface Project {
	id: number;
	name: string;
	owner: Owner;
	screens: Screen[];
}

interface Owner {
	id: number;
	name: string;
}

interface Screen {
	id: number;
	name: string;
	clientFilename: string;
}

// .... Then, I can use these LOCAL interface definitions in my signatures:

export public class MyStuff {
	
	public project: Project;
	
	constructor( project: Project ) {
		this.project = project;
	}

}

Anyway, not really here nor there, but what you said made me think of the way TypeScript uses interfaces.

Reply to this Comment

@Ben

Exactly! I haven't done any TypeScript in a while, but that's precisely what I'm talking about. Would be nice if CFML moved along in that direction as well. CFScript definitely has a JS feel to it in many cases :)

Reply to this Comment

@Chris,

It would be interesting. I definitely do love the fact that my ColdFusion and my JavaScript are converging on a particular look-and-feel. In fact, just the other day, I had written an algorithm in ColdFusion that one of my team-members ported over to JavaScript in a different repo; and, we basically just removed the argument and function attributes and the rest of it just worked :D It was very cool.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.