Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Darren Walker
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Darren Walker@bayornet )

Using Function LocalMode Modern To More-Safely Render ColdFusion Templates In Lucee 5.3.2.77

By Ben Nadel on
Tags: ColdFusion

Historically, when you set an unscoped, non-var'ed variable inside a ColdFusion Function, the variable is applied to the variables scope (except for when that assignment is inside a CFThread tag). In Lucee CFML, however, we can use the Function directive - localmode - to change this behavior. When you set the localmode directive to "modern", unscoped variable assignments will be applied to the local scope, not to the variables scope. One place in which this could be really helpful in Lucee CFML 5.3.2.77 is in the dynamic rendering of ColdFusion templates.

The other day, I was tasked with generating a number of static HTML files based on ColdFusion templates. To do this, I was CFInclude'ing a ColdFusion template within the body of a CFSaveContent tag. I was then writing the output of said content buffer to a flat-file with an .htm extension.

ASIDE: This is how most Framework rendering engines work - they execute your View templates inside of a CFSaveContent buffer; and then, wrap your View template inside of your Layout template using the same methodology; and then, write the resultant output to the response stream.

Consider this ColdFusion template:

<cfscript>

	// Assert the expected values.
	// --
	// NOTE: Using param in this case as an excuse to declare more variables.
	param
		name = "categorization"
		type = "string"
		default = "My Friends" // Will assign this value if not provided.
	;
	param
		name = "friends"
		type = "array"
	;

	// Just creating a mapping as an excuse to declare another variable :D 
	myFriends = friends.map(
		( friend ) => {

			return( friend.name );

		}
	);

</cfscript>
<cfoutput>
	
	<h2>
		#encodeForHtml( categorization )#
	</h2>

	<ul>
		<!---
			In "classic" ColdFusion mode, the declaration of the iteration item,
			"friend", causes "friend" to be stored into the "variables" scope. Depending
			on how this template is being used, improper understanding of this behavior
			would likely cause a "race-condition" at some point.
		--->
		<cfloop index="friend" array="#myFriends#">
			<li>
				#encodeForHtml( friend )#
			</li>
		</cfloop>
	</ul>

</cfoutput>

As you can see, this ColdFusion template expects certain contextual variables, like friends to exist. It then renders the list of friends directly to the page output. However, as it does this, notice that it is also declaring a few additional variables: myFriends, friend (the loop item), and, potentially, categorization.

As stated before, these additional variables would normally be stored in the variables scope; which, depending on the context, could cause serious race-conditions within an application. Using Lucee CFML 5.3.2.77, however, we can more safely evaluate this ColdFusion template within a Function using localmode to help eliminate these race-conditions.

To see what I mean, consider the following execution context. In this code, we're going to render the above CFML template inside of a CFSaveContent tag that is, itself, inside a Function with localmode set to modern:

<cfscript>

	/**
	* I execute the "template.cfm" ColdFusion template with the given values and return
	* the generated output.
	* 
	* NOTE: By using localmode="modern", we are safely capturing any unscoped variable
	* assignment in the function's local scope - not the Variables scope.
	*/
	public string function renderTemplate() localmode = "modern" {

		savecontent variable = "local.generatedTemplateOutput" {

			// NOTE: The ARGUMENTS scope of this function context will implicitly provide
			// passed-in values to the following template evaluation.
			include "./template.cfm";

		}

		return( generatedTemplateOutput );

	}

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

	renderedContent = renderTemplate(
		friends = [
			{ id: 1, name: "Sarah" },
			{ id: 2, name: "Jo" },
			{ id: 3, name: "Kim" }
		]
	);

	dump( label = "VARIABLES Scope", var = variables );

</cfscript>

As you can see, I am providing the expected template variables via the arguments scope of the renderTemplate() Function. This function then evaluates the ColdFusion template and returns the buffered output. And, when we run the above Lucee CFML code, we get the following page output:

Variables declared in the CFML template are safely encapsulated using localmode='modern' in Lucee CFML 5.3.2.77.

As you can see, when we CFDump the variables scope, none of the new variables that we declared inside of the CFML template are polluting the variables scope. That's because our use of localmode="modern" safely captures those variable declarations inside the local scope of the renderTemplate() function.

NOTE: The CFML template can still implicitly reference the variables scope. And, it can still write to the variables scope if it explicitly prefixes variable assignments with the variables scope.

Let's now contrast this behavior with the traditional - or "classic" - behavior of ColdFusion. This time, we're going to re-run the test with the localmode directive set to classic:

<cfscript>

	/**
	* I execute the "template.cfm" ColdFusion template with the given values and return
	* the generated output.
	* 
	* CAUTION: By using localmode="classic" - which is the same as omitting localmode
	* altogether - we are allowing unscoped variable assignments to persist to the
	* Variables scope.
	*/
	public string function renderTemplate() localmode = "classic" {

		savecontent variable = "local.generatedTemplateOutput" {

			// NOTE: The ARGUMENTS scope of this function context will implicitly provide
			// passed-in values to the following template evaluation.
			include "./template.cfm";

		}

		return( generatedTemplateOutput );

	}

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

	renderedContent = renderTemplate(
		friends = [
			{ id: 1, name: "Sarah" },
			{ id: 2, name: "Jo" },
			{ id: 3, name: "Kim" }
		]
	);

	dump( label = "VARIABLES Scope", var = variables );

</cfscript>

As you can see, this time, our renderTemplate() Function is using localmode="classic", which is the same as omitting the localmode directive altogether. And, when we run this Lucee CFML, we get the following output:

Variables declared in the CFML template bleed into the Variables scope using localmode='classic' in Lucee CFML 5.3.2.77.

As you can see, this time, the newly-declared variables within our CFML template are "bleeding" into the variables scope of the calling context. In a multi-threaded application (ie, every user-facing application), this behavior can (and has) caused serious - and mysterious - race-conditions. This is almost never the behavior that the developer wants.

ASIDE: To be clear, if our CFML template scoped the declared variables using local., then things would be OK. The problem is, when looking at a CFML template, it is not obvious that this is needed.

Race-conditions can be the cause of many problems in a multi-threaded application. And, unfortunately, the classic behavior of unscoped variables in a ColdFusion CFML template can easily create race-conditions if you are not thinking carefully about how your CFML template is being evaluated. Thankfully, in Lucee CFML 5.3.2.77, we can use a localmode of modern to help prevent these "unexpected" race-conditions. This makes the dynamic rendering of CFML templates much safer.



Reader Comments

@All,

In this post, I mentioned dynamically rendering CFML templates during static site generation. As such, I wanted to quickly follow-up with a post that looks specifically at generating static sites using Lucee / ColdFusion and localmode:

https://www.bennadel.com/blog/3679-using-function-localmode-to-render-templates-during-static-site-generation-in-lucee-5-3-2-77.htm

The example is really simple (in scope). But, those were the requirements that I had.

Reply to this Comment

@All,

Here's a quick follow-up on some "unexpected" behavior you can get with nested Functions / Closures when you set localmode="modern" at the application level (ie, creating an application-wide default behavior):

https://www.bennadel.com/blog/3680-unexpected-variable-assignment-using-function-localmode-modern-with-nested-closures-in-lucee-5-3-2-77.htm

Personally, I don't think I would set localmode at the Application level. I would leave it in "classic" mode; and then, switch to "modern" at lower-levels where it adds more value.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
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.