Saturday, 11 April 2015

Lucee 5 beta: createComponent()

I'm moving away from Lucee 5's new additions to CFC implementation today, and having a look at one of the new functions they've added: createComponent(). Before clicking that link or reading on: try to guess what the function does.

The explanation of the function is thus (paraphrasing from that page):

Function signature:
Component createComponent(String componentName, ArgumentCollection arguments)

(where an argumentCollection is either an array of ordered arguments, or a struct of named arguments).

Also note it does not return a Component it returns an Object (ie: an instance of a component).

They go on to say:
This function is a replacement for createObject("component",...) and you can use this function in the same way, however unlike createObject("component",...) this functions invokes the init method of the component, if there is one, and allows you to pass arguments to the init method.
son = createComponent("Person", {firstName="Zachary", lastName="Cameron Lynch"})

Honestly, I don't really see how this is any better than this:
son = createObject("Person").init(firstName="Zachary", lastName="Cameron Lynch")

They also pick up on one's other likely reaction to this:
Maybe you ask yourself "why this function?" when I can do the same with the new operator as follows:
son = new Person(firstName="Zachary", lastName="Cameron Lynch")
This is true, but this function is made for the dynamic loading of components, for example components defined in a configuration file:
config = {
    componentName = "Person",
    arguments = {firstName="Zachary", lastName="Cameron Lynch"}
son = createComponent(config.componentName, config.arguments)

(note: the text is theirs, the code is mine). So... um... pretty much like one can already do with new then?

son = new "#config.componentName#"(argumentCollection=config.arguments)

And if one wanted a function version of this for some reason, why the hell create a new function instead of just augmenting the existing function? This (proof of concept) would work fine:

// dynamicUsingImprovedCreateObject.cfm

createObject = function(componentOrType, componentOrInitArgs={}){
    switch (componentOrType) {
        case "component":
            return createObject(componentOrInitArgs)
        case "java":
            return createObject("java", componentOrInitArgs)
        // etc for other options
            return createObject(componentOrType).init(argumentCollection=componentOrInitArgs)

include "config.cfm"

echo("Component using traditional syntax")
o = variables.createObject("component", config.componentName).init(argumentCollection=config.initArgs)

echo("<hr>Java using new syntax")
o = variables.createObject("java", "java.lang.String").init("hi")

echo("<hr>Component using new syntax")
o = variables.createObject(config.componentName, config.initArgs)

config.cfm just creates the same struct as listed above with Zachary's name in it. So in this example we leverage the fact that createObject() already takes different sets of arguments depending on the situation:
  • It takes as a first argument one of a series of predetermined strings, eg: "component", "java", "dotnet", "corba" (snigger); or a string containing the name/path to a CFC.
  • If the second argument is present, then it's a name/path to the type of resource (CFML component, Java class, etc).
Basically the first argument is optional (way to go, CFML!) if the type is "component". This can be leveraged for CFCs that if the first argument is a CFC name/path, then the second argument can be its constructor's argument collection. This is all slightly messy, but the function itself is already slightly messy, and I don't think this makes it worse. Furthermore, createComponent() is just wrong. It doesn't create a component. It creates an object. A component is the CFC file. When one instantiates a component, one gets an object of that type.

A function called createComponent() should return a component. What that might be, I have no idea, but probably an object representing the actual component. EG like what the method .getClass() returns in Java.

Now I agree it's not ideal that CFCs were implemented to not have proper constructors (what the f*** were you thinking, Macromedia? Jesus), and a bit suck we've needed to adopt the standard practice of having an init() method we then call by hand. It's annoying that createObject() returns an uninitialised object, and then one needs to explicitly call init(). And it's weird that new does call init(), and createObject() doesn't. But the sensible solution there is to fix createObject()! It makes no sense to have both createObject() and createComponent() that basically do the same thing in CFML.

And, TBH, how is doing this work a priority? They'd've been better off fixing a coupla extra bugs than wasting time on this. But now that they've done it, they should do it properly. The current implementation is just PHPifying CFML. No-one wants that.