Emily Christiansen
Matt Gifford
A more modern alternative to cfinclude
is to create components.
Components in ColdFusion behave similarly to objects in many other
programming languages. First, start by creating a new file, called
Greeting.cfc
. ColdFusion uses .cfc
to denote files that are components.
Inside of Greeting.cfc
, add a component block:
component {
}
Think of this component like a bucket of code that you can carry around. You can hand it to a friend, and she can execute it, or she can hand it to her friend, or give it back to you. She can put more code in the bucket, or read values from it too. Components have properties, both public and private, and methods (functions). Properties are like labels on your bucket. You can read the label, and you can change its value.
For now, let's add functions to the component.
component {
public string function getFullName(
required string firstName,
required string lastName
){
var fullName = arguments.firstName & ' ' & arguments.lastName;
return fullName;
}
public string function getGreeting(
required string firstName,
required string lastName
){
var fullName = getFullName( argumentCollection = arguments );
var greeting = "Hello, " & fullName;
return greeting;
}
}
Suppose we wanted make different types of greetings. Right now, the
getGreeting
function only allows us to say "Hello". What we can do is
create a baseGreeting
property that will be available to all of the functions in
the component.
component accessors="true" {
property name="baseGreeting" type="string" default="Hello, ";
public function init(
string baseGreeting = variables.baseGreeting
){
setBaseGreeting( arguments.baseGreeting );
return this;
}
public string function getFullName(
required string firstName,
required string lastName
){
var fullName = arguments.firstName & ' ' & arguments.lastName;
return fullName;
}
public string function getGreeting(
required string firstName,
required string lastName,
string baseGreeting = getBaseGreeting()
){
var fullName = getFullName( argumentCollection = arguments );
var greeting = arguments.baseGreeting & fullName;
return greeting;
}
}
What have we done here? We've added a baseGreeting
string property to the component, setting the default value to our default greeting.
We've also added a new attribute to the component
definition, accessors="true"
. This will allow the component to provide explicit accessor (getter) and mutator (setter) functions for the properties we define. This means we can call getBaseGreeting
or setBaseGreeting
to fetch or update the value without having to write specific functions within the component ourselves.
We've also added a new method at the top of the component, init
. This is the constructor method which will set the baseGreeting
value with whatever value we choose to send in, or it will use the property default value, before returning the component itself.
By defining the property with a default value, that variable is now available within the variables
scope, and can only be accessed from within the Greeting component via that scope. We can call it from outside of the component by calling getBaseGreeting()
.
Let's look at how we can call this revised component and methods to obtain our greeting.
<cfscript>
objGreeting = createObject( 'component', 'path.to.component.Greeting' );
myGreeting = invoke( objGreeting, 'getGreeting', {
firstName = 'Matt',
lastName = 'Gifford'
} );
writeOutput( myGreeting );
</cfscript>
This would result in the following response: Hello, Matt Gifford
.
We can pass in an alternative greeting and receive a different output:
<cfscript>
objGreeting = createObject( 'component', 'path.to.component.Greeting' );
myGreeting = invoke( objGreeting, 'getGreeting', {
firstName = 'Matt',
lastName = 'Gifford',
baseGreeting = 'Welcome, '
} );
writeOutput( myGreeting );
</cfscript>
This would result in the following response: Welcome, Matt Gifford
.
First, we created a new instance of the Greeting component using the createObject
method. CreateObject
takes in two parameters:
We then used the invoke
method to call a specific publicly acessible method from the component instance. To do so, we passed in the variable reference to the object, the name of the function within that component to call, and finally we passed in a struct or values that are arguments for the method.
The first thing you probably noticed about the above section is that it is an awful lot of code to do three things:
Fortunately, there is a way to simplify this process. Let's start by instantiating our object:
<cfset objGreeting = CreateObject( "Component", "path.to.component.Greeting" ).init()>
You may notice that we have called the init
method without sending in any values. In this case, the default baseGreeting
property value will be used. We can override this during instantiation should we wish by passing in a new value like so:
<cfset objGreeting = CreateObject( "Component", "path.to.component.Greeting" ).init( baseGreeting = 'Hola, ' )>
Now that we have the object, we can call the function:
<cfset myGreeting = objGreeting.getGreeting(
firstName = "Matt",
lastName = "Gifford"
)>
Notice how we have switched to a dot notation, similar to our examples of helper functions above. Now we get the sense of the object (the Greeting) doing something (the function). This is far more readable and descriptive than the alternative.
Finally, we can output the results:
<cfoutput>
#myGreeting#
</cfoutput>
Ultimately, our finished, refactored example looks like this:
<cfset objGreeting = CreateObject( "Component", "path.to.component.Greeting" ).init()>
<cfset myGreeting = objGreeting.getGreeting( firstName = "Matt", lastName = "Gifford" )>
<cfoutput>
#myGreeting#
</cfoutput>
Let's change how we pass in the first and last name values for the individual.
As mentioned, we can pass any type of property into a ColdFusion component, including other components.
Let's create a new component called Person.cfc
that accepts the firstName
and lastName
values as required properties:
component accessors="true" {
property name="firstName" type="string";
property name="lastName" type="string";
public Person function init(
required string firstName,
required string lastName
){
setFirstName( arguments.firstName );
setLastName( arguments.lastName );
return this;
}
public string function getFullName(){
return this.getFirstName() & ' ' & this.getLastName();
}
}
Once again, we have utilised the explicit accessors for the component, and we are passing through the required properties into the init
method when we construct a new instance of the object.
We have also moved the getFullName
method into this new object so we can use it whenever we need to as it relates directly to the person, and not the greeting.
Let's now update the Greeting
component to accept this new object as a required value:
component accessors="true" {
property name="person" type="Person";
property name="baseGreeting" type="string" default="Hello, ";
public Greeting function init(
required Person person,
string baseGreeting = variables.baseGreeting
){
setPerson( arguments.person );
setBaseGreeting( arguments.baseGreeting );
return this;
}
public string function getFullName(){
return getPerson().getFullName();
}
public string function getGreeting(
string baseGreeting = getBaseGreeting()
){
var fullName = this.getFullName( argumentCollection = arguments );
var greeting = arguments.baseGreeting & fullName;
return greeting;
}
}
Here we have added a new person
property to the component, setting the type
attribute to match the name of the object. We have also updated the init
method to require this new object as an argument, and we are setting the propery value using the setPerson
method.
We still have our original getFullName
method within this Greeting component,
but instead of concatenating the strings here we are sending that request
through to the getFullName
method within the injected Person
object.
Our updated call to fetch the greeting would now look like this:
<cfscript>
objPerson = createObject( 'component', 'Person' ).init(
firstName = 'Matt',
lastName = 'Gifford'
);
objGreeting = createObject( 'component', 'GreetingInjection' ).init( person = objPerson );
writeOutput( objGreeting.getGreeting() );
</cfscript>
By doing this, we now have two components that can be reused throughout our application's code base, and more importantly they both be tested to ensure they work as expected as and when they are updated or refactored.
We can streamline this code a little more. Instead of using the built-in CreateObject
function, we can instead create and instantiate the component in one hit using the new
function:
<cfscript>
objPerson = new Person( firstName = 'Matt', lastName = 'Gifford' );
objGreeting = new Greeting( person = objPerson );
writeOutput( objGreeting.getGreeting() );
</cfscript>
The results are the same, but by using the new
function you are implicitly calling the init
function as you also create a new instance of the object.
ColdFusion offers a wide variety of ways to accomplish code reuse. By leveraging these techniques, you will find your code to be less repetitive and more fun to write. Code reuse is not an end in and of itself; rather, it is a means to another end: maintainability. Having code that is broken up into concise, self-documenting parts enables you to read and interpret code much more quickly.