Getting Inherited CFC MetaData
Posted by
Brad Wood
May 06, 2012 08:19:00 UTC
ColdFusion's getMetaData function is a very handy way to introspect the functions, properties, and annotations of a component in an easy-to-digest format of arrays and structs. ColdFusion even nests structs to represent the metadata of the classes your component extends as well. Recently, for an enhancement I was building for WireBox, the DI/AOP framework inside ColdBox, I wanted to be able to condense all the metadata down for the component in question as well as all the components it inherits from to a single list of functions, properties, and annotations. After a short bit of Googling, I didn't find any code that did that so I decided it was a perfect opportunity to write a function that did just that.
Here is what the metadata looks like for a regular ColdFusion component. You can see that When that component extends another component, ColdFusion includes a key called "extends" in the struct which contains the metadata of the super class like so:
class1.cfc
class2.cfc
This is a perfect use for our friend, recursion; which is when a function calls itself, creating a nested call stack of executions. Recursion uses more memory than iterative solutions, but usually results in simpler code.
I wrote a function called getInheritedMetadata(), which accepts either an instance of a CFC or a string which is a path to a CFC. That way, getInheritedMetadata() can take the place of both getMetaData() and getComponentMetaData(). The function starts by creating the familiar metadata struct that ColdFusion gives us, and keeps calling itself for each super class until it finds the top-most class in the inheritance chain. At that point, it starts building a new metadata struct (I didn't want overwrite CF's original metadata since it is cached.)
As the call stack unwinds and the function works its way back down the chain to the bottom class, it appends in the properties, functions, and annotations of each class overriding the super class just like real inheritance works. Once the function makes it to the end (back where it started), it has a new struct that represents the combined metadata of all the components in the inheritance tree. I also add an array into the combined metadata called inheritanceTrail which lists out all the classes that were combined together.
Here is the code:
So, using the two components from above, here is the output from the getInheritedMetadata() function:
Download code here
Here is what the metadata looks like for a regular ColdFusion component. You can see that When that component extends another component, ColdFusion includes a key called "extends" in the struct which contains the metadata of the super class like so:
class1.cfc
[code]<cfcomponent displayName="class1" extends="class2" output="true" customAnnotation="class1 rocks"> <cfproperty name="name" default="Susan"> <cfproperty name="age"> <cffunction name="getName" hint="overridden get for name"> </cffunction> <cffunction name="getAge"> </cffunction> </cfcomponent> [/code]
class2.cfc
[code]<cfcomponent displayName="class2" output="false" customAnnotation="class2 rocks"> <cfproperty name="name" default="Bill"> <cffunction name="getName" hint="base get for name"> </cffunction> </cfcomponent> [/code]
struct | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CUSTOMANNOTATION | class1 rocks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DISPLAYNAME | class1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
EXTENDS |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
FULLNAME | class1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
FUNCTIONS |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
NAME | class1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
OUTPUT | true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PATH | D:\coldboxtest\class1.cfc | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PROPERTIES |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TYPE | component |
This is a perfect use for our friend, recursion; which is when a function calls itself, creating a nested call stack of executions. Recursion uses more memory than iterative solutions, but usually results in simpler code.
I wrote a function called getInheritedMetadata(), which accepts either an instance of a CFC or a string which is a path to a CFC. That way, getInheritedMetadata() can take the place of both getMetaData() and getComponentMetaData(). The function starts by creating the familiar metadata struct that ColdFusion gives us, and keeps calling itself for each super class until it finds the top-most class in the inheritance chain. At that point, it starts building a new metadata struct (I didn't want overwrite CF's original metadata since it is cached.)
As the call stack unwinds and the function works its way back down the chain to the bottom class, it appends in the properties, functions, and annotations of each class overriding the super class just like real inheritance works. Once the function makes it to the end (back where it started), it has a new struct that represents the combined metadata of all the components in the inheritance tree. I also add an array into the combined metadata called inheritanceTrail which lists out all the classes that were combined together.
Here is the code:
[code]<cffunction name="getInheritedMetaData" output="false" hint="Returns a single-level metadata struct that includes all items inhereited from extending classes."> <cfargument name="component" type="any" required="true" hint="A component instance, or the path to one"> <cfargument name="md" default="#structNew()#" hint="A structure containing a copy of the metadata for this level of recursion."> <cfset var local = {}> <!--- First time through, get metaData of component. ---> <cfif structIsEmpty(md)> <cfif isObject(component)> <cfset md = getMetaData(component)> <cfelse> <cfset md = getComponentMetaData(component)> </cfif> </cfif> <!--- If it has a parent, stop and calculate it first. ---> <cfif structKeyExists(md,"extends")> <cfset local.parent = getInheritedMetaData(component,md.extends)> <!--- If we're at the end of the line, it's time to start working backwards so start with an empty struct to hold our condensesd metadata. ---> <cfelse> <cfset local.parent = {}> <cfset local.parent.inheritancetrail = []> </cfif> <!--- Override ourselves into parent ---> <cfloop collection="#md#" item="local.key"> <!--- Functions and properties are an array of structs keyed on name, so I can treat them the same ---> <cfif listFind("FUNCTIONS,PROPERTIES",local.key)> <cfif not structKeyExists(local.parent,local.key)> <cfset local.parent[local.key] = []> </cfif> <!--- For each function/property in me... ---> <cfloop array="#md[local.key]#" index="local.item"> <cfset local.parentItemCounter = 0> <cfset local.foundInParent = false> <!--- ...Look for an item of the same name in my parent... ---> <cfloop array="#local.parent[local.key]#" index="local.parentItem"> <cfset local.parentItemCounter++> <!--- ...And override it ---> <cfif compareNoCase(local.item.name,local.parentItem.name) eq 0> <cfset local.parent[local.key][local.parentItemCounter] = local.item> <cfset local.foundInParent = true> <cfbreak> </cfif> </cfloop> <!--- ...Or add it ---> <cfif not local.foundInParent> <cfset arrayAppend(local.parent[local.key],local.item)> </cfif> </cfloop> <!--- For everything else (component-level annotations), just override them directly into the parent ---> <cfelseif NOT listFind("EXTENDS,IMPLEMENTS",local.key)> <cfset local.parent[local.key] = md[local.key]> </cfif> </cfloop> <cfset arrayPrePend(local.parent.inheritanceTrail,local.parent.name)> <cfreturn local.parent> </cffunction> [/code]
So, using the two components from above, here is the output from the getInheritedMetadata() function:
struct | |||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CUSTOMANNOTATION | class1 rocks | ||||||||||||||||||||||||
DISPLAYNAME | class1 | ||||||||||||||||||||||||
FULLNAME | class1 | ||||||||||||||||||||||||
FUNCTIONS |
|
||||||||||||||||||||||||
INHERITANCETRAIL |
|
||||||||||||||||||||||||
NAME | class1 | ||||||||||||||||||||||||
OUTPUT | true | ||||||||||||||||||||||||
PATH | D:\coldboxtest\class1.cfc | ||||||||||||||||||||||||
PROPERTIES |
|
||||||||||||||||||||||||
TYPE | component |
Download code here
John Allen
Very interesting and very cool.