Tushar Bhaware
Tushar Bhaware

Reputation: 2525

How to call a function from another function using the reference of object of a cfc?

Sorry about the question phrase. I couldn't find better way to describe it. But my problem is as follows:

I have 3 cfc's namely settings.cfc, prices.cfc and helpers.cfc. These cfc's extend 4th cfc controller.cfc. The helper.cfc is as follows:

<cfcomponent extends="Controller">
    <cffunction name="formatCurrency">
        <cfset formattedCurrency = 1 />    
        <cfreturn formattedCurrency>        
    </cffunction>
    <cffunction name="processTemplateVariables">
       <cfargument name="templateText" default="defaultText" >
       <cfset formatCurrency() />
       <cfreturn formattedCurrency >        
    </cffunction>
</cfcomponent>

The settings.cfc has a setApplicationVariables method which we use to set the application level variables. In this cfc, i have created an object of helpers.cfc and put that object into the application scope. The settings.cfc is as follows:

<cfcomponent extends="Controller">
   <cffunction name="setApplicationVariables">    
      <cfset application.helpers = createObject("component","controllers.Helpers") />  
   </cffunction>
</cfcomponent>

The settings.cfc gets invoked on application start which in turn creates a object of helpers.cfc and put it into the application scope.

We create a reference to the method ProcessTemplateVariables in the controller.cfc as follows:

<cfcomponent extends="Wheels">
   <cfset getFormattedCurrency = application.helpers.processTemplateVariables >
</cfcomponent>

In the prices.cfc, we use this reference to call the function processTemplateVariables, which it does. But it does not call the function formatCurrency that is called inside from the processTemplateVariables and it throws error "variable formatCurrency is undefined".

But if i use the application.helpers.processTemplateVariables(templateText="someText"), it works.
It also works, when i use cfinvoke as bellow:

<cfinvoke method="processTemplateVariables" component="controllers.helpers" templateText="someText" returnvariable="content">

The prices.cfc is as follows:

<cfcomponent extends="Controller">
    <cffunction name="index">
        <!--- does not work, throws 'the formatCurrency() variable is undefined' --->
        <cfdump var="#getFormattedCurrency("someText")#"><cfabort>
        <!--- works --->    
        <cfinvoke method="processTemplateVariables" component="controllers.helpers" templateText="someText" returnvariable="content">
        <!--- works --->
        <cfset application.helpers.processTemplateVariables("someText") />   
    </cffunction>
</cfcomponent>

I am not sure why using reference is not working. Sorry about the earlier confusion but your comments made me dig deeper and i could found out that it was reference that was culprit. Is there any way to make this work with reference, that would be cool?

Upvotes: 5

Views: 4008

Answers (2)

Anurag
Anurag

Reputation: 1036

The limitation is understood well from the answer by Leigh.

To focus on your requirement here: using a short name alias for the functions, You can still use your original code, with a little trick of adding all the dependent functions as a reference, so that they are available inside the controller.cfc scope similar to the getFormattedCurrency.

Edit 1:

<cfcomponent extends="Wheels">
   <cfset getFormattedCurrency = application.helpers.processTemplateVariables />
   <cfset formatCurrency = application.helpers.formatCurrency />
</cfcomponent>

Now, for the fun part, it's quite possible to access a function from another cfc and still preserve all the dependencies. I was surprised as well when I recalled an amazing post by Bennadel here. This is simply amazing. But to warn you this practice is discouraged. So I took up with your setup and went ahead anyway. And it all worked like a charm with no issues encountered so far(but I'm quite sure there could arise some complications). The point of problem with original usage was that the function has a dependency on formatCurrency, which does not exist in the "context" of the calling page as educated by Leigh in his answer. So what if you can just copy scoped objects or even the functions from another component to you controller.cfc, sounds weird and amazing at the same time but it's possible using the <cfinclude> tag inside the controller.cfc (note: not encouraged) based upon the idea what Bennadel used for his example.

Edit2:

Your controller.cfc should look something like this:

<cfcomponent extends="Wheels">
   <!--- Placed inside the controller's scope itself, outside every other function --->
   <cfinclude template="helpers.cfc" />
   ....<rest of your code>....
   ...........
</cfcomponent>

Note that you don't even need to create a short-hand alias for the functions now. All the components and views can directly use the function name as you intended. No need to point out that there would be chances of Naming collision if any other component extending the Controller.cfc has a function with the same name as any of the function inside the component library that is being imported. But those can be solved by following more specialization for the functions into multiple components OR as simple as using a prefix as a part of the coding standard to the function names to avoid any such future scenarios.

Upvotes: 1

Leigh
Leigh

Reputation: 28873

Update:

This blog entry (by Adam Cameron) has a better description. To summarize:

.. it pulls the method out of the CFC, so it will be running in the context of the calling code, not the CFC instance. Depending on the code in the method, this might or might not matter.

In your specific case it does matter. The function has a dependency on formatCurrency, which does not exist in the "context" of the calling page, and that is why you get an "undefined" error.


(From comments)

Yeah, I am pretty sure you cannot do that. Each function is compiled into an individual class: specifically a static inner class. (You can see the inner class names if you dump out the function name without parenthesis ie #application.helpers.formatCurrency#) In other words, it is disconnected from any specific instance - and by extension - the other functions.

When you create an instance of the component, all of the functions are stored its variables scope. So when you invoke "processTemplateVariables" - from within the instance - it has access to the other functions via the component's variables scope. When your code creates a reference to that function, what you are actually getting is completely disconnected from the parent instance ie application.helpers. So it won't have access to any of the other functions. Hence why you get an "undefined" error.

Upvotes: 2

Related Questions