Skip to main content
Ben Nadel at the jQuery Conference 2011 (Cambridge, MA) with: Niall Kader
Ben Nadel at the jQuery Conference 2011 (Cambridge, MA) with: Niall Kader

Sanity Check For Reassigning Method Arguments In ColdFusion

By
Published in Comments (2)

At PAI Industries, we use the CFWheels framework for our ColdFusion applications. Wheels includes an ORM (Object Relational Mapper) in which the model components live in a dual state. In some cases, they can be instances of a specific entity; and, in other cases, they can be "service objects" that provide static-like methods. This duality requires that I use some new code patterns. And, one thing that I want to sanity check in ColdFusion is what happens when you override an arguments scope value.

Consider a method, compute(). In Wheels, if I'm calling this method as a member method, the inputs might be this-scope properties. But, if I'm calling this method as a static method, the inputs might be arguments-scope properties. As such, this method has to contain logic that works nicely in either case.

I've been accomplishing this by allowing all of the method arguments to be undefined. Then, internally to the method, I'm re-assigning each value using a variety of fall-backs in a compound Elvis operator assignment expression:

component {
public void function compute( string value ) {
value = ( arguments.value ?: this.value ?: "Default Value" );
writeDump([
variables: variables?.value,
this: this?.value,
arguments: arguments?.value,
local: local?.value
]);
}
}
view raw Entity.cfc hosted with ❤ by GitHub

As you can see, internally to the method, I'm re-assigning the value variable. The chained Elvis operators give precedence to the arguments scope (for "static" invocation), then the this scope (for "entity" invocation), and then finally a default value.

The thing I wanted to sanity check is where that value assignment ends up. I assumed that it simply overwrites the arguments.value; but, I wanted to put my mind at ease.

To test, I tried invoking this method using both use-cases:

<cfscript>
// Invoke as "member method" on instantiated model.
e = new Entity();
e.value = "member value";
e.compute();
writeOutput( "<hr>" );
// Invoke as "static method" on model definition.
e = new Entity();
e.compute( "hello" );
</cfscript>
view raw test.cfm hosted with ❤ by GitHub

When I run this ColdFusion code, I get the following results:

As member method:

  • variables: Empty:null
  • this: member value
  • arguments: member value
  • local: Empty:null

As static method:

  • variables: Empty:null
  • this: Empty:null
  • arguments: hello
  • local: Empty:null

In both cases, our chained Elvis operator assignment ends up overwriting the arguments scoped value, which is what I had hoped it was doing. So, this puts my mind at rest.

But, as I'm writing this, it occurs to me that I can probably simplify this by coding this chained fallback assignment into the argument default itself. In ColdFusion, each optional argument can be assigned a default in the method signature. And, whose to say that assignment can't, itself, be a chained Elvis operator assignment?

Consider this rewrite:

component {
// CAUTION: This breaks in ACF (but works in Lucee).
public void function compute(
string value = ( this.value ?: "Default value" )
) {
writeDump([
variables: variables?.value,
this: this?.value,
arguments: arguments?.value,
local: local?.value
]);
}
}
view raw Entity2.cfc hosted with ❤ by GitHub

In theory, this is doing the exact same thing; only, it's moving the assignment into the method signature instead of computing the assignment in the method body. And, this works in Lucee CFML (and in BoxLang 1.0); but, unfortunately, it breaks in Adobe ColdFusion 2023.

Not all is lost, though. Adobe ColdFusion does allow expressions, including method calls, in its default argument assignment. So, maybe we can move this to a private helper method that performs the null coalescing for us:

component {
public void function compute(
string value = coalesce( this?.value, "Default value" )
) {
writeDump([
variables: variables?.value,
this: this?.value,
arguments: arguments?.value,
local: local?.value
]);
}
private any function coalesce(
any value,
any valueFallback = ""
) {
return ( value ?: valueFallback );
}
}
view raw Entity3.cfc hosted with ❤ by GitHub

All we've done here is move the Elvis operator (?:) out of the method signature and into a method call. And, when we run this in either Lucee CFML or Adobe ColdFusion, we get the same output as our first experiment:

As member method:

  • variables: Empty:null
  • this: member value
  • arguments: member value
  • local: Empty:null

As static method:

  • variables: Empty:null
  • this: Empty:null
  • arguments: hello
  • local: Empty:null

Outstanding! I like the way this looks and feels.

Concrete Example

So far, this has been a rather abstract exploration of the ColdFusion mechanics. Let's end with a concrete example. Consider a method that computes a "full name" based on a first, middle, and last name. We can have a Person.cfc that looks like this - note that Person.cfc is extending Entity3.cfc which we just looked at above. This allows it to inherit that coalesce() method:

component extends = Entity3 {
public string function fullName(
string firstName = coalesce( this?.firstName ),
string middleName = coalesce( this?.middleName ),
string lastName = coalesce( this?.lastName )
) {
return trim( "#firstName# #middleName# #lastName#" )
.reReplace( "\s+", " ", "all" )
;
}
}
view raw Person.cfc hosted with ❤ by GitHub

This fullName() method is just concatenating all of the values and removing superfluous whitespace. It uses the inherited coalesce() method to populate the arguments scope with this-scoped values if they exist; or, fallback to the empty string (part of the coalesce() method signature).

Let's now test this as both a "member method" call and a "static method" call:

<cfscript>
p = new Person();
// Invoke as "member method" of instantiated model.
p.firstName = "Kit";
p.middleName = "Amanda"
p.lastName = "Lobo"
writeOutput( p.fullName() );
writeOutput( "<hr>" );
p = new Person();
// Invoke as "static method" of model definition.
writeOutput( p.fullName( firstName = "Kit", lastName = "Lobo" ) );
</cfscript>
view raw test2.cfm hosted with ❤ by GitHub

If we run this concrete example in both Adobe ColdFusion and Lucee CFML, we get the following output:

  • Kit Amanda Lobo
  • Kit Lobo

As you can see, when invoked as a "member method", it used the instance "entity properties"; and, when invoked as a "static method", it used the passed-in arguments.

This feels really good to me. I think it's readable and intuitive; and, works nicely with the dual nature of CFWheels models.

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

Reader Comments

16,020 Comments

After posting this, I did one more sanity check with the Adobe ColdFusion code. While this argument signature throws an error:

method( string value = ( this.value ?: "" ) )

... this argument signature works:

method( string value = echoValue( this.value ?: "" ) )

In this case, let's assume that echoValue() is a method that just returns whatever was passed to it. So, Adobe ColdFusion will allow the Elvis operator in the method call within a function signature; but, it just won't compile if the Elvis operator is directly part of the default assignment.

This just means that we could simplify the code.

But, it's important to remember that Adobe ColdFusion's Elvis operator (?:) implementation is buggy and incorrectly treats "empty string" as a Falsy value. Which means, we probably still want to go with the coalesce() approach since it means we can normalize the "null value" treatment internally.

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

Markdown formatting: Basic formatting is 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.
Cancel
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