Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Dynamically write implicit getters and setters in ColdFusion/Lucee Components?

I want to be able to dynamically write a set of getters and setters in CFML/LUCEE components ( No hardcoded cfproperty tags).

<!--- MyComp.cfc --->
<cfcomponent displayname="MyComp" hint="MyComp" accessors="true">
    <cffunction name="init">
       <cfargument name="dynamicprops" type="array">
       <cfloop array="#dynamicprops#" index="item">
          <!--- 
           Now what? I cannot do a cfsavecontent and write props here.
           It demands cfproperty just after the cfcomponent begins. I 
           tried to do with closures but they are not acually setters 
           and getters. Does anyone know how to better do it? 
          ---> 
      </cfloop>
    </cffunction>
</cfcomponent>

<!--- example call --->
<cfset mc = CreateObject("component","MyComp").init( [{"name"="a","default"=1}] ) />

Then I want to be able to call mc.setA( 100 ) and mc.getA(). But does not happen.

So my humble question is how can I dynamically write setters and getters on component?

PS: Please remeber that I have tried the closure way:

 variables[item.name] = item.default;
 variables["set"&item.name] = function(_val){ variables[item.name] =_val; }
 variables["get"&item.name] = function(){ return variables[item.name; }

Couldn't get worked. How can I do it? Thanks :)

like image 372
edam Avatar asked Feb 12 '18 23:02

edam


2 Answers

You could use onMissingMethod() for this.

component name="myComponent" hint="myComponent.cfc"{

    function init( struct dynamicProperties={} ){
        dynamicProperties.each( function( name, default ){
            variables[ name ] = default;
        } );
        return this;
    }

    function onMissingMethod( name, args ){
        if( name.Left( 3 ) IS "get" )
            return get( name );
        if( ( name.Left( 3 ) IS "set" ) AND ( args.Len() IS 1 ) )
            return set( name, args[ 1 ] );
        cfthrow( type="NonExistentMethod", message="The method '#name#' doesn't exist" );
    }

    public any function get( required string accessorName ){
        var propertyName = parsePropertyName( accessorName );
        if( !variables.KeyExists( propertyName ) )
            cfthrow( type="NonExistentProperty", message="The property '#propertyName#' doesn't exist" );
        return variables[ propertyName ];
    }

    public void function set( required string accessorName, required any value ){
        var propertyName = parsePropertyName( accessorName );
        if( !variables.KeyExists( propertyName ) )
            cfthrow( type="NonExistentProperty", message="The property '#propertyName#' doesn't exist" );
        variables[ propertyName ] = value;
    }

    private string function parsePropertyName( accessorName ){
        return accessorName.RemoveChars( 1, 3 );
    }

}

Pass it your struct of property names/default values and it will "listen" for getters/setters that match. Any that don't will result in an exception.

<cfscript>
myDynamicProperties = { A: 0, B: 0 }; // this is a struct of names and default values
mc = new myComponent( myDynamicProperties );
mc.setA( 100 );
WriteDump( mc.getA() ); // 100
WriteDump( mc.getB() ); // 0
WriteDump( mc.getC() ); // exception
</cfscript>

UPDATE 1: Property name array replaced with name/default value struct as init argument to allow default values to be set.

UPDATE 2: If you want to pass an array of structs containing your name/default value pairs e.g.

dynamicProperties = [ { name: "A", default: 1 }, { name: "B", default: 2 } ];

then the init() method would be:

function init( array dynamicProperties=[] ){
    dynamicProperties.each( function( item ){
        variables[ item.name ] = item.default;
    } );
    return this;
}

UPDATE 3: If you must use tags and <cfloop> to set your dynamic properties then this is all you need in your init method:

<cfloop array="#dynamicProperties#" item="item">
  <cfset variables[ item.name ] = item.default>
</cfloop>

onMissingMethod won't fire if you try to invoke the dynamic methods as properties like this:

method = mc[ "set#property#" ];
method( value );

Instead just make the set() and get() methods in the component public and invoke them directly:

mc.set( property, value );
mc.get( property );
like image 59
CfSimplicity Avatar answered Nov 06 '22 00:11

CfSimplicity


Consider using accessors

<cfcomponent displayname="MyComp" hint="MyComp" accessors="true">

Source: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tags-c/cfcomponent.html

like image 3
James A Mohler Avatar answered Nov 06 '22 00:11

James A Mohler