Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Dave Ferguson and Simon Free and Tim Cunningham and Jason Dean
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Dave Ferguson Simon Free Tim Cunningham Jason Dean

Making A Case For Var Declarations In ColdFusion Templates

By
Published in Comments (5)

Last week, I opened a feature request in the Adobe bug tracker to allow for var declarations in CFML templates. After I opened this, I shared it within the Working Code discord; and, all those who responded did so in opposition to the idea. As such, I wanted to take a moment to more clearly articulate my case for allowing var declarations in, essentially, any ColdFusion context.

The Current Situation

Today, the var keyword is explicitly defined in the Adobe ColdFusion documentation as pertaining to Function local variables. When you declare a variable inside a function block using the var keyword, ColdFusion restricts access to said variable to within the body of the function (and to any ColdFusion closures that are defined within that function body).

As it stands now, the var keyword can only be used within a function. And, any attempt to use the var keyword outside of a function will lead to the ColdFusion error:

The local variable environment cannot be declared outside of a function.

All variables defined with the var keyword must be declared inside a function.

For example, if I try to execute this CFML template (my-view.cfm), I will get the aforementioned error:

<cfscript>

	var environments = [ "development", "staging", "production" ];

	for ( var environment in environments ) {

		writeOutput( environment );

	}

</cfscript>

Of course, if I copy-paste this code into a function body and then execute the function, it will work just fine. Which feels like a completely unnecessary differentiation in behavior; and, has always diverged from the behavior of JavaScript—the world's most popular programming language.

If I try to take the above CFML template (my-view.cfm) and CFInclude it into a function body, which for all intents and purposes moves the var keyword into a function context, I still get an error:

<cfscript>

	writeOutput( renderView( "my-view.cfm" ) );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I capture and return the rendering of the given CFML template.
	*/
	private string function renderView( required string viewTemplate ) {

		savecontent variable = "local.viewOutput" {
			// Render the given view into the buffer.
			include "./#viewTemplate#";
		}

		return viewOutput;

	}

</cfscript>

Attempting to execute this CFML template and invoke the renderView() function still throws the same var error. The fact that this technique throws an error in Adobe ColdFusion is surprising. I believe it means that the var analysis is a compile time issue not a runtime issue. But, I'm just guessing.

A Dangerous Syntax Constraint

In the above case, the error is problematic. Because removing the var declarations fundamentally changes the meaning of the code. If I update the view to remove the var keywords:

<cfscript>

	// CAUTION: No more `var` keywords.

	environments = [ "development", "staging", "production" ];

	for ( environment in environments ) {

		writeOutput( environment );

	}

</cfscript>

And then update my renderView() example to output the variables scope after executing the function:

<cfscript>

	writeOutput( renderView( "my-view-2.cfm" ) );

	// Output the page context private scope.
	writeDump(
		var = variables,
		label = "VARIABLES Scope"
	);

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I capture and return the rendering of the given CFML template.
	*/
	private string function renderView( required string viewTemplate ) {

		savecontent variable = "local.viewOutput" {
			// Render the given view into the buffer.
			include "./#viewTemplate#";
		}

		return viewOutput;

	}

</cfscript>

We get the following page output:

Screenshot of CFDump showing that both the 'environments' and 'environment' variables have leaked out into the 'variables' scope.

As you can see, both the environments and the environment variables have leaked out of the function body and into the page's variables scope.

This is the technique that many ColdFusion frameworks use to render views (see FW/1's internalView() function). Of course, views are normally tag-base and interleave CFML and HTML tags. As such, you might be used to seeing view code that looks more like this:

<cfoutput>

	<!--- "environments" provided as request-context (RC) variable. --->
	<cfloop array="#rc.environments#" index="environment">
		<p>
			#encodeForHtml( environment )#
		</p>
	</cfloop>

</cfoutput>

This ColdFusion code just committed the same offense. The environment variable declared as the CFLoop index has leaked out of the parent function and into the variables scope. To prevent this, you'd have to explicitly add a local. scoping to the index attribute:

<cfloop array="..." index="local.environment">

In a ColdFusion framework like FW/1, this is less of an issue since the ColdFusion framework component is newly instantiated on every request. As such, variable leakage is, at the very least, locked-down to the current request.

But, if this were part of a different rendering technique, such as when rendering CFMail content or performing static site generation, those variable leaks would almost certainly be persisted across requests which could have devastating consequences in a multi-tenant environment (ie, one user seeing another user's data due to persisted variable leakage).

Aside: Lucee CFML provides the localmode construct for overriding this behavior, which makes it ideal for dynamic view rendering. This would be a great feature for Adobe ColdFusion to add as well.

Not only is restricting var usage to the function body a seemingly unnecessary constraint, it's a dangerous constraint.

The Wrong Solution

There will almost certainly be people who read this article and think to themselves, "That's why every variable reference should be explicitly scoped." This is the wrong solution. Having to explicitly scope every variable makes life harder for web developers. Languages need to be evolving to create a better DX (Developer Experience)—not forcing developers to rely on arcane techniques.

An Evolving Notion of var (My Feature Request)

Right now, people are very hung up on the notion that var is specifically for function bodies. But, if you think just a little more abstractly, what var is doing is merely scoping a given variable to the closest "variable container".

In ColdFusion, there are really only two variable containers: the function local scope and the page scope. The function local scope is relatively self-explanatory. And, you can think of the "page scope" as being akin to the variables scope. When you make a request to a ColdFusion template, that template receives the top-level page scope (which may or may not be the Application.cfc page scope, depending on your use of the onRequest() life-cycle method). Then, each ColdFusion component and custom tag invocation receives its own unique page scope (and its own, isolated variables scope).

Aside: The CFThread tag is kind of its own variables container; but, under the hood it's being executed as a function and acts as a function-like container.

If you keep the idea of these two types of variable containers in mind, you can begin to think of the var keyword as locking variable access to the closest variable container.

As such, when the var keyword is used inside a function body, it will lock access of that variable to the function local scope. And, when the var keyword is used inside a CFML template / custom tag / component pseudo constructor, etc., it will lock access of that variable to the page scope.

This creates a more consistent syntax (the var keyword can be used anywhere); and, it creates a more consistent sense of variable scoping (the variable is always scoped to the closest "variable container"). This also leads to a simpler mental model where code can be copy-pasted from one context into another without having to worry about whether or not the var keyword is being used.

This also does nothing to hurt backwards compatibility of the CFML language since it's basically codifying what unscoped template-level variable declarations are already doing.

Variable Leakage Is Still a Problem

To be clear, this does nothing to combat variable leakage (ie, forgetting to var-scope a function local variable). Only something like localmode would do that. This is an unrelated issue (albeit an important one).

Want to use code from this post? Check out the license.

Reader Comments

40 Comments

I've actually gotten into the strong habit of using local. for just about everything. I rarely use var anymore.

Developing primarily on top of Coldbox, all the views run (I think as includes) within a function somewhere.

Also, local. works in straight .cfm files, too, which I often use when I'm just testing an idea or trying to solve some weird problem, outside the framework in my "scratch" site.

238 Comments

I have always felt this way, though I could have never articulated it quite so well. I often write my code in a page's context, then refactor into functions and find it frustrating to have to worry about adding (or removing) the var statement depending on context.

15,848 Comments

@Will,

I didn't know that local works in CFM templates. I'm guessing that what it does is actually create variables.local.value behind the scenes. That's been a quirky behavior of ColdFusion forever—that you can just assign to an arbitrarily deep struct-path and ColdFusion will on-the-fly create the struct for you.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel