-
Notifications
You must be signed in to change notification settings - Fork 24
CFC Primer Part 2: Structure of CFCs
By Matt Woodward (matt at mach-ii.com)
- The Structure of CFCs
- The
cfcomponent
tag - The Default Constructor
- The
<cffunction>
,<cfargument>
, and<cfreturn>
tags - Getters and Setters
- Putting It All Together
- Next parts of this primer
A very basic CFC may be nothing more than a collection of UDFs. In some cases it may even make sense to create UDF libraries wrapped inside a CFC. But the real power of CFCs comes to play when behavior and attributes are combined in a single package. This potent combination allows CFML developers to create objects that contain both data and behavior. CFCs make it possible to create more flexible and safer applications because data are not "roaming out in the wild" to be modified by any part of the application.
The following discussion explains the structure of a basic CFC. Person.cfc will be used as an example. CFCs are plain text files (as are CFML templates), but instead of saving the file with a .cfm or .cfml extension, the file is saved with a .cfc extension. CFCs also have a more rigid syntactical structure than CFML templates.
All CFCs start with the <cfcomponent>
tag and some tag attributes, all of which are optional:
<cfcomponent displayname="Person" output="false" hint="I am a Person">
Although the attributes inside the <cfcomponent>
tag are optional, including them is highly recommended for documentation purposes. For example, if the displayname and hint attributes are used, documentation for the components is automatically generated and can be viewed by calling the CFC file in a browser, provided the user has permission to access the ColdFusion administrator.
The output attribute of the <cfcomponent>
tag simply dictates whether or not any output will be generated by the CFC. Since it has become a "best practice" among most CFML developers to never have CFCs generate output, it is recommended to set output to "false" because this reduces the amount of white space generated within a request.
The extends attribute of the <cfcomponent>
dictates how one CFC extends, or inherits from, another CFC. It is beyond the scope of this primer to address inheritance, which is a very powerful feature of CFCs.
In OO languages, a constructor is a method that is called (or "invoked" in OO terminology) when a specific instance, or unique copy, of an object is instantiated (created). CFCs do not use traditional constructors as found in other OO languages, but CFCs do contain a "default constructor" or "pseudo-constructor," which is code that is automatically executed whenever an instance of the CFC is created.
Any code that is placed after the opening <cfcomponent>
tag but before the first <cffunction>
tag will be executed when the object is created. Actually, any code inside a CFC file that is not inside a <cffunction>
tag will be executed when the CFC is created, but if the pseudo-constructor is used, for the sake of clarity it is recommended that all the pseudo-constructor code be placed at the top of the file, after the <cfcomponent>
tag but before the first <cffunction>
tag.
Let's assume it's necessary to set the variable foo to a value of "bar" every time a particular CFC is instantiated. If <cfset foo = "bar">
is inserted after the <cfcomponent>
tag and before the first tag, the variable foo will automatically be set to the value "bar" every time a new instance of the CFC is created.
This is certainly handy, but it has become a "best practice" among CFML developers to handle constructors a bit differently by using the <cffunction>
, <cfargument>
, and cfreturn>
tags.
As indicated above, the <cffunction>
, <cfargument>
, and cfreturn>
tags were introduced in ColdFusion 6. These three tags allow UDFs to be defined in a much more powerful way than was possible in ColdFusion 5.
The <cffunction>
tag, which has several attributes, acts as the opening tag of a UDF. The <cfargument>
tag specifies what data (or "arguments") are passed into the <cffunction>
. These arguments are named, can either be required or not required, can optionally have a data type specified, and can provide a default value when the argument is not required. The <cfreturn>
tag returns data to the caller of the <cffunction>
; however, this is optional because not all functions will return data.
The default constructor is useful in certain situations, but it has become a "best practice" among CFML developers to create a <cffunction>
in CFCs called init
, which serves as a more explicit constructor. The explicit init
function gives developers more control over the object's constructor and exactly when the constructor is called. The opening <cffunction>
tag for the init method, which serves as the Person CFC's constructor, is as follows:
<cffunction name="init" access="public" output="false" returntype="Person"
hint="I am the Constructor">
Note the attributes inside the <cffunction>
tag:
Attribute | Description | Tip |
---|---|---|
name | Name of the function | Name must be unique (start name with a verb) |
access | Dictates how and from where the function may be accessed | See discussion below |
output | Similar to the output attribute of the <cfcomponent>tag, this controls whether or not the contents of the function will generate any output |
Usually best to set this to "false" for all functions |
returntype | The data type of what is returned when this function is called | Constructors should always return the object itself |
hint | Used for documentation purposes | A great way to document an application |
Now let's define what makes up the Person object. In this example, firstName, lastName, and birthdate will be used as pieces of data associated with a person.
When a new Person CFC is created (or "instantiated"), you may or may not want to set values for firstName, lastName, and birthdate. The init() method allows for the CFC to be created without passing values for the CFC's arguments because all the arguments are set as optional as opposed to required. A constructor that takes no arguments, or in which all the arguments are optional, is known as a "no-arg constructor," because no arguments are required in order to create an instance of the CFC and call its constructor. Because the arguments are present, however, you may pass the arguments in when calling the constructor to set the values of the data within the CFC.
The structure of a CFC and the functions within a CFC are strict. Therefore, the <cfargument>
tags (when used) must appear immediately after an opening <cffunction>
tag. After the arguments are declared, a setter method for each argument will be called to set the value of the attribute in the CFC to the value of the argument passed in.
Let's look at the argument tags first:
<cffunction name="init" access="public" output="false" returntype="Person"
hint="I am the Constructor">
<cfargument name="firstName" type="string" required="false" default=""
hint="The person's first name" />
<cfargument name="lastName" type="string" required="false" default=""
hint="The person's last name" />
<cfargument name="birthdate" type="date" required="false" default="#createDate(1900, 1, 1)#"
hint="The person's birthdate" />
Like the <cfcomponent>
and <cffunction>
tags, the <cfargument>
tag has attributes, most of which are optional (but it is recommended to use all the attributes):
Attribute | Description | Tip |
---|---|---|
name | Name of the argument | Use descriptive names and avoid ambiguous names like "a" or "z8" |
type | Datatype of the argument | String, numeric, struct, array, UUID, DotPathToCFC, any |
required | Boolean indicating whether or not the argument is required | If set to "true" and the argument is not passed in, an error will be thrown |
default | Default value for the argument | If required is set to "true," the default value will be ignored |
hint | Used for documentation purposes | A great way to document an application |
The arguments that are passed into the cffunction are placed in the arguments scope, referred to by using arguments., which is then followed by the argument name. For example, the firstName argument would be referred to using arguments.firstName.
Next in the constructor, setter methods (explained in greater detail below) are called to set the values of the attributes within the CFC. These are not required, but this structure has become common practice for this type of object. For example:
<cffunction name="init" access="public" output="false" returntype="Person"
hint="I am the Constructor">
<cfargument name="firstName" type="string" required="false" default=""
hint="The person's first name" />
<cfargument name="lastName" type="string" required="false" default=""
hint="The person's last name" />
<cfargument name="birthdate" type="date" required="false" default="#createDate(1900, 1, 1)#"
hint="The person's birthdate" />
<cfset setFirstName(arguments.firstName) />
<cfset setLastName(arguments.lastName) />
<cfset setBirthdate(arguments.birthdate) />
Finally, since this is a constructor, it returns the object itself. To return the object itself, use the keyword this, which is shorthand for referring to the object itself. (Note that the use of this to return the object itself is not related to the this scope which was discussed earlier.) The cffunction is closed after the <cfreturn>
tag, which completes the constructor:
<cffunction name="init" access="public" output="false" returntype="Person"
hint="I am the Constructor">
<cfargument name="firstName" type="string" required="false" default=""
hint="The person's first name" />
<cfargument name="lastName" type="string" required="false" default=""
hint="The person's last name" />
<cfargument name="birthdate" type="date" required="false" default="#createDate(1900, 1, 1)#"
hint="The person's birthdate" />
<cfset setFirstName(arguments.firstName) />
<cfset setLastName(arguments.lastName) />
<cfset setBirthdate(arguments.birthdate) />
<cfreturn this />
</cffunction>
The custom setter methods within the constructor setFirstName()
, setLastName()
, and setBirthdate()
are created within the CFC in additional <cffunction>
blocks.
Getter and setter methods are common within "bean"-type objects, which are also known as "value objects." These types of objects are typically used to represent single entities (the "nouns") within OO applications. Including getters and setters in the Person CFC allows other components within the application to access data within the Person CFC. Other components cannot access or modify data in the Person CFC without its knowledge.
Getters and setters protect the data within the CFC. Without methods that control access to data within the CFC, data could be accessed and changed easily, making the data within the CFC less safe. Inserting a function in front of getting and setting data in the CFC also allows developers to perform additional tasks, such as data type validation and security checks, before returning or altering data within the CFC.
The following is an example of the getter and setter for the firstName attribute in the Person CFC (other getters and setters are very similar to this example):
<cffunction name="setFirstName" access="public" output="false" returntype="void"
hint="I set the firstName value">
<cfargument name="firstName" type="string" required="true"
hint="The new firstName value" />
<cfset variables.firstName = arguments.firstName />
</cffunction>
<cffunction name="getFirstName" access="public" output="false" returntype="string"
hint="I return the firstName value">
<cfreturn variables.firstName />
</cffunction>
First, take a look at the setFirstName method. As explained above, functions may or may not return a value to the caller of the function. In cases where the function does not return anything, it is customary to set the returntype attribute to "void," which means that the function does not return anything. Specifying a return type of "void" also helps to debug an application and makes things safer. If "void" is specified as the return type and there is a <cfreturn>
tag within the function that returns something, an error will be thrown. (Note that an empty <cfreturn>
tag can be present in a function that returns void, but this practice is not recommended.)
The <cfargument>
tag has already been discussed. What may be new to you is the variables scope used in the <cfset>
statement within the function. The variables scope (which will be discussed in greater detail below) is a scope available within the CFC itself but not directly accessible outside the CFC. This means that the methods within the CFC must be used to access the data within the CFC.
Next, look at the getFirstName method. Note that in the <cffunction>
tag a return type of "string" is used because a value is being returned from this method (as opposed to the return type of "void" in the setFirstName method). This method is quite simple, but it protects data and allows developers to add functionality within the getter method if needed. Remember, without a getter and setter method wrapped around the data, developers have less control over who accesses the data in the CFC, how other individuals access it, and what these individuals can and cannot do with it.
The complete Person CFC, including all the getter and setter methods, is provided in the following example:
<cfcomponent displayname="Person" output="false" hint="I am a Person">
<cffunction name="init" access="public" output="false" returntype="Person"
hint="I am the Constructor">
<cfargument name="firstName" type="string" required="false" default=""
hint="The person's first name" />
<cfargument name="lastName" type="string" required="false" default=""
hint="The person's last name" />
<cfargument name="birthdate" type="date" required="false" default="#createDate(1900, 1, 1)#"
hint="The person's birthdate" />
<cfset setFirstName(arguments.firstName) />
<cfset setLastName(arguments.lastName) />
<cfset setBirthdate(arguments.birthdate) />
<cfreturn this />
</cffunction>
<cffunction name="setFirstName" access="public" output="false" returntype="void"
hint="I set the firstName value">
<cfargument name="firstName" type="string" required="true"
hint="The new firstName value" />
<cfset variables.firstName = arguments.firstName />
</cffunction>
<cffunction name="getFirstName" access="public" output="false" returntype="string"
hint="I return the firstName value">
<cfreturn variables.firstName />
</cffunction>
<cffunction name="setLastName" access="public" output="false" returntype="void"
hint="I set the lastName value">
<cfargument name="lastName" type="string" required="true"
hint="The new lastName value" />
<cfset variables.lastName = arguments.lastName />
</cffunction>
<cffunction name="getLastName" access="public" output="false" returntype="string"
hint="I return the lastName value">
<cfreturn variables.lastName />
</cffunction>
<cffunction name="setBirthdate" access="public" output="false" returntype="void"
hint="I set the birthdate value">
<cfargument name="birthdate" type="date" required="true"
hint="The new birthdate value" />
<cfset variables.birthdate = arguments.birthdate />
</cffunction>
<cffunction name="getBirthdate" access="public" output="false" returntype="date"
hint="I return the birthdate value">
<cfreturn variables.birthdate />
</cffunction>
</cfcomponent>