This section provides information that will help you use user-defined functions more effectively.
Consider the following techniques for making your functions available to your ColdFusion pages:
The next section describes other techniques for making UDFs available to your ColdFusion pages.
User-defined function names are essentially ColdFusion variables. ColdFusion variables are names for data. Function names are names (references) for segments of CFML code. Therefore, like variables, functions belong to scopes.
Like ColdFusion variables, UDFs exist in a scope:
<cfset Request.MyFunc = Variables.MyFunc>
You can now use the function from any page in the Request scope by calling Request.MyFunc.
The following table describes the advantages and disadvantages of scopes that you might considering using for your functions:
You can effectively manage functions that are used in application pages and custom tags by doing the following:
cfinclude
tag to include the function definition page on the application page, but do not include it on any custom tag pages.
This way you only need to include the functions once per request and they are available throughout the life of the request. For example, create a myFuncs.cfm page that defines your functions and assigns them to the Request scope using syntax such as the following:
function MyFunc1(Argument1, Argument2)
{ Function definition goes here } Request.MyFunc1 = MyFunc1
The application page includes the myFuncs.cfm page:
<cfinclude template="myfuncs.cfm">
The application page and all custom tags (and nested custom tags) call the functions as follows:
Request.MyFunc1(Value1, Value2)
This section describes how to partially break the rule described in the section "Referencing caller variables". Here, the function defines variables in the Request scope. However, it is a specific solution to a specific issue, where the following circumstances exist:
In these circumstances, you can improve efficiency and save processing time by defining your function's variables in the Request scope, rather than the Function scope. The function tests for the Request scope variables and initializes them if they do not exist. In subsequent calls, the variables exist and the function does not reset them.
The NumberAsString
function, written by Ben Forta and available from www.cflib.org, takes advantage of this technique.
Because function names are ColdFusion variables, you can pass a function's name as an argument to another function. This technique allows a function to use another function as a component. For example, a calling page can call a calculation function, and pass it the name of a function that does some subroutine of the overall function.
This way, the calling page could use a single function for different specific calculations, such as calculating different forms of interest. The initial function provides the framework, while the function whose name is passed to it can implement a specific algorithm that is required by the calling page.
The following simple example shows this use. The binop
function is a generalized function that takes the name of a function that performs a specific binary operation and two operands. The binop
function simply calls the specified function and passes it the operands. This code defines a single operation function, the sum function. A more complete implementation would define multiple binary operations.
<cfscript>
function binop(operation, operand1, operand2) { return (operation(operand1, operand2); } function sum(addend1, addend2) { return addend1 + addend2;} x = binop(sum, 3, 5); writeoutput(x); </cfscript>
When you call a UDF in the body of a tag that has a query
attribute, such as a cfloop query=...
tag, any function argument that is a query column name passes a single element of the column, not the entire column. Therefore, the function must manipulate a single query element.
For example, the following code defines a function to combine a single first name and last name to make a full name. It queries the CompanyInfo database to get the first and last names of all employees, then it uses a cfoutput
tag to loop through the query and call the function on each row in the query.
<cfscript>
function FullName(aFirstName, aLastName) { return aFirstName & " " & aLastName; } </cfscript> <cfquery name="GetEmployees" datasource="CompanyInfo"> SELECT FirstName, LastName FROM Employee </cfquery> <cfoutput query="GetEmployees"> #FullName(FirstName, LastName)#<br> </cfoutput>
You generally use functions that manipulate many rows of a query outside tags that loop over queries. Pass the query to the function and loop over it inside the function. For example, the following function changes text in a query column to uppercase. It takes a query name as an argument.
function UCaseColumn(myquery, colName)
{ var currentRow = 1; for (; currentRow lte myquery.RecordCount; currentRow = currentRow + 1) { myquery[colName][currentRow] = UCase(myquery[colName][currentRow]); } Return ""; }
The following code uses a script that calls the UCaseColumn function to convert all the last names in the GetEmployees query to uppercase. It then uses cfoutput
to loop over the query and display the contents of the column.
<cfscript>
UCaseColumn(GetEmployees, "LastName"); </cfscript> <cfoutput query="GetEmployees"> #LastName#<br> </cfoutput>
You can use the IsCustomFunction
function to determine whether a name represents a UDF. The IsCustomFunction
function generates an error if its argument does not exist. As a result, you must ensure that the name exists before calling the function, for example, by calling the IsDefined
function. The following code shows this use:
<cfscript>
if( IsDefined("MyFunc")) if( IsCustomFunction( MyFunc )) WriteOutput("MyFunc is a user-defined function"); else WriteOutput("Myfunc is defined but is NOT a user-defined function"); else WriteOutput("MyFunc is not defined"); </cfscript>
You do not surround the argument to IsCustomFunction
in quotation marks, so you can use this function to determine if function arguments are themselves functions.
If your user-defined function uses the Evaluate
function on arguments that contain strings, you must make sure that all variable names you use as arguments include the scope identifier. Doing so avoids conflicts with function-only variables.
The following example returns the result of evaluating its argument. It produces the expected results, the value of the argument, if you pass the argument using its fully scoped name, Variables.myname. However, the function returns the value of the function local variable if you pass the argument as myname, without the Variables scope identifier.
<cfscript>
myname = "globalName"; function readname( name ) { var myname = "localName"; return (Evaluate( name )); } </cfscript> <cfoutput> <!--- This one collides with local variable name ---> The result of calling readname with myname is: #readname("myname")# <br> <!--- This one finds the name passed in ---> The result of calling readname with Variables.myname is: #readname("Variables.myname")# </cfoutput>
Structures, queries, and complex objects such as COM objects are passed to UDFs by reference, so the function uses the same copy of the data as the caller. Arrays are passed to user-defined functions by value, so the function gets a new copy of the array data and the array in the calling page is unchanged by the function. As a result, you must handle arrays differently from all other complex data types.
For your function to modify the caller's copy of a structure, query, or object, you must pass the variable as an argument. Because the function gets a reference to the caller's structure, the caller variable reflects all changes in the function. You do not have to return the structure to the caller. After the function, returns, the calling page accesses the changed data by using the structure variable that it passed to the function.
If you do not want a function to modify the caller's copy of a structure, query, or object, use the Duplicate
function to make a copy and pass the copy to the function.
If you want your function to modify the caller's copy of the array, the simplest solution is to pass the array to the function and return the changed array to the caller in the function return
statement. In the caller, use same variable name in the function argument and return variable.
The following example shows how to directly pass and return arrays. In this example, the doubleOneDArray
function doubles the value of each element in a one-dimensional array.
<cfscript>
//Initialize some variables //This creates a simple array. a=ArrayNew(1); a[1]=2; a[2]=22; //Define the function. function doubleOneDArray(OneDArray) { var i = 0; for ( i = 1; i LE arrayLen(OneDArray); i = i + 1) { OneDArray[i] = OneDArray[i] * 2; } return OneDArray; } //Call the function. a = doubleOneDArray(a); </cfscript> <cfdump var="#a#">
This solution is simple, but it is not always optimal:
If you do not use the return
statement to return the array to the caller, you can pass the array as an element in a structure and change the array values inside the structure. Then the calling page can access the changed data by using the structure variable it passed to the UDF.
The following code shows how to rewrite the previous example using an array in a structure. It returns True as a status indicator to the calling page and uses the structure to pass the array data back to the calling page.
<cfscript>
//Initialize some variables. //This creates an simple array as an element in a structure. arrayStruct=StructNew(); arrayStruct.Array=ArrayNew(1); arrayStruct.Array[1]=2; arrayStruct.Array[2]=22; //Define the function. function doubleOneDArrayS(OneDArrayStruct) { var i = 0; for ( i = 1; i LE arrayLen(OneDArrayStruct.Array); i = i + 1) { OneDArrayStruct.Array[i] = OneDArrayStruct.Array[i] * 2; } return True; } //Call the function. Status = doubleOneDArrayS(arrayStruct); WriteOutput("Status: " & Status); </cfscript> </br> <cfdump var="#arrayStruct#">
You must use the same structure element name for the array (in this case Array) in the calling page and the function.
A recursive function is a function that calls itself. Recursive functions are useful when a problem can be solved by an algorithm that repeats the same operation multiple times using the results of the preceding repetition. Factorial calculation, used in the following example, is one case where recursion is useful. The Towers of Hanoi game is also solved using a recursive algorithm.
A recursive function, like looping code, must have an end condition that always stops the function. Otherwise, the function will continue until a system error occurs or you stop the ColdFusion Server.
The following example calculates the factorial of a number, that is, the product of all the integers from 1 through the number; for example, 4 factorial is 4 X 3 X 2 X 1 = 24.
function Factorial(factor)
{ If (factor LTE 1) return 1; else return factor * Factorial(factor -1); }
If the function is called with a number greater than 1, it calls itself using an argument one less than it received. It multiplies that result by the original argument, and returns the result. Therefore, the function keeps calling itself until the factor is reduced to 1. The final recursive call returns 1, and the preceding call returns 2 * 1, and so on until all the initial call returns the end result.
Caution: If a recursive function calls itself too many times, it causes a stack overflow. Always test any recursive functions under conditions that are likely to cause the maximum number of recursions to ensure that they do not cause a stack overflow.
This section discusses the following topics:
try
/catch
or cftry
/cfcatch
blocks and the cfthrow
and cfrethrow
tags to handle and generate exceptionsThe technique you use depends on the circumstances of your function and application and on your preferred programming style. However, most functions should use the second or third technique, or a combination of the two. The following sections discuss the uses, advantages, and disadvantages of each technique, and provides examples of their use.
Your function can test for errors and use the WriteOutput
function to display an error message directly to the user. This method is particularly useful for providing immediate feedback to users for simple input errors. You can use it independently or in conjunction with either of the other two error-handling methods.
For example, the following variation on a "Hello world" function displays an error message if you do not enter a name in the form:
<cfform method="POST" action="#CGI.script_name#">
<p>Enter your Name: <input name="name" type="text" hspace="30" maxlength="30"> <input type="Submit" name="submit" value="OK"> </cfform> <cfscript> function HelloFriend(Name) { if (Name is "") WriteOutput("You forgot your name!"); else WriteOutput("Hello " & name &"!"); return ""; } if (IsDefined("Form.submit")) HelloFriend(Form.name); </cfscript>
The following table describes the code:
In some cases, such as those where the function cannot provide a corrective action, the function cannot, or should not, handle the error directly. In these cases, your function can return information to the calling page. The calling page must handle the error information and act appropriately.
Consider the following mechanisms for providing status information:
Each of these methods can have variants, and each has advantages and disadvantages. Which technique you use should depend on the type of function, the application in which you use it, and your coding style.
The following example, which modifies the function used in "A User-defined function example", uses one version of the status variable method. It provides two forms of error information:
After changes to handle errors, the TotalInterest
function looks like the following. Code that is changed from the example in "A User-defined function example" is in bold.
<cfscript>
function TotalInterest(principal, annualPercent, months, status) { Var years = 0; Var interestRate = 0; Var totalInterest = 0; principal = trim(principal); principal = REReplace(principal,"[\$,]","","ALL"); annualPercent = Replace(annualPercent,"%","","ALL"); if ((principal LE 0) OR (annualPercent LE 0) OR (months LE 0)) { Status.errorMsg = "All values must be greater than 0"; Return -1; } interestRate = annualPercent / 100; years = months / 12; totalInterest = principal*(((1+ interestRate)^years)-1); Return DollarFormat(totalInterest); } </cfscript>
The following table describes the code that has been changed or added to the previous version of this example. For a description of the initial code, see "A User-defined function example".
The code that calls the function now looks like the following. Code that is changed from the example in "A User-defined function example" is in bold.
<cfset status = StructNew()>
<cfset myInterest = TotalInterest(Form.Principal, Form.AnnualPercent,Form.Months, status)> <cfif myInterest EQ -1> <cfoutput> ERROR: #status.errorMsg#<br> </cfoutput> <cfelse><cfoutput>
Loan amount: #Form.Principal#<br> Annual percentage rate: #Form.AnnualPercent#<br> Loan duration: #Form.Months# months<br> TOTAL INTEREST: #myInterest#<br></cfoutput>
</cfif>
The following table describes the code that has been changed or added:
UDFs written in CFScript can handle exceptions using the try
and catch
statements. UDFs written using the cffunction
tag can use the cftry
, cfcatch
, cfthrow
, and cfrethrow
tags. Using exceptions corresponds to the way many functions in other programming languages handle errors, and can be an effective way to handle errors. In particular, it separates the functional code from the error-handling code, and it can be more efficient than other methods at runtime, because it does not require testing and branching.
Exceptions in UDFs have the following two dimensions:
A UDF should use try/catch blocks to handle exceptions in the same conditions that any other ColdFusion application uses try/catch blocks. These are typically circumstances where the function uses an external resource, such as a Java, COM, or CORBA object, a database, or a file. When possible, your application should prevent, rather than catch, exceptions caused by invalid application data. For example, the application should prevent users from entering a zero value for a form field that is used to divide another number, rather than handling exceptions generated by dividing by zero.
When ColdFusion catches an exception, the function can use any of the following methods to handle the exception:
cffunction
tag, throw a custom exception, or rethrow the exception so that it will be caught by the calling ColdFusion page. For more information on throwing and rethrowing exceptions, see "Handling runtime exceptions with ColdFusion tags," in Chapter 14.
If you define your function using the cffunction
tag, you can use the cfthrow
and cfrethrow
tags to throw errors to the page that called the function. You can use this technique whenever your UDF identifies an error, instead of displaying a message or returning an error status. For example, the following code rewrites the example from "Providing status information" to use the cffunction
tag and CFML, and to throw and handle an exception if any of the form values are not positive numbers.
The lines that identify invalid data and throw the exception are in bold. The remaining lines are equivalent to the CFScript code in the previous example. However, the code that removes unwanted characters must precede the error checking code.
<cffunction name="TotalInterest">
<cfargument name="principal" required="Yes">
<cfargument name="annualPercent" required="Yes">
<cfargument name="months" required="Yes">
<cfset principal = trim(principal)>
<cfset principal = REReplace(principal,"[\$,]","","ALL")>
<cfset annualPercent = Replace(annualPercent,"%","","ALL")>
<cfif ((principal LE 0) OR (annualPercent LE 0) OR (months LE 0))> <cfthrow type="InvalidData" message="All values must be greater than 0."> </cfif><cfset years = 0>
<cfset interestRate = 0>
<cfset totalInterest = 0>
<cfset interestRate = annualPercent / 100>
<cfset years = months / 12>
<cfset totalInterest = principal*
(((1+ interestRate)^years)-1)>
<cfreturn DollarFormat(totalInterest)>
</cffunction>
The code that calls the function and handles the exception looks like the following. The changed lines are in bold.
<cftry>
<cfset status = StructNew()> <cfset myInterest = TotalInterest(Form.Principal, Form.AnnualPercent, Form.Months, status)> <cfoutput> Loan amount: #Form.Principal#<br> Annual percentage rate: #Form.AnnualPercent#<br> Loan duration: #Form.Months# months<br> TOTAL INTEREST: #myInterest#<br> </cfoutput> <cfcatch type="InvalidData"> <cfoutput> #cfcatch.message#<br> </cfoutput> </cfcatch> </cftry>