Not yet rated

Problem

I want to know what value at run time a private variable is holding inside an object. This is useful for debugging.

Solution

Use method injection to inject a method into the object at runtime. This method contains predefined codes to retrieve private variables.

Detailed explanation

Private scope is good to hide some internal values and executions from the public world. But sometimes during debugging, it would be handy to know what value a particular variable or some variables are holding inside an object. For example, we want to know if there is any code leakage that creates unintended variables after calling a particular function.

Coldfusion has a way to inject a method into an object at runtime. Or more precisely copy (by reference I think) a method from one object/cfc into another.

So what we could do is to write a method to get all private variables and put it inside a util.cfc component:

<!--- Retrieve private variables --->
<cffunction name="getPrivate" displayname="getPrivate" access="private" returntype="struct" hint="Retrieve private variables" output="false">
    
    <cfargument name="group" type="boolean" required="no" default="true" hint="Group the local variables">
    <cfset var result = StructNew()>
    <cfset var thisVar = "">
    
    <!--- Group the variables? --->
    <cfif arguments.group>
        <cfset result.simpleValues = StructNew()>
        <cfset result.arrays = StructNew()>
        <cfset result.structs = StructNew()>
        <cfset result.queries = StructNew()>
        <cfset result.objects = StructNew()>
        <cfset result.privateFunctions = StructNew()>
       
        <cfloop collection="#variables#" item="thisVar">
       
            <!--- Simple values? --->
            <cfif isSimpleValue(variables[thisVar])>
                <cfset result.simpleValues[thisVar] = variables[thisVar]>
           
            <!--- Array? --->
            <cfelseif isArray(variables[thisVar])>
                <cfset result.arrays[thisVar] = variables[thisVar]>
           
            <!--- Struct? --->
            <cfelseif isStruct(variables[thisVar]) AND thisVar neq "this">
                <cfset result.structs[thisVar] = variables[thisVar]>
           
            <!--- Query? --->
            <cfelseif isQuery(variables[thisVar])>
                <cfset result.queries[thisVar] = variables[thisVar]>
           
            <!--- Object? --->
            <cfelseif isObject(variables[thisVar]) AND thisVar neq "this">
                <cfset result.objects[thisVar] = variables[thisVar]>
           
            <!--- Private custom functions? --->
            <cfelseif isCustomFunction(variables[thisVar]) AND variables[thisVar].access eq "private">
                <cfset result.privateFunctions[thisVar] = variables[thisVar]>
            </cfif>
        </cfloop>
    <cfelse>
        <cfset result = variables>
    </cfif>
    
    <cfreturn result>
    
</cffunction>

I group the result by type here for readibility, but if you dont want, you can just return the whole variables scope. But if you do so, beware that variables scope contains public variables and methods as well. So the result could be overwhelming.

Then we write another method to inject this method into another object. We are still inside the util.cfc here:

<!--- Get private variables inside an object --->
<cffunction name="getPrivateVariables" displayname="getPrivateVariables" access="public" hint="Get private variables inside an object" output="false">

    <cfargument name="obj" type="component" required="yes" hint="The object to retrieve the private variables from">
    <cfargument name="group" type="boolean" required="no" default="true" hint="Group the local variables">
    <cfset var result = StructNew()>
    
    <!--- Create a random function name --->
    <cfset var funcName = reReplaceNoCase("f_#createUUID()#", "[^a-zA-Z0-9_]", "", "ALL")>
    <cfset arguments.obj[funcName] = variables.getPrivate>
    <cfinvoke component="#arguments.obj#" method="#funcName#" group="#arguments.group#" returnvariable="result">
    
    <!--- Remove this function --->
    <cfset StructDelete(arguments.obj, funcName)>
    
    <cfreturn result>
    
</cffunction>

Notice that I create a random method name to avoid overwriting an existing method just in case. Also, after getting the variables I want, I delete the method to return the object back to its original state. A few other notes:

- Although I define the getPrivate function above as private, but once injected, they can still be called from outside

- Once injected, the variables scope now refers to the scope of the new object instead of those of the util.cfc which is exacly what we want

Finally, everytime we want to know about private variables of a particular object, we just need to call the above method like this:

<!---
    Let's say we have a Person class, person.cfc
    And here we create a new object out of this class.
--->  
<cfset objPerson = createObject("component", "person").init("Paul", 20)>  
 
<!--- Here is how we retrieve private variables inside this object --->  
<cfset objUtility = createObject("component", "utility")>  
<cfset privateResult = objUtility.getPrivateVariables(objPerson)> 

+
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

Report abuse

Related recipes