|
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 pageThe 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 codeThe 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 pageThe 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 codeThe 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.
|
|
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 codeTo 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.
|