Article
Create Scalable Applications with ColdFusion Components
Defining a CFC
The first step in integrating a ColdFusion component into our existing application is to define it. Every CFC should have clearly understood roles and responsibilities. In the case of our BlogManager CFC, we want it to capture all of the business rules and data access logic required for the blog functionality.
Once this important decision about our component is made, implementing the component in ColdFusion is easy. Each CFC is defined in a single file. The filename is the desired name of the component with a .cfc extension. In this case, we'll define our component in a file named BlogManager.cfc.
Our CFC can live nearly anywhere on our file system, but if it's not in the same directory as the code that will use it, we need to refer to it using a "dot notation" syntax from the web root directory or from a mapping created in the ColdFusion Administrator.
In this instance we'll place the BlogManager CFC in a subfolder called components beneath our main application folder bookblogs, which is directly below the web root directory. We therefore use the full component name of bookblogs.components.BlogManager to refer to the component from other locations. Separating your CFCs from other files in the web application is usually a good idea -- it encourages reuse across many different pages, regardless of their location.
Inside our .cfc file we define our component. Our BlogManager CFC will initially look something like this:
<cfcomponent output="false" hint="Manages all Blog-related business rules
and data access">
<cfset this.dsn = application.dsn />
<cffunction name="getBlogEntries" access="public" returnType="query"
output="false"
hint="Returns a query containing all blog entries in
descending order by posted date">
<!--- Retrieve All Blog Entries --->
<cfquery datasource="#this.dsn#"
name="blogEntries">
SELECT * FROM BlogEntries
ORDER BY Posted DESC
</cfquery>
<cfreturn blogEntries />
</cffunction>
</cfcomponent>
Surrounding all of the code in our component is a single set of <cfcomponent> tags. Within these tags, we can write plain CFML in the form of independent statements and familiar user-defined functions (UDFs). We have defined two attributes on the opening <cfcomponent> tag: output and hint. Although optional, including both of these attributes is considered good practice.
Setting the output attribute to false prevents extraneous code within the component from being included in your page output. The hint attribute allows you to describe the purpose of your CFC. This does not affect the operation of your component but makes it easier for other developers to determine the role and function you have chosen for your component.
The first element within the <cfcomponent> tag is a single <cfset> statement. Any code outside of functions in your component -- such as this <cfset> -- will be executed whenever you first create or invoke it. This specific statement stores the name of the data source for the database, using the special this scope, for later use by the component. As your experience with CFCs grows, you'll find a lot of uses for storing data in your components.
Following the <cfset> statement is our function getBlogEntries defined by the <cffunction> tag. Functions inside of components are often called methods. Apart from the mandatory name attribute, the other optional attributes returnType, output, hint, and access are defined. returnType, output, and hint are standard <cffunction> attributes that have the same meaning here as when used with normal UDFs.
The returnType attribute specifies the type of data the function will return. You can set this to a simple or complex data type, such as numeric, string, or struct. We can even use a component name. If we supply this value, ColdFusion will ensure the value returned from the function matches this data type. The output and hint attributes are identical to the same attributes on the <cfcomponent> tag. The access attribute determines which code can run the function and is fully discussed in the "Security Considerations" note included later in the article. Including these attributes is optional, but is considered best practice.
Within the getBlogEntries function, we use a <cfquery> tag to pass an SQL query to the database. Notice how we can access this .dsn variable to determine the data source name. The query in this example is fixed, but you could just as easily generate the query dynamically by using arguments passed into the method. The query result is then returned to the caller with a <cfreturn> tag.
This is just the beginning of the possibilities offered by our new component. At the moment it can only retrieve a list of blog entries. Since we wanted the BlogManager component to handle all business logic and data access relating to blogs, we also need functions to create, read, update, and delete blog entries. Although we won't be creating them in this article, these methods can be found in the source code download.
For the moment, however, let's see how we can use the BlogManager component and the getBlogEntries function in our applications.
Using CFCs in Your ColdFusion Applications
Within a page that needs to use the functionality defined by the component, we can execute the function with the <cfinvoke> tag. The <cfinvoke> tag is a powerful tag that can create components from their definition files and execute specific functions. All we need to do is specify the component, the method (remember, this is just another name for function) and where we want the result stored. Our listing of all of the available blog posts (listBlogEntries.cfm) can be changed to use the component as shown below:
<cfset pageTitle = "Blogs" />
<cfinclude template="../layout/layoutQueries.cfm">
<cfinclude template="../layout/header.cfm">
<cfinvoke component="bookblogs.component.BlogManager"
method="getBlogEntries"
returnvariable="blogEntries" />
<h2>All Blog Entries</h2>
<table width="90%">
<tr>
<th>Date</th>
<th>User</th>
<th>Title</th>
</tr>
<cfoutput query="blogEntries">
<tr>
<td>#dateFormat(posted)#</td>
<td>#username#</td>
<td>#title#</td>
</tr>
</cfoutput>
</table>
<cfinclude template="../layout/footer.cfm">
As the listing shows, we have completely removed the previous <cfquery> and replaced it with the <cfinvoke> tag. The query that is returned the blogEntries variable which we use to build the HTML table is exactly the same. We've saved a couple of lines in our page, but more importantly, our other pages can use this function too, after only a small amount of modification.
Our member profile page displays a list of blog entries for that specific member, and our homepage only displays a limited amount of the latest entries. If we add a couple of arguments to our getBlogEntries function, we can make the SQL dynamic, and make the function useful enough to be called from all of the pages that need to retrieve blog entries:
<cffunction name="getBlogEntries" access="public" returnType="query"
output="false"
hint="Returns a query containing all blog entries,
optionally filtered by memberId and limited by to set
number of entries (supplied from the most recent) in
descending order by posted date">
<cfargument name="filterByMemberId" type="numeric" required="false"
default="0"
hint="If supplied, only blog entries by this member
are returned" />
<cfargument name="maxEntries" type="numeric" required="false"
default="9999"
hint="If supplied, limits the total number of blog entries
returned to this amount" />
<!--- Retrieve Blog Entries, optionally filtered by member and
maximum number of rows returned --->
<cfquery datasource="#this.dsn#" name="blogEntries"
maxRows="#arguments.maxEntries#">
SELECT * FROM BlogEntries, Members
WHERE BlogEntries.username = members.email
<cfif arguments.memberId GT 0>
AND memberId = #arguments.filterByMemberId#
</cfif>
ORDER BY Posted DESC
</cfquery>
<cfreturn blogEntries />
</cffunction>
Here we are using optional arguments, defined using <cfargument> tags, to dynamically change our SQL statement each time the method is invoked. The filterByMemberId argument is checked within the query; if it differs from the default, an additional WHERE clause is applied to our SQL statement. The maxEntries argument, on the other hand, is always applied to the maxRows attribute of the <cfquery> tag, but with a very large default value.
To call the function from our member profile page (profile.cfm), we would use the following syntax for <cfinvoke>:
<cfinvoke component="bookblogs.components.BlogManager"
method="getBlogEntries"
returnvariable="theirBlogEntries">
<cfinvokeargument name="filterByMemberId" value="#url.memberId#" />
</cfinvoke>
Notice we are now using a nested tag, <cfinvokeargument>, to pass the member ID to the filterByMemberId argument of our function. Similarly, on our homepage (index.cfm) we now invoke the component function using the maxEntries argument to limit the results returned:
<cfinvoke component="bookblogs.components.BlogManager"
method="getBlogEntries"
returnvariable="recentBlogEntries">
<cfinvokeargument name="maxEntries"
value="#application.numberOfBlogEntriesOnHomePage#" />
</cfinvoke>
If neither argument is provided, the function still behaves as it originally did, which is suitable for use on the blog entry listing page.