How to see if a function exists in a CFC

{ Posted By : Eric Cobb on January 21, 2011 }
7591 Views
Related Categories: Tips 'n Tricks, CFML

Just recently I came across a situation where I needed to determine if a specific method was available in a CFC. I have a case where I'm creating specific methods for specific departments. So in the code, if Department A has a method, do this, if not do that. As it turns out, this is super super simple to do from within the CFC.

How simple is it?
It is so simple that I couldn't believe it when I tried it and it really worked. I had to do a double take because, well, I'm really not used to my code working right the first time. I actually felt smert for a change, like I knew what I was doing! It's really just as easy as using StructKeyExists(). That's right, just use good ole StructKeyExists(), that's all there is to it. If you want, you can use IsDefined() instead, but I prefer to use StructKeyExists().

Here is an example. In the following CFC we have 2 methods, MethodA and MethodB. When MethodA is called, we want to see if MethodB exists, and if it does return the value from it. Otherwise, we return MethodA's value.

<cffunction name="MethodA" output="false" returntype="boolean">
    <cfif StructKeyExists(variables,"MethodB">
        <cfreturn MethodB() />
    <cfelse>
        <cfreturn false />
    </cfif>
</cffunction>

<cffunction name="MethodB" output="false" returntype="boolean">
    <cfreturn true />
</cffunction>

But what about from a CFM file?
I was just getting to that. So, now that we know how easy it is to determine if a function is in a component from within that component, let's look at trying to figure it out from within a CFM file. As it turns out, this is a little trickier, but it's still pretty simple to do.

Now, if you are actually using cffunction in your CFM file, then you should be able to call StructKeyExists() (or IsDefined()) from within that CFM file and everything work fine. However, if you want your CFM file to check for a function in a CFC, you've got to do a little prep work first.

CORRECTION: As Henry points out in the comments below, you actually CAN use StructKeyExists() in a CFM file to check for a function in a CFC. Apparently I had something goofy going on in all of my testing which led me to believe it wouldn't work. After seeing Henry's comment I tried it again and it worked fine, first try. ;)

To start with, we need to take a peek inside the CFC to see what is in there. This can be done using either getMetadata() or getComponentMetadata(). Both of the functions return the same information, but they each take different parameters. getMetadata() takes a CFC object, while getComponentMetadata() takes the dot-delimited path to your CFC.

Here is an example of the differences:

<!--- example of getMetadata(). --->
<cfset variables.cfcToCheck = CreateObject("component","path.to.your.component")>
<cfset variables.cfcInfo = getMetadata(variables.cfcToCheck)>


<!--- example of getComponentMetadata(). --->
<cfset variables.cfcToCheck = "path.to.your.component">
<cfset variables.cfcInfo = getComponentMetadata(variables.cfcToCheck)>

As I said, the results returned from both of these functions (which we stored in the "cfcInfo" variable) are virtually identical, so use whichever one you want, the important thing is that we have now connected to our CFC and have some information about it. A lot of information. In fact, if you dump the "cfcInfo" variable we just created you will see it has structures that contain arrays that contain structures that contain arrays and so on.

OK, we have the CFC info, now what?
Now that we have the data for our CFC, we need to start working towards getting a list of the function names. Lucky for us, our "cfcInfo" variable just happens to contain an array (inside a structure) of all of the info for each function in our CFC. So, the first thing we need to do is get that information in a variable of its own.

<!--- get an array of all of the info for each function. --->
<cfset variables.cfcFunctions = variables.cfcInfo.functions>

Easy enough, right? All we have to do now is loop through our array and get the name of each function. We'll then add the function's name to a list.

<cfset variables.functionList = "">
<!--- loop through each function and add its name to the list. --->
<cfloop array="#variables.cfcFunctions#" index=" variables.i">
    <cfset variables.functionList = listAppend(variables.functionList, variables.i.name)>
</cfloop>

Now that we have a list of the names of all of the functions inside our CFC, we can just run checks against it to see if a function exists or not.

<cfif ListFindNoCase(variables.functionList,"MethodA")>
...Do stuff for folks who use Method A...
<cfelse>
...Do stuff for folks who do not use Method A...
</cfif>

And that's about all there is to it! I actually wound up putting all of this together as a method in one of my "utility" CFCs so it can be called wherever I need it in my application. All I have to do is pass in a component object or a path to my CFC and it returns a list of function names.

To sum it all up
Here is a completed working example for finding out if a function exists in a component.

<!--- you can optionally just specify the dot-delimited path to your CFC. --->
<cfset variables.cfcToCheck = CreateObject("component","path.to.your.component")>

<!--- check to see if we're dealing with a CFC path, or an actual CFC object. --->
<cfif IsSimpleValue(variables.cfcToCheck)>
    <!--- this takes a string path to the location of the cfc --->
    <cfset variables.cfcInfo = getComponentMetadata(variables.cfcToCheck)>
<cfelse>
    <!--- this takes a reference to a cfc object --->
    <cfset variables.cfcInfo = getMetadata(variables.cfcToCheck)>
</cfif>

<!--- get an array of all of the info for each function. --->
<cfset variables.cfcFunctions = variables.cfcInfo.functions>
<!--- create a variable to hold our list of function names. --->
<cfset variables.functionList = "">

<!--- loop through each function and add its name to the list. --->
<cfloop array="#variables.cfcFunctions#" index=" variables.i">
    <cfset variables.functionList = listAppend(variables.functionList, variables.i.name)>
</cfloop>

<!--- using our list, determine if a function exists or not. --->
<cfif ListFindNoCase(variables.functionList,"MethodA")>
...Do stuff for folks who use Method A...
<cfelse>
...Do stuff for folks who do not use Method A...
</cfif>
Comments
Tony Nelson's Gravatar You'll want to be careful to take CFC inheritance into account. For example, if a CFC extends another CFC, the inherited methods won't show up directly inside getMetaData(cfc).functions, but instead they'll be located inside getMetaData(cfc).extends.functions.

Here's a quick script to recursively collect all inherited functions inside a component:

<cfset component = createObject("component", "path.to.your.component") />
<cfset metaData = getMetaData(component) />
<cfset functionList = "" />

<cfloop condition="#structKeyExists(metaData, 'extends')#">
   
   <cfif structKeyExists(metaData, "functions") and isArray(metaData.functions)>
      
      <cfloop array="#metaData.functions#" index="function">
         
         <cfif not listFindNoCase(functionList, function.name)>
            <cfset functionList = listAppend(functionList, function.name) />
         </cfif>
      
      </cfloop>
   
   </cfif>
   
   <cfset metaData = metaData.extends />

</cfloop>

<cfdump var="#functionList#" />
# Posted By Tony Nelson | 1/21/11 1:28 PM
Henry's Gravatar Or you can simply use StructKeyExists(myCFC,"publicMethodName")
# Posted By Henry | 1/21/11 1:42 PM
Tony Nelson's Gravatar @Henry,

Assuming you're working with an instance of the CFC and not a class path, your way is the more direct approach.

On a slightly edge-case note, you'll also want to watch out for uses of onMissingMethod. Just because a component doesn't "have" a method, doesn't mean the component can't respond to calling the method.
# Posted By Tony Nelson | 1/21/11 1:49 PM
Eric Cobb's Gravatar @Tony - Awesome! Thanks for the tip, I had not even considered inheritance. It's not something we use here at work, so I rarely think about it.

@Henry - I don't believe it, I tried several times to get StructKeyExists() to work from a CFM file and I couldn't. It either threw errors or was always false. That's the whole reason for going out and figuring out how to get the info from getMetaData() (and the reason for this blog post). And now, when you posted that, I tried it again and it worked! Oh, well, now I know two ways to do it. :)
# Posted By Eric Cobb | 1/21/11 2:09 PM
Pavan's Gravatar Does anyone know how to get a list of all local cfc variables? The ones that are declared inside a cfc but outside all the functions.
# Posted By Pavan | 3/12/12 7:53 PM
Eric Cobb's Gravatar @Pavan - If you don't specify a scope when setting a variable in a CFC, it automatically gets put in that CFC's "variables" scope. This is true even if the variable is set outside of a function. So, you should be able to see these variables by calling <cfdump var="#variables#" /> from within a function.
# Posted By Eric Cobb | 3/12/12 9:44 PM
John N.'s Gravatar Eric, am glad you were #1 on google results. Thank you for saving hours of search and memory squeeze :)
# Posted By John N. | 8/2/12 2:43 PM