A few years back, I wrote this answer to a question from which the following idea emerged.
I call these "Type Descriptors", as I have never been able to find this pattern documented or formally described anywhere - not to be confused with type descriptors in C#, but it's the most accurate name I've been able to come up with, so bear with me.
Here's my first attempt at a "formal" description:
A Type Descriptor is a type that provides meta-information about another type.
This pattern can be seen as an alternative to run-time reflection and annotations, and as such is equally applicable to languages that do or do not support reflection or annotations.
Type Descriptors are particularly useful as a means of describing aspects of model types found in for example domain and view-models, as well as in specialized types of models, such as form-models in a business- or web-application. Often such applications have many common aspects, such as the need to label inputs and perform common validations for e.g. required input, data types and formats, etc. - the information required by helper objects (or services) to handle these aspects fits comfortably in descriptors.
A descriptor mirrors the shape of the type it describes - for example, if a model type User has properties like id, name and email, it's type descriptor must have properties id, name and email, but where the properties on the model type have various value types (in this case for example integer and string) the properties of the descriptor are objects that provide information about those properties.
Maintaining identical model type and descriptor shapes may seem like duplication, in terms of property-names being identical in the model type and type descriptor - however, this does not violate the DRY principle as such, since the model type and descriptor, strictly speaking, are two different types that only happen to have the same shape, but do not have any overlapping concerns or duplication of any code; only the property names are duplicates.
I program mostly in PHP on a daily basis, and have used this pattern successfully to build applications. The following example is in PHP, though this could be implemented in any language capable of resolving properties by name at run-time.
A tiny bit of framework is required to implement this, so here goes...
A model-type implementing the following interface can provide a "type descriptor" - a type that describes the model-type itself:
interface TypeInfo
{
function getType();
}
Individual model-properties are described by "property descriptors" in the "type descriptor", for which I use a base class along the lines of this:
abstract class PropertyInfo
{
/** @var string */
public $name;
/** @var string */
public $label;
public function __construct($name)
{
$this->name = $name;
}
}
From this class, I derive "property descriptors" for specific types of properties - for example, this type describes a string property:
class StringInfo extends PropertyInfo
{
/**
* @var int|null
*/
public $max_length;
}
Now here's a sample User
model, and it's "type descriptor" UserType
:
class User implements TypeInfo
{
/** @var string */
public $name;
/** @var string */
public $email;
/** @return UserType */
public function getType()
{
return UserType::instance();
}
}
class UserType
{
/** @var StringInfo */
public $name;
/** @var StringInfo */
public $email;
public function __construct()
{
$this->name = new StringInfo('name');
$this->name->label = 'Full Name';
$this->name->max_length = 50;
$this->email = new StringInfo('email');
$this->email->label = 'E-mail Address';
$this->email->max_length = 128;
}
/** @return self */
public static function instance()
{
static $instance;
return $instance ?: $instance = new static();
}
}
As you can see, it's now possible to get the type descriptor either from a User
instance by e.g. $user->getType()
- notice that the property-names of the "property descriptors" in the "type descriptor" match the property-names in the actual model; using this pattern, it's my own responsibility to make sure the model and it's descriptor have the same shape.
Now I can write services and helpers etc. which may consume information from a "type descriptor" while working with model objects - for example, here's a very simple form helper:
class FormHelper
{
/** @var TypeInfo */
public $model;
/** @var string */
public $prefix;
/**
* @param TypeInfo $model
* @param string $prefix
*/
public function __construct(TypeInfo $model, $prefix)
{
$this->model = $model;
$this->prefix = $prefix;
}
/**
* @param StringInfo $prop
*
* @return string
*/
public function text(StringInfo $prop)
{
$name = $prop->name;
return '<label for="' . $name . '">' . htmlspecialchars($prop->label) . '</label>'
. '<input type="text" name="' . $this->prefix . '[' . $name . ']"'
. ($prop->max_length ? ' maxlength="' . $prop->max_length . '"' : '')
. ' value="' . $this->model->$name . '" id="' . $name . '" />';
}
}
You would use the form helper like this:
$user = new User;
$user->name = 'Rasmus';
$user->email = '[email protected]';
$form = new FormHelper($user, 'user');
$t = $user->getType();
echo $form->text($t->name) . "\n";
echo $form->text($t->email) . "\n";
Which would output something like this:
<label for="name">Full Name</label><input type="text" name="user[name]" maxlength="50" value="Rasmus" id="name" />
<label for="email">E-mail Address</label><input type="text" name="user[email]" maxlength="128" value="[email protected]" id="email" />
The two key reasons I enjoy working like this are (1) patterns like rendering forms, mapping post-data to model objects, mapping model-objects to database records, performing input validation and so forth can all be performed by formal helpers and service objects, all of which consume the same single source of information, and (2) everything is "statically typed", meaning only a single unchecked property-reference (by name, as a string) is made and encapsulated inside the "type descriptor", and type-hints and inference in a modern IDE (PhpStorm) can provide automated code inspections at development-time, I can safely use automated refactorings, and so forth.
Okay, a rather lengthy discourse, for what boils down to one question: does this pattern have a name? :-)
Side question: does this constitute (reflective) meta-programming? (The use of this pattern is very similar to e.g. using reflection to obtain annotations from a model, though I find this to be much safer, less complicated, more flexible and transparent.)
GetType()
just a Factory Method?getType()
isn't a factory method, I think the factory-method isUserType::instance()
? As for how this compares to working with reflection/annotations in C# - I like this much better, it feels more like "programming" and less like "hacking", since you're leveraging ordinary OO language features and common OOP patterns; reflection/annotations always felt a bit like raping the source code to me ;-)