Sunday, 29 September 2013

invokeImplicitAccessor is quite cool. Although has some odd quirks

G'day:
In the course of writing up some of my experiences taking the "Ruby Bits Part 2" course, I was messing around with some equivalent CFML code, and came across some interesting idiosyncrasies. What would CFML be without idiosyncrasies, eh?

As of ColdFusion 10 (and some version of Railo prior to 4.1.1.004), one can specify this in Application.cfc:

// Application.cfc
component {
    this.invokeImplicitAccessor = true; 
}

This means that when one directly accesses a property of an object, the property's accessors (setter, getter) are called implicitly. For example here we have a very basic Name.cfc:

// Name.cfc
component accessors=true {

    property first;
    property last;

}

We have set accessors=true on, so that ColdFusion will create the setters and getters for us, so now we can have this calling code:

// name.cfm
name = new Name();

name.first="Zachary";
name.last="Cameron Lynch";

writeOutput("First: #name.first#<br>");
writeOutput("Last: #name.last#<br>");

writeDump(var=name);

And the result is:

First: Zachary
Last: Cameron Lynch
component shared.git.blogExamples.invokeImplicitAccessor.Name
PROPERTIES
firstZachary
lastCameron Lynch
METHODS

There we go: the properties are being set correctly, even though we're not calling the setters: they're being called implicitly. Cool.

Hang on... let's roll back a bit. This code will also work, without the invokeImplicitAccessor setting:

// BasicName.cfc
component {}

And called thus:
// basicName.cfm
name = new BasicName();

name.first="Zachary";
name.last="Cameron Lynch";

writeOutput("First: #name.first#<br>");
writeOutput("Last: #name.last#<br>");

writeDump(var=name);

Yielding:

First: Zachary
Last: Cameron Lynch
component shared.git.blogExamples.invokeImplicitAccessor.BasicName
FIRSTZachary
LASTCameron Lynch

That definitely "works". All it's doing is leveraging the fact that one can stick variables into a CFC object if one likes, and they end up in the this scope. Note how those aren't properties? One might think "so what?", but if we look at a more complex example, it starts to make sense. Remember previously we had let CF take care of the setters and getters for us? Our code really made it seem like no getters or setters were being called, because there was no evidence to that effect. How about if we add some getter/setter methods and watch them being called?

// EnhancedName.cfc
component {

    function setFirst(first){
        writeOutput("#getFunctionCalledName()# called<br>");
        variables.first = arguments.first;
    }

    function setLast(last){
        writeOutput("#getFunctionCalledName()# called<br>");
        variables.last = arguments.last;
    }

    function getFirst(){
        writeOutput("#getFunctionCalledName()# called<br>");
        return variables.first;        
    }

    function getLast(){
        writeOutput("#getFunctionCalledName()# called<br>");
        return variables.last;        
    }
}

// enhancedName.cfm
name = new EnhancedName();

name.first="Zachary";
name.last="Cameron Lynch";

writeOutput("First: #name.first#<br>");
writeOutput("Last: #name.last#<br>");

writeDump(var=name);

SETFIRST called
SETLAST called
GETFIRST called
First: Zachary
GETLAST called
Last: Cameron Lynch
component shared.git.blogExamples.invokeImplicitAccessor.EnhancedName
METHODS

Here we can see clearly that the setters and getters are actually all being called, and indeed the variables are now simply going into the this scope of the object: they're safely tucked away in the variables scope, only accessible by their accessors. This. Is. Cool.

Aside #1

In ColdFusion 9 Adobe added something people mistakenly called "implicit accessors". These were just the accessor methods created automatically by ColdFusion when the accessors=true setting was put on the component definition. If we go back and have a closer look at that dump from the Name.cfc / name.cfm example, we see that the methods are there, inside the CFC:

component shared.git.blogExamples.invokeImplicitAccessor.Name
PROPERTIES
firstZachary
lastCameron Lynch
METHODS
GETFIRST
function GETFIRST
Arguments:none
ReturnType:any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
GETLAST
SETFIRST
function SETFIRST
Arguments:
NameRequiredTypeDefault
firstRequiredany
ReturnType:shared.git.blogExamples.invokeImplicitAccessor.Name
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
SETLAST

And we can demonstrate how they are called, by combining our two coding approaches above:

// nameWithAccessors.cfm
son = new Name();

son.first="Zachary";
son.last="Cameron Lynch";

writeOutput("First: #son.first#<br>");
writeOutput("Last: #son.last#<br>");

writeDump(var=son);

writeOutput("<hr>");

dad = new Name();

dad.setFirst("Adam");
dad.setLast("Cameron");

writeOutput("First: #dad.getFirst()#<br>");
writeOutput("Last: #dad.getLast()#<br>");

writeDump(var=dad);

I have run this code with invokeImplicitAccessor=false, so the accessors will not be called implicitly, they need to be called explicitly. And see the difference:

First: Zachary
Last: Cameron Lynch
component shared.git.blogExamples.invokeImplicitAccessor.Name
LASTCameron Lynch
FIRSTZachary
PROPERTIES
first[empty string]
last[empty string]
METHODS

First: Adam
Last: Cameron
component shared.git.blogExamples.invokeImplicitAccessor.Name
PROPERTIES
firstAdam
lastCameron
METHODS

We can still put our values in and out, but notice that in the first example, the accessors are not actually being called (as evidenced by the values are just being injected into the this scope). It's only when we explicitly call them, that they are... erm... called, and correctly set the properties (that's all ColdFusion's auto-generated accessors do: put the values in and out of the properties).

So the difference is:
  • setting accessors=true on the component creates synthesised accessors (apparently that's the technical name for this sort of thing), but one still needs to call them explicitly.
  • setting invokeImplicitAccessor=true in Application.cfc means that if you have accessors for the property (well: you don't even need the properties specified, in reality) then they will be called implicitly if you access the property.
Make sense?

Aside #2

Why the hell is this invokeImplicitAccessor setting on Application.cfc? Why is it not on the component? This makes more sense to me:

// EnhancedName.cfc
component invokeImplicitAccessor=true {

    // etc
}

I can see that it's handy to set at application level too, but as it relates to a component, it makes sense to be able to set it at component level too. Initially I thought it might be because some special compilation needs to take place for it to work in the calling code, but the calling code files don't know about Application.cfc when they're compiled, so it's not that. If anyone knows why, I'd be dead keen to know? I'll never find out from anyone at Adobe, but I bet if I ask Micha @ Railo he'll explain...

Bugs

There are always bugs. Consider a new component, Person. It's mostly the same as Name, except it has a date of birth, too. And the DOB is typed as a date:

// Person.cfc
component accessors=true {

    property firstName;
    property lastName;
    property date dob;
}

And I call it with this code:

son = new Person();

son.firstName="Zachary";
son.lastName="Cameron Lynch";

param URL.DOB=now();
son.dob = URL.DOB;

writeOutput("firstName: #son.firstName#<br>");
writeOutput("lastName: #son.lastName#<br>");
try {
    writeOutput("DOB: #son.dob#<br>");
}
catch (any e){
    writeOutput("ERROR: #e.type# #e.message# #e.detail#<br>#e.tagcontext[1].raw_trace#<br>");
}

The only thing to note here is I've parametrised the DOB value so I can pass values via the URL. When I  don't pass anything, I get predictable results:

firstName: Zachary
lastName: Cameron Lynch
DOB: {ts '2013-09-29 20:34:27'}


When I pass a date string in, it works as expected:

firstName: Zachary
lastName: Cameron Lynch
DOB: 2011-03-24


(I was kinda hoping CF might magic that into an actual date rather than keep it as a string, but I guess that's a bit far-fetched).

And when I pass an invalid value in (in this case, "NOT_A_DATE"):


firstName: Zachary
lastName: Cameron Lynch
ERROR: Expression Element DOB is undefined in SON.
at cfperson2ecfm480278231.runPage(C:\apps\adobe\ColdFusion\10\cfusion\wwwroot\shared\git\blogExamples\invokeImplicitAccessor\person.cfm:13)


We get an error. Fair enough. But did you spot where we are getting the error? on the call to the getter. Not on the call to the setter!?!!?  Huh?

It should be the setter that baulks at me passing it a value that fails its type-checking. I checked the object in case there was no type-checking on the synthesised setter, but there is:

component shared.git.blogExamples.invokeImplicitAccessor.Person
PROPERTIES
firstName[empty string]
lastName[empty string]
dob[empty string]
METHODS
GETDOB
GETFIRSTNAME
GETLASTNAME
SETDOB
function SETDOB
Arguments:
NameRequiredTypeDefault
dobRequireddate
ReturnType:shared.git.blogExamples.invokeImplicitAccessor.Person
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
SETFIRSTNAME
SETLASTNAME

The type-checking is definitely in there. So what's going on?

It's not really viable to see what's going on when CF is synthesising the accessors for me, so I added some in for the DOB property:

// Person.cfc
component accessors=true {

    property firstName;
    property lastName;
    property date dob;

    function setdob(date dob){
        writeOutput("#getFunctionCalledName()# called<br>");
        variables.dob = arguments.dob;
    }

    function getdob(){
        writeOutput("#getFunctionCalledName()# called<br>");
        return variables.dob;
    }    
}

So we've got some telemetry in there to tell us when the setter and getter are called. Here goes:

firstName: Zachary
lastName: Cameron Lynch
GETDOB called
DOB: NOT_A_DATE


I'm sorry: what?

So if I have the synthesised accessors in there, the setter silently fails, causing the getter to error because nothing was ever set. And if I have explicit accessor methods, then the setter still silently fails, but some how the bogus value makes its way into the variables scope, so the getter returns it.

WTF, ColdFusion?

To verify I'm not a loony, I tested this on Railo and... well, as expected: it gets it right. When I try to set the DOB to a non-date value, the implicit call to the setter fails as would make sense.

I'm sorry but flakiness like this makes me recant my "This. Is. Cool" from before, replacing it with "well that's a recipe for really annoying unexpected troubleshooting problems". Thanks, ColdFusion.

I don't even think that this is particularly edge-casey? Surely they have a unit test of this stuff which passes in illegal data into the implicit setter, and see what happens? Did they not notice it... not working? Again (I said this a while ago), this sort of thing really makes me worry about exactly how much unit testing of new features Adobe does. I understand they inherited a tonne of unit tests from Macromedia, but I seriously wonder whether they bother now. A case in point of how this is not an edge-case is that... this blog article wasn't going to be able this. It was going to be about how CFML is actually better (IMO) in a given area than Ruby is. But first I had to get up to speed with these implicit accessors: the first thing I did was test a typeless string or two (first and last names), and the very next thing I did - for the hell of it - was to check how it handles typeful data. And being passed inappropriate data. I wasn't specifically going out of my way to test this stuff, I just went out of my way to use it. And I found a pretty fundamental bug. Sigh.

Anyway, it's a cool feature. Perhaps just not ready for production use yet. I'll get a bug raised for this, and also ask Micha why the invokeImplicitAccessor  setting is at application-level, not component level.

And now it's too late to start the article I was gonna write, plus this is the fourth article I've written today I think, so I'm gonna go watch telly instead.

--
Adam