Sunday 4 September 2022

CFML: invokeImplicitAccessor on CFCs

G'day:

I noticed this yesterday when I was writing my "Kotlin: the next morning learning Kotlin stuff" article. I was looking into how Kotlin handles accessor methods on properties, and remembered CFML had a similar bit of functionality, and quickly revisited it by way of comparison. Whilst looking into it, I noticed that pretty much no-one has mentioned it too much, and it's not even mentioned in the appropriate place in Adobe's on CFML reference for cfcomponent. They only mention it on CFC Implicit notation, somewhat amusingly using code they have clearly lifted from this blog (their example code mentions my son by name). The article they lifted it from is about the original variant of the functionality: "invokeImplicitAccessor is quite cool. Although has some odd quirks". CFDocs mentions it in passing in their cfcomponent docs, but don't really explain it. Lucee doesn't yet support this functionality (see LDEV-171).

Back in ColdFusion 10, a feature was added to Application.cfc that one could specify this.invokeImplicitAccessor=true. This is poorly documented - no mention of it on the Application.cfc page in the docs for example - but I run through its functionalityin that "invokeImplicitAccessor is quite cool. Although has some odd quirks" I mentioned earlier. I'll not repeat it here. Setting this at application-level was always a poor implementation, as how code behaves should be handled in the code itself, not in some application settings. This implementation renders the functionality pretty much unusable in third-party modules (eg: libs one might install and use from ForgeBox), as it relies on application settings that might not exist. I guess the docs for the lib could say "you need to switch this on in Application.cfc", but even if that works, it's a shit way of going about things. This was raised with Adobe at the time the feature was going into the language, but the leadership of the ColdFusion Team in those days had a habit of not listening to their developer community, because they thought they knew better. Sigh.

Anyhow, they kinda fixed the situation in CF2016; they shifted the setting to be CFC-specific. So in a given CFC one can specify this in the component's properties. Here's a quick example:

//Person.cfc
component invokeImplicitAccessor=true {

    property string firstName;
    property string middleName;
    property string lastName;

    public void function setFirstName(required string value) {
        firstName = value
    }

    public void function setMiddleName(required string value) {
        middleName = value
    }

    public void function setLastName(required string value) {
        lastName = value
    }

    public string function getFullName() {
        return "#firstName# #middleName# #lastName#"
    }
}
// test.cfm
person = new Person()
person.firstName = "Annie"
person.middleName = "Jean"
person.lastName = "Easley"

writeOutput(person.fullName)

Result:

Annie Jean Easley

What's going on? Well it's a syntactical sugar / boilerplate reduction thing. It's just a way of exposing private properties publically without having to pepper getThing / setOtherThing etc throughout one's code. It's like a more thoughtful approach to the fairly wooden synthesised accessors that were already in CFML with the accessors CFC setting, and getter and setter options on the property definition. Those are a bit of an anti-pattern.

Just to demonstrate there's no cheating going on, we can try to access one of the write-only properties directly:

try {
    writeOutput(person.firstName)
} catch(any e) {
    writeOutput(e.message)
}

We have not defined a getter on firstName, so it's not accessible:

Element firstName is undefined in PERSON.

The implementation Adobe have given us here is still far from ideal. If they'd thought things through more, they'd've paid attention to how other languages handle properties' accessor methods, which would be more like this in a CFML-idiomatic sort of way:

property string variables.firstName {
    function get() {
        return firstName
    }
    function set(required string value){
        firstName = value
    }
}

There's no reason they could not have done that. Even if they needed to use the long-form / tag-oriented version of property:

property name="firstName" access="private" type="string" {
    function get() {
        return firstName
    }
    function set(required string value){
        firstName = value
    }
}

I mean that syntactical construct - a statement with a body block - is already supported in CFML. Just not the implementation of this specific functionality.

Anyway. We are where we are, and I figured someone ought to document this functionality. Done.

Righto.

--
Adam