Monday, 15 July 2013

CFML: getObjectMetadata()

G'day:
This is a strange article, this one. It started off just being a feature request which I was typing into the Adobe bug tracker, but decided to write my thoughts up here, by way of soliciting other people's thoughts too. On re-read just before pressing "publish", I realise the framing for the point is about five times longer than the point itself. Oops. Oh well. It's a slow news day here. It's Monday after all.

ColdFusion has a function getMetadata(),  which can be called on a variety of objects: an CFC instance, a user-defined function, a property, a query. I'm just focusing on on its usage with "objects".

Here's an example of getMetadata() being called on an object:

// Bloggable.cfc
interface {

    public void function g(required numeric x);

}

// Parent.cfc
/**
@hint Parent component
*/
component {

    /**
    * @hint Method defined in Parent
    */
    public void function g(required numeric x){
        return;
    }

}

// Sub.cfc
/**
@hint Demonstrating getMetadata() results
*/
component extends="Parent" implements="Bloggable" {

    /**
    * @hint Method defined in Sub
    * @x A number
    */
    public void function f(required numeric x){
        return;
    }

}

<!--- test.cfm --->
<cfdump var="#getMetadata(new Sub())#">

Output:

struct
EXTENDS
struct
EXTENDS
struct
FULLNAMEWEB-INF.cftags.component
NAMEWEB-INF.cftags.component
PATHC:\Apps\JRunservers\www.scribble.local\
cfusion.ear\cfusion.war\WEB-INF\cftags\component.cfc
TYPEcomponent
FULLNAMEcf.cfml.functions.system.getMetadata.blog.Parent
FUNCTIONS
array
1
struct
ACCESSpublic
HINTMethod defined in Parent
NAMEg
PARAMETERS
array
1
struct
NAMEx
REQUIREDtrue
TYPEnumeric
RETURNTYPEvoid
HINTParent component
NAMEcf.cfml.functions.system.getMetadata.blog.Parent
PATHD:\websites\www.scribble.local\cf\cfml\functions\system\getMetadata\blog\Parent.cfc
TYPEcomponent
FULLNAMEcf.cfml.functions.system.getMetadata.blog.Sub
FUNCTIONS
array
1
struct
ACCESSpublic
HINTMethod defined in Sub
NAMEf
PARAMETERS
array
1
struct
HINTA number
NAMEx
REQUIREDtrue
TYPEnumeric
RETURNTYPEvoid
HINTDemonstrating getMetadata() results
IMPLEMENTS
struct
Bloggable
struct
EXTENDS
struct
WEB-INF.cftags.interface
struct
FULLNAMEWEB-INF.cftags.interface
NAMEWEB-INF.cftags.interface
PATHC:\Apps\JRunservers\www.scribble.local\
cfusion.ear\cfusion.war\WEB-INF\cftags\interface.cfc
TYPEinterface
FULLNAMEcf.cfml.functions.system.getMetadata.blog.Bloggable
FUNCTIONS
array
1
struct
ACCESSpublic
NAMEg
PARAMETERS
array
1
struct
NAMEx
REQUIREDtrue
TYPEnumeric
RETURNTYPEvoid
NAMEcf.cfml.functions.system.getMetadata.blog.Bloggable
PATHD:\websites\www.scribble.local\cf\cfml\functions\system\getMetadata\blog\Bloggable.cfc
TYPEinterface
NAMEcf.cfml.functions.system.getMetadata.blog.Sub
PATHD:\websites\www.scribble.local\cf\cfml\functions\system\getMetadata\blog\Sub.cfc
TYPEcomponent


Lovely: everything we'd want to know about that object.

Despite being called on an object, getMetadata() returns the metadata for the CFC, not the actual object. Changes made to the object after it's been instantiated are not reflected in the returned metadata, eg:

<!--- test.cfm --->
<cfscript>
    o = new Sub();

    function h(){
        writeOutput("Hi!");
    }
    o.h = h;    // stick it in the object
    o.h(); // prove it works

    writeDump(getMetadata(o));    // but where is it?
</cfscript>

Output:

Hi!
struct
EXTENDS
FULLNAMEcf.cfml.functions.system.getMetadata.blog.Sub
FUNCTIONS
array
1
struct
ACCESSpublic
HINTMethod defined in Sub
NAMEf
PARAMETERS
array
1
struct
HINTA number
NAMEx
REQUIREDtrue
TYPEnumeric
RETURNTYPEvoid
HINTDemonstrating getMetadata() results
IMPLEMENTS
NAMEcf.cfml.functions.system.getMetadata.blog.Sub
PATHD:\websites\www.scribble.local\cf\cfml\functions\system\getMetadata\blog\Sub.cfc
TYPEcomponent

(I've collapsed a bit of it so as to minimise repetitive clutter).

This - to me - is a bug: we've got a function getComponentMetadata(), and that should be getting the metadata for a component. getMetadata(), when having an object passed to it, should get metadata on that object. Not on the object's underlying component. Anyway, I've already dwelt on that and I have nothing new to add.

What would work around this bug would be to have a getObjectMetadata(), which simply does what it claims it does: gets the metadata of the actual object passed to it, rather than the CFC it was an instance of.

That in itself is not so interesting, but I'd also envisage the metadata to be writable. For example, if I push another function into the metadata, this is reflected in the object. We can already push functions into objects - as demonstrated above - but there's other stuff we can't do. How about pushing functions into the object so that it fulfills an interface contract, and then also setting the object's implements metadata to tell it it now implements that interface; so it can then be used in situations in which we need that interface. Or - and god knows the ramifications of this - changing the extends metadata of the object so it extends some different parent component?

TBH, the most useful thing would be for it to return the metadata of the actual object, not the component the object is an instance of. But I've had situations in which being able to make an existing object fulfill an interface contract would have been useful. But then again I bring this upon  myself because I use interfaces. Maybe I just shouldn't.

Anyway, any thoughts on this sort of thing? I've raised it as E/R 3594771.



Update:
Ooh. I forgot... what does Railo do about all this? Well Railo does actually include some metadata for the injected function, h():

functions
Array
1
Struct
access
stringpublic
closure
booleanfalse
description
string
hint
stringMethod defined in Sub
name
stringf
output
booleantrue
owner
stringC:\Apps\railo-express-jre-win64\webapps\railo\www.scribble.local\cf\cfml\functions\system\getMetadata\blog\Sub.cfc
parameters
Array
1
Struct
hint
stringA number
name
stringx
required
booleantrue
type
stringnumeric
returnFormat
stringwddx
returntype
stringvoid
2
Public Function h
source:C:\Apps\railo-express-jre-win64\webapps\railo\www.scribble.local\cf\cfml\functions\system\getMetadata\blog\test2.cfm
arguments
labelnamerequiredtypedefaulthint
return typeany

Although it's clearly not being considered in quite the same way that the compile-time-present functions are. The metadata here is a bit sparse (and not correct, either). For the purposes of this example, I changed h() to be:

public void function h() hint="Hi!"{
    writeOutput("Hi!");
}

So in that dump, Railo is omitting the hint, and reporting the returntype incorrectly. If I dump output on the function itself, rather than the object, I get a more pleasing (if not aesthetically) result:

Struct
access
stringpublic
closure
booleanfalse
description
string
hint
stringHi!
name
stringh
output
booleantrue
owner
stringC:\Apps\railo-express-jre-win64\webapps\railo\www.scribble.local\cf\cfml\functions\system\getMetadata\blog\test.cfm
parameters
Array
returnFormat
stringwddx
returntype
stringvoid

So this is something. Good ole Railo.


--
Adam