The first part of this chapter introduced the basics of ColdFusion components, but there is a lot more to ColdFusion components than meets the eye. ColdFusion components offer a series of powerful tools and functionality that has never been available to ColdFusion developers and also offer a new model by which to develop and model applications. In the rest of the chapter, we will discuss the following:
Dealing with Exceptions in CFCsAs with all ColdFusion development, handling and dealing with exceptions is an important part of developing robust applications. Just like any other ColdFusion template, CFCs can use the CFCATCH and CFTRY blocks to catch and deal with exceptions. Although you are not forced to use CFCATCH and CFTRY, they can be invaluable in finding and dealing with bugs inside complex CFCs. This is especially true when you are working with many CFCs that interoperate and are dealing with unforeseen exceptions in a production environment. So let's look at the example in Listing 6.10. Listing 6.10 exmpqry2.cfc<cfcomponent> <cffunction name="getEmp" returnType="query"> <cftry> <cfquery name="empQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.EmailName, Employees.WorkPhone, Employees.Extension FROM Employees </cfquery> <!--- Use cfcatch to test for database errors.---> <!--- Print error messages. ---> <!--- Block executes only if a Database exception is thrown. ---> <cfcatch type="Database"> <h1>Database Error</h1> <cfoutput> <ul> <li><b>Message:</b> #cfcatch.Message# <li><b>Native error code:</b> #cfcatch.NativeErrorCode# <li><b>SQLState:</b> #cfcatch.SQLState# <li><b>Detail:</b> #cfcatch.Detail# </ul> </cfoutput> <cfset errorCaught = "Database"> </cfcatch> </cftry> <cfdump var=#empQuery#> </cffunction> <cffunction name="getStartDate" returnType="query"> <cftry> <cfquery name="deptQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.DateStarted, Employees.Department FROM Employees ORDER BY DateStarted ASC </cfquery> <cfcatch type="Database"> <h1>Database Error</h1> <cfoutput> <ul> <li><b>Message:</b> #cfcatch.Message# <li><b>Native error code:</b> #cfcatch.NativeErrorCode# <li><b>SQLState:</b> #cfcatch.SQLState# <li><b>Detail:</b> #cfcatch.Detail# </ul> </cfoutput> <cfset errorCaught = "Database"> </cfcatch> </cftry> <cfreturn #deptQuery#> </cffunction> <cffunction name="getEmpName" returnType="query" access="remote"> <cfargument name="lastName" required="true"> <cftry> <cfquery name="deptQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.DateStarted, Employees.Department FROM Employees WHERE LASTNAME LIKE '#arguments.lastName#' </cfquery> <cfcatch type="Database"> <h1>Database Error</h1> <cfoutput> <ul> <li><b>Message:</b> #cfcatch.Message# <li><b>Native error code:</b> #cfcatch.NativeErrorCode# <li><b>SQLState:</b> #cfcatch.SQLState# <li><b>Detail:</b> #cfcatch.Detail# </ul> </cfoutput> <cfset errorCaught = "Database"> </cfcatch> </cftry> <cfreturn #deptQuery#> </cffunction> <cffunction name="createEmp" returnType="query" access="remote"> <!--- cf arguments are needed to receive arguments as well as they act as a first line of validation ---> <cfargument name="FirstName" required="true"> <cfargument name="LastName" required="true"> <cfargument name="Title" required="false"> <cfargument name="EmailName" required="false"> <cfargument name="Extension" required="false"> <cfargument name="WorkPhone" required="false"> <cfargument name="Status" required="true"> <cfargument name="Department" required="true"> <cfargument name="DateStarted" required="true"> <!--- start insert query ---> <cftry> <cfquery name="empCreate" datasource="ICF" dbtype="ODBC" > Insert INTO Employees (FirstName,LastName, Title, EmailName, Extension, WorkPhone, Status, Department, DateStarted) Values ( '#arguments.FirstName#', '#arguments.lastName#', '#arguments.Title#', '#arguments.EmailName#', '#arguments.Extension#', '#arguments.WorkPhone#', #arguments.Status#, #arguments.Department#, #arguments.DateStarted#) </cfquery> <cfcatch type="Database"> <h1>Database Error</h1> <cfoutput> <ul> <li><b>Message:</b> #cfcatch.Message# <li><b>Native error code:</b> #cfcatch.NativeErrorCode# <li><b>SQLState:</b> #cfcatch.SQLState# <li><b>Detail:</b> #cfcatch.Detail# </ul> </cfoutput> <cfset errorCaught = "Database"> </cfcatch> </cftry> <cfoutput>Record was added</cfoutput> </cffunction> </cfcomponent> In this example, we have modified Listing 6.5 to include error trapping for a database exception. This will catch any database exception and pass back a detailed error message to the client. We could also do our error trapping as shown in Listing 6.11. Listing 6.11 exmpqry2.cfc Snippet 1<cffunction name="getEmp" returnType="query"> <cfquery name="empQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.EmailName, Employees.WorkPhone, Employees.Extension FROM Employees </cfquery> <cfif #empQuery.recordcount# LT 1> <cfthrow type="noQueryResult" message="No results were found. Please try again."> <cfelse> <cfdump var=#empQuery#> </cfif> </cffunction> Here we use conditional logic to see if there were any records to be found in the database and, if not, to pass back an error message that we catch on the client side (like what is shown in Listing 6.12). Listing 6.12 exmpqry2.cfc Snippet 2<h3>Time Display Page</h3> <cftry> <b>Get Employees:</b> <cfinvoke component="exmpqry2" method="getEmp" returnVariable="empResult"> <cfcatch type="noQueryResult"> <p>The following error occured while processing the component method:<br><b><cfoutput>#cfcatch.message#</cfoutput></b></p> </cfcatch> </cftry> <cfdump var=#empResult#> In general, any error handling method that you would normally use when writing CFML is fine. It is important to note, however, that although the ColdFusion MX Application Server usually provides helpful debugging information when developing CFML, because of the nature of CFCs, that debugging information might not be as helpful as one would like. It becomes very important to consistently use CFTRY blocks in your CFCs to catch exceptions and provide back to you specific information about what the actual problem is. Although this might not be very important if your application has only one or two CFCs, it becomes very important when you have many and are passing arguments between CFCs as well as using CFC's inheritance and other functionality (as we will see later in this chapter). Securing Your CFCsNowadays, it's an unfortunate necessity to build secure applications to protect your applications from denial-of-service attacks, hackers, and data thieves. You also sometimes need to be able to restrict information to parties who have a certain access privilege. ColdFusion MX provides functionality to accomplish all of these goals. First we are going to look at how to secure your CFCs at an access level that enables you to define exactly what can and cannot make requests or interact with your CFCs. Let's start by looking at the CFC access security levels in Table 6.2.
If you remember from Listings 6.5 and 6.6 where we were using forms to interact with our CFCs, we modified our CFC to include the access attribute in the cfcomponent tag, as shown in Listing 6.13. Listing 6.13 Snippet from exmplqury1.cfc<cffunction name="getStartDate" returnType="query" access="remote"> <cfquery name="deptQuery" datasource="ICF" dbtype="ODBC"> SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.DateStarted, Employees.Department FROM Employees ORDER BY DateStarted ASC </cfquery> <cfreturn #deptQuery#> </cffunction> In Listing 6.13, we have set this function's access attribute to remote, allowing our examples that used forms, URLs, and other methods of invoking CFCs to access the CFC. For example, edit the cffunction line in this code and remove the access attribute altogether. Now we have the following: <cffunction name="getEmpName" returnType="query" > Then try and call this CFC via a URL: <a href="exmpqry1.cfc?method=getEmpName&lastname=hahn"> Figure 6.6 shows you what you should see. Figure 6.6. An error message thrown to the client when a client without appropriate access privilege tries to connect to a CFC.
After you have tried this, change your code back to allow remote access. Anytime you want a CFC to be accessible from a form, URL, or POST operation, you are going to have to use the remote access level so that ColdFusion sees all GET and POST operations as remote procedures. Building Role-Based Component Method SecurityAlthough it's useful to be able to define what sort of access you want to give your CFCs from remote applications or internal applications, it's also useful to be able to restrict specific CFCs by user or a user's role. For example, if we wanted to create a CFC that deleted users from the database, we might do something like what is shown in Listing 6.14. Listing 6.14 deleteUser.cfc<cfcomponent> <cffunction name="deleteUser" returnType="query" roles="admin"> <cfargument name="UserID" required="true"> <cfquery name="deleteUser" datasource="ICF" dbtype="ODBC" > DELETE FROM USER WHERE User.UserID = #arguments.UserID# </cfquery> <cfreturn #deptQuery#> </cffunction> </cfcomponent> Obviously, we would not want just anyone to be able to delete users from tables, so here we have assigned the roles attribute to the administrator, (or admin). You can define multiple roles that have access to this CFC by entering multiple roles in the roles attribute and delimiting them with commas, as in the following: <cffunction name="deleteUser" returnType="query" roles="admin, dba, etc."> For more information on how to create roles and use ColdFusion MX application security, see the section "Application Security" in Chapter 17, "Common Application Development Requirements." ColdFusion Component PackagesA useful feature of CFCs is the capability to create component packages. Component packages are any grouping of CFCs that are stored in the same directory and allow you to refer to all components in that package using a dot notation to decrease the chance of name "collisions" when sharing components. Components stored in the same directory are members of a component package much like you would have, for example, with a Java JAR file, although not exactly the same. To create and invoke a CFC in a "package," all you have to do is move a collection of CFCs to a specific directory under your webroot and refer to the CFC from that point using dot notation as if you were referencing a file. Let's try an example. In your web root directory, create a folder named icf and another folder under that one called components. Now move exmplqury1.cfc to the components directory. Create a new ColdFusion page and save it in your webroot as test.exmpqurypackage.cfm. Create a new CFML template like this: <cfinvoke component="icf.components.exmplqury1" method="getEmp"> As you can see, this is really straightforward, but let's look at the same thing in CFScript. <cfscript> helloCFC = createObject("component", "icf.components.exmplqury1"); helloCFC.getEmp(); </cfscript> We can also call a packaged component from a URL as follows: http://localhost/icf/components/exmplqury1.cfc?method= getEmp As you develop your own CFCs, use packages to help you create and separate your application into functional blocks. For example, if you have a series of CFCs that you use for user management, you might group them as follows: Yourcompanyname/dev/usermanagment You can use anything that is not only unique but helps you define the nature of the CFCs in that package. Component InheritanceAs previously discussed, ColdFusion MX CFCs bring a whole new way of building applications to ColdFusion that developers have been trying to replicate with methodologies such as Fusebox and ObjectCF. In the preceding section, we mentioned that CFCs allow for the concept of inheritance, although developers more familiar with Java or C++ will find CFC inheritance more like file includes. Developers often find themselves creating a simple script, program, or function that models a certain set of functionality that they need for a specific case. For example, let's create a CFC that returns all the customers in our database (see Listing 6.15). Listing 6.15 getCustomers.cfc<cfcomponent> <cffunction name="getCustomers" returnType="query" access="remote"> <cfquery datasource="icf" name="getOrder"> SELECT Customer.CustomerID, Customer.CustomerFirstName, Customer.CustomerLastName, Customer.CustomerAddress, Customer.CustomerCity, Customer.CustomerStateID, Customer.CustomerZip, Customer.CustomerPhone, Customer.CustomerEmail, Customer.CustomerPassword, Customer.CustomerNotification FROM Customer, State WHERE Customer.CustomerStateID=State.StateID </cfquery> <cfreturn #getCustomers#> </cffunction> </cfcomponent> Now let's imagine that your manager says that he wants you to add some new functionality to the CFC, but you would rather not mess with your working code. Instead, you can just inherit the methods and functions of the other CFC and create a new CFC, as shown in Listing 6.16. Listing 6.16 getPreferredCustomers.cfc<cfcomponent extends="icf.getCustomers.cfc"> <cffunction name="getPreferredCustomers" returnType="query" access="remote"> <cfquery datasource="icf" name="getOrder"> SELECT Customer.CustomerID AS Customer_CustomerID, Order.OrderID, Order.CustomerID AS Order_CustomerID, Order.OrderDate, Order.OrderAddress, Order.OrderCity, Order.OrderStateID, Order.OrderZip, Order.CreditCardID, Order.OrderCardNumber, Order.OrderCardExpiration, Order.OrderNameOnCard, Customer.CustomerFirstName, Customer.CustomerLastName, Customer.CustomerAddress, Customer.CustomerCity, Customer.CustomerStateID, Customer.CustomerZip, Customer.CustomerPhone, Customer.CustomerEmail, Customer.CustomerPassword, Customer.CustomerNotification FROM Customer INNER JOIN Order ON Customer.CustomerID = Order.CustomerID Where recordset is GRT 5 </cfquery> <cfreturn #getPreferredCustomers#> </cffunction> </cfcomponent> Now you could just cut and paste your code and fiddle with it until you have a new CFC, but this way is much easier. You cannot CFINCLUDE your CFC because, of course, the CFML parser would get confused by the CFC component tags. But with CFCs and inheritance, you can simply "borrow" the getCustomers.cfc template's functionality if it's in a CFC like this: <cfcomponent extends="basecfcyouwishtoextend.cfc"> This enables you to inherit all of the preceding CFCs functionality while adding your new functionality for this special case. Inheritance thus enables you to invoke the CFC exactly the same as you normally would, but you know have access to the extra functionality of the inherited CFC. For example, you could now do the following: <cfinvoke component="getPreferredCustomers" method="getPreferredCustomers"> <cfinvoke component="getPreferredCustomers" method="getCustomers"> ColdFusion Components and MetadataCFCs have another great and useful function that will be a boon to managers, developers, designers, and CFC user/consumers ColdFusion calls it introspection. Once again, this is loosely based on the concept of introspection from object-oriented theory, but with CFC, you can think of it as a way for you to view or get metadata about a CFC. By metadata, we mean information about how the CFC functions, including information on methods, properties, parameters, and additional information. Component metadata, also known as component introspection, allows programs to discover the methods and properties exposed by components. Before we go into introspection in CFCs and more, let's first browse to a CFC and take a peek and see what it looks like. Open up a browser and enter in your URL the full path and name to a CFC. Try doing localhost:8100/examplqury1.cfc and see what you get. You should see something like Figure 6.7. Figure 6.7. Using a browser to view metadata about a ColdFusion component.
Note that anything you might want to know about the CFC is right there for you, from CFC parameters and whether they are required or not, to methods and how they are called, as well as access privileges or information. So why is this so useful to you? Well, if you work in an organization that has several developers as well as designers, you can collaborate without constantly having to ask each other what parameters are required, what a specific method returns, or what data needs to be passed to that method before it can be invoked! In fact, you could create your CFCs, and a competent Flash designer could call them using the Flash net services and CFC introspection without ever having to talk to you! Furthermore, CFCs provide a powerful method of providing insight into the structure of an application. Application architects, project managers, and team leaders can use the metadata to help them create and or update flowcharts, applications models in Unified Modeling Language, data diagrams, and so on without having to sit down with the developers and ask them about the CFC. Also introspection can act as a form of AutoDoc! For some applications, the information supplied by CFC introspection is all the documentation you need! So next time your manager asks you to fully document your application, at least when you work with CFCs, most of the documentation can be done via introspection. Finally, ColdFusion MX provides three other methods to get metadata from CFCs. Table 6.3 describes each of them.
Using these functions, you can create applications that can autodiscover component functionality for a host of different uses, including CFC modeling systems, web services, work flow and process flow applications, and many others. Because you can also output CFCs as WDDX, you can even create applications in other languages that can discover component functionality via metadata using the WDDX SDK. Introspection, inheritance, and CFCs provide powerful tools to developers and, more importantly, to IT managers and architects who are trying to apply software best practices and methodologies to web application development. SummaryIn this part of the chapter, we explored advanced features of CFC, such as how to handle exceptions, use access and roles-based security, and the features of inheritance and introspection. CFCs provide very rich functionality to help developers and architects design secure, robust, and well-designed ColdFusion applications, and CFCs should be used as a foundation for any large ColdFusion application. |