Changing Function Argument Defaults At Runtime In Lucee CFML 5.3.7.47
In the vast, vast majority of cases, when I author a Function in ColdFusion that has default argument values, those values are static. Meaning, the argument falls-back to a hard-coded token like 0
or ""
or false
. The other day, however, when I was exploring the Password4j password hashing library in Lucee CFML, I created a ColdFusion component that had dynamic Function Argument defaults that could be changed at runtime. To be honest, it felt a little strange at the time. As such, I wanted to isolate the use-case and think-it-through a bit in Lucee CFML 5.3.7.47.
To see what I mean, I've created a completely trite example in which I have a ColdFusion component that will generate a random value between two integers. It's basically just a wrapper around the built-in function, randRange()
; except that is comes with predefined min / max values. Note that I can change the min / max defaults at any time using the .withDefaults()
function:
component
output = false
hint = "I provide random integers between a given set of values."
{
/**
* I initialize the randomizer with the given min/max range.
*/
public void function init(
numeric minValue = 1,
numeric maxValue = 100
) {
variables.defaults = {
minValue: minValue,
maxValue: maxValue
};
}
// ---
// PUBLIC MEHTODS.
// ---
/**
* I get the next random value within the given min/max range. If no range is
* provided, uses default range.
*/
public numeric function next(
numeric minValue = defaults.minValue,
numeric maxValue = defaults.maxValue
) {
return( randRange( minValue, maxValue ) );
}
/**
* I update the default range with the given min/max values.
*/
public any function withDefaults(
required numeric minValue,
required numeric maxValue
) {
defaults.minValue = minValue;
defaults.maxValue = maxValue;
return( this );
}
}
As you can see, this ColdFusion component has a private struct, defaults
. This private struct is then used to define the fall-back values for our optional arguments in the .next()
method:
numeric minValue = defaults.minValue
numeric maxValue = defaults.maxValue
The .withDefaults()
method then allows me to change that private struct which will, in turn, change the default behavior of the .next()
method.
Now, when I instantiate this ColdFusion component, I can generate random values in different ranges:
<cfscript>
rando = new RandomValue( 1, 10 );
echoLine( "With range, 1 - 10" );
echoLine( rando.next() );
echoLine( rando.next() );
echoLine( rando.next() );
echoLine( rando.next() );
echoLine();
rando.withDefaults( 50, 100 );
echoLine( "With range, 50 - 100" );
echoLine( rando.next() );
echoLine( rando.next() );
echoLine( rando.next() );
echoLine( rando.next() );
echoLine();
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I write the given arguments to a single line, followed by a break-tag.
*/
public void function echoLine() {
echo( arrayToList( arguments, " " ) & "<br />" );
}
</cfscript>
First we use ranges 1-10, then 50-100. And, when we run this ColdFusion page, we get the following output:
With range, 1 - 10
3
5
6
4With range, 50 - 100
57
61
68
96
As you can see, despite the fact that we never call the .next()
method with any explicit arguments, we're able to change the min / max range by changing the defaults function fall-backs at runtime.
So, is this a good idea?
I think it depends on how you use it. Because your ColdFusion component is likely cached as a Single-instance (within the Application
scope or a dependency-injection container), it isn't going to be "thread safe". Meaning, if one request changes the function defaults, it's going to affect other requests, potentially causing confusing and inconsistent behavior.
In short, I wouldn't use this type of approach to change function defaults in the middle of the application's life-cycle.
That said, I think this does make sense as part of a "builder" pattern where the changing of the function argument defaults happen at instantiation time and then remain "static" for the rest of the application's life-cycle. For example, if we use the .withDefaults()
as part of the init()
process and then never touched it again:
<cfscript>
rando = new RandomValue()
.withDefaults( 1, 10 )
;
</cfscript>
Obviously, in this completely trite example there's no need to do this. But, imagine that our ColdFusion component has a complex configuration; and that breaking its configuration up into various method calls makes it easier to understand / optimize for different use-cases. For example, if we revisit our Password4j example from the other day, we could have instantiation code that looks like this:
<cfscript>
password = new Password()
.withArgon2Defaults(
iterations = 10,
outputLength = 32
)
.withBCryptDefaults(
costFactor = 11
)
.withSCryptDefaults(
resources = 3,
parallelisation = 2,
outputLength = 32
)
;
</cfscript>
In this case, the fluent API allows us to change the default values for our Function arguments at runtime; but, we're really only ever doing this at instantiation time. Then, for the rest of the application's life-cycle, we just use whatever defaults were configured.
From a technical standpoint, there's nothing very interesting about changing Function argument defaults at runtime. But, it was an approach that I don't remember doing before in ColdFusion. As such, I thought it might merit a moment of reflection.
Want to use code from this post? Check out the license.
Reader Comments