Inheriting CFC functionality from components outside the webroot
I’ve been working on setting up a common set of components at my workplace which we can leverage in some of the dozens of one-off applications we have developed in-house. In the current environment, each application lives largely in its own world, with separate application settings. Yet many of them share a great deal of code, access the same database, merely presenting a different view to the users depending on their roles in or out of the company.
This is a ripe opportunity to develop some more rigorously controlled components which would allow us to reuse code and reduce the number of bugs.
Here’s the problem. I’ve got a structure like this:
/ webroot – this contains a base, public facing application with it’s own application.cfm (not cfc)
–/subapplication — separate app with application cfc and authentication
–/internally facing subapp
–/–/internally facing sub/sub/app
Each of these has their own application cfm functionality which I can’t disturb on an overall level (it’s fifty or more working apps). We’ll be replacing them and updating them a few at a time, at which time I intend to work them into the overall framework.
So, my constraints are that when I create a new app, it resides below the webroot, and its application.cfc will exist at it’s own level. But I want to be able to have components which can inherit methods from parent classes available to all the apps.
Here’s the problem: when you create a Coldfusion component, you create it from a directory beneath the one the application resides in. Here is a nice description of Coldfusion components and objects and how they work. Even more useful information about ORM properties and using Coldfusion objects in that context is here. It’s beyond the scope of my work right now.
Here’s the structure I’m looking for. First, in my shared component store, I’m creating a CFC which has a lot of shared functions in it. For purposes of our example, I’m only including on function, init()
<cfcomponent output="no" hint=" /component-store/com/app/functions.cfc This component handles utility functions which are non-application specific. "> <cffunction name="INIT" access="public" output="false" returntype="void"> <cfargument name="salesdb" required="no" default="SalesLink"> <cfargument name="edidb" required="no" default="EDI"> <cfargument name="corpdb" required="no" default="Corporate"> <cfargument name="test_status" type="boolean" required="no" default="no"> <cfset variables.salesdb = #salesdb#> <cfset variables.edidb = #edidb#> <cfset variables.corpdb= #corpdb#> <cfset variables.test_status = #arguments.test_status#> cffunction>
So basically my component here doesn’t actually do anything other than initialize itself. But the real component has a bunch of useful utility functions which will be called from multiple applications, each of which will create a new instance of the object in its own scope. Now, remember my requirement is that I be able to instantiate this even though it’s not in the path of my application. So at the webroot level — above my application directory, I’ll create a /component-store directory which is not accessible through the webserver.
Now, inside my application directory, I’m going to create a functions.cfc intended to extend that global function cfc. Inside my local one, I’m going to have some local application only functions. Let’s create that cfc:
<cfcomponent output="false" extends="component-store.components.functions" persistent="false"> <!--- report errors ---> <cffunction name="ReportProblem" output="false" access="public" returntype="void"> <cfargument name="message" required="yes" type="string"> <cfargument name="session_vars" required="yes" type="struct"> <cfargument name="other_vars" requored="yes" type="struct"> <cfsavecontent variable="contents"> <cfoutput> User #session_vars.username# subnitted the following problem report.<br /> <br /> #arguments.message# <br /> <br /> <h3>Other variables and information</h3> </cfoutput> <h1>Session</h1> <cfdump var="#session_vars#"> <h1>Variables</h1> <cfdump var="#other_vars#"> </cfsavecontent> <cfmail to="whoever" from="whoever" Subject="Error Report" type="html"> #contents# </cfmail> <cfreturn> </cffunction> cfcomponent>
Okay, so this is a very simple component. It extends my already existing cfc, and adds a new function called ReportProblem() which emails a problem report based on whatever information is passed to it. So let’s go ahead and create this object and see what happens. I’m going to create the functions object in the application scope:
application.functions=createObject("component", "com.app.functions").init(salesdb,edidb,corpdb, application.test_status);
and… finally, I’ll create a index.cfm with two lines:
So here’s the output:
Huh. What we should have seen under functions is a structure representing the component, and it should have included our ReportProblem method as well as the init() method inherited from the base component. Let’s see what happens if we call a method on it. I’ll add this line to the page. I’m expecting this to generate an error:
<cfset application.functions.ReportProblem("Help Me",session,variables)>
|Message||Element FUNCTIONS is undefined in a Java object of type class [Ljava.lang.String;.|
Basically, what’s happening here is when I create the object, coldfusion can’t instantiate it, because it can’t find the cfc it’s extending. So I need to go back to the drawing board. I’ll revert index.cfm back to it’s state where it’s dumping the application scope. But how can I get my functions.cfc to extend the global functions.cfc?
In my Google search from there, I came across this post from Ben Nadel, talking about extending application.cfc with a proxy component. Light bulb! You can include, inside a CFC, a cfinclude to another path.
So, here’s what I did. First, I created a proxy directory below my application path:
In the proxy directory, I’ll create a proxy functions.cfc. It only contains one line that actually does anything..
<cfcomponent> <cfinclude template="/component-store/components/functions.cfc"> </cfcomponent>
No, for my functions. cfc in this application, I’m going to change it’s extends property to point to my proxy cfc as follows:
<cfcomponent output="false" extends="com.app.proxy.functions" persistent="false">
Let’s call it and see what happens! Boom! First, an error, because I had a typo: the actual path beneath my cfc is proxy.functions, not com.app.proxy.functions. Try again and:
Remember our initial code. The original global version of the cfc has the init function. The local application specific cfc contains the ReportProblem function. Because we’ve got inheritance working now, it’s indistinguishable to the application which one you are calling.
There’s a probably a much more elegant way to accomplish this, but this did the trick!