Using <cfproperty> to create default CFC property values
My current ORM learnings have brought me into contact with cfproperty
, a tag I've never previously needed since its capabilities pre-CF9 were limited. To create usable properties in components (as in properties v. methods) you set them and their default values directly in the variables scope (or possibly in a variables.instance
struct so you can refer to them as a group).
// Cat.cfc
<cfcomponent displayname="Cat">
<cfscript>
function init()
{
variables.instance =
{
name=""
,type="tabby"
}
return this;
}
</cfscript>
</cfcomponent>
Getting better: better getting (and setting)
In CF9, cfproperty
is much more useful, but it still won't always do what its name and attributes suggest: i.e. define and set default values for your CFC properties.
What it will do now is automatically generate a synthesized (a.k.a implicit) setter and getter for each property if you so choose by adding the accessors
attribute to the component.
<cfcomponent displayname="Cat" accessors="true">
<cfproperty name="name" default="">
<cfproperty name="type" default="Tabby">
<cfscript>
function init()
{
return this;
}
</cfscript>
</cfcomponent>
This means we can call getName()
and setName("value")
without having to physically code these methods in the cfc (or write your own generic getter/setters using onMissingMethod()
in Application.cfc)
Not quite there
All very nice, except that if we invoke my CF9 Cat.cfc as follows and try and use the auto getters...
myCat = new Cat();
WriteDump( myCat.getName() );
WriteDump( myCat.getType() );
...we get:
The auto-generated get
methods do exist (otherwise we'd have got an error), but the properties are apparently undefined, despite having defined them with cfproperty
. Why aren't we seeing the default values set in the tags?
The problem seems to be that CF doesn't put the properties into the variables scope until each one is assigned a value, as explained in this post by CiarĂ¡n Archer, and reported as a bug by Luis Majano. In other words we can only get it if we first set it.
myCat = new Cat();
myCat.setName( "Tibbles" );
WriteDump( myCat.getName() );
The above outputs "Tibbles" as you'd expect.
Adobe have partially documented this when they say:
Features of properties with setter and getter methods include the following [...]
- [...]The default attribute has no effect on the property and does not set an initial property value.
But the whole point of defaults is to make sure values exist whether or not they are set, and not being able to rely on cfproperty
for this means we're going to have to keep initialising our properties the old way.
But wait...
Except that... with the ORM app I'm currently playing with I noticed that the cfproperty
defaults were being set as expected.
So I thought I'd try making my test Cat pretend that it was part of an ORM application, even though it was a completely standalone CFC with no database, no Application.cfc and therefore no ORM settings. How? By just setting the persistent
attribute on the component.
<cfcomponent displayname="Cat" persistent="true" accessors="true">
<cfproperty name="name" default="">
<cfproperty name="type" default="Tabby">
<cfscript>
function init()
{
return this;
}
</cfscript>
</cfcomponent>
Now when we re-run the code with no setters...
myCat = new Cat();
WriteDump( myCat.getName() );
WriteDump( myCat.getType() );

...we get our default values: [empty string] Tabby
I'm not sure what else happens behind the scenes when you use the persistent
attribute, but I'm using it in all the model objects in my non-ORM-enabled blogging app, and so far I've noticed no untoward effects on performance or the database.
The cat seems pretty relaxed about it too.
Comments