Saturday 18 April 2015

CFML: initmethod and CFC inheritance

G'day:
This was supposed to be one of three PHP articles I have up my sleeve and need to write, but whilst testing some Lucee stuff I came to be thinking about this. That said, this is a general CFML question, not a Lucee one.

In the context of object initialisation, CFCs have always been a bit of a mess. Our "learn`d" colleagues at Macromedia decided we'd not need constructors, and instead just allowed us to put code between methods in the CFC file, and all that code would be run when the object is initialised. FFS, what the hell were they thinking?

Later in the piece the CFML community got together in a rare moment of unified collaboration, and settled on the idea that one would implement a class's constructor by convention, using an init() method. And this was the convention:

o = createObject("MyClass").init(args);

Basically one would always create an init() method, and always call it when creating objects. Even if the init() was simply this:

function init(){
    return this;
}

This results in uniform code, and a uniform experience. And uniform expectations.

When ColdFusion 9 was released, Adobe added the new operator:

o = new MyClass(args);

This is a lot more industry-familiar than createObject(), as well as being more succinct. And it calls the constructor method! It was an excellent addition to the language. It's how you should be creating all your objects these days.

In a rare moment of prescience from Adobe (unkind?), they also managed to predict that not everyone will be following the init() convention for their constructor method; they might call their constructors constructor(), or the same name as the class (emulating Java), or whatever. So at the same time they added the initmethod attribute of component:

component initmethod=constructor {

    function constructor(){
        return this;
    }

}

So when one called new, the correct method would be called. Excellent!

(note: this has never been supported on Railo or now Lucee. I only noticed this today, and Micha confirmed no-one else ever mentioned it either! Bug raised: LDEV-296).

I'm desperately trying to fight a fight with Lucee about how they're handling CFML objects ("Lucee 5 beta: a direct question about createObject()"), and as part of this I started looking at whether initMethod could help at all. Well: no, given they don't support it. But I continued to fiddle about with some code, and found an interesting bit of "unexpected behaviour" in ColdFusion's implementation.

Here's some code:

// P.cfc
component initmethod=constructor {

    function constructor(){
        writeOutput("P.constructor() ran<br>");
        return this;
    }
}


// C.cfc
component extends=P {

    function init(){
        writeOutput("C.init() ran<br>");
        return this;
    }

    function constructor(){
        super.constructor(argumentCollection=arguments);
        writeOutput("C.constructor() ran<br>");
        return this;
    }
}

// test.cfm
o = new C();

What I have here is:

  • A parent and child class (P & C respectively);
  • on the parent class I specify the initMethod is constructor();
  • but I do not mention that on the child class.
  • I create a child object with new.

My expectation is that C would inherit the metadata from P, if there was no overriding metadata, therefore initMethod would apply to C as well as P. But no:

C.init() ran

To be clear, I expected this:

P.constructor() ran
C.constructor() ran


So my question is... what would you expect? What should we expect here? Is there a legit case for my expectation, or is there a legit case that the component settings from a parent CFC does not filter down to the child CFC?

Thoughts?

PS: do you ever use initmethod? Did you even know it existed (no shame in that, btw, so 'fess up ;-)? Add a comment.
PPS: ticket raised: 3972123.

--
Adam