Twitter: raymondcamden


Address: Lafayette, LA, USA

Ask a Jedi: Custom Tags, OnRequestStart, UDFs, and Antimatter Engines

02-07-2008 7,631 views ColdFusion 20 Comments

Asa asks:

I have a question for your ColdFusion Holiness. I have a file containing all my UDFs and I'm including it in onRequestStart:

<cffunction name="onRequestStart">
<!--- Include Global Functions --->
<cfinclude template="/global_functions.cfm">
</cffunction>

But the functions are not available inside my Custom Tags. A Google search said something about the wrong variable scope. Doesn't onRequestStart get called for custom tags too? What's the best way to be able to use them in my custom tags?

There are a few things in play here, so let's tackle it one by one. First off, if you cfinclude a file of UDFs via onRequestStart, the UDFs will not be available to your CFM files. It won't be... unless you also have onRequest. The presence of onRequest in Application.cfc has the side effect of copying your Application.cfc methods and variables into your templates Variables scope.

So I'd be willing to bet you did this - and noticed you could run your UDFs just fine until you ran a custom tag. Custom tags have their own Variables scope. You could access the Variables scope of the parent by using the Caller scope. So to run a UDF named turnBritneyOn, you would use:

view plain print about
1<cfset result = caller.turnBritneyOn()>

This is icky though. What I recommend instead is a simpler approach. Copy your UDFs into the Request scope. This lets custom tags run the UDFs easier as well. So take this simple, very short UDF library:

view plain print about
1<cfscript>
2function doItBritneyStyle() {
3    return "Did it again...";
4}
5
</cfscript>

I'd modify it like so...

view plain print about
1<cfscript>
2req = structGet("request.udfs");
3function doItBritneyStyle() {
4    return "Did it again...";
5}
6req.doItBritneyStyle = doItBritneyStyle;
7
</cfscript>

The req=structGet line simply creates a pointer to a structure called request.udfs. If the structure doesn't exist, it is created. If it does exist, I simply get a pointer to it. This lets me then do req.UDF = UDF after each udf as a quick way to copy each UDF into the request scope.

On my templates and custom tags, I can then just do:

view plain print about
1<cfset result = request.udfs.doItBritneyStyle()>

Lastly - onRequestStart, or any of the other Application.cfc methods, are not called for a custom tag call since a custom tag simply runs more CF code. It doesn't start a new request. The same applies to calling a UDF or cfincluding a file.

Oh... antimatter engines. Yes, I did mention them. This post actually has little to do with antimatter, but after reading this list today, it's definitely on my mind: Stupid Plot Tricks

My favorite: 140. If I board a derelict ship, and it appears that the former crew and passengers all died in some horrible fashion, I will immediately leave the ship, destroy it, and toss the wreckage into the nearest stellar object.

20 Comments

  • Commented on 02-08-2008 at 6:36 AM
    I had a revelation one time that I think will help people who don't 100% understand the OnRequest() event method; when you include the requested template as part of the OnRequest() method, you are creating a "mixin" for that method call. In lay terms, you are taking the target template and making it PART OF the Application.cfc. What people have to realize is that the requested template that executes is actually executing as if it WERE the Application.cfc. Therefore, the Application.cfc THIS and VARIABLES scope aren't actually getting "copied" to the template's VARIABLES scope because they are not two different things - the Application.cfc's VARIABLES scope and the requested template's VARIABLES scope are the same objects.

    When I had this mental breakthrough, it make working with the OnRequest() method so much easier to understand.
  • Gareth #
    Commented on 02-08-2008 at 7:08 AM
    So it's just like putting it as a cfinclude inside the application.cfc file (haven't really had an option to use the application.cfc file yet)?
  • Commented on 02-08-2008 at 7:42 AM
    It's exactly like that:

    <cffunction name="OnRequest">
    <cfargument name="Page" />
    <cfinclude template="#ARGUMENTS.Page#" />
    </cffunction>
  • John Farrar #
    Commented on 02-08-2008 at 8:58 AM
    Ray, one interesting thing I found during CF7 (or 6, not sure) was the very low performance hit for instantiating bunches of UDFs into request scope as you mentioned. I am not sure that over all this is any more efficient than creating CFCs in CF8, has anyone tested that out for performance?
  • Commented on 02-08-2008 at 10:14 AM
    I tend to NOT get concerned about ms to ms performance checks. I'd say use whichever method you want. I can say that from time to time, I've needed basic UDFs in CFCs. In that case, my UDF library becomes a CFC I can pass to other CFCs if they need it, and in my 'normal' CFML files, I just tweak the call from request.udf.goo() to application.udf.goo() or somesuch.
  • Commented on 02-09-2008 at 1:43 PM
    A main purpose of the application scope is to have only one instance of code initialized in memory (across multiple requests) to preserve memory resources on the server, vs having multiple instances of the same code unnecessarily multiplied in memory by the number of requests.

    Wouldn't putting the udf object into the application scope (using onApplicationStart()) and referencing it directly there (using application.udf.foo()) be more efficient (memory wise) than having it in the variables and/or request scopes of multiple requests?
  • John Farrar #
    Commented on 02-09-2008 at 2:16 PM
    That is one of the development gives and takes. That is indeed the most efficient. (using Application) Yet, there should also be a mechanism in place to make updates easy to keep things fresh.
  • Commented on 02-09-2008 at 3:06 PM
    <<That is one of the development gives and takes....Yet, there should also be a mechanism in place to make updates easy to keep things fresh.>>

    The give and take shouldn't be too onerous.

    In onRequestStart():

    <cfif StructKeyExists(url,"reinitudf")>
       <cflock scope="application" type="exclusive" timeout="10">
          <cfset application.udf=createObject('component','udf').init() /><!--- or however you load the udf --->
       </cflock>
    </cfif>

    This updates application.udf without updating all the other application variables.
  • Commented on 02-13-2008 at 7:45 AM
    After buying FusionDebug, I noticed that my UDFs file (til now called as a cfinclude on any relevant .cfm page), created a gazillion function variables that I do not want to see, esp. since these are general use UDFs and the functions.cfm file is reused between projects.

    I went throuth and updated each function to include
    <code>req = structGet("request.udfs");
    </code> at the beginning of the <cfscript> for the page, and added a
    <code>req.foo=foo;</code> after each function.

    Now.... Just where do I add this to the application.cfc? OnRequestStart? As an Include? Hmmmm, This is where I am stuck.
  • Commented on 02-13-2008 at 9:37 AM
    @Tami

    To limit the function variables to the function, make sure you use var when setting any variables inside the function (e.g., <cfset var variable="">).

    (Someone please correct me if I'm wrong..) this is what I would recommend as the most efficient setup for global functions stored in a udf.cfm file:

    1) Convert the cfm file to a cfc component file
    a) change the file extension from .cfm to .cfc
    b) add a simple <cfcomponent> tag to the file (see example cfc component file below)

    2) Add these lines to the onApplicationStart() function in your application.cfc:

    <!--- Load udf as an application variable to be shared across requests --->
    <cfset application.udf=createObject('component','udf').init() />

    [Note: This assumes the udf.cfc is stored in the same directory as the application.cfc file.]

    3) Add these lines to the onRequestStart() function in your application.cfc (to allow you to reload the udf functions if you change them by adding ?reinitudf to the query string of any page without having to restart the application):

    <!--- Reload udf if ?reinitudf is in the query string --->
    <cfif StructKeyExists(url,"reinitudf")>
       <cflock scope="application" type="exclusive" timeout="10">
          <cfset application.udf=createObject('component','udf').init() />
       </cflock>
    </cfif>

    4) Then to call a udf function, simply do this in any page:

    <cfset result=application.udf.addTwoNumbers(1,2) />

    or

    <cfoutput>#application.udf.addTwoNumbers(1,2)#</cfoutput>

    Here is an example CFC udf file:

    <cfcomponent name="udf" hint="Global Functions">

       <!--- This function is used to reload the udf component if any functions are modified --->
       <cffunction name="init" access="public" output="false" returntype="struct">
          <cfreturn this />
       </cffunction>

       <cffunction name="addTwoNumbers" access="public" returntype="numeric" hint="Adds two numbers and returns the sum.">
          <cfargument name="numberOne" type="numeric" required="yes" />
          <cfargument name="numberTwo" type="numeric" required="yes" />
          <!--- Note the var in front of the variable name --->
          <cfset var sum=numberOne+numberTwo />

          <cfreturn sum />
       
       </cffunction>

    </cfcomponent>
  • Commented on 02-13-2008 at 11:11 AM
    Thanks Adam, This is about what I need, esp. the application.cfc settings.
    However, the UDF isn't set up as
    <component>
    <cffunction name='udf1'...>
    :
    </cffunction>
    <cffunction name='udf2'...>
    :
    <cffunction>

    It is currently set up as single file w/ about 50 functions

    <cfscript>
    req = structGet("request.udfs");
    function udf1(var1){
    return foo; {
    req.udf1=udf1;

    function udf2{var1,var2){
    return foo;
    req.udf2=udf2;
    </cfscript>


    Questions:
    1) why would I want these to be storede as application level udfs instead of request? Seems like a lot of overhead tied up for functions that may or may not be called.
    2) Can I stick all the functions within one <cffunction> call, or do I need to rewrite each function call to be an individual <cffunction><cfscript>function xx{ }</cfscript></cffunction>? (BIG PITA factor to redo all these dinky functions like that)


    So can my functions.cfm converted into functions.cfc look
    like:
    <component>
    <cfscript>
    req = structGet("request.udfs");
    function udf1{}
    req.udf1=udf1;
    function udf2{}
    req.udf2=udf2; ...
    </cfscript>
    </component>

    or

    do I have to set it up as
    <cfcomponent>
    <cffuntion name='udf1'....>
    <cfreturn foo>
    </cffunction>

    <cffunction name='udf2'...>
    <cfreturn foo>
    </cffunction> ...(ad naseum)
    </cfcomponent>
  • Commented on 02-13-2008 at 11:18 AM
    <snip>
    To limit the function variables to the function, make sure you use var when setting any variables inside the function (e.g., <cfset var variable="">).
    </snip>

    Oh, and I forgot, the fusion debug variables are the functions, not the vars in the functions. You can see it here:
    http://hhwd.com/screenshots/fusionfnvars.gif
  • Commented on 02-13-2008 at 12:10 PM
    @Tami - the functions in the udf.cfc can be written either with <cffunction> tags or as scripted functions using <cfscript>. You can even have a mix of both if you want.

    So the earlier example ucf.cfc could look like this:

    <cfcomponent name="udf" hint="Global Functions">

       <cfscript>
       // This function is used to reload the udf component if any functions are modified
       function init() {
          return this;
       }
       function addTwoNumbers(numberOne,numberTwo) {
          var sum=numberOne+numberTwo;
          return sum;
       }
       </cfscript>

    </cfcomponent>

    Note 1: if you're storing the udf.cfc in the application scope, these lines are not needed and should be removed to free up memory:

    req = structGet("request.udfs");
    req.udf1=udf1;

    Note 2: A benefit of using <cfscript> is that the code is more succinct (less code) and may run a little faster.

    The benefit of using <cffunction> tags is that you have more options and built-in functionality for data type validation, security (including exposing the function as a web service), and documentation.
  • Commented on 02-13-2008 at 12:20 PM
    @Adam....
    <smack> Perfect... Let me get to work and see how it comes out. thanks for the clarifications! :)
  • Commented on 02-13-2008 at 1:21 PM
    @Tami - Regarding memory..

    I store most, if not all, global functions in my application.udf, even if each function is used only on a single page. My file is about the size of an average page request, so it stores an extra page request in memory, which is usually not a strain on resources.

    The alternative is to load a function each time a page request is loaded. (See method below.) The potential downside is that the memory used with this method increases as your number of concurrent page requests grows.

    To keep things simple, I just put all the functions in the application.udf and access them as application.udf.function(), for these reasons:

    1) the syntax is universal: application.udf.function()
    (I don't have to think about it)
    2) putting all udf functions in the application scope is limited to a page view of memory max
    (I don't have to worry about it)
    3) as mentioned, loading udf functions into the request scope with each page request has the possibility of using more memory, and with any significant amount of traffic will use more memory, than the application scope
    4) using the application scope eliminates the need to
    a) determine on which pages to load a udf function into the request scope
    b) place the initialization code in each desired page
    c) maintain the initialization code in each page
    d) yes, the full udf can be loaded into the request scope using onRequestStart(), but then you guarantee you will use more memory than using the application scope

    For functions that are seldomly used, I may put them in their own cfc, and call them on the page:

    file: /rarefunction.cfc

    <cfset request.rareFunction=createObject('component',rareFunction) />
    <cfoutput>#request.rareFunction.theFunction(1,2,3)#</cfoutput>

    Or if the function will be used only once during the request:

    <cfoutput>#createObject('component',rareFunction).theFunction(1,2,3)#</cfoutput>
  • Commented on 02-13-2008 at 2:38 PM
    And... this all tidies up some issues I have been having for a while, but too lazy to really deal with elegantly:

    While using <cfinclude> for my functions is quick and dirty (and a holdover from CF5)if I included it in a DW template (thus every page created has the <cfinclude template='function.cfm'> call in it), this method didn't have universal scoping.

    When I wanted to call a udf in one of my other CFCs I invariably had to add a <cfinclude> for the functions there, which made the cfc not a truly encapsulated piece of code. Your method and Ray's above both clean up the redundancy issues and the scoping issues :).

    Hero cookies to Adam for today (and by proxy, Ray). It all works terrifically.
  • Commented on 02-13-2008 at 2:54 PM
    @Tami - Glad to hear.

    @Raymond and Asa - Thanks for the thread.
  • Commented on 12-27-2008 at 4:54 PM
    I put the application.udf into effect and all runs great - until I ran a sub folder (/admin) using an Application.cfc that extends the root level Application.cfc (via a proxy cfc)

    When running the /admin folder all seemed well until I reset the application. At which point I got the error .... Element UDF is undefined in a Java object of type class [Ljava.lang.String;.

    It seems that the Application.udf was not instantiated when the application reset. My /admin/Application.cfc file does not contain an onApplicaitonStart Function so I just assumed it would use the root level function - which includes the application.udf declaration.

    So I have a question for anyone... If my root level Application.cfc contains <code>   <cffunction name="onApplicationStart" returnType="boolean" output="false">
          <cfset application.gadget = createObject("component","org.ews.root").init('xxxxx')>
          <cfset application.udf = createObject('component','org.ews.udf').init() />
          <cfreturn true>
       </cffunction></code>
    Should I declare the gadget and udf variables in the /admin/Applicaiton.cfc as well in order to make this work - or is there something else I might be missing?
    And if I should place the declaration in both files - How do I do that? Should it maybe be a cfmodule call or the like since you can't call .org.ews.udf

    Thanks all.
    Chris
  • Commented on 12-27-2008 at 5:44 PM
    Did you give the Admin Application.cfc a new name?
  • Commented on 12-27-2008 at 6:01 PM
    No Ray I did not. It would seem that it is running the declaration from the root level app.cfc. I forgot to upload a new version of the root application to the server.

    So at this point that works but I think I really have the business logic all messed up as to how to set the session.admin.(loggedin, userid, name, etc).

    So I will fight with that for a few hours before I get frustrated and ask for help from the world. Thanks for checking in.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty