NAME
Form::Tiny::Manual::Internals - details on form implementation
DESCRIPTION
This guide gets in depth into Form::Tiny metaobject model. This knowledge should not be required to make full use of the module, but is required to extend it or contribute to it.
How does the module work?
Behind the scenes, the module stores objects of Form::Tiny::Meta for each package that was turned into a form by a proper Form::Tiny call. Meta object is an object that stores all information about the form layout, but no information about form input or validation state. Each class of Form::Tiny::Form contains the form_meta
method, which returns this meta object for the given package.
Form building is done just by calling methods on the meta object. Each helper of the DSL is just a single call to a metaobject method, for example, the following calls are equivalent:
form_field 'field-name' => %args;
__PACKAGE__->form_meta->add_field('field-name' => %args);
The actual form object only stores data that is relevant to form state, not to form layout. Thanks to this model, the layout needs not to be rebuilt each time a new form object is constructed, which speeds up construction.
Additional behavior like filtering is implemented by composing new roles to the meta object and declaring new hooks in setup
method. Refer to the code of Form::Tiny::Plugin::Filtered to see how meta roles may define additional behavior.
Why use meta object model at all?
The 1.00 series of the module did not implement a meta object, and in turn implementing DSL keywords ended up being a hack, abusing Perl's ability to replace symbols in the package with strict mode turned off. New implementation allowed to get rid of all that dark magic, replacing it with something readable and reliable.
Static forms vs dynamic forms
The form metaobject keeps track of whether it is static or dynamic via the is_dynamic
property.
A dynamic form is a form that has at least one dynamic field. Dynamic fields are fields that are built by a subroutine.
Static forms are preferred over dynamic forms because they allow us to make certain optimizations and overall know more about a form from just having its metaobject. Dynamic forms are more of a closed box, and the only way to know what's inside is by building them with an instantiated form.
For the sake of completeness and backwards compatibility, we continue to support dynamic forms in the base distribution.
Form blueprint
Form metaobjects can be used to produce a Perl hash reference with an identical structure to fields data, called blueprint. For example, a form with those fields:
scalar1
array1.*
array2.*.*
hash1.scalar2
hash1.scalar3
Will generate this blueprint:
{
scalar1 => FieldDefinition,
array1 => [FieldDefinition],
array2 => [
[FieldDefinition]
],
hash1 => {
scalar2 => FieldDefinition,
scalar3 => FieldDefinition
}
}
Each leaf will be a Form::Tiny::FieldDefinition instance, and subforms will be turned into their own blueprints and embedded. This can be used to do more post-processing on a $form->fields
hashref compared to the full form definition, without the need to loop the form fields array and manually handle all the possibilities.
For arguments, see "blueprint" in Form::Tiny::Meta.
Finalizing the meta
After creating the meta object with create_form_meta
function, the object exists in an incomplete state. It is so that superclasses can be applied before finalizing the metaobject and applying inheritance. Calling get_package_form_meta
will apply all changes needed to achieve complete state.
Finalized meta is not really a complete form - the inheritance and basic setup is done, but it still misses all of its parts like hooks and fields. Since we use no keyword just before finishing the package like Moose's meta->make_immutable
, the form meta has no way of knowing when it has actually been filled with those elements. The current strategy to work around this (when it is needed at all) is to update meta incrementally when adding those elements.
Using the module without the syntactic sugar
It is entirely possible, although a bit tedious, to use the module without importing Form::Tiny package. The following example declares a form with a single field taken from the example above.
These two packages should result in a form with the same capabilities.
package MyForm1;
use Moo;
use Form::Tiny::Utils qw(:meta_handlers);
my $meta = create_form_meta(__PACKAGE__, qw())
->set_form_roles(['Form::Tiny::Form']);
__PACKAGE__->form_meta->add_field(
'field-name' => (
required => 1,
)
);
package MyForm2;
use Form::Tiny;
form_field 'field-name' => (
required => 1,
);
Notice: trying to build your form from scratch to match exactly what Form::Tiny importer is doing should only be done for educational purposes. Changes in import procedure are not covered by the 3 month deprecation period policy.