Accessing implicit setter/getter - coldfusion

I have a component with a couple property definitions and an init() method. I am able to access getUuid() but not getSandwich().
component output="false" accessors="true" {
property
name="uuid"
type="string"
default=""
hint="The sandwich ID";
property
name="sandwich"
type="string"
default=""
hint="The fucking sandwich";
public any function init() {
this.setUuid(CreateUUID());
this.setSandwich = "Peanut Butter and Banana";
return this;
}
}
The Uuid property and corresponding getSandwich() method are available in the instance as expected where in the case of sandwich, the property is not set even though the value is applied to the setSandwich() method.

(From the comments)
this.setSandwich = "Peanut Butter and Banana";
Is that a typo or bug (ie that overwrites the setSandwich() method with a simple string)? Did you mean:
this.setSandwich( "Peanut Butter and Banana" );
?

Related

When use CFML code in the 'pseudo constructor' vs 'function init()' in a component

Given e.g. of the Pseudo Constructor in CFML:
component{
// Pseudo Constructor start
... here comes some cfml scripting code..
// Pseudo Constructor end
function init(){
return this;
}
}
I already understand that the Pseudo Constructor in a .cfc component:
Is the cfml code inbetween the component's beginning and the function init().
That the Pseudo Constructor's code runs immediately on components creation, even before the instantiating constructor "function init()" is invoked.
The variables created in the Pseudo Constructor are also available to all other components functions (Lexical Scoping)
Even code inbetween some component functions are also refered as Pseudo Constructors by some experienced cfml devs.
Please consider that I'm not refering to the use of cfproperty or property here, but to any other code in the Pseudo Constructor.
Still, I've not seen a good example or use case in CFML to help me decide. Could any experienced OOP CFML developer elaborate an example to understand it better:
When to use a code in the Pseudo Constructor and not the "function init()"?
Why NOT use the code in the instantiating constructor "function init()", so that the variables can be accessed/referenced with "this" keyword in any other components functions?
Please give an example code in such a manner that may help me and others decide when to use one over the other in the future?
After investigating a little deeper I came up with my own conclusion that I'd like to share with all interrested CFML developers. If any experienced OOP CFML developer has more precise information, I'd be really glad to know.
I've created a component named Cube.cfc that may answer the question for itself. My conclusion basically is that "pseudo constructors" allow to create some kind of "static" functions, because these fucntions can make use of these variables without instantiation of an object. Note that I didn't want to use the term "static" because these functions lacks of the naming attribute "static" (as far as I'm aware, only Lucee supports real "static" functions at the moment). Also, the example I'm showing only seems to work with createObject() and not the implicit constructors e.g. new Component(), because new Component() would instantiate the object immediately. But: using createObject and the pseudo constructor will at least allow to mimic static functions. Please note that my example component is just to be descriptive.
In the following example im using available functions that won't need any object instantiation. These functions can be used to retrieve some usefull informations that are not bound to any created/instantiated object.
Cube.cfc: a simple component to create cube objects
component displayname="Cube" accessors ="true" {
// class properties
property name="name" type="string";
property name="model" type="string";
property name="borderColor" type="string";
property name="material" type="string";
property name="dimension" type="struct";
// pseudo constructor
variables.models =[
"model-a",
"model-b",
"model-c"
];
variables.modelNameMaterialMapping ={
"model-a": "wood",
"model-b": "steel",
"model-c": "silver"
};
variables.modelNameDimensionsMapping ={
"model-a": {"height": 100, "length": 100, "width": 100 },
"model-b": {"height": 133, "length": 133, "width": 133 },
"model-c": {"height": 85, "length": 85, "width": 85 }
};
public any function init(
string name,
string borderColor,
string model
){
setName( arguments.name );
setBorderColor( arguments.borderColor );
setModel( arguments.model );
setMaterial( getMaterialByModelName( arguments.model ) );
setDimension( getDimensionByModelName( arguments.model ) );
return this;
}
//this function won't need any instantiating of an object because it uses variables of the pseudo constructor
public string function getMaterialByModelName( string modelName ){
return modelNameMaterialMapping[ arguments.modelName ];
}
//this function won't need any instantiating of an object because it uses variables of the pseudo constructor
public struct function getDimensionByModelName( string modelName ){
return modelNameDimensionsMapping[ arguments.modelName ];
}
//this function won't need any instantiating of an object
public string function isValidModel( string model ){
return variables.models.contains( arguments.model );
}
}
index.cfm:
<cfscript>
CubeService = CreateObject("component","Cube");
writeDump( CubeService );
writeDump( GetMetaData( CubeService ) );
modelsForChecking=[
"model-a",
"model-k",
"model-c",
"model-z"
];
// loop through model information without having any object instantiated
for( model in modelsForChecking){
if( CubeService.isValidModel( model )){
writeOutput("Cube ""#model#"" is valid.<br>");
writeOutput( "Cube models ""#model#"" are made of ""#CubeService.getMaterialByModelName( model )#"" and a dimension of ""#CubeService.getDimensionByModelName( model ).width#x#CubeService.getDimensionByModelName( model ).length#x#CubeService.getDimensionByModelName( model ).height#""<br>");
}else{
writeOutput("Cube ""#model#"" is NOT a valid model.<br>");
}
}
//intantiate a specific cube object with the name "CubeOne";
writeOutput( "Instantiate an object with the component:<br>");
CubeOne=CubeService.init("CubeOne", "white", "model-c" );
// dump properties of the specific cube "CubeOne"
writeDump( CubeOne );
// get width with the accessor getter for property "dimension" for the cube named "CubeOne"
writeOutput("""CubeOne"" has a width of #CubeOne.getDimension().width# <br>");
</cfscript>
If you run the above files you'll note that the functions:
getMaterialByModelName( "model-a" ),
getDimensionByModelName( "model-b"),
isValidModel( "model-z" )
don't need any instantiated object. They just retrieve some usefull information about the cube models without running any init() function.
That causes me to assume the following thumb rules:
Use variables that are bound to a property of an instantiated object within the "init()" functions.
Use variables in the 'Pseudo Constructor' whenever you need to use functions with those variables before having used the init() function.
Note that my component is just to be an descriptive example. If somebody comes up with more detailed information about the topic or I need to be corrected in my assumptions, I'd be glad to know.
IMPORTANT UPDATE: As #SOS thankfully commented, Adobe Coldfusion supports static functions since Coldfusion 2021. These "non-instantiated-object-related" functions can now be directly invoked with Component::staticFunctionName( args ) without using any preceeding CreateObject() nor the implicit constructor new Component()! As #SOS also commented, looks like "using static is probably the best approach for 2021+" in CFML because now both CFML engines Lucee and Coldfusion fully supports them.
For completeness I'm placing an adapted/rewritten version of my example code as a reference for 2021+:
Cube.cfc
component displayname="Cube" accessors ="true" {
// class properties
property name="name" type="string";
property name="model" type="string";
property name="borderColor" type="string";
property name="material" type="string";
property name="dimension" type="struct";
// set static varibales
static {
private models =[ "model-a", "model-b", "model-c" ];
private modelNameMaterialMapping ={
"model-a": "wood",
"model-b": "steel",
"model-c": "silver"
};
private modelNameDimensionsMapping ={
"model-a": {"height": 100, "length": 100, "width": 100 },
"model-b": {"height": 133, "length": 133, "width": 133 },
"model-c": {"height": 85, "length": 85, "width": 85 }
};
};
public any function init(
string name,
string borderColor,
string model
){
setName( arguments.name );
setBorderColor( arguments.borderColor );
setModel( arguments.model );
setMaterial( static.getMaterialByModelName( arguments.model ) );
setDimension( static.getDimensionByModelName( arguments.model ) );
return this;
}
public static string function getMaterialByModelName( string modelName ){
return static.modelNameMaterialMapping[ arguments.modelName ];
}
public static struct function getDimensionByModelName( string modelName ){
return static.modelNameDimensionsMapping[ arguments.modelName ];
}
public static string function isValidModel( string model ){
return static.models.contains( arguments.model );
}
}
index.cfm
<cfscript>
modelsForChecking=[
"model-a",
"model-k",
"model-c",
"model-z"
];
// loop through model information without having any object instantiated by calling static functions
for( model in modelsForChecking){
if( Cube::isValidModel( model )){
writeOutput("Cube ""#model#"" is valid.<br>");
writeOutput( "Cube models ""#model#"" are made of ""#Cube::getMaterialByModelName( model )#"" and a dimension of ""#Cube::getDimensionByModelName( model ).width#x#Cube::getDimensionByModelName( model ).length#x#Cube::getDimensionByModelName( model ).height#""<br>");
}else{
writeOutput("Cube ""#model#"" is NOT a valid model.<br>");
}
}
//intantiate a specific cube object with the name "CubeOne";
writeOutput( "Instantiate an object with the component:<br>");
CubeOne=new Cube("CubeOne", "white", "model-c" );
// dump properties of the specific cube "CubeOne"
writeDump( CubeOne );
// get width with the accesso getter for property dimension for the cube named "CubeOne"
writeOutput("""CubeOne"" has a width of #CubeOne.getDimension().width# <br>");
</cfscript>
For further reference, see:
Static functions for CFC in Lucee
Static functions for CFC in Adobe
CFML: static methods and properties - by Adam Camaron

Getting values from THIS scope after init function (persistency inside CFC)

I'm initiating a CFC like this.
<cfscript>
lock scope="application" timeout="5" {
application.mycfc = new mycfc();
}
writeOutput(application.mycfc.readVars());
</cfscript>
In the CFC, I'm setting some properties.
component output="false" accessors="true" {
property name="title";
property name="foo";
this.title = "mycfc";
function init() {
this.foo = "bar";
// I can now properly read this.title, or this.foo.
return this;
}
function readVars() {
// Here, I can read this.title, from the constructor space, but I can't
// read this.foo. It's just blank (because the default value of the
// `default` attribute of `property` is "")
}
}
Because of the implementation (caching in Application), I can instead use application.mycfc.foo in readVars().
Because of this name, it's hard to Google for details. I thought it would be persistent throughout the CFC's life, but apparently it is not?
I surely could do something like
var self = application[this.title]; // or application.mycfc
Or perhaps even
this = application[this.title];
In functions where I want to get/set without typing application.mycfc each time.
Just trying to make sure I'm not doing something wrong, or reinventing the wheel.
In my real implementation, I'm pulling from rows from a database to populate a struct.
Scopes in ColdFusion components (.cfc):
this
is the public scope, read/write from anywhere
properties
is a magical scope, read/write only via accessors (a.k.a. getters/setters) from anywhere
variables
is the private scope, read/write only within your component
All of these scopes can coexist, but this.x is NOT the same field as property name="x"!
Since you are using a component with accessors="true", all your property fields can only be read via getter and written via setter. So if you want to write your title property, use setTitle("mycfc"); instead of this.title = "mycfc";. Same goes for the foo property. Use setFoo("bar"); instead of this.foo = "bar";. If you want to read the properties, use application.mycfc.getTitle() and application.mycfc.getFoo(). If you want to set properties at runtime, use application.mycfc.setTitle("something"). Note that writing to a shared scope such as application should happen in a cflock to avoid race conditions (thread-safety).
If you don't need accessors at all, you can simply use public fields instead (accessors is missing here, i.e. set to false):
component output="false" {
this.title = "mycfc";
this.foo = "";
function init() {
this.foo = "bar";
return this;
}
function readVars() {
return this;
}
}
application.mycfc = new mycfc();
writeOutput(application.mycfc.title); // mycfc
writeOutput(application.mycfc.foo); // bar
application.mycfc.title = "something";
writeOutput(application.mycfc.title); // something
writeOutput(application.mycfc.foo); // bar
Public fields are usually not recommended though as they break encapsulation.

iTop - Get caller's IP in tickets

In iTop, How is it possible to save caller's IP address in tickets (User Request and Incident)
I tried to modify datamodel.itop-tickets.xml in my extension module. I added a field named 'ip' successfully but in <methods> section I can not get client's IP using $_SERVER['REMOTE_ADDR'] .
<methods>
<method id="DBInsertNoReload" _delta="redefine">
<static>false</static>
<access>public</access>
<type>Overload-DBObject</type>
<code><![CDATA[
public function DBInsertNoReload()
{
$oMutex = new iTopMutex('ticket_insert');
$oMutex->Lock();
$iNextId = MetaModel::GetNextKey(get_class($this));
$sRef = $this->MakeTicketRef($iNextId);
$this->Set('ref', $sRef);
$iKey = parent::DBInsertNoReload();
$oMutex->Unlock();
return $iKey;
$this->Set('ip', $_SERVER['REMOTE_ADDR'] );
}
]]></code>
</method>
</methods>
There is another option, in itop customs extension you can include another datamodel. (you can use XML or PHP datamodel).
So, you have to create a new php file and write the class you want inside to extend the datamodel. You have to extends them with : https://www.combodo.com/documentation/api-ref-extensions/packages/Extensibility.html
If you use the interface "iApplicationObjectExtension", you can use the method OnDBInsert to set other field in your object/
for example
Class YourClassName implements iApplicationObjectExtension {
public function OnIsModified($oObject){}
public function OnCheckToWrite($oObject){}
public function OnCheckToDelete($oObject){}
public function OnDBUpdate($oObject, $oChange = null){}
public function OnDBDelete($oObject, $oChange = null){}
public function OnDBInsert($oObject, $oChange = null) {
if ($oObject instanceof UserRequest) {
// Do what you want with $oObject
$oObject->DBUpdate(); // Update object
}
}
}
After lots of attempts I finally found the solution :)
We must redefine a method of type LifeCycleAction and so I've just redefined ComputeImpactedItems method in both Inciudent and UserRequest classes.
For make it much clear I show one of them here:
<class id="Incident">
<methods>
<method id="ComputeImpactedItems" _delta="redefine">
<static>false</static>
<access>public</access>
<type>LifecycleAction</type>
<code><![CDATA[ public function ComputeImpactedItems()
{
// This method is kept for backward compatibility
// in case a delta redefines it, but you may call
// UpdateImpactedItems directly
$this->UpdateImpactedItems();
// This line is added by this exstension for saving caller's ip
$this->Set('ip', $_SERVER['REMOTE_ADDR']);
}]]></code>
</method>
</methods>
</class>

Coldfusion extended component missing parameters

I'm trying to get a CFC (webCFC) with a remote function to return an instance of a different CFC (objCFC).
Here are the CFCs:
#webCFC
component {
remote function displayCFC(version=1) {
if(version==1) {
return new baseCFC();
} else {
return new objCFC();
}
}
}
#baseCFC
component
accessors="true"
persistent="true"
{
property name="name" default="pete";
}
#objCFC
component
extends="baseCFC"
persistent="true"
accessors="true"
{
property name="age" default="30";
}
If I call this URL: /webCFC.cfc?method=displayCFC&returnFormat=json, I get this response:
{
"name" : "pete"
}
which is fine. If I call this URL: /webCFC.cfc?method=displayCFC&returnFormat=json&version=2, then the response is missing the property from baseCFC
{
"age" : 30
}
I would expect the response to look like this:
{
"name" : "pete",
"age" : 30
}
I know that I can use the setName() and getName() functions on objCFC, it is definatly extending baseCFC but the extended properties don't show if I access the CFC through the browser.
Is it possible to get this to work?
This could be related to the seralizejson bug (not sure when will it ever be bug free).
A workaround would be to implement your own getMemento() or toJSON() method that returns all the desired properties in a struct. Then serializeJSON that struct instead.

Can function argument have hint in cfscript (CF9)?

Can function argument have hint in cfscript (CF9)?
CFML style:
<cffunction name="myFunc" output="false" returntype="void">
<cfargument name="arg1" type="arg1" default="default" hint="my hint">
...
</cffunction>
CF9 cfscript style:
public void function myFunc(string arg1='default') {
...
}
Where to specify hint of the argument (arg1) above?
The easiest way is to to use JavaDoc notation.
component{
/**
* #hint This is a hint
* #arg1 This is an argument hint
* #arg2 This is another argument hint
*/
public void function myFunc(string arg1='default', numeric arg2) {
return TRUE;
}
}
I've not played with cf9, but you can do something like this in CF8:
<cffunction name="myFunc" output="false" returntype="void">
<cfargument name="arg1" type="arg1" default="default" hint="my hint">
<cfscript>
//do stuff
</cfscript>
</cffunction>
Not ideal, but maybe an acceptable comprimise.