ColdFusion Components, commonly known as CFCs, were introduced in ColdFusion MX. In essence, they are simple templates written in existing CFML tags and CFScript. As such, they are not complex, confusing, or difficult to understand. If you can code CFML, you can create CFCs.
In this chapter, we will cover the following:
The basic structure of a ColdFusion component
The component tags, functions, and methods
Passing parameters using the argument scope
For those with any experience with ColdFusion, components should be relatively commonplace. Object-Oriented Programming (OOP) relies heavily on the use of ColdFusion components, so before proceeding onto the ins and outs of OOP, let's re-familiarize ourselves with components within ColdFusion. This introduction will also serve as a reference when looking at subjects later in the book.
ColdFusion Components use the same ColdFusion Markup Language (CFML) as 'standard' ColdFusion pages. The core difference is the file extension components must be saved with a .cfc
file extension as opposed to the .cfm
file extensions for template pages.
The basic structure of a ColdFusion Component is:
In simple terms, CFCs themselves form a framework within ColdFusion, allowing you to write structured, clear, and organized code. They make application development easier to manage, control, and maintain.
Note
ColdFusion Components use the same CFML as 'standard' ColdFusion pages. The core difference is the file extension.
It is not unusual for applications to grow and seem overly complex. Pages containing detailed information, such as business logic, data access and manipulation, data validation, and layout/presentation logic, can become untidy and hard to manage.
Creating and developing applications using CFCs enables you to separate the code logic from the design and presentation, and build an application based around, if not using, traditional Model View Controller (MVC) framework methodologies.
Utilizing CFCs and creating a clear structured format for your code will help reduce the complexity of logic within your pages and improve the application speed. Having a clearly structured, well organized code base will make it easier to develop as an individual and share resources within a team. This is the instant benefit of CFC development.
A well-written CFC will allow you to reuse your functions, or methods, across your entire application, helping to reduce the risk of code duplication. It will keep your component libraries and code base to a more easily manageable size, preventing it from becoming convoluted and difficult to follow.
ColdFusion components are an incredibly powerful and valuable means of creating efficient code. They allow you to:
Share properties and variables between other methods and functions
Share and interact with functions contained within other CFCs
Inherit the properties and methods of a base component
Overwrite methods and functions within other components
CFCs also give you the ability to clearly document and comment your code, letting you and other developers know what each function and property should do, what it should be expecting to receive to do the job and what output it will give you. ColdFusion components are able to read themselves and display this data to you, using a form of introspection, which we will cover in Chapter 2.
Although CFCs are an effective tool for code reuse, this is not to say they should be used for every reusable function within your application. They are not a complete replacement for custom tags and user-defined functions.
When you load a CFC (instantiate the component), this uses up more processing time than it would to call a custom tag or a User-Defined Function (UDF) into use. Once a CFC has been instantiated, however, calling a method or function within the component will take approximately the same time as it would to call a UDF.
It is important, therefore, that CFCs should not necessarily be used as a complete replacement for any UDFs or custom tags that you have in your application. Any code you write can, of course, be optimized, and changes can be made as you learn new things, but UDFs and custom tags perform perfectly well. Using them as they are will help to keep any processing overheads on your application to a minimum.
You may have already written custom tags and user-defined functions that allow similar functionality and reusability, for example, a series of UDFs that interact with a shopping cart. By grouping your functions within specific components according to their use and purpose, you can successfully keep your code library organized and more efficient.
You can also further clean your code library by compiling or grouping multiple related components into a package, clearly named and stored in a directory within your application.
A typical method for organizing your CFC library is to create a directory structure based on your company or domain name, followed by a directory whose name references the purpose of the included components, for example, 'com.coldfumonkeh.projecttracker
' in the webroot of your application.
Within this directory, you would then create a directory for each group (or package), of components, with a name reflecting or matching the component name and purpose.
Use your ColdFusion Components to create a component structure, or a library, that contains grouped methods and functions, particularly if the methods share properties or data.
It is not unusual for applications to grow and seem overly complex. Pages containing detailed information, such as business logic, data access and manipulation, data validation, and layout/presentation logic, can become untidy and hard to manage.
Creating and developing applications using CFCs enables you to separate the code logic from the design and presentation, and build an application based around, if not using, traditional Model View Controller (MVC) framework methodologies.
Utilizing CFCs and creating a clear structured format for your code will help reduce the complexity of logic within your pages and improve the application speed. Having a clearly structured, well organized code base will make it easier to develop as an individual and share resources within a team. This is the instant benefit of CFC development.
A well-written CFC will allow you to reuse your functions, or methods, across your entire application, helping to reduce the risk of code duplication. It will keep your component libraries and code base to a more easily manageable size, preventing it from becoming convoluted and difficult to follow.
ColdFusion components are an incredibly powerful and valuable means of creating efficient code. They allow you to:
Share properties and variables between other methods and functions
Share and interact with functions contained within other CFCs
Inherit the properties and methods of a base component
Overwrite methods and functions within other components
CFCs also give you the ability to clearly document and comment your code, letting you and other developers know what each function and property should do, what it should be expecting to receive to do the job and what output it will give you. ColdFusion components are able to read themselves and display this data to you, using a form of introspection, which we will cover in Chapter 2.
Although CFCs are an effective tool for code reuse, this is not to say they should be used for every reusable function within your application. They are not a complete replacement for custom tags and user-defined functions.
When you load a CFC (instantiate the component), this uses up more processing time than it would to call a custom tag or a User-Defined Function (UDF) into use. Once a CFC has been instantiated, however, calling a method or function within the component will take approximately the same time as it would to call a UDF.
It is important, therefore, that CFCs should not necessarily be used as a complete replacement for any UDFs or custom tags that you have in your application. Any code you write can, of course, be optimized, and changes can be made as you learn new things, but UDFs and custom tags perform perfectly well. Using them as they are will help to keep any processing overheads on your application to a minimum.
You may have already written custom tags and user-defined functions that allow similar functionality and reusability, for example, a series of UDFs that interact with a shopping cart. By grouping your functions within specific components according to their use and purpose, you can successfully keep your code library organized and more efficient.
You can also further clean your code library by compiling or grouping multiple related components into a package, clearly named and stored in a directory within your application.
A typical method for organizing your CFC library is to create a directory structure based on your company or domain name, followed by a directory whose name references the purpose of the included components, for example, 'com.coldfumonkeh.projecttracker
' in the webroot of your application.
Within this directory, you would then create a directory for each group (or package), of components, with a name reflecting or matching the component name and purpose.
Use your ColdFusion Components to create a component structure, or a library, that contains grouped methods and functions, particularly if the methods share properties or data.
You can use these following tags to create a ColdFusion Component.
Tag |
Purpose |
---|---|
cfcomponent |
The core CFC tag that defines the component structure. All other content in the component is wrapped within this tag. |
cffunction |
Creates a method (function) within the component. |
cfargument |
Creates a parameter, otherwise known as an argument, to be sent to the function. |
cfproperty |
Can be used to define and document the properties within your component. Can also be used to define variables within a CFC that is used as a web service. |
These previously mentioned tags are written within the .cfc
file that defines the ColdFusion component.
Note
In the world of object-oriented programming, you will commonly hear or see reference to the word 'Class'. A class is essentially a blueprint that is used to instantiate an object, and typically contains methods and instance variables.
When discussing a Class in the context of ColdFusion development, we are basically referencing a ColdFusion component, so when you see or read about classes, remember it is essentially an alias for a CFC.
To get started, in this example, we will create a component and functions to output the message "Hello world".
Create a new file called greetings.cfc
and save it within your ColdFusion webroot.
The following is a component base tag; add this code into the new CFC to define the component:
<cfcomponent displayName="greetings"> </cfcomponent>
Listing 1.1 component base tags
As you can see, the name attribute within the CFC matches the name of the file. The cfcomponent
tags form the base structure of our ColdFusion Component. No other code can be placed outside of these tags, as it will simply display an error.
It may be helpful to think of the cfcomponent
tag as the wrapping paper on a parcel. It forms the outer shell of the package, holding everything else nicely in place.
We have now created the component, but at the moment it does not actually do anything. It has no function to run. We need to add a method into the CFC to create a function to call and use within our application. The following code is a basic function definition; place it between the opening and closing cfcomponent
tags:
<cffunction name="sayHello"> <!--- the CFML code for the method will go here ---> </cffunction>
Listing 1.2 basic function definition
You have now added a method to the CFC. The cffunction
tags are nested within the cfcomponent
tags. We now need to add some CFML code within the cffunction
tags to create our method and perform the operation. Let's create a variable within the function that will be our display message. The following code is for declaring a string variable; place it inside the cffunction
tags:
<cffunction name="sayHello"> <cfset var strHelloMessage = 'Hello World!' /> </cffunction>
Listing 1.3 declaring a string variable
We have created a string variable containing the text to display to the browser.
To return the data we need to add an extra tag into the method. This is possible by using the cfreturn
tag, which returns results from a component method. The cfreturn
tag has one required attribute that is the expression or value you wish to return.
Add the following code to your CFC so our method will return the welcome message and the completed component will look like this:
<cfcomponent displayName="greetings"> <cffunction name="sayHello"> <cfset var strHelloMessage = 'Hello World!' /> <cfreturn strHelloMessage /> </cffunction> </cfcomponent>
Listing 1.4 returning data from the function
Since the release of ColdFusion 9, developers now have the ability to also write ColdFusion components in complete script syntax instead of pure tag form.
To write the previous component in this format, the code would look as follows:
component displayname="greetings" { function sayHello(){ // the CFML code for the method will go here var strHelloMessage='Hello World'; return strHelloMessage; } }
Listing 1.5 component declaration in the script syntax
Although written using cfscript
syntax, there is no requirement to wrap the code within<cfscript>
tags, instead we can write it directly within the .cfc
page.
We do not even need to contain the code within cfcomponent
tags, as the entire content of the component will be compiled as cfscript
if left as plain text without tags.
There it is, a simple ColdFusion Component. The method is created using the cffunction
tags, wrapped up nicely within the cfcomponent
tags, and the value returned using the cfreturn
tag. Now that we have written the function, how do we call it?
In this example, we will call the component and run the method by using the createObject() function. Create a new file called hello.cfm
and add the following code to the template:
<cfset objGreeting = createObject('component', 'greetings') /> <cfoutput>#objGreeting.sayHello()#</cfoutput>
Listing 1.6 creating the component object
In the previous code, we have created an instance of the greetings CFC, which we can reference by using the objGreeting
variable. We have then accessed the sayHello()
method within the component, surrounded by cfoutput
tags, to display the returned data.
Save the file and view it within your browser. You should now see the welcome message that we created within the method.
Imagine we are sending some data through to a login page in our application within the URL
scope; the first and last name of a particular person. On the page, we want to join the two values and combine them into one string to form the individual's full name. We could write the code directly on the page, as follows:
<cfoutput> Hello, #URL.firstName# #URL.lastName# </cfoutput>
Listing 1.7 displaying URL variables as a string
Although this works, you can revise the code and transform it into a ColdFusion function to concatenate the two values into the required single string and return that value:
<cffunction name="getName"> <cfset var strFullName = URL.firstName & ' ' & URL.lastName /> <cfreturn strFullName /> </cffunction>
Listing 1.8 concatenate variables into string
You can then call this function within your .cfm
page to output the resulting string from the function:
<cfoutput> #getName()# </cfoutput>
However, within this code you have restricted yourself to using only the specific URL
scope. What if the first name and last name values were in the FORM
scope, or pulled from a query? This block of code is useful only for values within the form scope.
To allow us to be able to pass in any parameters into the getName()
function, we need to use the cfargument
tag to send data into the method. By changing the function in the following code example, the method will create the concatenated string and produce the same results from two parameters or arguments that you choose to pass in.
<cffunction name="getName"> <cfargument name="firstName" type="string" /> <cfargument name="lastName" type="string" /> <cfset var strFullName = arguments.firstName & ' ' & arguments.lastName /> <cfreturn strFullName /> </cffunction>
Listing 1.10 using arguments within your function
The cfargument
tag creates a parameter definition within the component method, and allows you to send in arguments for inclusion into the functions.
The Arguments
scope only exists in a method. The scope contains any variables that you have passed into that method, and you can access the variables within the Arguments
scope in the following ways:
using structure notation -
Arguments.variablename
orArguments["variablename"]
using array notation -
Arguments[1]
By defining two arguments and sending in the values for the first and last names, you have created an unrestricted function that is not tied to a specific scope or set of hardcoded values. You can instead choose what values to pass into it on your calling page:
<cfoutput> #getName('Gary', 'Brown')# </cfoutput>
Lsiting 1.11a sending parameters into our function
Now that we have removed any restrictions to the values we pass in, and taken away any references to hardcoded variables, we can reuse this function, sending in whichever values or variables we choose to. For example, we could use variables from the FORM
scope, URL
scope, or query items to concatenate the string:
<cfoutput> #getName(form.firstName, form.lastName)# </cfoutput>
Listing 1.11b sending parameters into our function
Let's take our getName()
method and add it into the greeting.cfc
file. By doing so, we are grouping two methods that have a similarity in purpose into one component. This is good programming practice and will aid in creating manageable and clearly organized code.
Our greeting.cfc
should now look like this:
<cfcomponent name="greetings"> <cffunction name="sayHello"> <cfset var strHelloMessage = 'Hello World!' /> <cfreturn strHelloMessage /> </cffunction> <cffunction name="getName"> <cfargument name="firstName" type="string" /> <cfargument name="lastName" type="string" /> <cfset var strFullName = arguments.firstName & ' ' & arguments.lastName /> <cfreturn strFullName /> </cffunction> </cfcomponent>
As we have seen, you can easily access the methods within a defined CFC and output the data in a .cfm
template page.
You can also easily access the functionality of one method in a CFC from another method. This is particularly useful when your component definition contains grouped functions that may have a relationship based upon their common purpose.
To show this, let's create a new method that will use the results from both of our existing functions within the greetings.cfc
file. Instead of displaying a generic "Hello World" message, we will incorporate the returned data from the getName()
method and display a personalized greeting.
Create a new method within the CFC, called personalGreeting.
<cffunction name="personalGreeting"> <cfargument name="firstName" type="string" /> <cfargument name="lastName" type="string" /> <cfscript> strHello = sayHello(); strFullName = getName(firstName=arguments.firstName, lastName=arguments.lastName); strHelloMessage = strHello & ' My name is ' & strFullName; </cfscript> <cfreturn strHelloMessage /> </cffunction>
Listing 1.13 personalGreeting
method
Within this method, we are calling our two previously defined methods. The returned value from the sayHello()
method is being stored as a string variable, "strHello
".
We then retrieve the returned value from the getName()
method and store this in a string variable "strFullName". As we have written the getName()
function to accept two arguments to form the concatenated name string, we also need to add the same two arguments to the personalGreeting()
method, as done in the previous code. They will then be passed through to the getName()
method in exactly the same way as if we were calling that function directly.
Using the two variables that now hold the returned data, we create our strHelloMessage
variable, which joins the two values, and is then returned from the method using the cfreturn
tag.
In this method, we used CFScript instead of CFML and cfset
tags, which were used in our previous functions. There is no hard and fast rule for this. You can use whichever coding method you find the most comfortable.
Let's call this method on our hello.cfm
template page, using the following code:
<!--- instatiate the component ---> <cfset objGreeting = createObject('component', 'greetings') /> <!--- access the method and assign results to a string ---> <cfset strPersonalGreeting = objGreeting.personalGreeting( firstName="Gary", lastName="Brown") /> <cfoutput>#strPersonalGreeting#</cfoutput>
Listing 1.14 calling the personalGreeting
method
We are sending in the same arguments that we were passing through to the original getName()
method, in the same way. This time we are passing these through using the newly created personalGreeting()
method.
You should now see a personalized greeting message displayed in your browser:
In our previous personalGreeting()
method, we included two separate functions, sayHello()
and getName()
, into the main method. This is not an uncommon practice, and is what you would expect when writing detailed components with relationships between its included functions.
One issue that can arise when developing in this way is when two or more methods contain a variable of the same name and the value of that variable is accessed or changed by one of the methods.
As an example, the following code contains two functions, baseNumber()
and multiplyNumbers()
.
While the cfcomponent
tag has been excluded in this example, this could also easily be turned into a CFC by wrapping the functions within cfcomponent
tags.
<cfoutput> <cffunction name="baseNumber" returnType="numeric"> <cfargument name="a" type="numeric" required="true" /> <cfset x = arguments.a /> <cfreturn x /> </cffunction> <cffunction name="multiplyNumbers" returntype="string"> <cfargument name="a" type="numeric" required="true" /> <cfargument name="b" type="numeric" required="true" /> <!--- multiply our basenumber value by 10 ---> <cfset x = 10 /> <cfset y = baseNumber(a) /> <cfreturn y & " multiplied by " & x & " = " & x * arguments.b /> </cffunction> <cfloop from="1" to="10" index="i"> #multiplyNumbers(i,i)#<br /> </cfloop> </cfoutput>
Listing 1.15 two user-defined functions
A cfloop
tag runs a loop from 1 to 10. The multiplyNumbers()
function accepts two arguments. In this example, these are both the index numbers of the loop. We want to multiply our baseNumber
value (argument 'a'), by 10 for each loop, creating a 10 times table list. To do this, the multiplyNumbers()
function has a hardcoded value (x) that is set to the value of 10.
The desired results you would expect from this code should be:
1 multiplied by 10 = 10 2 multiplied by 10 = 20
However, this is not the case. If you save the code to a .cfm
template and run it in your browser, you will get the following result:
This is clearly not the result you would expect. So what's happening to cause this issue? Let's take another look at our two functions:
<cffunction name="baseNumber" returnType="numeric"> <cfargument name="a" type="numeric" required="true" /> <cfset x = arguments.a /> <cfreturn x /> </cffunction> <cffunction name="multiplyNumbers" returntype="string"> <cfargument name="a" type="numeric" required="true" /> <cfargument name="b" type="numeric" required="true" /> <!--- multiply our basenumber value by 10 ---> <cfset x = 10 /> <cfset y = baseNumber(a) /> <cfreturn y & " multiplied by " & x & " = " & x * arguments.b /> </cffunction>
Listing 1.16 examining the two methods
You can see that both functions have a variable called x
. The baseNumber()
function stores the value of the argument as the x
variable, which it returns into the multiplyNumbers()
function for use in the equation. The multiplyNumbers()
function also has a variable called x
, which is the hardcoded number we wish to use as a multiplier, in this case 10.
Within the function, the returned value from the baseNumber()
method is assigned to y
for use in the equation, but as this included function is run, it overwrites the value of the hardcoded x
variable with its own x
value. This, in turn, is passed into the equation, which throws off the expected results.
In the previous example, the x
value in both functions is public, meaning that it can be altered or overwritten by any included functions, or if in a CFC, any defined method within the component. They are, in essence, set as 'open' variables that can be accessed and amended.
By running the two functions in this way, with openly accessible variables, it has the effect of ruining our ten times table. Imagine that we had a method controlling the shopping cart in an e-commerce application, updating quantities and costs, perhaps even stock levels of products. If we left these public variables open, they could be accessed by any included functions, and the values could change dramatically altering our shopping cart and its data.
To avoid this issue, the best practice is to set any local function variables to only be accessed by that particular function. This is achieved by using the Var
keyword when setting variables. By applying variable to the Var
scope, you are restricting public access to them and declaring that they are only accessible within the method in which they are defined. This removes any chance that external functions will corrupt the values.
Note
You should always use the Var
keyword on variables that are used only inside of the function in which they are declared.
Let's alter our code to include the Var
keyword to ensure the variables are available only to the functions in which they are written:
<cffunction name="baseNumber" returnType="numeric"> <cfargument name="a" type="numeric" required="true" /> <cfset Var x = arguments.a /> <cfreturn x /> </cffunction> <cffunction name="multiplyNumbers" returntype="string"> <cfargument name="a" type="numeric" required="true" /> <cfargument name="b" type="numeric" required="true" /> <cfset Var x = 10 /> <cfset var y = baseNumber(a) /> <cfreturn y & " multiplied by " & x & " = " & x * arguments.b /> </cffunction>
Listing 1.17 Var scoping our variables
If we save the code with the Var
keyword applied to the variables and view the page in the browser, you will now see the correct results displayed:
Regardless of the type of variable you are using within your component methods, (a query, string, integer, array, or structure) if it is used only within the function in which it is declared, it needs to be Var
scoped to protect it and to avoid any unwanted amendments by other functions.
Up to ColdFusion 8, all Var
scoped variables were required to be placed after any arguments within the function (if there are any included), and before any CFML.
Enhancements in ColdFusion 9 removed this restriction, and Var
scoped variables can be placed anywhere within a code block or function.
While there are no strict conventions when naming your Var
scoped variables, be aware that a naming conflict will arise if your local variable name is the same as any defined argument name (or the name of another local variable).
<cffunction name="baseNumber" returnType="numeric"> <cfargument name="x" type="numeric" required="true" /> <cfset Var x = arguments.x /> <cfreturn x /> </cffunction>
Listing 1.18 baseNumber
function
For example, if we have written the baseNumber()
method as in the previous code, with the argument and local variable both called x
, this would display an error, as a local variable within a function cannot be declared twice.
Once you are ready to use your CFCs, you need to access the methods placed within. There are two ways to access a CFC:
object instantiation
invoking the CFC
When you instantiate a CFC, you create an instance of the component. This instance preserves the data within the CFC for as long as it exists. You would typically create an instance of a component at the top of the page. By doing so, you would have access to its methods and functions on the entire page, without having to create a new instance for each call.
There are three methods available to you to create an instance of the CFC:
As used in our earlier examples, the createObject()
function creates and returns a ColdFusion object.
<cfscript> objGreeting = createObject('component', 'greetings'); </cfscript>
Listing 1.19
Here, we are creating a new instance of the "greetings" component. The first parameter tells the createObject()
function that we want a component, and the second references the name of the component of which we want to create an instance.
While the CFC is in the same directory as the calling page, in the previous example, if we had the CFC within a different directory in the webroot, for example, a folder named components
; this second parameter would read as follows:
createObject('component', 'components.greetings');
This is because the second parameter is a dot notation representation of the path to the component.
Similar to the createObject()
function, the cfobject
tag has three attributes.
<cfobject name="greetingsObject" component="greetings" type="component" />
Listing 1.20
The name attribute defines the name of the returned variable of the CFC instance so you can access the component to use your methods. The component attribute represents a dot notation path to the CFC you wish to instantiate. The third attribute, type
, is optional, and has the default value component
.
The enhancements in ColdFusion 9 now provide an alternative way of creating an instance of a component object without using the createObject()
function.
We can now create the object through the use of the new
operator, like so:
<cfscript> // create the object objGreeting = new greeting(); </cfscript>
Listing 1.21
You can invoke your component and access the method simultaneously by using the cfinvoke
tag. When you invoke (call) the CFC using this tag, you are not creating an instance of the component that will be preserved and available for use elsewhere within your CFML page. Instead, you are creating an instance of the CFC that comes into existence as soon as you invoke the method, and ceases to exist as soon as the requested method has returned a result.
In essence, you are bringing the component to life long enough to get the details you need from it and closing it down as soon as the information is returned.
Let's make a call to our sayHello()
method within the greetings.cfc
component.
Add the following code to your hello.cfm
template page:
<cfinvoke component="greetings" method="sayHello" returnVariable="strHello" /> <cfoutput>#strHello#</cfoutput>
Listing 1.22
Here, we are invoking the greetings component, selecting the method within the CFC that we want to access, (in this case the sayHello()
function) and assigning a variable (strHello), to which the returned data will be saved for us to access it within the page.
Outputting the returnVariable
onto the page will provide us with the same result as we have seen before.
The cfinvoke
tag also allows us to pass in parameters to the methods we are calling, by means of the cfinvokeargument
tag.
<cfinvoke component="greetings" method="personalGreeting" returnVariable="strPersonalGreeting"> <cfinvokeargument name="firstName" value="Matt" /> <cfinvokeargument name="lastName" value="James" /> </cfinvoke>
Listing 1.23
We are sending our parameters used in the personalGreeting()
method in a similar format to the cfargument
tag, using the cfinvokeargument
tag, which is nested within the cfinvoke
tag. The cfinvokeargument
tag takes the name and value of the argument and sends it into the method you are calling.
Alternatively, when using the cfinvoke
tag, you can send through the parameters as named attribute-value pairs, providing one attribute per argument.
<cfinvoke component="greetings" method="personalGreeting" firstName="Gary" lastName="Brown" returnVariable="strPersonalGreeting" />
Listing 1.24
You can see in the previous code that the firstName
and lastName
parameters are written as attributes within the cfinvoke
tag itself.
The optional argumentCollection
attribute for the cfinvoke
tag accepts a structure in the form of an associative array of arguments to pass into the method.
<cfscript> // create a structure to hold the values stuArguments = structNew(); stuArguments.firstName = "James"; stuArguments.lastName = "Brown"; </cfscript> <cfinvoke component="greetings" method="personalGreeting" argumentCollection="#stuArguments#" returnVariable="strPersonalGreeting" />
Listing 1.25
The structure names must match the names of the arguments within the method.
As you have seen in the previous examples, we have sent parameters into the methods on our pages. There are two options available to send your arguments into your method call on an instantiated object.
You can send the two arguments through as a comma-delimited list. If you use this option, the order of the parameters you pass into the function call must match the order of arguments defined within the method.
In our personalGreeting()
function the first name is the first argument, and the last name is the second argument, therefore you would do the following to call the method:
<cfset strPersonalGreeting = objGreeting.personalGreeting("Daft","Vader") />
Listing 1.26
An alternative method of sending arguments into a function is to use named values. This option ensures that the values passed through are assigned to the correct argument within the method. This means you do not have to place the parameters within the function call in any specific order, as the name of the parameter will match the name of the argument within the method.
<cfset strPersonalGreeting = objGreeting.personalGreeting(firstName="Daft",lastName="Vader") />
Listing 1.27
Let's look at another simple use case for creating reusable components and functions, which will also highlight some benefits of passing arguments into your methods.
Create a new CFC called contacts.cfc
, and add your cfcomponent
tags to define the component.
Add the following method in the contacts.cfc
file. This new function runs a SELECT query on the Project Tracker application database to retrieve a recordset of all contacts:
<cffunction name="getContacts"> <cfset var rstContacts = "" /> <cfquery name="rstContacts" datasource="projectTracker"> SELECT firstName,lastName FROM Owners </cfquery> <cfreturn rstContacts /> </cffunction>
Listing 1.28
We now have a function returning our query data. We also want to have a query to pull out a specific record based on the record ID of a particular person.
We can create a second function to handle this as well:
<cffunction name="getContact"> <cfargument name="ID" type="numeric" /> <cfset var rstContact = "" /> <cfquery name="rstContact" datasource="projectTracker"> SELECT firstName,lastName FROM Owners WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.ID#" /> </cfquery> <cfreturn rstContact /> </cffunction>
Listing 1.29
These two methods within the contacts.cfc
file interact with our database and return query data. However, the two queries pull out exactly the same information from the database. The only difference between the queries is that getContact()
returns records for a specific user, based upon the ID value. We can easily streamline our CFC by combining these two methods into one, which will remove unnecessary code from our files and theoretically turn one function into two.
In this example, we will combine the two SELECT queries into one method, using the cfargument
tag.
<cffunction name="getContact"> <cfargument name="ID" type="numeric" default="0" /> <cfset var rstContact = "" /> <cfquery name="rstContact" datasource="projectTracker"> SELECT firstName,lastName FROM Owners <cfif arguments.ID GT 0> WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.ID#" /> </cfif> </cfquery> <cfreturn rstContact /> </cffunction>
Listing 1.30
The cfargument
tag has a default
attribute, which allows you to provide a default value for an argument if you do not pass one into the method. As the customer ID parameter type is set to "numeric", we have set the default value of the argument to "0". This value will now always be available within our method and will stay at the default value until a parameter is passed in to the function.
By providing a default value, we are now able to wrap a cfif
tag block around the WHERE
clause of the query. If the value of arguments.ID
(the value of the parameter within the arguments scope) is greater than 0, that is, if we have passed a numeric value into the method ourselves, then include the WHERE
clause when running the SQL within the cfquery
tags.
By doing this, we have merged the two methods within the contacts.cfc
into one, optimizing our code and allowing it to perform more than one function.
Let's run this query. Create a new page template called query.cfm
, and paste in the following code to create the object and run the method without sending in any parameters:
<!--- instantiate the object ---> <cfset objContacts = createObject('component', 'contacts') /> <!--- dump the results ---> <cfdump var="#objContacts.getContact()#" />
Listing 1.31
If no ID value is sent through as a parameter, meaning the default value is 0, the method will return the full recordset of all content from the Owners
database table:
Amend the code by adding in a numeric value to pass through as the ID argument:
<!--- dump the results ---> <cfdump var="#objContacts.getContact(2)#" />
Listing 1.32
If we provide an ID within the argument, the method will only return the row for the contact that has the matching ID value:
The dumped object now shows us the parameter sent through in the arguments scope and the SQL query that now includes the WHERE
clause.
The simple solution of using an argument with a default value and a cfif
statement to control the flow has streamlined and reduced the amount of extraneous code within your application.
In our contacts.cfc
, we have defined a method which contains a query. This query has the datasource name attribute defined to correctly reference the database setup within the ColdFusion administration console.
As with all good development, we want to restrict hardcoding any values or references wherever we can and instead use variables to define them, (in this case the datasource name attribute).
A common practice in application development is to create your datasource name and store it in the Application
scope, ensuring its availability to every page template that is called, for example:
<cfset application.dsn = "projectTracker" />
You could use the Application
scope variable application.dsn
directly within your CFCs as the dynamic name referencing the datasource. However, this is not considered best coding practice, as you have instantly opened up your component methods to a fixed scope variable.
One of the main goals in component development is to create closed CFCs and methods that do not need to worry about whether or not a fixed variable exists.
If we refer back to the getName()
function on page 7, we can see how it was originally fixed to read the first and last name from the URL scope. We resolved that issue by removing any fixed scope references and optimized the method by adding cfargument
tags and the ability to pass in parameters.
We will do the same for our contacts.cfc
to send in our datasource name for use in the cfquery
tags.
Instead of creating a new argument for each method within the component that requires the datasource, we will create a new function that will hold the variables we need and will be open for all methods defined within the CFC to read variables from.
Let's modify our code within the contacts.cfc
gallery to write the new function, init()
.
<cfcomponent name="contacts"> <cffunction name="init"> <cfargument name="datasource" type="string" required="true" /> <cfscript> Variables.attributes = structNew(); Variables.attributes.dsn = arguments.datasource; </cfscript> <cfreturn this /> </cffunction> <cffunction name="getContact"> <cfargument name="ID" type="numeric" default="0" /> <cfset var rstContact = "" /> <cfquery name="rstContact" datasource="projectTracker"> SELECT firstName,lastName FROM Owners <cfif arguments.ID GT 0> WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.ID#" /> </cfif> </cfquery> <cfreturn rstContact /> </cffunction> </cfcomponent>
Listing 1.33
You can now see the init()
method defined within the component. The concept of a constructor within an object is a common practice in most languages. We have included a cfargument
tag with the name datasource
, which will allow us to send in the name of the datasource we wish to use within this object.
Within the CFScript block, we then create a new structure that assigns the value of the datasource argument to the struct value dsn
, and the structure has been assigned to the Variables
scope within the CFC.
We can then amend our getContact()
method and alter the datasource attribute to use the new reference, stored in the Variables
scope:
<cffunction name="getContact"> <cfargument name="ID" type="numeric" default="0" /> <cfset var rstContact = "" /> <cfquery name="rstContact" datasource="#variables.attributes.dsn#"> SELECT firstName,lastName FROM Owners <cfif arguments.ID GT 0> WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.ID#" /> </cfif> </cfquery> <cfreturn rstContact /> </cffunction>
Listing 1.34
By sending the value into the object constructor method when instantiating the component we have removed the hardcoded reference to the datasource.
The Variables
scope can be made available to the entire CFC by setting a value either within one of the methods or in the constructor. The value of that variable is then made available to any other method (including the constructor).
The Variables
scope can be used in a similar way to storing values within the Application
scope, whose values are always available throughout the entire application. This makes it ideal for sending in variables such as the datasource name.
To set the value of the datasource name into the Variables
scope, we need to call the init function to pass through the argument.
In previous examples, we have already used the createObject()
function to create an instance of the component. We are going to use exactly the same code, only this time we will append the init()function
to the end of instantiation method call:
<!--- instantiate the object ---> <cfset objContacts = createObject('component', 'contacts').init(datasource="projectTracker") />
Listing 1.35
By doing this, we have passed our datasource name as an argument into the init()
method within the contacts.cfc
. The argument value is then stored within the Variables
scope structure (Variables.attributes).
Note
Values stored within the Variables
scope last as long as the component instance exists, and therefore can persist between calls to methods of a CFC instance.
The Variables
scope within your CFC is available to any included pages (using the cfinclude
tag), and any Variables
scope variables that you have defined in the included page are also available to the CFC.
At the end of the function, we have the cfreturn
tag, which we have seen before. However, this particular method is returning a different value, which is This:
<cffunction name="init"> <cfreturn This /> </cffunction>
Listing 1.36
By adding a return type of This
to the cfreturn
tag, you are returning the entire object, including all of its methods, variables, and data.
In the query.cfm
calling page, use the cfdump
tag to display the object in the browser:
<!--- dump the contacts object ---> <cfdump var="#objContacts#" />
Listing 1.37
As the init()
method returns the object in the This
scope, we are able to access the object directly using the cfdump
tag.
The This
scope is similar to the Variables
scope due to the fact that it is 'globally' accessible to the entire CFC. In addition, the This
scope is accessible outside of the CFC, so you could call and reference the values from your object within your .cfm
template calling page.
For example, if we amended the code within the init()
method in the CFC from using the previously mentioned Variables
scope to the This
scope, we could access the datasource name from our calling page:
<cffunction name="init"> <cfargument name="datasource" required="true" /> <cfscript> This.attributes = structNew(); This.attributes.dsn = arguments.datasource; </cfscript> <cfreturn This /> </cffunction>
Listing 1.38
In the query.cfm
, we can now output the name of the datasource from the attributes structure stored within the This
scope:
<!--- dump the contacts object ---> <cfdump var="#objContacts#" /> <cfoutput>The datasource name is #objContacts.attributes.dsn#</cfoutput>
Listing 1.39
Notice that the attributes structure is now publicly available, allowing us to access the name of the datasource directly from the CFC.
This highlights the difference between the Variables
and This
scope. When the attributes were assigned to the Variables
scope, they were kept hidden from external views, despite being available to all methods within the CFC. As soon as we changed the init()
method to store attributes within the This
scope, the structure became a visible, 'public' variable that could be accessed outside of the CFC.
Although the This
scope is a required tool for returning a complete CFC object, it is not best practice to store variables within the scope. This is because they can be accessed and altered. Unless you specifically choose to alter your object's variables in this manner, this would not be a safe development practice.
We have looked at ColdFusion Components, what they are and what they are for. Within this chapter, we have also covered:
The basic structure of a CFC
The component tags
Grouping functions and methods
How to use arguments within your methods
Optimizing and encapsulating your functions to improve code portability
Instantiating your objects, as well as creating an object constructor method
The Arguments, This and Variables scope, and to Var scope function variables