Wednesday 25 February 2015

CFML: trying to understand accessors

G'day:
I've decided I'm the wrong person to be writing a book on CFML, given I clearly don't bloody understand even the fundamentals of it, in places. And this is after using it daily for over a dozen years.

I thought I understood CFCs in CFML inside out. No.

I'm writing up the chapter on OO in CFML (Gifford has covered this "Object-Oriented Programming in ColdFusion", but I think I can articulate the same in much less space), and was knocking together the code samples when I found myself completely flummoxed by what I was seeing in my sample code.

I'm trying to write a minimal example of a CFC that leverages the "accessors" attribute to implement synthesised accessors for me.

// Person.cfc
component accessors=true {

    property firstName;
    property lastName;

    function init(firstName, lastName){
        variables.firstName = arguments.firstName;
        variables.lastName = arguments.lastName;
    }

}


// person.cfm
person = new Person("Abigail", "Bowen");

writeDump(person);

This code is a snapshot of a moving target, I'd started with an empty component {}, and was adding more functionality as my narrative continued. At this point I was going to demonstrated how CFML's synthesised accessors work, having added the relevant properties, as well as switching accessors on.

I had not yet got around to actually using said accessors. All the code above should do is set firstName and lastName into the variables scope. You know... like how the code says.

But I ran it on Lucee and got this:


Note that the synthesised accessor methods have been created (good), but also my variables-scope assignments have also found their way into the property values. This is fine, but I wasn't expecting this, I would only expect that if I had been calling setFirstName() and setLastName().

I figured Lucee was being "helpful" and going against the CFML standard (as set by ColdFusion's behaviour), but no. Here's the same code on ColdFusion 11:


Um... OK, so this is clearly intentional. I tried to find some docs to explain precisely what the accessors flag is supposed to do, but there are none (that I can find).

I wondered whether it's simply a matter of when one has a property defined, then if there's a same-named variables-scoped variable, then CFML will treat that as the property value. I updated my CFC to get rid of the accessors flag:

// Person.cfc
component /*accessors=true*/ {

    property firstName;
    property lastName;

    function init(firstName, lastName){
        variables.firstName = arguments.firstName;
        variables.lastName = arguments.lastName;
    }

}

And re-ran the code:


So no synthesised accessors (as expected), but also now the properties aren't even being displayed (with the variables-scoped values or not).

I don't really understand this conflation of "properties" and the accessors setting? Surely the accessors setting should only impact whether those accessor methods get created?

If I was only seeing this on one of the platforms, I'd probably put it down to a vagary of how writeDump() interprets the property definitions. But it really does seem like there's some difference in behaviour I'm not quite getting.

Can anyone explain this? Are there any docs which explain it? If not... um... why not?

I'm gonna post this on Stack Overflow too ("Unexpected behaviour with accessors=true on a component"), in case any of my non-readers know what's going on.

Update


Whether or not the properties have a getter method seems to affect this behaviour at least on ColdFusion. eg:

// Person.cfc
component {

    property firstName;
    property lastName;

    function init(firstName, lastName){
        variables.firstName = arguments.firstName;
        variables.lastName = arguments.lastName;
    }

    function getFirstName(){
        writeLog("#getFunctionCalledName()#() called");
        return variables.firstName;
    }

    function getLastName(){
        writeLog("#getFunctionCalledName()#() called");
        return variables.lastName;
    }

}

Running this with a similar person.cfm file, I get this on ColdFusion (10 or 11):


Note that in the CFC I don't have the accessors flag set, it's merely the inclusion of the getters that changes this behaviour. Checking the logs, the getters are indeed called, too:

"Information","http-bio-8511-exec-7","02/26/15","14:10:16",,"getfirstName() called"
"Information","http-bio-8511-exec-7","02/26/15","14:10:16",,"getlastName() called"

This kinda explains ColdFusion's behaviour, and suggests it's purely a quirk of writeDump().

This does not change any behaviour on Lucee, so something else is going on there.

--
Adam