Fully documented, step-by-step example of declarative meta-programming
File: demo/index.php
Configure a simple auto-loader
Configure the cache-path. The static Annotations
class will configure any public
properties of AnnotationManager
when it creates it. The AnnotationManager::$cachePath
property is a path to a writable folder, where the AnnotationManager
caches parsed
annotations from individual source code files.
Register demo annotations.
For this example, we're going to generate a simple form that allows us to edit a Person
object. We'll define a few public properties and annotate them with some useful metadata,
which will enable us to make decisions (at run-time) about how to display each field,
how to parse the values posted back from the form, and how to validate the input.
Note the use of standard PHP-DOC annotations, such as @var string
- this metadata is
traditionally useful both as documentation to developers, and as hints for an IDE. In
this example, we're going to use that same information as advice to our components, at
run-time, to help them establish defaults and make sensible decisions about how to
handle the value of each property.
To build a simple form abstraction that can manage the state of an object being edited, we start with a simple, abstract base class for input widgets.
Each widget will maintain a list of error messages.
A widget needs to know which property of what object is being edited.
Widget classes will use this method to add an error-message.
This helper function provides a shortcut to get a named property from a
particular type of annotation - if no annotation is found, the $default
value is returned instead.
Each type of widget will need to implement this interface, which takes a raw POST value from the form, and attempts to bind it to the object's property.
After a widget successfully updates a property, we may need to perform additional
validation - this method will perform some basic validations, and if errors are
found, it will add them to the $errors
collection.
Each type of widget will need to implement this interface, which renders an HTML input representing the widget's current value.
This helper function returns a descriptive label for the input.
Finally, this little helper function will tell us if the field is required -
if a property is annotated with @required
, the field must be filled in.
The first and most basic kind of widget, is this simple string widget.
On update, take into account the min/max string length, and provide error messages if the constraints are violated.
On display, render out a simple <input type="text"/>
field, taking into account
the maximum string-length.
For the age input, we'll need a specialized StringWidget
that also checks the input type.
On update, take into account the min/max numerical range, and provide error messages if the constraints are violated.
Next, we can build a simple form abstraction - this will hold and object and manage the widgets required to edit the object.
The constructor just needs to know which object we're editing.
Using reflection, we enumerate the properties of the object's type, and using the
@var
annotation, we decide which type of widget we're going to use.
This helper-method is similar to the one we defined for the widget base class, but fetches annotations for the specified property.
When you post information back to the form, we'll need to update it's state, validate each of the fields, and return a value indicating whether the form update was successful.
Finally, this method renders out the form, and each of the widgets inside, with
a <label>
tag surrounding each input.
Now let's put the whole thing to work...
We'll create a Person
object, create a Form
for the object, and render it!
Try leaving the name field empty, or try to tell the form you're 120 years old - it won't pass validation.
You can see the state of the object being displayed below the form - as you can see, unless all updates and validations succeed, the state of your object is left untouched.