Get private variables & functions using function injection

Public vs Private

If you are familiar with OOP, you know that there are basically 2 types (scopes) of variables and functions:

  • Private: only available within the object itself
  • Public: also available to the caller, outside the object

(There are other scopes as well but I leave them out to keep things focused in this article)

To know what's available publicly, we can either read the documentation or use some functions like cfdump in Coldfusion.

But what about the private ones? How can we know what variables and functions are available privately inside the object? I know there are many good reasons for things to remain private and I totally agree that is the good architecture. But there are times I want to know how the internal variables change, what values they are holding at runtime. This is especially useful for debugging purpose.

Traditional way

Traditionally, what we can do is to write public functions that returns the private variables. For example, create a public function getName() to return the private variable Name. This is easy but it has an issue: the method has to be available inside every class that we want to explore. Plus, we usually have to write one function for almost every variable which makes this method clearly not practical.

New way

With Coldfusion, we have another way (not sure if it's available in other languages). We can inject a function into an object at run time to return what is available inside that object including those not available to the public natively. With that ability, here's what we can do:

  • Write a function to return the private variables and put it in some utility class. This is like the standard get method except it gets all the internal variables at one shot.
  • Everytime we want to get private variables of an object, we inject this function into the object and then call it. As the function is now duplicated and put into the new object, it will return the private variables of that object instead of those of the utility class.

The public function

Following that approach, the first thing we need to do is to write the function that returns internal variables and put it inside a utility class. Create a new utility.cfc file with this function:

<cfcomponent displayname="Utility">

	<!--- Retrieve private variables --->
	<cffunction name="getPrivate" displayname="getPrivate" access="private" returntype="struct" hint="Retrieve private variables">

		<cfset var result = StructNew()>
		<cfset result.simpleValues = StructNew()>
		<cfset result.arrays = StructNew()>
		<cfset result.structs = StructNew()>
		<cfset result.queries = StructNew()>
		<cfset result.objects = StructNew()>
		<cfset result.privateFunctions = StructNew()>
		
		<!--- Loop through all the private variables and categorise them --->
		<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>
	
		<cfreturn result>
	
	</cffunction>

</cfcomponent>	

This function will loop through the list of private variables and functions and then put them into the right group: simple values, arrays, structs, functions, etc. I group them here because otherwise I find the list quite messy and hard to find the variable/function I'm interested in. Feel free to add in other variable types that you think I have missed out.

If you don't want this grouping, you can simply return the Variables struct. However, keep in mind that doing so will give you public variables as well, plus the object itself which most of the time we are not interested in (if we want to know the object details we can just cfdump the object).

I made this function private because later on we will inject it into another object. It won't be called directly from this Utility class.

Function injection

The next thing we need to do is to inject this function into the object we want to explore. Open the Utility class and put in this injection function:

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

	<cfargument name="obj" type="component" required="yes" hint="The object to retrieve the private variables from">
	<cfset var result = StructNew()>
	
	<!--- Inject the function and then call it --->
	<cfset arguments.obj.getPrivate = variables.getPrivate>
	<cfset result = arguments.obj.getPrivate()>
	
	<cfreturn result>

</cffunction>

You might notice that the getPrivate function was defined as private in the utility.cfc but after injection, I can call the function directly. The reason is once injected, the function will automatically become public (I think that's because the function is injected by the public, so they have the right to see and use it :-D).

How to use it

We now have the complete utility class, the next thing is how to use it. We can call the function in a typical way passing in the object we want to explore.

Inside the caller page (eg. index.cfm):

<!---
	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)>

We will now have a nice struct returned with what's available privately inside the person object.

One step further

The above functions are enough for us to get private variables of any object. However, there are a few slight issues with it.

  1. Everytime we call this function, it will inject the function into the object. If the object already has a function with the same name (ie. getPrivate), it will be overwritten!
  2. The function will remain inside the object after it has been used which we don't want. We want them object to be left intact.

Here is what we can do to overcome these issues. Replace the above getPrivateVariables function with this new one:

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

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

</cffunction>

What I did here was:

  1. Generate a random string to be used as the new function name. We can rest assured this name will be unique. It is very very unlikely that the function CreateUUID() returns the same value as what you already define.
  2. Insert the function and call it to get the result
  3. Once we got the result, we can remove the function. This will put the object back into its original state.

What do you think about this method? Do you know any better way to do this or do you care about the private variables at all? Please let me know what you think at the comments.

Download the CFC

2 Comments

  • Keisha Lee
    08 Oct 2011, 10:02 PM

    Can you help me with this program?

    Write a windows console application that simulates an Automated Teller Machine (ATM) menu.
    I'm desperate! I am looking for a tutor right now.

  • Vinh Khoa Nguyen
    09 Oct 2011, 12:05 AM in reply to Keisha Lee

    I'm not an expert in Windows application. Sorry, I can't help you with that.

Leave comment

Hi, please enter your real name and email address. The email won't be published. Comments are moderated and will appear after checked for spams.

Reply to :