ColdFusion And Railo SuperClass's Switch "this" Between pseudo-Constructor and Init()

ColdFusion And Railo SuperClass's Switch "this" Between pseudo-Constructor and Init()

Posted by Brad Wood
Jun 19, 2014 17:53:00 UTC

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?

 


Carl Von Stetten

Brad,

If the order of execution when calling CFC's is the way I think it is, then the behavior you see makes sense. This is how I visualize it running when you use the new operator on subclasses (and I could be completely wrong):

  1. CF reads the subclass CFC.
  2. CF finds the "extends=" attribute, so it reads and instantiates the base class CFC into an object.
  3. The base class pseudo-constructor is run, and the properties and methods are loaded into the object.
  4. ColdFusion is now done with the base class and closes the "read".
  5. The methods are "copied" into what I loosely call the "super" scope.
  6. The subclass pseudo-constructor is run, and the properties and methods contained in the subclass are loaded into the object, overwriting any properties and methods that have the same names as properties and methods in the base class.
  7. Now the instantiation process is complete.
  8. The new operator calls the init() method as a convenience.

So by the time either the init() method ( or the super.init() method from within init() ) is called, the object is fully instantiated and no further reference to the base class is available. The getMetadata() function can only see what now exists in the "finished product" - thus the super.init() method can only see the name of the subclass.

Brad Wood

@Carl: I would assume the same. I guess my point is, does it HAVE to work that way, and SHOULD it work that way?

What if the CF engine created the full object hierarchy first and THEN executed the code in the pseudo-constructors and init()? Would that break anything? Would it make more sense?

Carl Von Stetten

Brad,

If you changed the order of execution as you suggest, then you'd never get the base class pseudo-constructor to run - it would be overwritten by the pseudo-constructor of the subclass prior to execution (unless it was somehow tossed into the "super" scope and then a way to call super.pseudo-constructor explicitly was added to CF).

I was about to suggest that getMetadata() is blind to the original base class, but that's not entirely true. GetMetadata() returns the path to the base class in the "extends" key. Maybe you can tap into that?

Brad Wood

> it would be overwritten by the pseudo-constructor of the subclass prior to execution

Would it? I certainly don't know enough about how ColdFusion or Railo internally handles the class hierarchy to make that determination. It's not like the code in the component is first class member or accessor/mutator. How would loose code get overwritten? I don't even know how it's stored! The fact that there is a separate "this", and "super" reference leads me to believe both components are preserved separately in memory. I don't see why the CFML engine can't decide when it feels like executing that code.

You are correct that subclass's metadata points to the its superclass. However, a superclass's metadata (in the pseudo-constructor at least) has no pointer to the sub class.

Henry Ho (@henrylearn2rock)

I'd be careful of the so-call pseudo-constructor. I think the code works there just because of the compiler was doing a pass to gather the function pointers for further compilation. Use of pseudo-constructor should be limited to some light use of setting constants. Use init() function for anything serious (e.g. invoking polymorphic methods).

Brad Wood

@Henry, the only reason I was looking at using the pseudo-constructor is because I was working with remote CFCs which are created by the CF engine. CF doesn't call the init method and since I don't have control over its creation I have no way to call it myself. In cases like that I am forced to work without the init. The ORM event handler falls in this same category. Trust me, given the option I will always use a standard method-based constructor for many reasons.

Sean Corfield

Your speak() example is faulty in several ways. For there to be polymorphic behavior, you must have a base class method which is overridden in the subclass.

Second, you can't expect a method to actually be polymorphic during construction - when the "pseudo-constructor" runs - only once construction is finished and we start initialization. So attempting to call speak() during construction is always going to call that class's version, not the polymorphic version.

This is how most OOP languages work by the way, which is why I was surprised anyone would expect anything different in CFML. I guess I keep forgetting how little most CFML devs know about OOP languages :(

Brad Wood

@Sean:

> you must have a base class method which is overridden in the subclass

I actually did that locally, but omitted it for brevity since it provided no value in the example and didn't fix the behavior. The base class simply called its own method-- still a fail. And since CF has no concept of abstract methods, there's no real point in even having one in the base class other than to include something like throw( 'unimplemented' ).

> only once construction is finished and we start initialization... So attempting to call speak() during construction is always going to call that class's version, not the polymorphic version.

Please provide a link to an Adobe document saying that. I don't really see any differentiation in the docs between construction and initialization. Your explanation sounds plausible in the context of the current behavior, but is it on purpose or accident?

> This is how most OOP languages work by the way

The other classical OO language I'm really familiar with is Java, and it doesn't work that way at all. Sure, static initializer blocks run on class creation, but there's no equivalent paradigm for that in CFML. The closest thing to a pseudo-constructor in Java would be a non-static initializer block which the compiler moves into the constructor-- again, not how CFML works. And method calls in Java from a base class's constructor or initializer blocks will run the overridden version in the subclass as well as have a "this" reference that points to the child class. Maybe you're not including Java in "most OOP languages", but the behavior I'm expecting in CFML would certainly work in java.

Offner Michael

the "pseudo-constructor" is a real constructor ;-) , means it is executed while the component is loaded. the base component is always loaded first, means when "animal" is loaded there is no "dog" component loaded yet, because it is loaded AFTER "animal" is loaded.

the "init" method on the other side is no real constructor, it is a simple function executed AFTER the component is loaded. that is the explanation to this behavior.

Micha

Brad Wood

@Micha, thanks for the note. Your explanation pretty much sums up what I figured was happening, but I'm still not really satisfied that anyone has given a solid reason of WHY this behavior exists. I've already shown how it limits what I can do in CFML (especially since the CF engine itself doesn't always call the init() ) and I've given an example of Java initializer blocks which DO have access to the sub class. I'd like someone to give me a solid reason why CFML should do what it's doing-- not just keep explaining to me how it already works.