Creating an object constructor
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.
Creating an init() function
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
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.
Calling your init() function
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.
The This scope
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.
Note
Values stored within the This
scope last as long as the component instance exists, and therefore can persist between calls to methods of a CFC instance.