Example: using nested tags, cfthrow, and cfrethrow

The following example shows many of the discussed techniques including nested cftry blocks and the cfthrow and cfrethrow tags. The example includes a simple calling page and a custom tag page:

  • The calling page does little more than call the custom tag with a single attribute, a name to be looked up in a database. It does show, however, how a calling page can handle an exception thrown by the custom tag.

  • The custom tag finds all records in the cfdocexamples database with a matching last name, and returns the results in a Caller variable. If it fails to connect with the main database, it tries a backup database.

The calling page

The calling page represents a section from a larger application page. To keep things simple, the example hard-codes the name to be looked up.

<cftry> 
    <cf_getEmps EmpName="Jones"> 
    <cfcatch type="myApp.getUser.noEmpName"> 
        <h2>Oops</h2> 
        <cfoutput>#cfcatch.Message#</cfoutput><br> 
    </cfcatch> 
</cftry> 
<cfif isdefined("getEmpsResult")> 
    <cfdump var="#getEmpsResult#"> 
</cfif>

Reviewing the code

The following table describes the code:

Code

Description

<cftry> 
    <cf_getEmps EmpName="Jones">

In a cftry block, calls the cf_getEmps custom tag (getEmps.cfm).

<cfcatch type="myApp.getUser.noEmpName"> 
    <h2>Oops</h2> 
    <cfoutput>#cfcatch.Message#</cfoutput><br> 
</cfcatch>

If the tag throws an exception indicating that it did not receive a valid attribute, catches the exception and displays a message, including the message variable set by the cfthrow tag in the custom tag.

<cfif isdefined("getEmpsResult")> 
    <cfdump var="#getEmpsResult#"> 
</cfif>

If the tag returns a result, uses the cfdump tag to display it. (A production application would not use the cfdump tag.)

The custom tag page

The custom tag page searches for the name in the database and returns any matching records in a getEmpsResult variable in the calling page. It includes several nested cftry blocks to handle error conditions. For a full description, see Reviewing the code section, following the example:

Save the following code as getEmps.cfm in the same directory as the calling page.

<!--- If the tag didn't pass an attribute, throw an error to be handled by 
            the calling page ---> 
<cfif NOT IsDefined("attributes.EmpName")> 
    <cfthrow Type="myApp.getUser.noEmpName" 
        message = "Last Name was not supplied to the cf_getEmps tag."> 
    <cfexit method = "exittag"> 
<!--- Have a name to look up ---> 
<cfelse> 
<!--- Outermost Try Block ---> 
    <cftry> 
 
<!--- Inner Try Block ---> 
        <cftry> 
<!--- Try to query the main database and set a caller variable to the result ---> 
            <cfquery Name = "getUser" DataSource="cfdocexamples"> 
                SELECT *  
                FROM Employee 
                WHERE LastName = '#attributes.EmpName#' 
            </cfquery> 
            <cfset caller.getEmpsResult = getuser> 
<!--- If the query failed with a database error, check the error type 
            to see if the database was found --->             
            <cfcatch type= "Database"> 
                <cfif (cfcatch.SQLState IS "S100") OR (cfcatch.SQLState IS 
                    "IM002")> 
 
<!--- If the database wasn't found, try the backup database ---> 
<!--- Use a third-level Try block ---> 
                    <cftry> 
                        <cfquery Name = "getUser" DataSource="cfdocexamplesBackup"> 
                            SELECT *  
                            FROM Employee 
                            WHERE LastName = '#attributes.EmpName#' 
                        </cfquery> 
                            <cfset caller.getEmpsResult = getuser> 
 
<!--- If still get a database error, just return to the calling page 
            without setting the caller variable. There is no cfcatch body. 
            This might not be appropriate in some cases.  
            The Calling page ends up handling this case as if a match was not 
            found ---> 
                            <cfcatch type = "Database" /> 
<!--- Still in innermost try block. Rethrow any other errors to the next 
            try block level --->                         
                        <cfcatch type = "Any"> 
                            <cfrethrow> 
                        </cfcatch> 
                    </cftry> 
 
<!--- Now in second level try block. 
            Throw all other types of Database exceptions to the next try  
                block level ---> 
                <cfelse> 
                    <cfrethrow> 
                </cfif> 
            </cfcatch> 
<!--- Throw all other exceptions to the next try block level ---> 
            <cfcatch type = "Any"> 
                    <cfrethrow> 
            </cfcatch> 
        </cftry> 
 
<!--- Now in Outermost try block.  
         Handle all unhandled exceptions, including rethrown exceptions, by 
            displaying a message and exiting to the calling page.---> 
        <cfcatch Type = "Any"> 
            <h2>Sorry</h2> 
            <p>An unexpected error happened in processing your user inquiry. 
                Please report the following to technical support:</p> 
            <cfoutput> 
                Type: #cfcatch.Type# 
                Message: #cfcatch.Message# 
            </cfoutput> 
            <cfexit method = "exittag"> 
        </cfcatch> 
    </cftry>     
</cfif>

Reviewing the code

The following table describes the code:

Code

Description

<cfif NOT IsDefined("attributes.EmpName")> 
<cfthrow Type="myApp.getUser.noEmpName" 
    message = "Last Name was not supplied to the cf_getEmps tag."> 
<cfexit method = "exittag">

Makes sure the calling page specified an EmpName attribute. If not, throws a custom error that indicates the problem and exits the tag. The calling page handles the thrown error.

<cfelse> 
<cftry>

If the tag has an EmpName attribute, does the remaining work inside an outermost try block. The cfcatch block at its end handles any otherwise-uncaught exceptions.

<cftry> 
<!--- Try to query the main database and set a caller variable to the result ---> 
    <cfquery Name = "getUser" DataSource="cfdocexamples"> 
        SELECT *  
        FROM Employee 
        WHERE LastName = '#attributes.EmpName#' 
    </cfquery> 
    <cfset caller.getEmpsResult = getuser>

Starts a second nested try block. This block catches exceptions in the database query.

If there are no exceptions, sets the calling page’s getEmpsResult variable with the query results.

<cfcatch type= "Database"> 
    <cfif (cfcatch.SQLState IS "S100") OR (cfcatch.SQLState IS "IM002")> 
     <cftry> 
        <cfquery Name = "getUser" DataSource="cfdocexamplesBackup"> 
        SELECT *  
        FROM Employee 
        WHERE LastName = '#attributes.EmpName#' 
        </cfquery> 
        <cfset caller.getEmpsResult = getuser>

If the query threw a Database error, checks to see if the error was caused by an inability to access the database (indicated by an SQLState variable value of S100 or IM002).

If the database was not found, starts a third nested try block and tries accessing the backup database. This try block catches exceptions in this second database access.

If the database inquiry succeeds, sets the calling page’s getEmpsResult variable with the query results.

<cfcatch type = "Database" />

If the second database query failed with a database error, gives up silently. Because the Database type cfcatch tag does not have a body, the tag exits. The calling page does not get a getEmpsResult variable. It cannot tell whether the database had no match or an unrecoverable database error occurred, but it does know that no match was found.

<cfcatch type = "Any"> 
    <cfrethrow> 
</cfcatch> 
</cftry>

If the second database query failed for any other reason, throws the error up to the next try block.

Ends the innermost try block

<cfelse> 
    <cfrethrow> 
    </cfif> 
</cfcatch>

In the second try block, handles the case in which the first database query failed for a reason other than a failure to find the database.

Rethrows the error up to the next level, the outermost try block.

<cfcatch type = "Any"> 
    <cfrethrow> 
</cfcatch> 
</cftry>

In the second try block, catches any errors other exceptions and rethrows them up to the outermost try block.

Ends the second try block.

<cfcatch Type = "Any"> 
    <h2>Sorry</h2> 
    <p>An unexpected error happened in processing your user inquiry. Please report the following to technical support:</p> 
    <cfoutput> 
    Type: #cfcatch.Type# 
    Message: #cfcatch.Message# 
    </cfoutput> 
    <cfexit method = "exittag"> 
</cfcatch> 
</cftry>     
</cfif>

In the outermost try block, handles any exceptions by displaying an error message that includes the exception type and the exception’s error message. Because there was no code to try that is not also in a nested try block, this cfcatch tag handles only errors that are rethrown from the nested blocks.

Exits the custom tag and returns to the calling page.

Ends the catch block, try block, and initial cfif block.

Testing the code

To test the various ways errors can occur and be handled in this example, try the following:

  • In the calling page, change the attribute name to any other value; for example, My Attrib. Then change it back.

  • In the first cfquery tag, change the data source name to an invalid data source; for example, NoDatabase.

  • With an invalid first data source name, change the data source in the second cfquery tag to cfdocexamples.

  • Insert cfthrow tags throwing custom exculpations in various places in the code and observe the effects.