I noticed an interesting behavior this week that was a little unexpected. The repro case involves two CFCs-- one extending the other and involves where the "this" reference points to.
Constructors
ColdFusion has two types of constructors-- that is, ways to run initialization code upon the creation of the component. The first way is called the pseudo-constructor and basically refers to ANY code inside the component that is not part of a method. That was the only way to run initialization code back when CFCs first came out in CFMX 6. The second way is via a method called "init()". This was a widely-used convention for years before CF9 started automatically calling it for you when you used the "new" keyword and "entityNew()" function.
Narcissism or Schizophrenia
Sometimes components want to know about themselves or even talk to themselves. Heck, I wrote a CFC last week that just won't shut up! That's why there is a keyword called this available inside every CFC. This is the same as the external references held by the code that created the component instance. The pseudo-constructor and init() method both have access to this.
The Code
Here is our super class, or base class.
animal.cfc
component {
writeOutput( 'Animal pseudo-constructor. "this" name: #getMetadata( this ).name#<br>' );
function init() {
writeOutput( 'Animal init. "this" name: #getMetadata( this ).name#<br>' );
}
}
Here is our sub class or concrete class that is a more specific type of animal.
dog.cfc
component extends='animal' {
writeOutput( 'Dog pseudo-constructor. "this" name: #getMetadata( this ).name#<br>' );
function init() {
super.init();
writeOutput( 'Dog init. "this" name: #getMetadata( this ).name#<br>' );
return this;
}
}
Remember the new operator will automatically call the init() constructor method.
index.cfm
<cfset new dog()>
So as you can see, each component logs the name of the component as reported by getMetadata() in both the pseudo-constructor and init() constructor.
The Behavior
Here is the output. It is the same for Railo 4.2, CF9, CF10, and CF11.
Animal pseudo-constructor. "this" name: animal
Dog pseudo-constructor. "this" name: dog
Animal init. "this" name: dog
Dog init. "this" name: dog
The this reference in both init() methods point to the "dog" component that I'm creating. However, the pseudo-constructor code in the base class "animal" has a this reference to "animal", not "dog". What bothers be about this is:
- The this reference changes unexpectedly in the base class
- There is no way for the base class to access the concrete class in its pseudo-constructor
- I can't find any Railo or Adobe ColdFusion docs that reference this behavior to show if it's expected or not.
That second bullet point is specifically what was tripping me up because the base class can't get a reference to the actual component being created. Sean Corfield mentioned on Twitter that a base class should not know about it's sub classes, but use polymorphic methods. However that DOESN'T WORK since the base class's pseudo-constructor has no reference to the sub class at all. To test this, put a speak() method in the dog CFC:
function speak() {
writeOutput( 'Woof!<br>' );
}
Now, try to call that from the base classes pseudo-constructor.
speak();
No matching function [SPEAK] found
Of course, this works from the init method, but my point is I think it should work in both constructors.
Another interesting note is that if you save a reference to this in the base pseudo-constructor and check it later, it will have changed to the concrete class. This made debugging a pain since I originally started appending references to an array in the request scope and dumping them after the component creation which yielded different results than the state of the this references at the point in time when the constructors were executing.
Conclusion?
I mentioned this on Twitter and got a couple different responses. What do you think? Should CFCs handle the this reference the same between their pseudo-constructor and regular constructor?