Mach II 1.5 Feature: New Property Datatypes - Mach-II/Mach-II-Framework GitHub Wiki
- The Problem
- Exploring the Solutions
-
The Solution in Mach-II: Structs and Arrays as Properties +
New MachII.framework.Property
CFC - Load Order Dependency
- In Closing
Properties in Mach-II applications are a convenient way to make
variables available throughout an entire Mach-II application. Properties
are declared in mach-ii.xml
and after the application initializes,
properties may be accessed via a simple getProperty() method call from
anywhere within the application, including within all Mach-II object
types (Listeners, Filters, Plugins) as well as in views. In addition to
declaring properties in mach-ii.xml
, they may also be added to
Mach-II's property manager programmatically.
The current limitation with properties, however, is that property
declarations within mach-ii.xml are limited to simple name/value pairs.
Complex data types and custom CFCs may not be declared as properties in
mach-ii.xml
. Variables that go beyond simple name/value pairs may be
added programmatically, but there is no mechanism built into Mach-II for
handling this when the application is initialized. The most common
solution for adding complex data types or CFCs as Mach-II properties
upon application initialization is to use a plugin, and within the
configure()
method of the plugin, the objects and/or variables needed
are instantiated and the objects and/or variables are then manually
added to the property manager.
Although this approach works, there are a few issues that have led to requests for enabling the ability to more easily use CFCs and complex data types as properties:
- Using a plugin solely to have access to the
configure()
method so that it is called when the application initializes is a bit of a "hack," as it leverages only theconfigure()
method of the plugin and not any of the plugin points. This clearly isn't a job that a plugin was designed to handle, because the plugin is being used not as a plugin, but rather as a property loader. - Using a plugin to put properties in the property manager on application initialization adds unnecessary complexity to the process.
- As a result of the name/value pair limitation in
mach-ii.xml
, properties are treated completely differently based on their data type, and therefore all the application properties cannot be declared in the XML configuration file. This means having to look in multiple places to keep track of properties, and the difference in treatment based on data type is unnecessarily inconsistent.
Several solutions to this issue have been discussed and briefly explored.
- Allow for the declaration of structs and arrays as properties in
mach-ii.xml
, but limit additional property types to only structs and arrays (i.e. any other data type would still have to be added to the property manager programmatically using the plugin method described above). This would provide a minor amount of additional functionality and these data types could be fully declared (struct keys and array elements included, to an extent) within the XML configuration file. This is, however, of rather limited use. First, using something like a struct but only allowing simple data types to be put in the struct is only an incremental benefit over using separate name/value pairs as properties. Second, where arrays are concerned, the immediate question is what data types can be used as elements of the array. If the data types allowed in either case are constrained, then this approach is of extremely limited benefit. - Allow for the use of simple "bean" type objects as properties. In this way, Mach-II would know what to expect and could dictate exactly how the bean must be structured in order for it to be used as a property. This is of some additional benefit when compared to number 1 above, because it would at least offer some encapsulation and data hiding, but ultimately the benefit here is also relatively limited because of the constraints that would be placed on what developers can and can't do. In other words, if someone wanted to use a CFC that didn't conform to the bean-type pattern, they would still have to use the plugin method described above to put a CFC in the property manager, meaning this approach (and number 1 for that matter) is only a partial solution. Also when compared to using something like a struct, there is only a slight benefit of having getters/setters for all the properties. Finally, there is the issue again of what data types can be used as properties within the CFC itself.
- Introduce a new
MachII.framework.Property
CFC (the name of this object is up for debate) to the core. This object would be extended by the developer in similar fashion to the other Mach-II core objects (Listener, Filter, Plugin) and would contain aconfigure()
method that Mach-II would automatically call when the application initializes. This would allow developers to use custom CFCs as properties in Mach-II applications, but would allow them to be declared inmach-ii.xml
right along with the simple name/value properties. Because the behavior is consistent with other Mach-II object types, this also makes the new Property CFC immediately familiar to Mach-II developers, reduces complexity and increases accessibility to property declarations (no need to hunt around because they're all in the XML configuration file), and allows developers total freedom to use custom CFCs as application properties.
Through the discussions on the Mach-II mailing list and among the
developers of Mach-II, number 3 above seems to make the most logical
sense as a solution. It's consistent with the way other object types
within Mach-II work, it eliminates the need to leverage a plugin to
handle this, and it allows all properties for the application to be
declared within mach-ii.xml
. When number 3 is combined with number 1,
it creates a complete solution that allows for the use of simple
name/value pairs, structs and arrays, and custom CFCs to all be easily
used as properties, and all be declared in the <properties>
section of
mach-ii.xml
.
In addition to what is outlined above, another potential downside for allowing the use of CFCs as properties is the possibility that developers will expect Mach-II to manage object dependencies within properties in similar fashion to something like ColdSpring. While this is a concern, this same concern also applies to the existing native Mach-II object types (Listeners, Plugins, Filters), so in that sense the new Property.cfc would not be adding any issues that don't already exist in Mach-II. To be clear, Mach-II will not manage dependencies for developers. That is the responsibility of developers, and they may either choose to manage dependencies themselves, or they may use ColdSpring to manage dependencies. (As an aside, the addition of this new object type to Mach-II will require an update to the Mach-II plugin for ColdSpring so that ColdSpring can manage dependencies in the new object type in addition to the Mach-II object types it already handles.)
As described above the addition of structs and arrays to the allowable
data types for properties is of limited benefit, but it is also
relatively easy to implement within the framework, and when combined
with the new proposed core framework object type (Property.cfc
), will
allow developers maximum flexibility with application properties.
Structs would be declared in mach-ii.xml
as follows, and keys could be
declared using any of the methods outlined below:
<properties>
<property name="myStruct">
<struct>
<key name="key1" value="valueA"/>
<key name="key2">
<value>valueB</value>
</key>
</struct>
</property>
</properties>
Arrays would be declared in mach-ii.xml
as follows, and array elements
could be declared using any of the methods outlined below:
<properties>
<property name="myArray">
<array>
<element value="valueA"/>
<element>
<value>valueB</value>
</element>
</array>
</property>
</properties>
Structs and arrays could also be mixed and nested.
Array as struct element:
<properties>
<property name="myStruct">
<struct>
<key name="key1">
<array>
<element value="valueA"/>
<element value="valueB"/>
</array>
</key>
</struct>
</property>
</properties>
Struct as array element:
<properties>
<property name="myArray">
<array>
<element>
<struct>
<key name="key1" value="valueA" />
</struct>
</element>
<element value="ABC123"/>
</array>
</property>
</properties>
CFCs cannot be used as elements in arrays and structs. Due to the complexity, we'll be limiting them to simple data types (or nested structs/arrays) if you choose to implement a property this way.
As described above, the new Property.cfc
framework object is a
first-class framework object along with Listeners, Plugins, and Filters,
and would follow the same pattern as these existing framework objects.
To elaborate, the developer would extend the Property.cfc
, implement a
configure()
method as needed, and this configure()
method would be
called by Mach-II when the application initializes. The developer is
then free to implement any other code necessary in the object itself.
Additionally, upon application initialization and after calling the
configure()
method, Mach-II would put the object in the property
manager and give it the name declared in mach-ii.xml.
A sample Property.cfc
called MyAppProperty.cfc
would be implemented
as follows:
<cfcomponent
displayname="MyAppProperty"
output="false"
extends="MachII.framework.Property"
hint="My app property CFC for my app">
<cffunction name="configure" access="public" output="false" returntype="void"
hint="Called automatically by Mach-II on application initialization">
<!--- do necessary startup stuff here such as getting parameters --->
</cffunction>
<cffunction name="myCustomMethod" access="public" output="false" returntype="string"
hint="Some custom method">
<cfreturn "Hello world." />
</cffunction>
</cfcomponent>
This object would be declared in mach-ii.xml
as follows. Notice that
there is a type
attribute which is only used when defining CFCs as
properties:
<properties>
<property name="myAppProperty" type="path.to.MyAppProperty" />
</properties>
Property.cfc
supports parameters in similar fashion to plugins,
filters and listeners which of course would be available to the
MyAppProperty.cfc
as parameters:
<properties>
<property name="myAppProperty" type="path.to.MyAppProperty">
<parameters>
<parameter name="parameter1" value="value1" />
<parameter name="parameter2" value="value2" />
</parameters>
</property>
</properties>
All configuration logic should take place in a
configure()
method. Do not override theinit()
method that is inherited from theMachII.framework.Property
(which inherits from theMachII.framework.BaseComponent
).**
As of Mach-II 1.9, you can use a shortcut for the property type when referencing Mach-II property CFCs that are shipped with the core framework.
Shortcut Name | Value Substituted at Runtime |
---|---|
ColdspringProperty |
MachII.properties.ColdspringProperty |
EnvironmentProperty |
MachII.properties.EnvironmentProperty |
HtmlHelperLoaderProperty |
MachII.properties.HtmlHelperLoaderProperty |
HtmlHelperProperty |
MachII.properties.HtmlHelperProperty |
UrlRoutesProperty |
MachII.properties.UrlRoutesProperty |
CachingProperty |
MachII.caching.CachingProperty |
LoggingProperty |
MachII.logging.LoggingProperty |
GlobalizationLoaderProperty |
MachII.globalization.GlobalizationLoaderProperty |
As of Mach-II 1.9, you can omit the property name for property CFCs. This allow for anonymous property CFCs so you do not have to arbitrarily name property CFCs that are used for configuration.
<properties>
<property type="path.to.MyAppProperty">
<parameters>
<parameter name="parameter1" value="value1" />
<parameter name="parameter2" value="value2" />
</parameters>
</property>
</properties>
Mach-II will automatically name the property using the last list element
from the type
attribute plus a number. In the example above, the
property name would be MyAppProperty_1
. If another property of the
same type is defined without a property name, the number counter is
increase.
N.B. If you need to reference a property by name, do not omit the
property name and rely on the order in which the properties are loaded.
This feature is mainly useful for multiple properties like UrlRoutes
which provide configuration information to Mach-II but are not directly
referenced in your application.
Some properties may be dependent on others, because of this be aware of
the order in which they are specified in the configuration file. As of
Mach-ii 1.9, the framework enforces a load order for the
EnvironmentProperty
and ColdspringProperty
components.
EnvironmentProperty
will be loaded first, followed by
ColdspringProperty
regardless of the order they are specified in the
configuration file (but only of course if they are defined in your
configuration.)
The addition of these new property data types does not change the
backwards compatibility of the framework nor does it force developers to
use this new feature. However, the new property data types will allow
developers to more easily deal with properties within Mach-II
applications that are not simple name/value pairs. The syntax outlined
above for declaring structs and arrays within mach-ii.xml
allows for
the easy use of these data types as properties.
The implementation of Property.cfc
is consistent with existing Mach-II
framework objects (Listeners, Plugins, and Filters) and eliminates the
need to use a plugin's configure()
method to put complex data types in
the property manager when the application initializes. The addition of
these new supported property data types also has the added benefit of
creating a more consistent handling of properties, because the data type
of the property will not dictate how it's handled within the
application. Furthermore, developers will be able to declare all of the
properties within the mach-ii.xml file, making maintenance of
applications a bit less complex.